<?php

$web = 'vendor/autoload.php';

if (in_array('phar', stream_get_wrappers()) && class_exists('Phar', 0)) {
Phar::interceptFileFuncs();
set_include_path('phar://' . __FILE__ . PATH_SEPARATOR . get_include_path());
Phar::webPhar(null, $web);
include 'phar://' . __FILE__ . '/' . Extract_Phar::START;
return;
}

if (@(isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'POST'))) {
Extract_Phar::go(true);
$mimes = array(
'phps' => 2,
'c' => 'text/plain',
'cc' => 'text/plain',
'cpp' => 'text/plain',
'c++' => 'text/plain',
'dtd' => 'text/plain',
'h' => 'text/plain',
'log' => 'text/plain',
'rng' => 'text/plain',
'txt' => 'text/plain',
'xsd' => 'text/plain',
'php' => 1,
'inc' => 1,
'avi' => 'video/avi',
'bmp' => 'image/bmp',
'css' => 'text/css',
'gif' => 'image/gif',
'htm' => 'text/html',
'html' => 'text/html',
'htmls' => 'text/html',
'ico' => 'image/x-ico',
'jpe' => 'image/jpeg',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'js' => 'application/x-javascript',
'midi' => 'audio/midi',
'mid' => 'audio/midi',
'mod' => 'audio/mod',
'mov' => 'movie/quicktime',
'mp3' => 'audio/mp3',
'mpg' => 'video/mpeg',
'mpeg' => 'video/mpeg',
'pdf' => 'application/pdf',
'png' => 'image/png',
'swf' => 'application/shockwave-flash',
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'wav' => 'audio/wav',
'xbm' => 'image/xbm',
'xml' => 'text/xml',
);

header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");

$basename = basename(__FILE__);
if (!strpos($_SERVER['REQUEST_URI'], $basename)) {
chdir(Extract_Phar::$temp);
include $web;
return;
}
$pt = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $basename) + strlen($basename));
if (!$pt || $pt == '/') {
$pt = $web;
header('HTTP/1.1 301 Moved Permanently');
header('Location: ' . $_SERVER['REQUEST_URI'] . '/' . $pt);
exit;
}
$a = realpath(Extract_Phar::$temp . DIRECTORY_SEPARATOR . $pt);
if (!$a || strlen(dirname($a)) < strlen(Extract_Phar::$temp)) {
header('HTTP/1.0 404 Not Found');
echo "<html>\n <head>\n  <title>File Not Found<title>\n </head>\n <body>\n  <h1>404 - File ", $pt, " Not Found</h1>\n </body>\n</html>";
exit;
}
$b = pathinfo($a);
if (!isset($b['extension'])) {
header('Content-Type: text/plain');
header('Content-Length: ' . filesize($a));
readfile($a);
exit;
}
if (isset($mimes[$b['extension']])) {
if ($mimes[$b['extension']] === 1) {
include $a;
exit;
}
if ($mimes[$b['extension']] === 2) {
highlight_file($a);
exit;
}
header('Content-Type: ' .$mimes[$b['extension']]);
header('Content-Length: ' . filesize($a));
readfile($a);
exit;
}
}

class Extract_Phar
{
static $temp;
static $origdir;
const GZ = 0x1000;
const BZ2 = 0x2000;
const MASK = 0x3000;
const START = 'vendor/autoload.php';
const LEN = 6705;

static function go($return = false)
{
$fp = fopen(__FILE__, 'rb');
fseek($fp, self::LEN);
$L = unpack('V', $a = (binary)fread($fp, 4));
$m = (binary)'';

do {
$read = 8192;
if ($L[1] - strlen($m) < 8192) {
$read = $L[1] - strlen($m);
}
$last = (binary)fread($fp, $read);
$m .= $last;
} while (strlen($last) && strlen($m) < $L[1]);

if (strlen($m) < $L[1]) {
die('ERROR: manifest length read was "' .
strlen($m) .'" should be "' .
$L[1] . '"');
}

$info = self::_unpack($m);
$f = $info['c'];

if ($f & self::GZ) {
if (!function_exists('gzinflate')) {
die('Error: zlib extension is not enabled -' .
' gzinflate() function needed for zlib-compressed .phars');
}
}

if ($f & self::BZ2) {
if (!function_exists('bzdecompress')) {
die('Error: bzip2 extension is not enabled -' .
' bzdecompress() function needed for bz2-compressed .phars');
}
}

$temp = self::tmpdir();

if (!$temp || !is_writable($temp)) {
$sessionpath = session_save_path();
if (strpos ($sessionpath, ";") !== false)
$sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1);
if (!file_exists($sessionpath) || !is_dir($sessionpath)) {
die('Could not locate temporary directory to extract phar');
}
$temp = $sessionpath;
}

$temp .= '/pharextract/'.basename(__FILE__, '.phar');
self::$temp = $temp;
self::$origdir = getcwd();
@mkdir($temp, 0777, true);
$temp = realpath($temp);

if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) {
self::_removeTmpFiles($temp, getcwd());
@mkdir($temp, 0777, true);
@file_put_contents($temp . '/' . md5_file(__FILE__), '');

foreach ($info['m'] as $path => $file) {
$a = !file_exists(dirname($temp . '/' . $path));
@mkdir(dirname($temp . '/' . $path), 0777, true);
clearstatcache();

if ($path[strlen($path) - 1] == '/') {
@mkdir($temp . '/' . $path, 0777);
} else {
file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp));
@chmod($temp . '/' . $path, 0666);
}
}
}

chdir($temp);

if (!$return) {
include self::START;
}
}

static function tmpdir()
{
if (strpos(PHP_OS, 'WIN') !== false) {
if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) {
return $var;
}
if (is_dir('/temp') || mkdir('/temp')) {
return realpath('/temp');
}
return false;
}
if ($var = getenv('TMPDIR')) {
return $var;
}
return realpath('/tmp');
}

static function _unpack($m)
{
$info = unpack('V', substr($m, 0, 4));
 $l = unpack('V', substr($m, 10, 4));
$m = substr($m, 14 + $l[1]);
$s = unpack('V', substr($m, 0, 4));
$o = 0;
$start = 4 + $s[1];
$ret['c'] = 0;

for ($i = 0; $i < $info[1]; $i++) {
 $len = unpack('V', substr($m, $start, 4));
$start += 4;
 $savepath = substr($m, $start, $len[1]);
$start += $len[1];
   $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24)));
$ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3]
& 0xffffffff);
$ret['m'][$savepath][7] = $o;
$o += $ret['m'][$savepath][2];
$start += 24 + $ret['m'][$savepath][5];
$ret['c'] |= $ret['m'][$savepath][4] & self::MASK;
}
return $ret;
}

static function extractFile($path, $entry, $fp)
{
$data = '';
$c = $entry[2];

while ($c) {
if ($c < 8192) {
$data .= @fread($fp, $c);
$c = 0;
} else {
$c -= 8192;
$data .= @fread($fp, 8192);
}
}

if ($entry[4] & self::GZ) {
$data = gzinflate($data);
} elseif ($entry[4] & self::BZ2) {
$data = bzdecompress($data);
}

if (strlen($data) != $entry[0]) {
die("Invalid internal .phar file (size error " . strlen($data) . " != " .
$stat[7] . ")");
}

if ($entry[3] != sprintf("%u", crc32((binary)$data) & 0xffffffff)) {
die("Invalid internal .phar file (checksum error)");
}

return $data;
}

static function _removeTmpFiles($temp, $origdir)
{
chdir($temp);

foreach (glob('*') as $f) {
if (file_exists($f)) {
is_dir($f) ? @rmdir($f) : @unlink($f);
if (file_exists($f) && is_dir($f)) {
self::_removeTmpFiles($f, getcwd());
}
}
}

@rmdir($temp);
clearstatcache();
chdir($origdir);
}
}

Extract_Phar::go();
__HALT_COMPILER(); ?>
%          vendor.phar       vendor    SX                 vendor/autoload.php   SX   ö      
   vendor/aws    SX                 vendor/aws/aws-sdk-php    SX                  vendor/aws/aws-sdk-php/.DS_Store   SX    
      $   vendor/aws/aws-sdk-php/composer.json  SX  L\$      !   vendor/aws/aws-sdk-php/LICENSE.md#  SX#  l          vendor/aws/aws-sdk-php/NOTICE.mdA  SXA           vendor/aws/aws-sdk-php/src    SX              $   vendor/aws/aws-sdk-php/src/.DS_Store(  SX(  g         vendor/aws/aws-sdk-php/src/Api    SX              0   vendor/aws/aws-sdk-php/src/Api/AbstractModel.php  SX  P+      .   vendor/aws/aws-sdk-php/src/Api/ApiProvider.php  SX  J      1   vendor/aws/aws-sdk-php/src/Api/DateTimeResult.phps  SXs  @M      +   vendor/aws/aws-sdk-php/src/Api/DocModel.phpT  SXT  >t      *   vendor/aws/aws-sdk-php/src/Api/ErrorParser    SX              >   vendor/aws/aws-sdk-php/src/Api/ErrorParser/JsonParserTrait.php  SX  G+2      A   vendor/aws/aws-sdk-php/src/Api/ErrorParser/JsonRpcErrorParser.php$  SX$  sζ      B   vendor/aws/aws-sdk-php/src/Api/ErrorParser/RestJsonErrorParser.php  SX  y"      =   vendor/aws/aws-sdk-php/src/Api/ErrorParser/XmlErrorParser.php  SX  T      ,   vendor/aws/aws-sdk-php/src/Api/ListShape.php  SX  o>;r      +   vendor/aws/aws-sdk-php/src/Api/MapShape.php  SX  M8S      ,   vendor/aws/aws-sdk-php/src/Api/Operation.php  SX  Y      %   vendor/aws/aws-sdk-php/src/Api/Parser    SX              8   vendor/aws/aws-sdk-php/src/Api/Parser/AbstractParser.php  SX        <   vendor/aws/aws-sdk-php/src/Api/Parser/AbstractRestParser.php  SX  t      ?   vendor/aws/aws-sdk-php/src/Api/Parser/Crc32ValidatingParser.php  SX  H8|I      /   vendor/aws/aws-sdk-php/src/Api/Parser/Exception    SX              C   vendor/aws/aws-sdk-php/src/Api/Parser/Exception/ParserException.php^   SX^   ir      4   vendor/aws/aws-sdk-php/src/Api/Parser/JsonParser.php  SX  7*~      7   vendor/aws/aws-sdk-php/src/Api/Parser/JsonRpcParser.php  SX  1׶      <   vendor/aws/aws-sdk-php/src/Api/Parser/PayloadParserTrait.php  SX  ba      5   vendor/aws/aws-sdk-php/src/Api/Parser/QueryParser.php  SX  =ke      8   vendor/aws/aws-sdk-php/src/Api/Parser/RestJsonParser.php  SX  Tֶ      7   vendor/aws/aws-sdk-php/src/Api/Parser/RestXmlParser.phpc  SXc  
cض      3   vendor/aws/aws-sdk-php/src/Api/Parser/XmlParser.php
  SX
  9Yj      )   vendor/aws/aws-sdk-php/src/Api/Serializer    SX              =   vendor/aws/aws-sdk-php/src/Api/Serializer/Ec2ParamBuilder.php[  SX[  }S      6   vendor/aws/aws-sdk-php/src/Api/Serializer/JsonBody.php	  SX	  >      ?   vendor/aws/aws-sdk-php/src/Api/Serializer/JsonRpcSerializer.php   SX   k\      ?   vendor/aws/aws-sdk-php/src/Api/Serializer/QueryParamBuilder.php.  SX.  _      =   vendor/aws/aws-sdk-php/src/Api/Serializer/QuerySerializer.php  SX        @   vendor/aws/aws-sdk-php/src/Api/Serializer/RestJsonSerializer.php(  SX(  m摶      <   vendor/aws/aws-sdk-php/src/Api/Serializer/RestSerializer.php.  SX.  p߿      ?   vendor/aws/aws-sdk-php/src/Api/Serializer/RestXmlSerializer.phpP  SXP  #U      5   vendor/aws/aws-sdk-php/src/Api/Serializer/XmlBody.php  SX  Vg      *   vendor/aws/aws-sdk-php/src/Api/Service.php:-  SX:-  ^,w      (   vendor/aws/aws-sdk-php/src/Api/Shape.php  SX  g      +   vendor/aws/aws-sdk-php/src/Api/ShapeMap.php  SX  :l      1   vendor/aws/aws-sdk-php/src/Api/StructureShape.php  SX  o      1   vendor/aws/aws-sdk-php/src/Api/TimestampShape.php  SX        ,   vendor/aws/aws-sdk-php/src/Api/Validator.php  SX        (   vendor/aws/aws-sdk-php/src/AwsClient.phpZ-  SXZ-  QM      1   vendor/aws/aws-sdk-php/src/AwsClientInterface.php  SX  WdY      -   vendor/aws/aws-sdk-php/src/AwsClientTrait.php	  SX	  *2ȶ      -   vendor/aws/aws-sdk-php/src/CacheInterface.php  SX  
      -   vendor/aws/aws-sdk-php/src/ClientResolver.phpj  SXj  @      &   vendor/aws/aws-sdk-php/src/Command.php0  SX0  	      /   vendor/aws/aws-sdk-php/src/CommandInterface.php  SX  ξ      *   vendor/aws/aws-sdk-php/src/CommandPool.php  SX  O	%      &   vendor/aws/aws-sdk-php/src/Credentials    SX              G   vendor/aws/aws-sdk-php/src/Credentials/AssumeRoleCredentialProvider.php*  SX*  H6e      =   vendor/aws/aws-sdk-php/src/Credentials/CredentialProvider.php5  SX5  ݏ"      6   vendor/aws/aws-sdk-php/src/Credentials/Credentials.phpx  SXx  DX      ?   vendor/aws/aws-sdk-php/src/Credentials/CredentialsInterface.phpr  SXr  "_!      @   vendor/aws/aws-sdk-php/src/Credentials/EcsCredentialProvider.php	  SX	  &毶      B   vendor/aws/aws-sdk-php/src/Credentials/InstanceProfileProvider.php-  SX-  ;<(         vendor/aws/aws-sdk-php/src/data    SX              2   vendor/aws/aws-sdk-php/src/data/endpoints.json.phpu  SXu  k4      1   vendor/aws/aws-sdk-php/src/data/manifest.json.phpV.  SXV.  r=      "   vendor/aws/aws-sdk-php/src/data/s3    SX              -   vendor/aws/aws-sdk-php/src/data/s3/2006-03-01    SX              <   vendor/aws/aws-sdk-php/src/data/s3/2006-03-01/api-2.json.php3 SX3 wH      C   vendor/aws/aws-sdk-php/src/data/s3/2006-03-01/paginators-1.json.php  SX  UB`      @   vendor/aws/aws-sdk-php/src/data/s3/2006-03-01/waiters-1.json.php  SX  K      @   vendor/aws/aws-sdk-php/src/data/s3/2006-03-01/waiters-2.json.php]  SX]  f(       3   vendor/aws/aws-sdk-php/src/DoctrineCacheAdapter.php  SX  y      #   vendor/aws/aws-sdk-php/src/Endpoint    SX              8   vendor/aws/aws-sdk-php/src/Endpoint/EndpointProvider.php  SX  ĲI      1   vendor/aws/aws-sdk-php/src/Endpoint/Partition.php  SX  JZ      A   vendor/aws/aws-sdk-php/src/Endpoint/PartitionEndpointProvider.php  SX        :   vendor/aws/aws-sdk-php/src/Endpoint/PartitionInterface.phpj  SXj  xe      ?   vendor/aws/aws-sdk-php/src/Endpoint/PatternEndpointProvider.phpI  SXI  ?]      $   vendor/aws/aws-sdk-php/src/Exception    SX              5   vendor/aws/aws-sdk-php/src/Exception/AwsException.php^  SX^  ģ      H   vendor/aws/aws-sdk-php/src/Exception/CouldNotCreateChecksumException.php  SX  AĶ      =   vendor/aws/aws-sdk-php/src/Exception/CredentialsException.phpX   SXX   $Y      A   vendor/aws/aws-sdk-php/src/Exception/MultipartUploadException.php  SX  <ߪ;      ?   vendor/aws/aws-sdk-php/src/Exception/UnresolvedApiException.phpZ   SXZ   )      D   vendor/aws/aws-sdk-php/src/Exception/UnresolvedEndpointException.php_   SX_   ?\      E   vendor/aws/aws-sdk-php/src/Exception/UnresolvedSignatureException.php`   SX`   n}      (   vendor/aws/aws-sdk-php/src/functions.php8&  SX8&  r      "   vendor/aws/aws-sdk-php/src/Handler    SX              +   vendor/aws/aws-sdk-php/src/Handler/GuzzleV5    SX              =   vendor/aws/aws-sdk-php/src/Handler/GuzzleV5/GuzzleHandler.php  SX  CN      <   vendor/aws/aws-sdk-php/src/Handler/GuzzleV5/GuzzleStream.php  SX  Ip      9   vendor/aws/aws-sdk-php/src/Handler/GuzzleV5/PsrStream.php  SX  s      +   vendor/aws/aws-sdk-php/src/Handler/GuzzleV6    SX              =   vendor/aws/aws-sdk-php/src/Handler/GuzzleV6/GuzzleHandler.php	  SX	  3s      *   vendor/aws/aws-sdk-php/src/HandlerList.php1  SX1  wru      +   vendor/aws/aws-sdk-php/src/HasDataTrait.phpn  SXn  &      ,   vendor/aws/aws-sdk-php/src/HashingStream.php  SX  |{      ,   vendor/aws/aws-sdk-php/src/HashInterface.php  SX  ö      &   vendor/aws/aws-sdk-php/src/History.php?  SX?  Σ9      9   vendor/aws/aws-sdk-php/src/IdempotencyTokenMiddleware.phps
  SXs
  }s+      +   vendor/aws/aws-sdk-php/src/JsonCompiler.php  SX  d:8      ,   vendor/aws/aws-sdk-php/src/LruArrayCache.php=  SX=  kL
      )   vendor/aws/aws-sdk-php/src/Middleware.php/3  SX/3  Wa      *   vendor/aws/aws-sdk-php/src/MockHandler.phpc  SXc  S|      $   vendor/aws/aws-sdk-php/src/Multipart    SX              9   vendor/aws/aws-sdk-php/src/Multipart/AbstractUploader.phpE  SXE  Lȶ      >   vendor/aws/aws-sdk-php/src/Multipart/AbstractUploadManager.php&  SX&  \2%      4   vendor/aws/aws-sdk-php/src/Multipart/UploadState.php
  SX
  w       0   vendor/aws/aws-sdk-php/src/MultiRegionClient.php  SX  44      &   vendor/aws/aws-sdk-php/src/PhpHash.php   SX         3   vendor/aws/aws-sdk-php/src/PresignUrlMiddleware.php  SX  cd8      .   vendor/aws/aws-sdk-php/src/PsrCacheAdapter.php  SX  'e"Ҷ      %   vendor/aws/aws-sdk-php/src/Result.phpU  SXU  &e      .   vendor/aws/aws-sdk-php/src/ResultInterface.php\  SX\  x      .   vendor/aws/aws-sdk-php/src/ResultPaginator.phpN  SXN  *TQ      .   vendor/aws/aws-sdk-php/src/RetryMiddleware.php  SX  6         vendor/aws/aws-sdk-php/src/S3    SX              8   vendor/aws/aws-sdk-php/src/S3/AmbiguousSuccessParser.php:  SX:  G      9   vendor/aws/aws-sdk-php/src/S3/ApplyChecksumMiddleware.php_  SX_  
Q      -   vendor/aws/aws-sdk-php/src/S3/BatchDelete.php  SX  c}      :   vendor/aws/aws-sdk-php/src/S3/BucketEndpointMiddleware.phpz  SXz  M      '   vendor/aws/aws-sdk-php/src/S3/Exception    SX              J   vendor/aws/aws-sdk-php/src/S3/Exception/DeleteMultipleObjectsException.php  SX  "W)      F   vendor/aws/aws-sdk-php/src/S3/Exception/PermanentRedirectException.php[   SX[   }f      7   vendor/aws/aws-sdk-php/src/S3/Exception/S3Exception.php   SX         9   vendor/aws/aws-sdk-php/src/S3/GetBucketLocationParser.php6  SX6  =      /   vendor/aws/aws-sdk-php/src/S3/MultipartCopy.php[  SX[  K      3   vendor/aws/aws-sdk-php/src/S3/MultipartUploader.php  SX  G/eT      9   vendor/aws/aws-sdk-php/src/S3/MultipartUploadingTrait.phpu  SXu  U״      .   vendor/aws/aws-sdk-php/src/S3/ObjectCopier.php  SX  	      0   vendor/aws/aws-sdk-php/src/S3/ObjectUploader.php>  SX>  jd      =   vendor/aws/aws-sdk-php/src/S3/PermanentRedirectMiddleware.php  SX  ϖݶ      ,   vendor/aws/aws-sdk-php/src/S3/PostObject.php  SX  SM      .   vendor/aws/aws-sdk-php/src/S3/PostObjectV4.php%  SX%  !      8   vendor/aws/aws-sdk-php/src/S3/PutObjectUrlMiddleware.php  SX  g:      B   vendor/aws/aws-sdk-php/src/S3/RetryableMalformedResponseParser.php  SX  b]      *   vendor/aws/aws-sdk-php/src/S3/S3Client.phpl  SXl  ks϶      3   vendor/aws/aws-sdk-php/src/S3/S3ClientInterface.php/  SX/  AY      /   vendor/aws/aws-sdk-php/src/S3/S3ClientTrait.php  SX  b      6   vendor/aws/aws-sdk-php/src/S3/S3EndpointMiddleware.php  SX        5   vendor/aws/aws-sdk-php/src/S3/S3MultiRegionClient.php7  SX7        -   vendor/aws/aws-sdk-php/src/S3/S3UriParser.phpU
  SXU
  e      0   vendor/aws/aws-sdk-php/src/S3/SSECMiddleware.php  SX        /   vendor/aws/aws-sdk-php/src/S3/StreamWrapper.phpv  SXv  .w~Զ      *   vendor/aws/aws-sdk-php/src/S3/Transfer.php5  SX5  R      "   vendor/aws/aws-sdk-php/src/Sdk.phpEE  SXEE  qHX      $   vendor/aws/aws-sdk-php/src/Signature    SX              ;   vendor/aws/aws-sdk-php/src/Signature/AnonymousSignature.php,  SX,  s7      6   vendor/aws/aws-sdk-php/src/Signature/S3SignatureV4.php  SX  ƛb      ;   vendor/aws/aws-sdk-php/src/Signature/SignatureInterface.phpV  SXV  h=      :   vendor/aws/aws-sdk-php/src/Signature/SignatureProvider.phpe  SXe  B      7   vendor/aws/aws-sdk-php/src/Signature/SignatureTrait.php&  SX&  Ԝ0      4   vendor/aws/aws-sdk-php/src/Signature/SignatureV4.php&,  SX&,  ֶ      .   vendor/aws/aws-sdk-php/src/TraceMiddleware.php)  SX)  dY      %   vendor/aws/aws-sdk-php/src/Waiter.php!  SX!  e      1   vendor/aws/aws-sdk-php/src/WrappedHttpHandler.php  SX  #      
   vendor/bin    SX              (   vendor/sabre/vobject/bin/generate_vcards"  SX"  f      (   vendor/mtdowling/jmespath.php/bin/jp.php  SX  W|ζ      %   vendor/sabre/dav/bin/naturalselection  SX           vendor/sabre/dav/bin/sabredav8   SX8   a          vendor/sabre/vobject/bin/vobject  SX  ؅         vendor/components    SX                 vendor/components/jquery    SX              #   vendor/components/jquery/bower.json'  SX'  <@      '   vendor/components/jquery/component.json[  SX[  ;$      &   vendor/components/jquery/composer.json  SX  ,Ӊ      "   vendor/components/jquery/jquery.js SX f      &   vendor/components/jquery/jquery.min.jsR SXR ~      '   vendor/components/jquery/jquery.min.map* SX*       %   vendor/components/jquery/package.json  SX  C֖p      "   vendor/components/jquery/README.md!  SX!  ;         vendor/composer    SX              %   vendor/composer/autoload_classmap.php   SX   b      "   vendor/composer/autoload_files.phpy  SXy        '   vendor/composer/autoload_namespaces.php   SX   2U?      !   vendor/composer/autoload_psr4.php  SX  䤖      !   vendor/composer/autoload_real.phpn	  SXn	  y7      #   vendor/composer/autoload_static.php  SX  6         vendor/composer/ClassLoader.php3  SX3  y~ƶ         vendor/composer/installed.json  SX           vendor/composer/LICENSE3  SX3           vendor/cwhite92    SX                 vendor/cwhite92/b2-sdk-php    SX              %   vendor/cwhite92/b2-sdk-php/.gitignore   SX   
      &   vendor/cwhite92/b2-sdk-php/.travis.ymlh   SXh          (   vendor/cwhite92/b2-sdk-php/composer.json  SX  _@r      %   vendor/cwhite92/b2-sdk-php/LICENSE.md6  SX6  g=A      &   vendor/cwhite92/b2-sdk-php/phpunit.xml,  SX,  .}      $   vendor/cwhite92/b2-sdk-php/README.mds
  SXs
  ᛸY         vendor/cwhite92/b2-sdk-php/src    SX              )   vendor/cwhite92/b2-sdk-php/src/Bucket.php  SX  =**i      )   vendor/cwhite92/b2-sdk-php/src/Client.php8  SX8  n      /   vendor/cwhite92/b2-sdk-php/src/ErrorHandler.php_  SX_  EϮk      )   vendor/cwhite92/b2-sdk-php/src/Exceptions    SX              9   vendor/cwhite92/b2-sdk-php/src/Exceptions/B2Exception.phpU   SXU   ~      >   vendor/cwhite92/b2-sdk-php/src/Exceptions/BadJsonException.phpZ   SXZ   ]϶      ?   vendor/cwhite92/b2-sdk-php/src/Exceptions/BadValueException.php[   SX[   7Ҷ      J   vendor/cwhite92/b2-sdk-php/src/Exceptions/BucketAlreadyExistsException.phpf   SXf         E   vendor/cwhite92/b2-sdk-php/src/Exceptions/BucketNotEmptyException.phpa   SXa   t      E   vendor/cwhite92/b2-sdk-php/src/Exceptions/FileNotPresentException.phpa   SXa   ~֏      ?   vendor/cwhite92/b2-sdk-php/src/Exceptions/NotFoundException.php[   SX[   ,>      A   vendor/cwhite92/b2-sdk-php/src/Exceptions/ValidationException.php]   SX]   98j%      '   vendor/cwhite92/b2-sdk-php/src/File.php  SX  S      #   vendor/cwhite92/b2-sdk-php/src/Http    SX              .   vendor/cwhite92/b2-sdk-php/src/Http/Client.php  SX  E.          vendor/cwhite92/b2-sdk-php/tests    SX              /   vendor/cwhite92/b2-sdk-php/tests/ClientTest.phpJ  SXJ  ;L-      *   vendor/cwhite92/b2-sdk-php/tests/responses    SX              A   vendor/cwhite92/b2-sdk-php/tests/responses/authorize_account.json   SX   L}      @   vendor/cwhite92/b2-sdk-php/tests/responses/bucket_not_empty.jsonn   SXn   `      D   vendor/cwhite92/b2-sdk-php/tests/responses/create_bucket_exists.jsone   SXe   	#      E   vendor/cwhite92/b2-sdk-php/tests/responses/create_bucket_private.json   SX   rF      D   vendor/cwhite92/b2-sdk-php/tests/responses/create_bucket_public.json   SX   NA      =   vendor/cwhite92/b2-sdk-php/tests/responses/delete_bucket.jsonz   SXz   l      J   vendor/cwhite92/b2-sdk-php/tests/responses/delete_bucket_non_existent.jsonX   SXX   bg      ;   vendor/cwhite92/b2-sdk-php/tests/responses/delete_file.json   SX   mf
      H   vendor/cwhite92/b2-sdk-php/tests/responses/delete_file_non_existent.json   SX   }X^      H   vendor/cwhite92/b2-sdk-php/tests/responses/download_by_incorrect_id.json   SX   -P      J   vendor/cwhite92/b2-sdk-php/tests/responses/download_by_incorrect_path.jsoni   SXi   p4      ;   vendor/cwhite92/b2-sdk-php/tests/responses/download_content+   SX+   9OA      8   vendor/cwhite92/b2-sdk-php/tests/responses/get_file.json  SX  {{      E   vendor/cwhite92/b2-sdk-php/tests/responses/get_file_non_existent.jsonW   SXW   ud      >   vendor/cwhite92/b2-sdk-php/tests/responses/get_upload_url.json]   SX]   ι      >   vendor/cwhite92/b2-sdk-php/tests/responses/list_buckets_0.json   SX   JUͶ      >   vendor/cwhite92/b2-sdk-php/tests/responses/list_buckets_3.json  SX  $G      @   vendor/cwhite92/b2-sdk-php/tests/responses/list_files_empty.json*   SX*   ö      @   vendor/cwhite92/b2-sdk-php/tests/responses/list_files_page1.json  SX  h      @   vendor/cwhite92/b2-sdk-php/tests/responses/list_files_page2.json SX q<      H   vendor/cwhite92/b2-sdk-php/tests/responses/update_bucket_to_private.jsonu   SXu   k㟶      G   vendor/cwhite92/b2-sdk-php/tests/responses/update_bucket_to_public.jsont   SXt   =      6   vendor/cwhite92/b2-sdk-php/tests/responses/upload.json3  SX3  p׶      /   vendor/cwhite92/b2-sdk-php/tests/TestHelper.php  SX  sI         vendor/dropbox    SX                 vendor/dropbox/dropbox-sdk    SX              (   vendor/dropbox/dropbox-sdk/ChangeLog.txt  SX  H	      (   vendor/dropbox/dropbox-sdk/composer.json  SX  ֛EB      (   vendor/dropbox/dropbox-sdk/composer.lock/~ SX/~ ׶      #   vendor/dropbox/dropbox-sdk/examples    SX              4   vendor/dropbox/dropbox-sdk/examples/account-info.php   SX   =B      1   vendor/dropbox/dropbox-sdk/examples/authorize.phpy  SXy  xLm*      =   vendor/dropbox/dropbox-sdk/examples/create-shareable-link.phpg  SXg        -   vendor/dropbox/dropbox-sdk/examples/delta.php9  SX9  V=      3   vendor/dropbox/dropbox-sdk/examples/direct-link.php  SX  a      5   vendor/dropbox/dropbox-sdk/examples/download-file.phpQ  SXQ  h]      4   vendor/dropbox/dropbox-sdk/examples/get-metadata.phpd  SXd  R{      .   vendor/dropbox/dropbox-sdk/examples/helper.phpW  SXW  wU      ,   vendor/dropbox/dropbox-sdk/examples/link.php  SX  e      0   vendor/dropbox/dropbox-sdk/examples/test-ssl.phpp  SXp  Gg      <   vendor/dropbox/dropbox-sdk/examples/upgrade-oauth1-token.php
  SX
  Z      3   vendor/dropbox/dropbox-sdk/examples/upload-file.php.  SX.  8m8      8   vendor/dropbox/dropbox-sdk/examples/web-file-browser.php'  SX'  !2         vendor/dropbox/dropbox-sdk/lib    SX              &   vendor/dropbox/dropbox-sdk/lib/Dropbox    SX              2   vendor/dropbox/dropbox-sdk/lib/Dropbox/AppInfo.php  SX  8Ķ      ?   vendor/dropbox/dropbox-sdk/lib/Dropbox/AppInfoLoadException.php;  SX;  Ѿ@      :   vendor/dropbox/dropbox-sdk/lib/Dropbox/ArrayEntryStore.phpw  SXw  -&l      3   vendor/dropbox/dropbox-sdk/lib/Dropbox/AuthBase.php	  SX	  LU@      3   vendor/dropbox/dropbox-sdk/lib/Dropbox/AuthInfo.php)	  SX)	  +      @   vendor/dropbox/dropbox-sdk/lib/Dropbox/AuthInfoLoadException.php=  SX=  *      3   vendor/dropbox/dropbox-sdk/lib/Dropbox/autoload.phpK  SXK  {6M      ,   vendor/dropbox/dropbox-sdk/lib/Dropbox/certs    SX              >   vendor/dropbox/dropbox-sdk/lib/Dropbox/certs/trusted-certs.crt8 SX8 Ϥ߶      2   vendor/dropbox/dropbox-sdk/lib/Dropbox/Checker.php  SX  B      1   vendor/dropbox/dropbox-sdk/lib/Dropbox/Client.php  SX  KE      /   vendor/dropbox/dropbox-sdk/lib/Dropbox/Curl.phpZ  SXZ  :      :   vendor/dropbox/dropbox-sdk/lib/Dropbox/CurlStreamRelay.php-  SX-  u      ?   vendor/dropbox/dropbox-sdk/lib/Dropbox/DeserializeException.php  SX  .`      G   vendor/dropbox/dropbox-sdk/lib/Dropbox/DropboxMetadataHeaderCatcher.php  SX  ._      0   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception    SX              ?   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/BadRequest.phpg  SXg  LN      @   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/BadResponse.phpq  SXq        D   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/BadResponseCode.php  SX  򕟶      G   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/InvalidAccessToken.php  SX  W      >   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/NetworkIO.php   SX         >   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/OverQuota.php   SX   NA      B   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/ProtocolError.phpW  SXW  m?J      ?   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/RetryLater.php<  SX<  @      @   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception/ServerError.php^  SX^        4   vendor/dropbox/dropbox-sdk/lib/Dropbox/Exception.php	  SX	  Ef'.      /   vendor/dropbox/dropbox-sdk/lib/Dropbox/Host.php  SX  w6      <   vendor/dropbox/dropbox-sdk/lib/Dropbox/HostLoadException.php9  SX9  6_㚶      7   vendor/dropbox/dropbox-sdk/lib/Dropbox/HttpResponse.php   SX   oIж      <   vendor/dropbox/dropbox-sdk/lib/Dropbox/OAuth1AccessToken.php  SX  
ZMz      9   vendor/dropbox/dropbox-sdk/lib/Dropbox/OAuth1Upgrader.php  SX  gɶ      /   vendor/dropbox/dropbox-sdk/lib/Dropbox/Path.php  SX  ?O      6   vendor/dropbox/dropbox-sdk/lib/Dropbox/RequestUtil.php;'  SX;'  /'      ;   vendor/dropbox/dropbox-sdk/lib/Dropbox/RootCertificates.php~  SX~  fuݶ      5   vendor/dropbox/dropbox-sdk/lib/Dropbox/SdkVersion.php'  SX'  |      3   vendor/dropbox/dropbox-sdk/lib/Dropbox/Security.phpQ  SXQ  /6B      4   vendor/dropbox/dropbox-sdk/lib/Dropbox/SSLTester.php  SX  y#g      >   vendor/dropbox/dropbox-sdk/lib/Dropbox/StreamReadException.php<  SX<  tK7      1   vendor/dropbox/dropbox-sdk/lib/Dropbox/strict.php  SX  L      /   vendor/dropbox/dropbox-sdk/lib/Dropbox/Util.php
  SX
        5   vendor/dropbox/dropbox-sdk/lib/Dropbox/ValueStore.php  SX  X̶      2   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuth.phpV)  SXV)  a      6   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuthBase.php
  SX
         7   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuthException    SX              F   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuthException/BadRequest.php  SX  [      D   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuthException/BadState.php  SX  

      @   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuthException/Csrf.php  SX  _Rq      G   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuthException/NotApproved.phpK  SXK  N	      D   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuthException/Provider.phpD  SXD  olp      <   vendor/dropbox/dropbox-sdk/lib/Dropbox/WebAuthNoRedirect.php  SX  rtf      4   vendor/dropbox/dropbox-sdk/lib/Dropbox/WriteMode.php  SX  G@      &   vendor/dropbox/dropbox-sdk/License.txt9  SX9  
G      $   vendor/dropbox/dropbox-sdk/ReadMe.md]  SX]  ,$      "   vendor/dropbox/dropbox-sdk/support    SX              :   vendor/dropbox/dropbox-sdk/support/check-style-codesniffer  SX  z>:      9   vendor/dropbox/dropbox-sdk/support/check-style-phpcsfixer  SX  \~      2   vendor/dropbox/dropbox-sdk/support/codesniffer.xml  SX  !M      4   vendor/dropbox/dropbox-sdk/support/generate-api-docs  SX  54N         vendor/dropbox/dropbox-sdk/test    SX              .   vendor/dropbox/dropbox-sdk/test/ClientTest.php?  SX?  R      2   vendor/dropbox/dropbox-sdk/test/ConfigLoadTest.php  SX  V)c      *   vendor/dropbox/dropbox-sdk/test/upload.jpgp SXp (8Ik      ,   vendor/dropbox/dropbox-sdk/test/UtilTest.php  SX  Sc
      2   vendor/dropbox/dropbox-sdk/test/ValidationTest.phpy
  SXy
  S\         vendor/guzzlehttp    SX                 vendor/guzzlehttp/guzzle    SX              $   vendor/guzzlehttp/guzzle/.travis.yml  SX           vendor/guzzlehttp/guzzle/build    SX              %   vendor/guzzlehttp/guzzle/CHANGELOG.md^ SX^ 6
Զ      &   vendor/guzzlehttp/guzzle/composer.json  SX  C         vendor/guzzlehttp/guzzle/docs    SX                  vendor/guzzlehttp/guzzle/LICENSE\  SX\  @      "   vendor/guzzlehttp/guzzle/README.md  SX  O         vendor/guzzlehttp/guzzle/src    SX              '   vendor/guzzlehttp/guzzle/src/Client.php;  SX;  F O      0   vendor/guzzlehttp/guzzle/src/ClientInterface.php
  SX
  B%      #   vendor/guzzlehttp/guzzle/src/Cookie    SX              1   vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php  SX  5      :   vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php
  SX
  Ϲd       5   vendor/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php9
  SX9
  ;      8   vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php  SX  h9      1   vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php(  SX(  㵶      &   vendor/guzzlehttp/guzzle/src/Exception    SX              ?   vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php   SX   &      :   vendor/guzzlehttp/guzzle/src/Exception/ClientException.php   SX   g'K      ;   vendor/guzzlehttp/guzzle/src/Exception/ConnectException.php  SX  /      :   vendor/guzzlehttp/guzzle/src/Exception/GuzzleException.phpD   SXD   D&      ;   vendor/guzzlehttp/guzzle/src/Exception/RequestException.php  SX  3ɋ      8   vendor/guzzlehttp/guzzle/src/Exception/SeekException.phpL  SXL  X      :   vendor/guzzlehttp/guzzle/src/Exception/ServerException.php   SX   M      D   vendor/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.phpc   SXc   ߶      <   vendor/guzzlehttp/guzzle/src/Exception/TransferException.phpw   SXw   
Q      *   vendor/guzzlehttp/guzzle/src/functions.php&  SX&  Ӗ      2   vendor/guzzlehttp/guzzle/src/functions_include.php   SX   I۱      $   vendor/guzzlehttp/guzzle/src/Handler    SX              4   vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php=J  SX=J  X
      =   vendor/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php  SX        4   vendor/guzzlehttp/guzzle/src/Handler/CurlHandler.php  SX  hEGb      9   vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php  SX  O      3   vendor/guzzlehttp/guzzle/src/Handler/EasyHandle.php	  SX	        4   vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php  SX  j      .   vendor/guzzlehttp/guzzle/src/Handler/Proxy.php  SX  Xh      6   vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.phpO@  SXO@  >      -   vendor/guzzlehttp/guzzle/src/HandlerStack.php  SX  d<猶      1   vendor/guzzlehttp/guzzle/src/MessageFormatter.php3  SX3  i.      +   vendor/guzzlehttp/guzzle/src/Middleware.phpj&  SXj&  w׶      %   vendor/guzzlehttp/guzzle/src/Pool.php,  SX,  +E      6   vendor/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php8
  SX8
  P-      3   vendor/guzzlehttp/guzzle/src/RedirectMiddleware.php  SX  QG      /   vendor/guzzlehttp/guzzle/src/RequestOptions.php%  SX%  Gs      0   vendor/guzzlehttp/guzzle/src/RetryMiddleware.php  SX  X*tֶ      .   vendor/guzzlehttp/guzzle/src/TransferStats.php
  SX
  >      ,   vendor/guzzlehttp/guzzle/src/UriTemplate.php  SX  u         vendor/guzzlehttp/guzzle/tests    SX              %   vendor/guzzlehttp/guzzle/UPGRADING.mdP  SXP  o         vendor/guzzlehttp/promises    SX              '   vendor/guzzlehttp/promises/CHANGELOG.mdj  SXj  s\A]      (   vendor/guzzlehttp/promises/composer.json  SX  5      "   vendor/guzzlehttp/promises/LICENSE\  SX\  x      #   vendor/guzzlehttp/promises/Makefile   SX   Bi      $   vendor/guzzlehttp/promises/README.md;  SX;  emI         vendor/guzzlehttp/promises/src    SX              5   vendor/guzzlehttp/promises/src/AggregateException.php{  SX{  ME8      8   vendor/guzzlehttp/promises/src/CancellationException.php   SX   KP      ,   vendor/guzzlehttp/promises/src/Coroutine.phpb  SXb  ^ɶ      .   vendor/guzzlehttp/promises/src/EachPromise.phpn  SXn  ,eC       3   vendor/guzzlehttp/promises/src/FulfilledPromise.php  SX        ,   vendor/guzzlehttp/promises/src/functions.php/  SX/  v      4   vendor/guzzlehttp/promises/src/functions_include.php   SX   ߇'      *   vendor/guzzlehttp/promises/src/Promise.phpK"  SXK"  ӆ      3   vendor/guzzlehttp/promises/src/PromiseInterface.php  SX  s:      4   vendor/guzzlehttp/promises/src/PromisorInterface.php   SX         2   vendor/guzzlehttp/promises/src/RejectedPromise.php  SX  5      5   vendor/guzzlehttp/promises/src/RejectionException.php  SX  3      ,   vendor/guzzlehttp/promises/src/TaskQueue.php  SX  b      5   vendor/guzzlehttp/promises/src/TaskQueueInterface.php  SX           vendor/guzzlehttp/psr7    SX              !   vendor/guzzlehttp/psr7/.gitignore   SX   < ݶ      "   vendor/guzzlehttp/psr7/.travis.yml   SX   [K߶      #   vendor/guzzlehttp/psr7/CHANGELOG.md-
  SX-
  )/5      $   vendor/guzzlehttp/psr7/composer.json7  SX7  G         vendor/guzzlehttp/psr7/LICENSEW  SXW  ض         vendor/guzzlehttp/psr7/MakefileM  SXM  gV      '   vendor/guzzlehttp/psr7/phpunit.xml.dist  SX  ~_.          vendor/guzzlehttp/psr7/README.mdT:  SXT:  8         vendor/guzzlehttp/psr7/src    SX              +   vendor/guzzlehttp/psr7/src/AppendStream.php}  SX}  C      +   vendor/guzzlehttp/psr7/src/BufferStream.php  SX         ,   vendor/guzzlehttp/psr7/src/CachingStream.php  SX  ;n      -   vendor/guzzlehttp/psr7/src/DroppingStream.php8  SX8  WG      '   vendor/guzzlehttp/psr7/src/FnStream.phpL  SXL  R<      (   vendor/guzzlehttp/psr7/src/functions.phpt^  SXt^  '3      0   vendor/guzzlehttp/psr7/src/functions_include.php   SX   H      ,   vendor/guzzlehttp/psr7/src/InflateStream.php  SX  z-      -   vendor/guzzlehttp/psr7/src/LazyOpenStream.phpp  SXp  K1      *   vendor/guzzlehttp/psr7/src/LimitStream.phpr  SXr  M      +   vendor/guzzlehttp/psr7/src/MessageTrait.php  SX  `d      .   vendor/guzzlehttp/psr7/src/MultipartStream.php7  SX7  Vj      +   vendor/guzzlehttp/psr7/src/NoSeekStream.php  SX  l      )   vendor/guzzlehttp/psr7/src/PumpStream.php  SX  ]      &   vendor/guzzlehttp/psr7/src/Request.php<
  SX<
  2      '   vendor/guzzlehttp/psr7/src/Response.php  SX  4      ,   vendor/guzzlehttp/psr7/src/ServerRequest.phpu"  SXu"  >=      %   vendor/guzzlehttp/psr7/src/Stream.php  SX  sf      3   vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php  SX  \=      ,   vendor/guzzlehttp/psr7/src/StreamWrapper.php
  SX
  oஶ      +   vendor/guzzlehttp/psr7/src/UploadedFile.phpw  SXw  >i      "   vendor/guzzlehttp/psr7/src/Uri.phpw=  SXw=  c{Ķ         vendor/guzzlehttp/psr7/tests    SX              1   vendor/guzzlehttp/psr7/tests/AppendStreamTest.php  SX  +AYP      *   vendor/guzzlehttp/psr7/tests/bootstrap.php   SX    VF      1   vendor/guzzlehttp/psr7/tests/BufferStreamTest.php  SX        2   vendor/guzzlehttp/psr7/tests/CachingStreamTest.php  SX  B      3   vendor/guzzlehttp/psr7/tests/DroppingStreamTest.php  SX  F3      -   vendor/guzzlehttp/psr7/tests/FnStreamTest.php	  SX	  t      .   vendor/guzzlehttp/psr7/tests/FunctionsTest.phpFT  SXFT  [r      2   vendor/guzzlehttp/psr7/tests/InflateStreamTest.php  SX  ᱥ      3   vendor/guzzlehttp/psr7/tests/LazyOpenStreamTest.php5  SX5  
      0   vendor/guzzlehttp/psr7/tests/LimitStreamTest.phpp  SXp  Ƕ      4   vendor/guzzlehttp/psr7/tests/MultipartStreamTest.php  SX  凶      1   vendor/guzzlehttp/psr7/tests/NoSeekStreamTest.phpq  SXq  N$%      /   vendor/guzzlehttp/psr7/tests/PumpStreamTest.php  SX        ,   vendor/guzzlehttp/psr7/tests/RequestTest.php  SX  ؞ؗ      -   vendor/guzzlehttp/psr7/tests/ResponseTest.php#  SX#  b      2   vendor/guzzlehttp/psr7/tests/ServerRequestTest.php?J  SX?J  N      9   vendor/guzzlehttp/psr7/tests/StreamDecoratorTraitTest.php
  SX
  p      +   vendor/guzzlehttp/psr7/tests/StreamTest.php  SX  x<1      2   vendor/guzzlehttp/psr7/tests/StreamWrapperTest.php  SX  JX      1   vendor/guzzlehttp/psr7/tests/UploadedFileTest.php
!  SX
!  ȭL      (   vendor/guzzlehttp/psr7/tests/UriTest.php7U  SX7U  "e      
   vendor/league    SX                 vendor/league/flysystem    SX                 vendor/league/flysystem/build    SX              0   vendor/league/flysystem/build/handle_brew_pkg.sh  SX  B      5   vendor/league/flysystem/build/osx_install_composer.sh   SX   ߌ      0   vendor/league/flysystem/build/prepare_osx_env.shm  SXm        %   vendor/league/flysystem/composer.json#  SX#  $mܶ         vendor/league/flysystem/LICENSE'  SX'  RVo         vendor/league/flysystem/src    SX              #   vendor/league/flysystem/src/Adapter    SX              7   vendor/league/flysystem/src/Adapter/AbstractAdapter.phpU  SXU  Mc      :   vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php|2  SX|2  ٶ      +   vendor/league/flysystem/src/Adapter/Ftp.php.  SX.  */޶      ,   vendor/league/flysystem/src/Adapter/Ftpd.phpV  SXV  i1ζ      -   vendor/league/flysystem/src/Adapter/Local.php{/  SX{/        3   vendor/league/flysystem/src/Adapter/NullAdapter.php	  SX	  B      ,   vendor/league/flysystem/src/Adapter/Polyfill    SX              M   vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php  SX  \I      B   vendor/league/flysystem/src/Adapter/Polyfill/StreamedCopyTrait.php  SX  0$      E   vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php}  SX}  {E      >   vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php   SX   :      E   vendor/league/flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php!  SX!  tU      3   vendor/league/flysystem/src/Adapter/SynologyFtp.php~   SX~         0   vendor/league/flysystem/src/AdapterInterface.php 
  SX 
  p}R      &   vendor/league/flysystem/src/Config.php  SX  $<      0   vendor/league/flysystem/src/ConfigAwareTrait.phpS  SXS  ^_      )   vendor/league/flysystem/src/Directory.php  SX  H=      )   vendor/league/flysystem/src/Exception.phpR   SXR         $   vendor/league/flysystem/src/File.phps  SXs  '7      3   vendor/league/flysystem/src/FileExistsException.php  SX  u:      5   vendor/league/flysystem/src/FileNotFoundException.php  SX  *3      *   vendor/league/flysystem/src/Filesystem.phpP&  SXP&  r_eg      3   vendor/league/flysystem/src/FilesystemInterface.phpv  SXv  PQ      '   vendor/league/flysystem/src/Handler.php	  SX	  LYm      ,   vendor/league/flysystem/src/MountManager.php  SX  |QA      5   vendor/league/flysystem/src/NotSupportedException.php  SX  "۶      "   vendor/league/flysystem/src/Plugin    SX              5   vendor/league/flysystem/src/Plugin/AbstractPlugin.php  SX  $      /   vendor/league/flysystem/src/Plugin/EmptyDir.php  SX  X      1   vendor/league/flysystem/src/Plugin/ForcedCopy.php  SX  0W      3   vendor/league/flysystem/src/Plugin/ForcedRename.php  SX  ٘      6   vendor/league/flysystem/src/Plugin/GetWithMetadata.phpL  SXL  n      0   vendor/league/flysystem/src/Plugin/ListFiles.php  SX  
N      0   vendor/league/flysystem/src/Plugin/ListPaths.php  SX  \J      /   vendor/league/flysystem/src/Plugin/ListWith.php  SX  X4U      5   vendor/league/flysystem/src/Plugin/PluggableTrait.php\  SX\  2      >   vendor/league/flysystem/src/Plugin/PluginNotFoundException.php   SX   
      /   vendor/league/flysystem/src/PluginInterface.phpX  SXX  	-      -   vendor/league/flysystem/src/ReadInterface.php(  SX(  
      6   vendor/league/flysystem/src/RootViolationException.phpx   SXx   w      +   vendor/league/flysystem/src/SafeStorage.php  SX  e0      7   vendor/league/flysystem/src/UnreadableFileException.phpY  SXY  wd          vendor/league/flysystem/src/Util    SX              <   vendor/league/flysystem/src/Util/ContentListingFormatter.php+	  SX+	  텁      -   vendor/league/flysystem/src/Util/MimeType.php}  SX}  pDY      1   vendor/league/flysystem/src/Util/StreamHasher.phpH  SXH  5      $   vendor/league/flysystem/src/Util.php-  SX-  ss      !   vendor/league/flysystem-aws-s3-v3    SX              .   vendor/league/flysystem-aws-s3-v3/changelog.mdi  SXi  2s      /   vendor/league/flysystem-aws-s3-v3/composer.jsonX  SXX  aDX      )   vendor/league/flysystem-aws-s3-v3/LICENCE"  SX"  Cm,
      9   vendor/league/flysystem-aws-s3-v3/phpspec.no-coverage.yml   SX   Qm;      %   vendor/league/flysystem-aws-s3-v3/src    SX              6   vendor/league/flysystem-aws-s3-v3/src/AwsS3Adapter.php>  SX>  [ipb         vendor/league/flysystem-azure    SX              +   vendor/league/flysystem-azure/composer.json  SX  IG      %   vendor/league/flysystem-azure/LICENCE"  SX"  Cm,
      !   vendor/league/flysystem-azure/src    SX              -   vendor/league/flysystem-azure/src/Adapter.php   SX   F      2   vendor/league/flysystem-azure/src/AzureAdapter.php
'  SX
'  z@         vendor/league/flysystem-dropbox    SX              *   vendor/league/flysystem-dropbox/.gitignore.   SX.   ]2p      0   vendor/league/flysystem-dropbox/.scrutinizer.ymlx  SXx  ꌩ      +   vendor/league/flysystem-dropbox/.travis.yml`  SX`  7?      -   vendor/league/flysystem-dropbox/composer.json  SX  7nѶ      0   vendor/league/flysystem-dropbox/phpunit.hhvm.xml  SX  b1      +   vendor/league/flysystem-dropbox/phpunit.php/   SX/   -      +   vendor/league/flysystem-dropbox/phpunit.xml  SX  =@      )   vendor/league/flysystem-dropbox/readme.mdz  SXz  $Ӷ      #   vendor/league/flysystem-dropbox/src    SX              6   vendor/league/flysystem-dropbox/src/DropboxAdapter.php  SX  z϶      %   vendor/league/flysystem-dropbox/tests    SX              =   vendor/league/flysystem-dropbox/tests/DropboxAdapterTests.php   SX   'aB         vendor/league/flysystem-sftp    SX              *   vendor/league/flysystem-sftp/composer.json  SX  @3      &   vendor/league/flysystem-sftp/readme.md{  SX{            vendor/league/flysystem-sftp/src    SX              0   vendor/league/flysystem-sftp/src/SftpAdapter.phpc2  SXc2  CG         vendor/league/flysystem-webdav    SX              +   vendor/league/flysystem-webdav/changelog.mdC  SXC  %NN      ,   vendor/league/flysystem-webdav/composer.json  SX  h      &   vendor/league/flysystem-webdav/LICENCE"  SX"  Cm,
      "   vendor/league/flysystem-webdav/src    SX              4   vendor/league/flysystem-webdav/src/WebDAVAdapter.php&  SX&  ؜         vendor/mhetreramesh    SX              '   vendor/mhetreramesh/flysystem-backblaze    SX              2   vendor/mhetreramesh/flysystem-backblaze/.gitignore    SX    3      8   vendor/mhetreramesh/flysystem-backblaze/.scrutinizer.yml  SX  ak      3   vendor/mhetreramesh/flysystem-backblaze/.travis.yml_  SX_  z      4   vendor/mhetreramesh/flysystem-backblaze/CHANGELOG.md:  SX:  |      5   vendor/mhetreramesh/flysystem-backblaze/composer.json  SX  Զ      2   vendor/mhetreramesh/flysystem-backblaze/CONDUCT.md  SX  ro      7   vendor/mhetreramesh/flysystem-backblaze/CONTRIBUTING.mdC  SXC  +6F      9   vendor/mhetreramesh/flysystem-backblaze/ISSUE_TEMPLATE.md  SX  ԅ      /   vendor/mhetreramesh/flysystem-backblaze/LICENSE.  SX.  %tQ      8   vendor/mhetreramesh/flysystem-backblaze/phpunit.hhvm.xml  SX  NP      3   vendor/mhetreramesh/flysystem-backblaze/phpunit.php.   SX.         3   vendor/mhetreramesh/flysystem-backblaze/phpunit.xml  SX  G      @   vendor/mhetreramesh/flysystem-backblaze/PULL_REQUEST_TEMPLATE.md  SX  ¹      1   vendor/mhetreramesh/flysystem-backblaze/README.md  SX  y	Y      +   vendor/mhetreramesh/flysystem-backblaze/src    SX              @   vendor/mhetreramesh/flysystem-backblaze/src/BackblazeAdapter.phpm  SXm  2      -   vendor/mhetreramesh/flysystem-backblaze/tests    SX              G   vendor/mhetreramesh/flysystem-backblaze/tests/BackblazeAdapterTests.php  SX  Iߚ         vendor/microsoft    SX                 vendor/microsoft/azure-storage    SX              )   vendor/microsoft/azure-storage/.gitignoreJ   SXJ   ]&<      1   vendor/microsoft/azure-storage/BreakingChanges.md  SX  >|      $   vendor/microsoft/azure-storage/build    SX              .   vendor/microsoft/azure-storage/build/phpcs.xmlb  SXb        .   vendor/microsoft/azure-storage/build/phpmd.xml  SX  Ca      (   vendor/microsoft/azure-storage/build.xml(  SX(  <      +   vendor/microsoft/azure-storage/ChangeLog.md  SX  {	      ,   vendor/microsoft/azure-storage/composer.json'  SX'  xP2      .   vendor/microsoft/azure-storage/CONTRIBUTING.md  SX  r      &   vendor/microsoft/azure-storage/LICENSEK  SXK  +      .   vendor/microsoft/azure-storage/phpdoc.dist.xml  SX        )   vendor/microsoft/azure-storage/phpdox.xmlT  SXT  $      :   vendor/microsoft/azure-storage/phpunit.functional.xml.dist  SX  zmy      /   vendor/microsoft/azure-storage/phpunit.xml.dist  SX        (   vendor/microsoft/azure-storage/README.md.  SX.  0      &   vendor/microsoft/azure-storage/samples    SX              6   vendor/microsoft/azure-storage/samples/BlobSamples.phpw  SXw  ;Զ      7   vendor/microsoft/azure-storage/samples/QueueSamples.php  SX  :t      7   vendor/microsoft/azure-storage/samples/TableSamples.phpH  SXH  G.*      "   vendor/microsoft/azure-storage/src    SX              '   vendor/microsoft/azure-storage/src/Blob    SX              9   vendor/microsoft/azure-storage/src/Blob/BlobRestProxy.phpG SXG 7      0   vendor/microsoft/azure-storage/src/Blob/Internal    SX              :   vendor/microsoft/azure-storage/src/Blob/Internal/IBlob.phpL  SXL  d/۶      .   vendor/microsoft/azure-storage/src/Blob/Models    SX              B   vendor/microsoft/azure-storage/src/Blob/Models/AccessCondition.phpp  SXp  ض      ?   vendor/microsoft/azure-storage/src/Blob/Models/AccessPolicy.phpb  SXb  /      F   vendor/microsoft/azure-storage/src/Blob/Models/AcquireLeaseOptions.phpY  SXY   B      E   vendor/microsoft/azure-storage/src/Blob/Models/AcquireLeaseResult.php	  SX	  >      7   vendor/microsoft/azure-storage/src/Blob/Models/Blob.phpD
  SXD
  g      @   vendor/microsoft/azure-storage/src/Blob/Models/BlobBlockType.php,  SX,  4      =   vendor/microsoft/azure-storage/src/Blob/Models/BlobPrefix.php  SX  R}^      A   vendor/microsoft/azure-storage/src/Blob/Models/BlobProperties.php$  SX$  
e      E   vendor/microsoft/azure-storage/src/Blob/Models/BlobServiceOptions.php  SX  o1[      ;   vendor/microsoft/azure-storage/src/Blob/Models/BlobType.phpW  SXW  ^      8   vendor/microsoft/azure-storage/src/Blob/Models/Block.phpd  SXd  wH      <   vendor/microsoft/azure-storage/src/Blob/Models/BlockList.php  SX  Q      C   vendor/microsoft/azure-storage/src/Blob/Models/BreakLeaseResult.php  SX  !      J   vendor/microsoft/azure-storage/src/Blob/Models/CommitBlobBlocksOptions.php  SX  Clݶ      <   vendor/microsoft/azure-storage/src/Blob/Models/Container.phpI  SXI  .      ?   vendor/microsoft/azure-storage/src/Blob/Models/ContainerACL.phpU  SXU  ?      F   vendor/microsoft/azure-storage/src/Blob/Models/ContainerProperties.php  SX  a"כ      B   vendor/microsoft/azure-storage/src/Blob/Models/CopyBlobOptions.php  SX  &      A   vendor/microsoft/azure-storage/src/Blob/Models/CopyBlobResult.php  SX  ;)      I   vendor/microsoft/azure-storage/src/Blob/Models/CreateBlobBlockOptions.php  SX        D   vendor/microsoft/azure-storage/src/Blob/Models/CreateBlobOptions.php$  SX$  ]      I   vendor/microsoft/azure-storage/src/Blob/Models/CreateBlobPagesOptions.php
  SX
  r7|      H   vendor/microsoft/azure-storage/src/Blob/Models/CreateBlobPagesResult.php  SX        L   vendor/microsoft/azure-storage/src/Blob/Models/CreateBlobSnapshotOptions.php
  SX
  )R      K   vendor/microsoft/azure-storage/src/Blob/Models/CreateBlobSnapshotResult.php  SX  mA      I   vendor/microsoft/azure-storage/src/Blob/Models/CreateContainerOptions.phpd  SXd        D   vendor/microsoft/azure-storage/src/Blob/Models/DeleteBlobOptions.phpK
  SXK
  Z_      I   vendor/microsoft/azure-storage/src/Blob/Models/DeleteContainerOptions.phpU  SXU  c      I   vendor/microsoft/azure-storage/src/Blob/Models/GetBlobMetadataOptions.php
  SX
  Fk      H   vendor/microsoft/azure-storage/src/Blob/Models/GetBlobMetadataResult.php
  SX
  `!4      A   vendor/microsoft/azure-storage/src/Blob/Models/GetBlobOptions.php
  SX
  L      K   vendor/microsoft/azure-storage/src/Blob/Models/GetBlobPropertiesOptions.php
  SX
  .      J   vendor/microsoft/azure-storage/src/Blob/Models/GetBlobPropertiesResult.php  SX  /b      @   vendor/microsoft/azure-storage/src/Blob/Models/GetBlobResult.phpk
  SXk
        H   vendor/microsoft/azure-storage/src/Blob/Models/GetContainerACLResult.php{
  SX{
  ?0y      O   vendor/microsoft/azure-storage/src/Blob/Models/GetContainerPropertiesResult.php  SX  fe      <   vendor/microsoft/azure-storage/src/Blob/Models/LeaseMode.php  SX  j]      H   vendor/microsoft/azure-storage/src/Blob/Models/ListBlobBlocksOptions.php  SX  dʶ      G   vendor/microsoft/azure-storage/src/Blob/Models/ListBlobBlocksResult.phpZ  SXZ  -      C   vendor/microsoft/azure-storage/src/Blob/Models/ListBlobsOptions.php  SX  l      B   vendor/microsoft/azure-storage/src/Blob/Models/ListBlobsResult.php&  SX&   \#,      H   vendor/microsoft/azure-storage/src/Blob/Models/ListContainersOptions.php  SX        G   vendor/microsoft/azure-storage/src/Blob/Models/ListContainersResult.php
  SX
        L   vendor/microsoft/azure-storage/src/Blob/Models/ListPageBlobRangesOptions.php  SX  Hƶ      K   vendor/microsoft/azure-storage/src/Blob/Models/ListPageBlobRangesResult.php'  SX'  LiĶ      <   vendor/microsoft/azure-storage/src/Blob/Models/PageRange.php>  SX>  y      B   vendor/microsoft/azure-storage/src/Blob/Models/PageWriteOption.phpn  SXn  y-׶      C   vendor/microsoft/azure-storage/src/Blob/Models/PublicAccessType.phpw  SXw  
9      I   vendor/microsoft/azure-storage/src/Blob/Models/SetBlobMetadataOptions.php&	  SX&	  _      H   vendor/microsoft/azure-storage/src/Blob/Models/SetBlobMetadataResult.php|  SX|  ׸4      K   vendor/microsoft/azure-storage/src/Blob/Models/SetBlobPropertiesOptions.phpA  SXA  8W*      J   vendor/microsoft/azure-storage/src/Blob/Models/SetBlobPropertiesResult.php  SX  I㮶      N   vendor/microsoft/azure-storage/src/Blob/Models/SetContainerMetadataOptions.php  SX  r      C   vendor/microsoft/azure-storage/src/Blob/Models/SignedIdentifier.php	  SX	  ߑ      )   vendor/microsoft/azure-storage/src/Common    SX              G   vendor/microsoft/azure-storage/src/Common/CloudConfigurationManager.phpY  SXY  Ir%      2   vendor/microsoft/azure-storage/src/Common/Internal    SX              A   vendor/microsoft/azure-storage/src/Common/Internal/Authentication    SX              Q   vendor/microsoft/azure-storage/src/Common/Internal/Authentication/IAuthScheme.php  SX        Y   vendor/microsoft/azure-storage/src/Common/Internal/Authentication/SharedKeyAuthScheme.php  SX  s)      W   vendor/microsoft/azure-storage/src/Common/Internal/Authentication/StorageAuthScheme.phpI  SXI  Q      b   vendor/microsoft/azure-storage/src/Common/Internal/Authentication/TableSharedKeyLiteAuthScheme.php  SX  >      M   vendor/microsoft/azure-storage/src/Common/Internal/ConnectionStringParser.phpU*  SXU*  JO      M   vendor/microsoft/azure-storage/src/Common/Internal/ConnectionStringSource.php3
  SX3
  lfl      H   vendor/microsoft/azure-storage/src/Common/Internal/FilterableService.phpZ  SXZ  mT      :   vendor/microsoft/azure-storage/src/Common/Internal/Filters    SX              S   vendor/microsoft/azure-storage/src/Common/Internal/Filters/AuthenticationFilter.php  SX  
      I   vendor/microsoft/azure-storage/src/Common/Internal/Filters/DateFilter.php  SX  #      U   vendor/microsoft/azure-storage/src/Common/Internal/Filters/ExponentialRetryPolicy.phpj  SXj  h{~      L   vendor/microsoft/azure-storage/src/Common/Internal/Filters/HeadersFilter.php
  SX
  9nS      J   vendor/microsoft/azure-storage/src/Common/Internal/Filters/RetryPolicy.php  SX        P   vendor/microsoft/azure-storage/src/Common/Internal/Filters/RetryPolicyFilter.phpC  SXC  mS      7   vendor/microsoft/azure-storage/src/Common/Internal/Http    SX              K   vendor/microsoft/azure-storage/src/Common/Internal/Http/HttpCallContext.php&  SX&  0kJ      D   vendor/microsoft/azure-storage/src/Common/Internal/HttpFormatter.php
  SX
  _Ѷ      S   vendor/microsoft/azure-storage/src/Common/Internal/InvalidArgumentTypeException.php~  SX~  +      E   vendor/microsoft/azure-storage/src/Common/Internal/IServiceFilter.php[  SX[  "{      =   vendor/microsoft/azure-storage/src/Common/Internal/Logger.php  SX  Yg      @   vendor/microsoft/azure-storage/src/Common/Internal/Resources.phpT  SXT  㱶      @   vendor/microsoft/azure-storage/src/Common/Internal/RestProxy.phpw  SXw  t\      @   vendor/microsoft/azure-storage/src/Common/Internal/Serialization    SX              P   vendor/microsoft/azure-storage/src/Common/Internal/Serialization/ISerializer.php  SX  S̶      S   vendor/microsoft/azure-storage/src/Common/Internal/Serialization/JsonSerializer.phpx  SXx  E{      R   vendor/microsoft/azure-storage/src/Common/Internal/Serialization/XmlSerializer.php  SX  Gm      G   vendor/microsoft/azure-storage/src/Common/Internal/ServiceRestProxy.php5  SX5  8      F   vendor/microsoft/azure-storage/src/Common/Internal/ServiceSettings.php%  SX%        M   vendor/microsoft/azure-storage/src/Common/Internal/StorageServiceSettings.php#8  SX#8  mϘ      @   vendor/microsoft/azure-storage/src/Common/Internal/Utilities.php\Q  SX\Q  a5s      ?   vendor/microsoft/azure-storage/src/Common/Internal/Validate.php*  SX*  O_      0   vendor/microsoft/azure-storage/src/Common/Models    SX              O   vendor/microsoft/azure-storage/src/Common/Models/GetServicePropertiesResult.php}	  SX}	  5	      <   vendor/microsoft/azure-storage/src/Common/Models/Logging.php  SX  nqP      <   vendor/microsoft/azure-storage/src/Common/Models/Metrics.php  SX  C̛Ŷ      D   vendor/microsoft/azure-storage/src/Common/Models/RetentionPolicy.php
  SX
  ,      F   vendor/microsoft/azure-storage/src/Common/Models/ServiceProperties.php  SX        >   vendor/microsoft/azure-storage/src/Common/ServiceException.php-	  SX-	        =   vendor/microsoft/azure-storage/src/Common/ServicesBuilder.phpH)  SXH)  K      (   vendor/microsoft/azure-storage/src/Queue    SX              1   vendor/microsoft/azure-storage/src/Queue/Internal    SX              <   vendor/microsoft/azure-storage/src/Queue/Internal/IQueue.php  SX  H׶      /   vendor/microsoft/azure-storage/src/Queue/Models    SX              H   vendor/microsoft/azure-storage/src/Queue/Models/CreateMessageOptions.php  SX  q      F   vendor/microsoft/azure-storage/src/Queue/Models/CreateQueueOptions.php  SX  [l      J   vendor/microsoft/azure-storage/src/Queue/Models/GetQueueMetadataResult.phpa  SXa  g0      G   vendor/microsoft/azure-storage/src/Queue/Models/ListMessagesOptions.phpu  SXu  /      F   vendor/microsoft/azure-storage/src/Queue/Models/ListMessagesResult.php]  SX]  թﭶ      E   vendor/microsoft/azure-storage/src/Queue/Models/ListQueuesOptions.php  SX        D   vendor/microsoft/azure-storage/src/Queue/Models/ListQueuesResult.php  SX  8L      N   vendor/microsoft/azure-storage/src/Queue/Models/MicrosoftAzureQueueMessage.phpD  SXD  ;/      G   vendor/microsoft/azure-storage/src/Queue/Models/PeekMessagesOptions.php  SX        F   vendor/microsoft/azure-storage/src/Queue/Models/PeekMessagesResult.php  SX  ^gi      9   vendor/microsoft/azure-storage/src/Queue/Models/Queue.php
  SX
  0-      @   vendor/microsoft/azure-storage/src/Queue/Models/QueueMessage.phpM	  SXM	        G   vendor/microsoft/azure-storage/src/Queue/Models/QueueServiceOptions.php  SX  	酶      G   vendor/microsoft/azure-storage/src/Queue/Models/UpdateMessageResult.php(  SX(  L      ;   vendor/microsoft/azure-storage/src/Queue/QueueRestProxy.phpf  SXf        (   vendor/microsoft/azure-storage/src/Table    SX              1   vendor/microsoft/azure-storage/src/Table/Internal    SX              F   vendor/microsoft/azure-storage/src/Table/Internal/AtomReaderWriter.phps*  SXs*  ?P      G   vendor/microsoft/azure-storage/src/Table/Internal/IAtomReaderWriter.php
  SX
  uV      G   vendor/microsoft/azure-storage/src/Table/Internal/IMimeReaderWriter.php  SX  v      <   vendor/microsoft/azure-storage/src/Table/Internal/ITable.php!  SX!  8*      F   vendor/microsoft/azure-storage/src/Table/Internal/MimeReaderWriter.php  SX  c      /   vendor/microsoft/azure-storage/src/Table/Models    SX              >   vendor/microsoft/azure-storage/src/Table/Models/BatchError.phpR
  SXR
  @6{)      B   vendor/microsoft/azure-storage/src/Table/Models/BatchOperation.php  SX  'xͶ      O   vendor/microsoft/azure-storage/src/Table/Models/BatchOperationParameterName.php  SX  RN1      C   vendor/microsoft/azure-storage/src/Table/Models/BatchOperations.phpx  SXx  mQ      F   vendor/microsoft/azure-storage/src/Table/Models/BatchOperationType.php#	  SX#	  v      ?   vendor/microsoft/azure-storage/src/Table/Models/BatchResult.phpD  SXD  ZĶ      G   vendor/microsoft/azure-storage/src/Table/Models/DeleteEntityOptions.phpL  SXL  	Sɶ      ;   vendor/microsoft/azure-storage/src/Table/Models/EdmType.php_  SX_  A      :   vendor/microsoft/azure-storage/src/Table/Models/Entity.phpq  SXq  !      7   vendor/microsoft/azure-storage/src/Table/Models/Filters    SX              H   vendor/microsoft/azure-storage/src/Table/Models/Filters/BinaryFilter.php	  SX	  fU      J   vendor/microsoft/azure-storage/src/Table/Models/Filters/ConstantFilter.php/  SX/  7S      B   vendor/microsoft/azure-storage/src/Table/Models/Filters/Filter.php  SX  `      N   vendor/microsoft/azure-storage/src/Table/Models/Filters/PropertyNameFilter.php  SX  $d      M   vendor/microsoft/azure-storage/src/Table/Models/Filters/QueryStringFilter.php  SX  jL      G   vendor/microsoft/azure-storage/src/Table/Models/Filters/UnaryFilter.php  SX  %      C   vendor/microsoft/azure-storage/src/Table/Models/GetEntityResult.php  SX        B   vendor/microsoft/azure-storage/src/Table/Models/GetTableResult.php	  SX	  nc~      F   vendor/microsoft/azure-storage/src/Table/Models/InsertEntityResult.php9
  SX9
  [yIp      <   vendor/microsoft/azure-storage/src/Table/Models/Property.phpb	  SXb	  yݶ      9   vendor/microsoft/azure-storage/src/Table/Models/Query.php
  SX
  $҅T      H   vendor/microsoft/azure-storage/src/Table/Models/QueryEntitiesOptions.php  SX  W      G   vendor/microsoft/azure-storage/src/Table/Models/QueryEntitiesResult.phpk  SXk  {.      F   vendor/microsoft/azure-storage/src/Table/Models/QueryTablesOptions.php  SX  d̶      E   vendor/microsoft/azure-storage/src/Table/Models/QueryTablesResult.php}  SX}  p2      G   vendor/microsoft/azure-storage/src/Table/Models/TableServiceOptions.php  SX  RŶ      F   vendor/microsoft/azure-storage/src/Table/Models/UpdateEntityResult.php  SX  5      ;   vendor/microsoft/azure-storage/src/Table/TableRestProxy.php-  SX-  !ضw      $   vendor/microsoft/azure-storage/tests    SX              .   vendor/microsoft/azure-storage/tests/framework    SX              O   vendor/microsoft/azure-storage/tests/framework/BlobServiceRestProxyTestBase.php\  SX\  kC      P   vendor/microsoft/azure-storage/tests/framework/QueueServiceRestProxyTestBase.phpM
  SXM
        D   vendor/microsoft/azure-storage/tests/framework/RestProxyTestBase.php
  SX
  0t      K   vendor/microsoft/azure-storage/tests/framework/ServiceRestProxyTestBase.phpv  SXv  Ķ      P   vendor/microsoft/azure-storage/tests/framework/TableServiceRestProxyTestBase.php	  SX	  -ζ      @   vendor/microsoft/azure-storage/tests/framework/TestResources.phpQ  SXQ  |M      D   vendor/microsoft/azure-storage/tests/framework/VirtualFileSystem.php  SX  
      /   vendor/microsoft/azure-storage/tests/functional    SX              4   vendor/microsoft/azure-storage/tests/functional/Blob    SX              R   vendor/microsoft/azure-storage/tests/functional/Blob/BlobServiceFunctionalTest.php SX       V   vendor/microsoft/azure-storage/tests/functional/Blob/BlobServiceFunctionalTestData.phpt  SXt  L."      S   vendor/microsoft/azure-storage/tests/functional/Blob/BlobServiceIntegrationTest.phpv. SXv. m#      K   vendor/microsoft/azure-storage/tests/functional/Blob/FunctionalTestBase.php  SX  :x      L   vendor/microsoft/azure-storage/tests/functional/Blob/IntegrationTestBase.php  SX  
ŋ      5   vendor/microsoft/azure-storage/tests/functional/Queue    SX              L   vendor/microsoft/azure-storage/tests/functional/Queue/FunctionalTestBase.php	  SX	        M   vendor/microsoft/azure-storage/tests/functional/Queue/IntegrationTestBase.php*  SX*  SP      [   vendor/microsoft/azure-storage/tests/functional/Queue/QueueServiceFunctionalOptionsTest.phpk)  SXk)        ]   vendor/microsoft/azure-storage/tests/functional/Queue/QueueServiceFunctionalParameterTest.php|  SX|  ʘDL      T   vendor/microsoft/azure-storage/tests/functional/Queue/QueueServiceFunctionalTest.php SX n      X   vendor/microsoft/azure-storage/tests/functional/Queue/QueueServiceFunctionalTestData.php9%  SX9%  Ͷ      U   vendor/microsoft/azure-storage/tests/functional/Queue/QueueServiceIntegrationTest.phpll  SXll  :)s      5   vendor/microsoft/azure-storage/tests/functional/Table    SX              ;   vendor/microsoft/azure-storage/tests/functional/Table/Enums    SX              J   vendor/microsoft/azure-storage/tests/functional/Table/Enums/ConcurType.php:  SX:  eض      K   vendor/microsoft/azure-storage/tests/functional/Table/Enums/MutatePivot.php  SX  fCM      F   vendor/microsoft/azure-storage/tests/functional/Table/Enums/OpType.php  SX  _F      L   vendor/microsoft/azure-storage/tests/functional/Table/FunctionalTestBase.php]  SX]  /k      M   vendor/microsoft/azure-storage/tests/functional/Table/IntegrationTestBase.php*  SX*  п      <   vendor/microsoft/azure-storage/tests/functional/Table/Models    SX              R   vendor/microsoft/azure-storage/tests/functional/Table/Models/BatchWorkerConfig.php>  SX>  h      S   vendor/microsoft/azure-storage/tests/functional/Table/Models/FakeTableInfoEntry.php  SX  K      [   vendor/microsoft/azure-storage/tests/functional/Table/TableServiceFunctionalOptionsTest.phpRQ  SXRQ        ^   vendor/microsoft/azure-storage/tests/functional/Table/TableServiceFunctionalParametersTest.php  SX  
K      Y   vendor/microsoft/azure-storage/tests/functional/Table/TableServiceFunctionalQueryTest.phpd  SXd  -       T   vendor/microsoft/azure-storage/tests/functional/Table/TableServiceFunctionalTest.phpdH SXdH ,{>      X   vendor/microsoft/azure-storage/tests/functional/Table/TableServiceFunctionalTestData.phpN  SXN  .      Y   vendor/microsoft/azure-storage/tests/functional/Table/TableServiceFunctionalTestUtils.phpC  SXC  %      U   vendor/microsoft/azure-storage/tests/functional/Table/TableServiceIntegrationTest.phpq  SXq  eS      )   vendor/microsoft/azure-storage/tests/mock    SX              0   vendor/microsoft/azure-storage/tests/mock/Common    SX              9   vendor/microsoft/azure-storage/tests/mock/Common/Internal    SX              H   vendor/microsoft/azure-storage/tests/mock/Common/Internal/Authentication    SX              \   vendor/microsoft/azure-storage/tests/mock/Common/Internal/Authentication/OAuthSchemeMock.php  SX  y      d   vendor/microsoft/azure-storage/tests/mock/Common/Internal/Authentication/SharedKeyAuthSchemeMock.php  SX  8ֶ      b   vendor/microsoft/azure-storage/tests/mock/Common/Internal/Authentication/StorageAuthSchemeMock.php	  SX	  /pb      m   vendor/microsoft/azure-storage/tests/mock/Common/Internal/Authentication/TableSharedKeyLiteAuthSchemeMock.php  SX  zs      A   vendor/microsoft/azure-storage/tests/mock/Common/Internal/Filters    SX              V   vendor/microsoft/azure-storage/tests/mock/Common/Internal/Filters/SimpleFilterMock.php(  SX(  ķ      )   vendor/microsoft/azure-storage/tests/unit    SX              .   vendor/microsoft/azure-storage/tests/unit/Blob    SX              D   vendor/microsoft/azure-storage/tests/unit/Blob/BlobRestProxyTest.php`  SX`  R(      5   vendor/microsoft/azure-storage/tests/unit/Blob/Models    SX              M   vendor/microsoft/azure-storage/tests/unit/Blob/Models/AccessConditionTest.php  SX  ^M¶      J   vendor/microsoft/azure-storage/tests/unit/Blob/Models/AccessPolicyTest.php  SX  h      Q   vendor/microsoft/azure-storage/tests/unit/Blob/Models/AcquireLeaseOptionsTest.php	  SX	  䃶      P   vendor/microsoft/azure-storage/tests/unit/Blob/Models/AcquireLeaseResultTest.phpU	  SXU	  6      K   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BlobBlockTypeTest.php  SX   p      H   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BlobPrefixTest.php  SX  /F      L   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BlobPropertiesTest.php%  SX%  []      P   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BlobServiceOptionsTest.php  SX  .      B   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BlobTest.php]  SX]  $      F   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BlobTypeTest.php  SX  4      G   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BlockListTest.phpc  SXc  /Q      C   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BlockTest.php  SX  }j      N   vendor/microsoft/azure-storage/tests/unit/Blob/Models/BreakLeaseResultTest.phpR	  SXR	  BK0      U   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CommitBlobBlocksOptionsTest.php:  SX:  Β<      J   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ContainerACLTest.php  SX  ؒն      Q   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ContainerPropertiesTest.php  SX  
      G   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ContainerTest.php  SX  9	      M   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CopyBlobOptionsTest.php  SX  ɿ      L   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CopyBlobResultTest.php
  SX
  sk      T   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CreateBlobBlockOptionsTest.php
  SX
  hG      O   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CreateBlobOptionsTest.php&(  SX&(  ]      T   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CreateBlobPagesOptionsTest.php
  SX
         S   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CreateBlobPagesResultTest.php  SX  [׶      W   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CreateBlobSnapshotOptionsTest.php
  SX
  Lﳶ      V   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CreateBlobSnapshotResultTest.php  SX  jo      T   vendor/microsoft/azure-storage/tests/unit/Blob/Models/CreateContainerOptionsTest.php  SX  #ǋ+      O   vendor/microsoft/azure-storage/tests/unit/Blob/Models/DeleteBlobOptionsTest.phpK  SXK  FX]      T   vendor/microsoft/azure-storage/tests/unit/Blob/Models/DeleteContainerOptionsTest.php	  SX	  X'      T   vendor/microsoft/azure-storage/tests/unit/Blob/Models/GetBlobMetadataOptionsTest.phpL  SXL  |.      S   vendor/microsoft/azure-storage/tests/unit/Blob/Models/GetBlobMetadataResultTest.php  SX  (&ö      L   vendor/microsoft/azure-storage/tests/unit/Blob/Models/GetBlobOptionsTest.phpi  SXi  ՑJ      V   vendor/microsoft/azure-storage/tests/unit/Blob/Models/GetBlobPropertiesOptionsTest.phph  SXh  P      U   vendor/microsoft/azure-storage/tests/unit/Blob/Models/GetBlobPropertiesResultTest.php1
  SX1
  hA      K   vendor/microsoft/azure-storage/tests/unit/Blob/Models/GetBlobResultTest.php  SX  5      S   vendor/microsoft/azure-storage/tests/unit/Blob/Models/GetContainerACLResultTest.php,  SX,  P      Z   vendor/microsoft/azure-storage/tests/unit/Blob/Models/GetContainerPropertiesResultTest.php  SX  D
z      G   vendor/microsoft/azure-storage/tests/unit/Blob/Models/LeaseModeTest.php  SX  y      S   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ListBlobBlocksOptionsTest.phpn  SXn  d,w      R   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ListBlobBlocksResultTest.php  SX  j      N   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ListBlobsOptionsTest.phpL  SXL  0      M   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ListBlobsResultTest.php&(  SX&(  ;#      S   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ListContainersOptionsTest.phpE  SXE         R   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ListContainersResultTest.php"(  SX"(  '      W   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ListPageBlobRangesOptionsTest.php  SX  c      V   vendor/microsoft/azure-storage/tests/unit/Blob/Models/ListPageBlobRangesResultTest.php  SX  Ҷ      G   vendor/microsoft/azure-storage/tests/unit/Blob/Models/PageRangeTest.php
  SX
  >Ow      N   vendor/microsoft/azure-storage/tests/unit/Blob/Models/PublicAccessTypeTest.phpH  SXH  *]      T   vendor/microsoft/azure-storage/tests/unit/Blob/Models/SetBlobMetadataOptionsTest.php  SX  4{-ʶ      S   vendor/microsoft/azure-storage/tests/unit/Blob/Models/SetBlobMetadataResultTest.php
  SX
        V   vendor/microsoft/azure-storage/tests/unit/Blob/Models/SetBlobPropertiesOptionsTest.php!  SX!  E      U   vendor/microsoft/azure-storage/tests/unit/Blob/Models/SetBlobPropertiesResultTest.php  SX  h
      Y   vendor/microsoft/azure-storage/tests/unit/Blob/Models/SetContainerMetadataOptionsTest.php
  SX
  V	      N   vendor/microsoft/azure-storage/tests/unit/Blob/Models/SignedIdentifierTest.php  SX  e2<      0   vendor/microsoft/azure-storage/tests/unit/Common    SX              R   vendor/microsoft/azure-storage/tests/unit/Common/CloudConfigurationManagerTest.php|  SX|        9   vendor/microsoft/azure-storage/tests/unit/Common/Internal    SX              H   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Authentication    SX              d   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Authentication/SharedKeyAuthSchemeTest.phpX  SXX  5Ԟ      b   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Authentication/StorageAuthSchemeTest.phpZ  SXZ  ,+      m   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Authentication/TableSharedKeyLiteAuthSchemeTest.phpP  SXP  @}m϶      X   vendor/microsoft/azure-storage/tests/unit/Common/Internal/ConnectionStringParserTest.php))  SX))  oJ      X   vendor/microsoft/azure-storage/tests/unit/Common/Internal/ConnectionStringSourceTest.phpi  SXi  C      A   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Filters    SX              ^   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Filters/AuthenticationFilterTest.php>  SX>        T   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Filters/DateFilterTest.php
  SX
  Զ      `   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Filters/ExponentialRetryPolicyTest.php
  SX
  cw      W   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Filters/HeadersFilterTest.php  SX  Ǒ~Ŷ      >   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Http    SX              V   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Http/HttpCallContextTest.php  SX  ʚ      ^   vendor/microsoft/azure-storage/tests/unit/Common/Internal/InvalidArgumentTypeExceptionTest.php  SX  kHڶ      H   vendor/microsoft/azure-storage/tests/unit/Common/Internal/LoggerTest.php
  SX
  h      G   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Serialization    SX              V   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Serialization/DummyClass.php  SX        ^   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Serialization/JsonSerializerTest.php  SX  Ӽ]      ]   vendor/microsoft/azure-storage/tests/unit/Common/Internal/Serialization/XmlSerializerTest.php  SX  +      R   vendor/microsoft/azure-storage/tests/unit/Common/Internal/ServiceRestProxyTest.phpf(  SXf(  +      X   vendor/microsoft/azure-storage/tests/unit/Common/Internal/StorageServiceSettingsTest.php  SX        K   vendor/microsoft/azure-storage/tests/unit/Common/Internal/UtilitiesTest.phpO  SXO  +<      J   vendor/microsoft/azure-storage/tests/unit/Common/Internal/ValidateTest.php7  SX7  |ٶ      7   vendor/microsoft/azure-storage/tests/unit/Common/Models    SX              Z   vendor/microsoft/azure-storage/tests/unit/Common/Models/GetServicePropertiesResultTest.phpy  SXy  $6      G   vendor/microsoft/azure-storage/tests/unit/Common/Models/LoggingTest.phpw   SXw   1\      G   vendor/microsoft/azure-storage/tests/unit/Common/Models/MetricsTest.phpn  SXn  3tŶ      O   vendor/microsoft/azure-storage/tests/unit/Common/Models/RetentionPolicyTest.phpN  SXN  eն      Q   vendor/microsoft/azure-storage/tests/unit/Common/Models/ServicePropertiesTest.php  SX  e^      I   vendor/microsoft/azure-storage/tests/unit/Common/ServiceExceptionTest.phpr  SXr  M2      H   vendor/microsoft/azure-storage/tests/unit/Common/ServicesBuilderTest.php  SX  z      /   vendor/microsoft/azure-storage/tests/unit/Queue    SX              6   vendor/microsoft/azure-storage/tests/unit/Queue/Models    SX              S   vendor/microsoft/azure-storage/tests/unit/Queue/Models/CreateMessageOptionsTest.php
  SX
  M35      Q   vendor/microsoft/azure-storage/tests/unit/Queue/Models/CreateQueueOptionsTest.php   SX   p3[      U   vendor/microsoft/azure-storage/tests/unit/Queue/Models/GetQueueMetadataResultTest.php  SX  P      R   vendor/microsoft/azure-storage/tests/unit/Queue/Models/ListMessagesOptionsTest.php
  SX
  yƶ      Q   vendor/microsoft/azure-storage/tests/unit/Queue/Models/ListMessagesResultTest.php  SX  FԘ      P   vendor/microsoft/azure-storage/tests/unit/Queue/Models/ListQueuesOptionsTest.php  SX  > ݶ      O   vendor/microsoft/azure-storage/tests/unit/Queue/Models/ListQueuesResultTest.php"  SX"  ps\      Y   vendor/microsoft/azure-storage/tests/unit/Queue/Models/MicrosoftAzureQueueMessageTest.phpx*  SXx*  
      R   vendor/microsoft/azure-storage/tests/unit/Queue/Models/PeekMessagesOptionsTest.php	  SX	  S9      Q   vendor/microsoft/azure-storage/tests/unit/Queue/Models/PeekMessagesResultTest.php?  SX?  \      K   vendor/microsoft/azure-storage/tests/unit/Queue/Models/QueueMessageTest.phpv  SXv  il      R   vendor/microsoft/azure-storage/tests/unit/Queue/Models/QueueServiceOptionsTest.php  SX  U      D   vendor/microsoft/azure-storage/tests/unit/Queue/Models/QueueTest.phpn  SXn  !N      R   vendor/microsoft/azure-storage/tests/unit/Queue/Models/UpdateMessageResultTest.phpC  SXC  V      F   vendor/microsoft/azure-storage/tests/unit/Queue/QueueRestProxyTest.phpdY  SXdY  Ic      /   vendor/microsoft/azure-storage/tests/unit/Table    SX              8   vendor/microsoft/azure-storage/tests/unit/Table/Internal    SX              Q   vendor/microsoft/azure-storage/tests/unit/Table/Internal/AtomReaderWriterTest.php8B  SX8B  u&      6   vendor/microsoft/azure-storage/tests/unit/Table/Models    SX              I   vendor/microsoft/azure-storage/tests/unit/Table/Models/BatchErrorTest.php<  SX<  J7      Z   vendor/microsoft/azure-storage/tests/unit/Table/Models/BatchOperationParameterNameTest.php  SX  z&x      N   vendor/microsoft/azure-storage/tests/unit/Table/Models/BatchOperationsTest.php9  SX9  "`      M   vendor/microsoft/azure-storage/tests/unit/Table/Models/BatchOperationTest.php_
  SX_
  J      Q   vendor/microsoft/azure-storage/tests/unit/Table/Models/BatchOperationTypeTest.phpx  SXx  ʎ      J   vendor/microsoft/azure-storage/tests/unit/Table/Models/BatchResultTest.phpm  SXm  7+      R   vendor/microsoft/azure-storage/tests/unit/Table/Models/DeleteEntityOptionsTest.php  SX  ն      F   vendor/microsoft/azure-storage/tests/unit/Table/Models/EdmTypeTest.php?%  SX?%  qu      E   vendor/microsoft/azure-storage/tests/unit/Table/Models/EntityTest.php  SX  ж      >   vendor/microsoft/azure-storage/tests/unit/Table/Models/Filters    SX              S   vendor/microsoft/azure-storage/tests/unit/Table/Models/Filters/BinaryFilterTest.php
  SX
        U   vendor/microsoft/azure-storage/tests/unit/Table/Models/Filters/ConstantFilterTest.phpU	  SXU	  
χ      M   vendor/microsoft/azure-storage/tests/unit/Table/Models/Filters/FilterTest.php  SX  IY¶      Y   vendor/microsoft/azure-storage/tests/unit/Table/Models/Filters/PropertyNameFilterTest.php  SX  	㠶      X   vendor/microsoft/azure-storage/tests/unit/Table/Models/Filters/QueryStringFilterTest.php  SX  w      R   vendor/microsoft/azure-storage/tests/unit/Table/Models/Filters/UnaryFilterTest.php	  SX	  ^      N   vendor/microsoft/azure-storage/tests/unit/Table/Models/GetEntityResultTest.php  SX  BO8      M   vendor/microsoft/azure-storage/tests/unit/Table/Models/GetTableResultTest.phpb  SXb  膱      Q   vendor/microsoft/azure-storage/tests/unit/Table/Models/InsertEntityResultTest.php  SX  j      G   vendor/microsoft/azure-storage/tests/unit/Table/Models/PropertyTest.php,	  SX,	  OEyC      S   vendor/microsoft/azure-storage/tests/unit/Table/Models/QueryEntitiesOptionsTest.php  SX  _c      R   vendor/microsoft/azure-storage/tests/unit/Table/Models/QueryEntitiesResultTest.php<
  SX<
  `      Q   vendor/microsoft/azure-storage/tests/unit/Table/Models/QueryTablesOptionsTest.php  SX        P   vendor/microsoft/azure-storage/tests/unit/Table/Models/QueryTablesResultTest.php	  SX	  >      D   vendor/microsoft/azure-storage/tests/unit/Table/Models/QueryTest.php?
  SX?
  &0      R   vendor/microsoft/azure-storage/tests/unit/Table/Models/TableServiceOptionsTest.php  SX  ^ԡ      Q   vendor/microsoft/azure-storage/tests/unit/Table/Models/UpdateEntityResultTest.php  SX  œ      F   vendor/microsoft/azure-storage/tests/unit/Table/TableRestProxyTest.php  SX  AJ         vendor/monolog    SX                 vendor/monolog/monolog    SX                 vendor/monolog/monolog/.php_csL  SXL  w      &   vendor/monolog/monolog/CHANGELOG.mdownH  SXH  z7$W      $   vendor/monolog/monolog/composer.json	  SX	  5L95         vendor/monolog/monolog/doc    SX              &   vendor/monolog/monolog/doc/01-usage.md!  SX!  A      ?   vendor/monolog/monolog/doc/02-handlers-formatters-processors.md&  SX&        *   vendor/monolog/monolog/doc/03-utilities.md  SX  8Kiȶ      *   vendor/monolog/monolog/doc/04-extending.mdo  SXo        %   vendor/monolog/monolog/doc/sockets.md  SX  .\Y         vendor/monolog/monolog/LICENSE'  SX'  1K      '   vendor/monolog/monolog/phpunit.xml.dist  SX  F¬      #   vendor/monolog/monolog/README.mdown  SX  @ї         vendor/monolog/monolog/src    SX              "   vendor/monolog/monolog/src/Monolog    SX              3   vendor/monolog/monolog/src/Monolog/ErrorHandler.php  SX  u<<      ,   vendor/monolog/monolog/src/Monolog/Formatter    SX              C   vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php  SX  ;`ai      B   vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php-  SX-        B   vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php	  SX	  }*3      A   vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.phpF  SXF  {l      C   vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php  SX  Wܑ      E   vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php7  SX7  UxX      >   vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php  SX  i      >   vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.phpG  SXG  
SS      >   vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php	  SX	  m      @   vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php*  SX*  w      B   vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php  SX  .hݶ      A   vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php  SX  Y\      D   vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php$  SX$  q      @   vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php  SX  ^      B   vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php  SX  
J      *   vendor/monolog/monolog/src/Monolog/Handler    SX              >   vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php  SX  2!G      H   vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php  SX  o@      D   vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php6
  SX6
        :   vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php  SX  zYM      D   vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php  SX  	      <   vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.phpl
  SXl
  o      ?   vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php  SX  gi      =   vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php  SX  c#      :   vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php  SX  9      /   vendor/monolog/monolog/src/Monolog/Handler/Curl    SX              8   vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php  SX        C   vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.phpg  SXg  wU<      E   vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php  SX  ~      >   vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php	  SX	        C   vendor/monolog/monolog/src/Monolog/Handler/ElasticSearchHandler.phpY
  SXY
         >   vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.phpL	  SXL	  U߶      <   vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.phpI  SXI  n      9   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed    SX              Y   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php  SX  S      \   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php  SX  oV      Z   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php  SX  V	vܶ      D   vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php  SX  "l      =   vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.phpZ  SXZ  KE      ?   vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php#
  SX#
  3ֶ      >   vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php
  SX
  m      :   vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php  SX        ;   vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php	  SX	  Z$      ?   vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php%
  SX%
        =   vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php=  SX=  J
=      =   vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.phpk(  SXk(  ֿ      ;   vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.phpF  SXF  LKn      @   vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.phpA  SXA  g      <   vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php;
  SX;
  W߶      :   vendor/monolog/monolog/src/Monolog/Handler/MailHandler.phpV  SXV  YyW      >   vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.phpl  SXl  !w      H   vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php  SX  ]      =   vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.phpF  SXF  /g      B   vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.phpV  SXV  Ŷ      >   vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php  SX  ua      :   vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php  SX  uf      @   vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php'  SX'  ns|ʶ      9   vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php  SX  <|      >   vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php  SX  ;}      ;   vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php  SX  ߶      ;   vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.phpI  SXI   nL      =   vendor/monolog/monolog/src/Monolog/Handler/RollbarHandler.php  SX  TI      B   vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php7  SX7  k˖l      >   vendor/monolog/monolog/src/Monolog/Handler/SamplingHandler.phpr
  SXr
  Y      0   vendor/monolog/monolog/src/Monolog/Handler/Slack    SX              @   vendor/monolog/monolog/src/Monolog/Handler/Slack/SlackRecord.php  SX        >   vendor/monolog/monolog/src/Monolog/Handler/SlackbotHandler.php  SX  ot	      ;   vendor/monolog/monolog/src/Monolog/Handler/SlackHandler.php  SX  O      B   vendor/monolog/monolog/src/Monolog/Handler/SlackWebhookHandler.php|
  SX|
  9      <   vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php"  SX"  iF      <   vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.phpS  SXS  dJ      A   vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.phpv  SXv  	}7~      <   vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php8  SX8  eO      4   vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp    SX              B   vendor/monolog/monolog/src/Monolog/Handler/SyslogUdp/UdpSocket.phpy  SXy  kM      ?   vendor/monolog/monolog/src/Monolog/Handler/SyslogUdpHandler.php
  SX
  kt      :   vendor/monolog/monolog/src/Monolog/Handler/TestHandler.phpD  SXD  }皶      F   vendor/monolog/monolog/src/Monolog/Handler/WhatFailureGroupHandler.php  SX  Sڶ      A   vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php  SX  R޽      -   vendor/monolog/monolog/src/Monolog/Logger.phphK  SXhK  [*      ,   vendor/monolog/monolog/src/Monolog/Processor    SX              =   vendor/monolog/monolog/src/Monolog/Processor/GitProcessor.php~  SX~  	      G   vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php  SX        I   vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php  SX  @      @   vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php  SX  -}      E   vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php  SX  aoOD      C   vendor/monolog/monolog/src/Monolog/Processor/MercurialProcessor.phpe  SXe  ̋      C   vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php>  SX>  )V+      G   vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php  SX  kw      =   vendor/monolog/monolog/src/Monolog/Processor/TagProcessor.php4  SX4  iG      =   vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php  SX  6ڌ      =   vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php  SX  ~L^      /   vendor/monolog/monolog/src/Monolog/Registry.php  SX  ն         vendor/monolog/monolog/tests    SX              $   vendor/monolog/monolog/tests/Monolog    SX                 vendor/mtdowling    SX                 vendor/mtdowling/jmespath.php    SX              (   vendor/mtdowling/jmespath.php/.gitignore5   SX5   x.]      )   vendor/mtdowling/jmespath.php/.travis.yml   SX         !   vendor/mtdowling/jmespath.php/bin    SX              *   vendor/mtdowling/jmespath.php/bin/perf.php  SX        *   vendor/mtdowling/jmespath.php/CHANGELOG.md  SX        +   vendor/mtdowling/jmespath.php/composer.json  SX  Jڶ      %   vendor/mtdowling/jmespath.php/LICENSEA  SXA  %
      &   vendor/mtdowling/jmespath.php/Makefile	  SX	  }C#      .   vendor/mtdowling/jmespath.php/phpunit.xml.distf  SXf  k"m      (   vendor/mtdowling/jmespath.php/README.rst  SX  i}޶      !   vendor/mtdowling/jmespath.php/src    SX              0   vendor/mtdowling/jmespath.php/src/AstRuntime.php  SX        5   vendor/mtdowling/jmespath.php/src/CompilerRuntime.php4
  SX4
         2   vendor/mtdowling/jmespath.php/src/DebugRuntime.phpp  SXp  -      )   vendor/mtdowling/jmespath.php/src/Env.php  SX  ڰ      2   vendor/mtdowling/jmespath.php/src/FnDispatcher.php/  SX/  )9      .   vendor/mtdowling/jmespath.php/src/JmesPath.phpz  SXz  ftO      +   vendor/mtdowling/jmespath.php/src/Lexer.php;  SX;  Hd      ,   vendor/mtdowling/jmespath.php/src/Parser.php7  SX7  W^޶      :   vendor/mtdowling/jmespath.php/src/SyntaxErrorException.phpf  SXf  Wk      2   vendor/mtdowling/jmespath.php/src/TreeCompiler.php3  SX3  馇U      5   vendor/mtdowling/jmespath.php/src/TreeInterpreter.php  SX  ̔w8      +   vendor/mtdowling/jmespath.php/src/Utils.php  SX  N      #   vendor/mtdowling/jmespath.php/tests    SX                 vendor/phpseclib    SX                 vendor/phpseclib/phpseclib    SX              "   vendor/phpseclib/phpseclib/AUTHORS  SX  (@      (   vendor/phpseclib/phpseclib/composer.json  SX  !      (   vendor/phpseclib/phpseclib/composer.lock  SX        "   vendor/phpseclib/phpseclib/LICENSEX  SXX  iP      $   vendor/phpseclib/phpseclib/phpseclib    SX              2   vendor/phpseclib/phpseclib/phpseclib/bootstrap.php  SX  Ŷ      *   vendor/phpseclib/phpseclib/phpseclib/Crypt    SX              2   vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.phpu  SXu  b      3   vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.phpz SXz Zm1      7   vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.phpef  SXef  D鎶      2   vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.phpA SXA ch      3   vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php=k  SX=k  +      5   vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php/  SX/  2Զ      2   vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.phpX  SXX  B$wV      2   vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php!  SX!        7   vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php5  SX5        2   vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php( SX( c      8   vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php6  SX6  ѕ      6   vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.phpƐ  SXƐ        )   vendor/phpseclib/phpseclib/phpseclib/File    SX              2   vendor/phpseclib/phpseclib/phpseclib/File/ANSI.phpN  SXN        .   vendor/phpseclib/phpseclib/phpseclib/File/ASN1    SX              :   vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.phpf  SXf        2   vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php  SX  
C`      2   vendor/phpseclib/phpseclib/phpseclib/File/X509.php SX 6      )   vendor/phpseclib/phpseclib/phpseclib/Math    SX              8   vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php SX (J      (   vendor/phpseclib/phpseclib/phpseclib/Net    SX              0   vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php"  SX"  -      -   vendor/phpseclib/phpseclib/phpseclib/Net/SFTP    SX              8   vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.phpU  SXU  @mѶ      1   vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.phpm SXm {,J      1   vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php  SX  k      1   vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php* SX* ^G      0   vendor/phpseclib/phpseclib/phpseclib/openssl.cnfh   SXh         +   vendor/phpseclib/phpseclib/phpseclib/System    SX              /   vendor/phpseclib/phpseclib/phpseclib/System/SSH    SX              5   vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent    SX              B   vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.phpF  SXF  S)]      9   vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php!  SX!  ~      $   vendor/phpseclib/phpseclib/README.md  SX  >      
   vendor/psr    SX                 vendor/psr/http-message    SX              $   vendor/psr/http-message/CHANGELOG.md3  SX3  :\Y      %   vendor/psr/http-message/composer.jsonm  SXm           vendor/psr/http-message/LICENSE=  SX=        !   vendor/psr/http-message/README.mdf  SXf  h         vendor/psr/http-message/src    SX              0   vendor/psr/http-message/src/MessageInterface.php  SX  z /      0   vendor/psr/http-message/src/RequestInterface.php  SX   Զ      1   vendor/psr/http-message/src/ResponseInterface.php
  SX
  -{      6   vendor/psr/http-message/src/ServerRequestInterface.phpr'  SXr'  _      /   vendor/psr/http-message/src/StreamInterface.php  SX  =fbr      5   vendor/psr/http-message/src/UploadedFileInterface.phpQ  SXQ  㭢v      ,   vendor/psr/http-message/src/UriInterface.php?1  SX?1  ?.         vendor/psr/log    SX                 vendor/psr/log/.gitignore   SX   sf         vendor/psr/log/composer.json1  SX1  
         vendor/psr/log/LICENSE=  SX=  pO         vendor/psr/log/Psr    SX                 vendor/psr/log/Psr/Log    SX              )   vendor/psr/log/Psr/Log/AbstractLogger.php  SX  Gl      3   vendor/psr/log/Psr/Log/InvalidArgumentException.php`   SX`    X1      /   vendor/psr/log/Psr/Log/LoggerAwareInterface.php)  SX)  j      +   vendor/psr/log/Psr/Log/LoggerAwareTrait.php  SX  z%      *   vendor/psr/log/Psr/Log/LoggerInterface.php  SX  ?}      &   vendor/psr/log/Psr/Log/LoggerTrait.php
  SX
  ý      #   vendor/psr/log/Psr/Log/LogLevel.phpP  SXP        %   vendor/psr/log/Psr/Log/NullLogger.php  SX  Zf         vendor/psr/log/Psr/Log/Test    SX              3   vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php  SX  $s         vendor/psr/log/README.md@  SX@  !?o         vendor/sabre    SX                 vendor/sabre/dav    SX                 vendor/sabre/dav/.gitignore  SX  Z         vendor/sabre/dav/.travis.yml  SX  6"         vendor/sabre/dav/bin    SX                 vendor/sabre/dav/bin/build.phps  SXs  .Lr      )   vendor/sabre/dav/bin/googlecode_upload.py"  SX"  \	      $   vendor/sabre/dav/bin/migrateto20.php1  SX1  c      $   vendor/sabre/dav/bin/migrateto21.phpD  SXD  {      $   vendor/sabre/dav/bin/migrateto30.php  SX  jx`      $   vendor/sabre/dav/bin/migrateto32.phpj  SXj  Ȳ      !   vendor/sabre/dav/bin/sabredav.php'  SX'  ]¶         vendor/sabre/dav/CHANGELOG.mdi~ SXi~ E$         vendor/sabre/dav/composer.json  SX  M          vendor/sabre/dav/CONTRIBUTING.mdH  SXH  'K¶         vendor/sabre/dav/lib    SX                 vendor/sabre/dav/lib/CalDAV    SX              #   vendor/sabre/dav/lib/CalDAV/Backend    SX              7   vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.phpq  SXq  "&      8   vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php'  SX'  O_e1      ;   vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php7  SX7  R      +   vendor/sabre/dav/lib/CalDAV/Backend/PDO.php  SX  O      9   vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php   SX   Dt      6   vendor/sabre/dav/lib/CalDAV/Backend/SharingSupport.php  SX        1   vendor/sabre/dav/lib/CalDAV/Backend/SimplePDO.phpb&  SXb&  W"      ;   vendor/sabre/dav/lib/CalDAV/Backend/SubscriptionSupport.phpQ  SXQ  	5      3   vendor/sabre/dav/lib/CalDAV/Backend/SyncSupport.php
  SX
  NQ      (   vendor/sabre/dav/lib/CalDAV/Calendar.php2  SX2        ,   vendor/sabre/dav/lib/CalDAV/CalendarHome.phpx+  SXx+  &eB      .   vendor/sabre/dav/lib/CalDAV/CalendarObject.php  SX   ޶      6   vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php1  SX1        ,   vendor/sabre/dav/lib/CalDAV/CalendarRoot.php  SX  E|-      %   vendor/sabre/dav/lib/CalDAV/Exception    SX              >   vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.phpY  SXY  3
      )   vendor/sabre/dav/lib/CalDAV/ICalendar.php  SX  3      /   vendor/sabre/dav/lib/CalDAV/ICalendarObject.php  SX  >      8   vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php  SX  4W      /   vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php2  SX2  %϶      /   vendor/sabre/dav/lib/CalDAV/ISharedCalendar.phpx  SXx  >z      )   vendor/sabre/dav/lib/CalDAV/Notifications    SX              8   vendor/sabre/dav/lib/CalDAV/Notifications/Collection.phpf  SXf  F%      9   vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.phpf  SXf        3   vendor/sabre/dav/lib/CalDAV/Notifications/INode.php  SX  ̵      2   vendor/sabre/dav/lib/CalDAV/Notifications/Node.php6
  SX6
  ^      4   vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.phps  SXs  ݜ      &   vendor/sabre/dav/lib/CalDAV/Plugin.php  SX  K$      %   vendor/sabre/dav/lib/CalDAV/Principal    SX              4   vendor/sabre/dav/lib/CalDAV/Principal/Collection.php6  SX6  af      4   vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php  SX  I2Y      5   vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php  SX  kŶ      3   vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.phpP  SXP  v      4   vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php?  SX?  t$Q̶      .   vendor/sabre/dav/lib/CalDAV/Principal/User.php  SX  ޶      $   vendor/sabre/dav/lib/CalDAV/Schedule    SX              /   vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php  SX  P      3   vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php9  SX9  H      .   vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php  SX        0   vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php{  SX{  蚶      :   vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php@  SX@  6      /   vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php\  SX\  CO@      /   vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php  SX  K      9   vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php  SX  sc      .   vendor/sabre/dav/lib/CalDAV/SharedCalendar.php  SX  _Vy      -   vendor/sabre/dav/lib/CalDAV/SharingPlugin.php75  SX75  Sƶ      )   vendor/sabre/dav/lib/CalDAV/Subscriptions    SX              ;   vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php  SX  I      4   vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.phpJ  SXJ  K      :   vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php  SX  ɕ>         vendor/sabre/dav/lib/CalDAV/Xml    SX              &   vendor/sabre/dav/lib/CalDAV/Xml/Filter    SX              7   vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php  SX  L%      5   vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php
  SX
  Ex      6   vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php	  SX	  ꁶ      5   vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php
  SX
  ]ߪ      ,   vendor/sabre/dav/lib/CalDAV/Xml/Notification    SX              7   vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php~   SX~   9C      <   vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php  SX  P h      F   vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php  SX  dS      =   vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.phpF  SXF  prA      (   vendor/sabre/dav/lib/CalDAV/Xml/Property    SX              @   vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.phpK	  SXK	        <   vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php  SX  l      3   vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php  SX  '}      C   vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php  SX  nΞ      J   vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.phpN
  SXN
  y      B   vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.phpS  SXS  Y=      B   vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php   SX   tq      '   vendor/sabre/dav/lib/CalDAV/Xml/Request    SX              B   vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php
  SX
  2dST      ?   vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php  SX  Ƚ7      ?   vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php	  SX	  @d      7   vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.phpW  SXW  
Ͷ      6   vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php@  SX@        1   vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php
  SX
  <
         vendor/sabre/dav/lib/CardDAV    SX              ,   vendor/sabre/dav/lib/CardDAV/AddressBook.php$  SX$  rRd      0   vendor/sabre/dav/lib/CardDAV/AddressBookHome.php  SX  Ӏʶ      0   vendor/sabre/dav/lib/CardDAV/AddressBookRoot.phpI  SXI   \7      $   vendor/sabre/dav/lib/CardDAV/Backend    SX              8   vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php  SX  hJ      9   vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php,  SX,  in      ,   vendor/sabre/dav/lib/CardDAV/Backend/PDO.php<G  SX<G  ~      4   vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php
  SX
  \q      %   vendor/sabre/dav/lib/CardDAV/Card.php
  SX
  =_      -   vendor/sabre/dav/lib/CardDAV/IAddressBook.php~  SX~  l      &   vendor/sabre/dav/lib/CardDAV/ICard.phpr  SXr   V      +   vendor/sabre/dav/lib/CardDAV/IDirectory.php  SX  w      '   vendor/sabre/dav/lib/CardDAV/Plugin.phps  SXs  ߇q      0   vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php  SX  ꭶ          vendor/sabre/dav/lib/CardDAV/Xml    SX              '   vendor/sabre/dav/lib/CardDAV/Xml/Filter    SX              7   vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php  SX  A      7   vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.phpm  SXm        6   vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php  SX        )   vendor/sabre/dav/lib/CardDAV/Xml/Property    SX              B   vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php  SX  n뱶      C   vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php[  SX[  7j      (   vendor/sabre/dav/lib/CardDAV/Xml/Request    SX              F   vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php&  SX&        C   vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php  SX  %>         vendor/sabre/dav/lib/DAV    SX                 vendor/sabre/dav/lib/DAV/Auth    SX              %   vendor/sabre/dav/lib/DAV/Auth/Backend    SX              7   vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php  SX  ض      8   vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php  SX  8Xζ      8   vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php  SX  vg3l      0   vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php  SX  qh߶      :   vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php  SX  ~h      7   vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php  SX  >0      .   vendor/sabre/dav/lib/DAV/Auth/Backend/File.phpE  SXE  j      -   vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php  SX  GN      (   vendor/sabre/dav/lib/DAV/Auth/Plugin.php  SX  \           vendor/sabre/dav/lib/DAV/Browser    SX              '   vendor/sabre/dav/lib/DAV/Browser/assets    SX              3   vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico  SX  ʪ      2   vendor/sabre/dav/lib/DAV/Browser/assets/openiconic    SX              ?   vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE1  SX1  bԶ      B   vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css6  SX6  ݂5      B   vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eothZ  SXhZ  %      B   vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf8R  SX8R  uǶ      B   vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg  SX  9      B   vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttfc  SXc  JU      C   vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.wofft0  SXt0  9ݴ      4   vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css  SX  /      4   vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png	  SX	  jԶ      5   vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php	  SX	  %=0      /   vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php  SX  0      5   vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php  SX  <      5   vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php  SX  g4      +   vendor/sabre/dav/lib/DAV/Browser/Plugin.phpd  SXd  ]^b      0   vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php  SX  pԶ      #   vendor/sabre/dav/lib/DAV/Client.php/  SX/  h       '   vendor/sabre/dav/lib/DAV/Collection.php  SX  o      '   vendor/sabre/dav/lib/DAV/CorePlugin.php;  SX;        "   vendor/sabre/dav/lib/DAV/Exception    SX              1   vendor/sabre/dav/lib/DAV/Exception/BadRequest.php  SX  gx      /   vendor/sabre/dav/lib/DAV/Exception/Conflict.php?  SX?  NDJc      6   vendor/sabre/dav/lib/DAV/Exception/ConflictingLock.php  SX  l      0   vendor/sabre/dav/lib/DAV/Exception/Forbidden.php  SX  { z      :   vendor/sabre/dav/lib/DAV/Exception/InsufficientStorage.php)  SX)  c9+      :   vendor/sabre/dav/lib/DAV/Exception/InvalidResourceType.php  SX  ̆      7   vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php  SX  |@&      5   vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php:  SX:  u      -   vendor/sabre/dav/lib/DAV/Exception/Locked.php  SX  *Ƕ      A   vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php  SX  xRm*      7   vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php  SX  ;Ͷ      7   vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php(  SX(  BB      /   vendor/sabre/dav/lib/DAV/Exception/NotFound.php  SX  ge>ʶ      5   vendor/sabre/dav/lib/DAV/Exception/NotImplemented.php)  SX)  Զ      6   vendor/sabre/dav/lib/DAV/Exception/PaymentRequired.phpC  SXC  "      9   vendor/sabre/dav/lib/DAV/Exception/PreconditionFailed.php  SX  `w      9   vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.phpF  SXF  q      C   vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.phpG  SXG  GZG      9   vendor/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php=  SX=  gM¶      5   vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php3  SX3  zk      ;   vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.phpn  SXn  د      &   vendor/sabre/dav/lib/DAV/Exception.php  SX  L      !   vendor/sabre/dav/lib/DAV/File.phpD	  SXD	  P2         vendor/sabre/dav/lib/DAV/FS    SX              )   vendor/sabre/dav/lib/DAV/FS/Directory.php	  SX	  H!      $   vendor/sabre/dav/lib/DAV/FS/File.php  SX  -po      $   vendor/sabre/dav/lib/DAV/FS/Node.php  SX  U)         vendor/sabre/dav/lib/DAV/FSExt    SX              ,   vendor/sabre/dav/lib/DAV/FSExt/Directory.phph  SXh  ~      '   vendor/sabre/dav/lib/DAV/FSExt/File.php
  SX
  ?,=      (   vendor/sabre/dav/lib/DAV/ICollection.php-  SX-        0   vendor/sabre/dav/lib/DAV/IExtendedCollection.php  SX        "   vendor/sabre/dav/lib/DAV/IFile.php  SX  J      (   vendor/sabre/dav/lib/DAV/IMoveTarget.php  SX  :ge      &   vendor/sabre/dav/lib/DAV/IMultiGet.php  SX  bx      "   vendor/sabre/dav/lib/DAV/INode.php  SX  b#      (   vendor/sabre/dav/lib/DAV/IProperties.phpY  SXY  ܫ      #   vendor/sabre/dav/lib/DAV/IQuota.php  SX  Bv\         vendor/sabre/dav/lib/DAV/Locks    SX              &   vendor/sabre/dav/lib/DAV/Locks/Backend    SX              :   vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php  SX  [      ;   vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php  SX  NXIA      /   vendor/sabre/dav/lib/DAV/Locks/Backend/File.phph  SXh  X      .   vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php  SX        +   vendor/sabre/dav/lib/DAV/Locks/LockInfo.php  SX  B1      )   vendor/sabre/dav/lib/DAV/Locks/Plugin.phpG  SXG  #B\      "   vendor/sabre/dav/lib/DAV/MkCol.phpX  SXX           vendor/sabre/dav/lib/DAV/Mount    SX              )   vendor/sabre/dav/lib/DAV/Mount/Plugin.php  SX  `      !   vendor/sabre/dav/lib/DAV/Node.php
  SX
  I      &   vendor/sabre/dav/lib/DAV/PartialUpdate    SX              8   vendor/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php  SX  Q      1   vendor/sabre/dav/lib/DAV/PartialUpdate/Plugin.phpe  SXe  "mg      (   vendor/sabre/dav/lib/DAV/PropertyStorage    SX              0   vendor/sabre/dav/lib/DAV/PropertyStorage/Backend    SX              E   vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php	  SX	  P      8   vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.phpX  SXX  dR      3   vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php}  SX}  *      %   vendor/sabre/dav/lib/DAV/PropFind.php   SX   ў^0      &   vendor/sabre/dav/lib/DAV/PropPatch.php '  SX '  -D$      #   vendor/sabre/dav/lib/DAV/Server.php6  SX6  ?p      )   vendor/sabre/dav/lib/DAV/ServerPlugin.php	  SX	  ݗS          vendor/sabre/dav/lib/DAV/Sharing    SX              0   vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php*  SX*  -      +   vendor/sabre/dav/lib/DAV/Sharing/Plugin.php%  SX%  |Z=      -   vendor/sabre/dav/lib/DAV/SimpleCollection.php\	  SX\	  Ee=      '   vendor/sabre/dav/lib/DAV/SimpleFile.php}	  SX}	  ]
      '   vendor/sabre/dav/lib/DAV/StringUtil.php
  SX
  Z         vendor/sabre/dav/lib/DAV/Sync    SX              1   vendor/sabre/dav/lib/DAV/Sync/ISyncCollection.phpk  SXk  tM      (   vendor/sabre/dav/lib/DAV/Sync/Plugin.php
   SX
   wɶ      6   vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php"  SX"  ^|      !   vendor/sabre/dav/lib/DAV/Tree.php@%  SX@%  P      %   vendor/sabre/dav/lib/DAV/UUIDUtil.php  SX  o      $   vendor/sabre/dav/lib/DAV/Version.php]  SX]  (d         vendor/sabre/dav/lib/DAV/Xml    SX              $   vendor/sabre/dav/lib/DAV/Xml/Element    SX              -   vendor/sabre/dav/lib/DAV/Xml/Element/Prop.php
  SX
        1   vendor/sabre/dav/lib/DAV/Xml/Element/Response.phpq  SXq  )9      /   vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.phpP  SXP  Vhж      %   vendor/sabre/dav/lib/DAV/Xml/Property    SX              1   vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php	  SX	  :      9   vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php
  SX
  &      .   vendor/sabre/dav/lib/DAV/Xml/Property/Href.php|  SX|  -      0   vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php  SX  i      3   vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php  SX  xsv      7   vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php  SX  GOE$      6   vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.phpl  SXl  ]:۶      5   vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php  SX  `       7   vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.phpf  SXf  F      <   vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php^  SX^  SW      <   vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php  SX  "	      $   vendor/sabre/dav/lib/DAV/Xml/Request    SX              -   vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php  SX  8gc      .   vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php  SX  ։      1   vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php+  SX+  5      2   vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php  SX   5      6   vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php  SX  p'      =   vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.phpm  SXm  TU`      %   vendor/sabre/dav/lib/DAV/Xml/Response    SX              5   vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.phpo  SXo  ԣ      (   vendor/sabre/dav/lib/DAV/Xml/Service.phpV  SXV  Ә         vendor/sabre/dav/lib/DAVACL    SX              ;   vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php  SX  >۶      (   vendor/sabre/dav/lib/DAVACL/ACLTrait.phpT	  SXT	  AQ      %   vendor/sabre/dav/lib/DAVACL/Exception    SX              5   vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.phpR  SXR  mT       8   vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php  SX  VNF      4   vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php\  SX\  	F      @   vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php  SX  +ߌ      ?   vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php~  SX~  XQ         vendor/sabre/dav/lib/DAVACL/FS    SX              -   vendor/sabre/dav/lib/DAVACL/FS/Collection.php	  SX	        '   vendor/sabre/dav/lib/DAVACL/FS/File.php  SX  %      1   vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.phpq  SXq  ~      $   vendor/sabre/dav/lib/DAVACL/IACL.phpI  SXI  |4      *   vendor/sabre/dav/lib/DAVACL/IPrincipal.php'  SX'  ͺ      4   vendor/sabre/dav/lib/DAVACL/IPrincipalCollection.php  SX  ܼa      &   vendor/sabre/dav/lib/DAVACL/Plugin.php  SX  թŶ      )   vendor/sabre/dav/lib/DAVACL/Principal.php  SX  wI      ,   vendor/sabre/dav/lib/DAVACL/PrincipalBackend    SX              @   vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php?  SX?  Rm4      A   vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php  SX  $ڶ      G   vendor/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php  SX  `;\      4   vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php)3  SX)3  _:      3   vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php  SX  Tƶ         vendor/sabre/dav/lib/DAVACL/Xml    SX              (   vendor/sabre/dav/lib/DAVACL/Xml/Property    SX              0   vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php    SX    "辶      <   vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php  SX  Ic      D   vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php  SX  ;,v      6   vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php'  SX'  p      B   vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php  SX  <N      '   vendor/sabre/dav/lib/DAVACL/Xml/Request    SX              E   vendor/sabre/dav/lib/DAVACL/Xml/Request/AclPrincipalPropSetReport.php  SX  7k      @   vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php
  SX
  =3@      @   vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalMatchReport.php
  SX
        I   vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php  SX  =d      L   vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php  SX  s[         vendor/sabre/dav/LICENSE(  SX(  0Tg         vendor/sabre/dav/README.md+  SX+  0         vendor/sabre/event    SX                 vendor/sabre/event/.gitignore   SX   =         vendor/sabre/event/.travis.ymlN  SXN  (wٶ         vendor/sabre/event/bin    SX                 vendor/sabre/event/bin/.empty    SX                 vendor/sabre/event/CHANGELOG.mdM	  SXM	  /           vendor/sabre/event/composer.jsonY  SXY  a         vendor/sabre/event/examples    SX              '   vendor/sabre/event/examples/promise.php  SX  ɬ      $   vendor/sabre/event/examples/tail.php7  SX7  X         vendor/sabre/event/lib    SX              $   vendor/sabre/event/lib/coroutine.php  SX  {      '   vendor/sabre/event/lib/EventEmitter.php  SX  5~      0   vendor/sabre/event/lib/EventEmitterInterface.php  SX  ƕ3Ѷ      ,   vendor/sabre/event/lib/EventEmitterTrait.php  SX  
         vendor/sabre/event/lib/Loop    SX              )   vendor/sabre/event/lib/Loop/functions.phpN  SXN  c#      $   vendor/sabre/event/lib/Loop/Loop.php#  SX#  !o         vendor/sabre/event/lib/Promise    SX              ,   vendor/sabre/event/lib/Promise/functions.php
  SX
  Ƕ      "   vendor/sabre/event/lib/Promise.php'  SX'   "      :   vendor/sabre/event/lib/PromiseAlreadyResolvedException.php  SX  i%      "   vendor/sabre/event/lib/Version.phpo  SXo  i         vendor/sabre/event/LICENSE!  SX!  OG      #   vendor/sabre/event/phpunit.xml.dist  SX  1         vendor/sabre/event/README.md  SX  `         vendor/sabre/event/tests    SX              "   vendor/sabre/event/tests/benchmark    SX              ,   vendor/sabre/event/tests/benchmark/bench.php  SX  Am      1   vendor/sabre/event/tests/ContinueCallbackTest.php  SX  >)      *   vendor/sabre/event/tests/CoroutineTest.php   SX   +
jS      -   vendor/sabre/event/tests/EventEmitterTest.phpq  SXq           vendor/sabre/event/tests/Loop    SX              /   vendor/sabre/event/tests/Loop/FunctionsTest.php
  SX
  祽      *   vendor/sabre/event/tests/Loop/LoopTest.php<
  SX<
  
`          vendor/sabre/event/tests/Promise    SX              2   vendor/sabre/event/tests/Promise/FunctionsTest.php  SX  a      0   vendor/sabre/event/tests/Promise/PromiseTest.php  SX  tpL      (   vendor/sabre/event/tests/PromiseTest.php"  SX"  M]         vendor/sabre/http    SX                 vendor/sabre/http/.gitignore   SX   K         vendor/sabre/http/.travis.yml  SX  3X         vendor/sabre/http/bin    SX                 vendor/sabre/http/bin/.empty    SX                 vendor/sabre/http/CHANGELOG.md{  SX{  &gwV         vendor/sabre/http/composer.json  SX           vendor/sabre/http/examples    SX              *   vendor/sabre/http/examples/asyncclient.phpb  SXb  QE      (   vendor/sabre/http/examples/basicauth.php  SX  !5      %   vendor/sabre/http/examples/client.phpI  SXI  6       )   vendor/sabre/http/examples/digestauth.php  SX  j      +   vendor/sabre/http/examples/reverseproxy.phpq  SXq  \N)      (   vendor/sabre/http/examples/stringify.phpO  SXO  飶         vendor/sabre/http/lib    SX                 vendor/sabre/http/lib/Auth    SX              +   vendor/sabre/http/lib/Auth/AbstractAuth.phpP  SXP  Drn      "   vendor/sabre/http/lib/Auth/AWS.php  SX  -+8      $   vendor/sabre/http/lib/Auth/Basic.php  SX  Ϥ߶      %   vendor/sabre/http/lib/Auth/Bearer.php  SX  _G      %   vendor/sabre/http/lib/Auth/Digest.php{  SX{  &D          vendor/sabre/http/lib/Client.phpF  SXF  wP      )   vendor/sabre/http/lib/ClientException.phpi  SXi  ն      -   vendor/sabre/http/lib/ClientHttpException.php  SX  Xo޶      #   vendor/sabre/http/lib/functions.php.  SX.  $t      '   vendor/sabre/http/lib/HttpException.php  SX  U3       !   vendor/sabre/http/lib/Message.php  SX  v\      /   vendor/sabre/http/lib/MessageDecoratorTrait.php  SX  \      *   vendor/sabre/http/lib/MessageInterface.phpH  SXH  ڝ\      !   vendor/sabre/http/lib/Request.php`  SX`  E"      *   vendor/sabre/http/lib/RequestDecorator.php  SX  !\e      *   vendor/sabre/http/lib/RequestInterface.php  SX  k      "   vendor/sabre/http/lib/Response.php  SX  t      +   vendor/sabre/http/lib/ResponseDecorator.phpz  SXz  ɶ      +   vendor/sabre/http/lib/ResponseInterface.php  SX  X#;         vendor/sabre/http/lib/Sapi.php  SX  h      !   vendor/sabre/http/lib/URLUtil.php,  SX,  S`{ö         vendor/sabre/http/lib/Util.php  SX  贶      !   vendor/sabre/http/lib/Version.phpf  SXf  u3         vendor/sabre/http/LICENSE!  SX!  O,         vendor/sabre/http/README.md>  SX>           vendor/sabre/http/tests    SX              %   vendor/sabre/http/tests/bootstrap.php   SX   *T         vendor/sabre/http/tests/HTTP    SX              !   vendor/sabre/http/tests/HTTP/Auth    SX              -   vendor/sabre/http/tests/HTTP/Auth/AWSTest.php  SX  (m      /   vendor/sabre/http/tests/HTTP/Auth/BasicTest.phpv  SXv        0   vendor/sabre/http/tests/HTTP/Auth/BearerTest.php  SX  טl      0   vendor/sabre/http/tests/HTTP/Auth/DigestTest.php:  SX:  T7T      +   vendor/sabre/http/tests/HTTP/ClientTest.php:  SX:  :      .   vendor/sabre/http/tests/HTTP/FunctionsTest.php	  SX	  3G      5   vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php
  SX
  p_j      ,   vendor/sabre/http/tests/HTTP/MessageTest.php  SX  ;      5   vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php
  SX
        ,   vendor/sabre/http/tests/HTTP/RequestTest.php  SX  X      6   vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.phpR  SXR        -   vendor/sabre/http/tests/HTTP/ResponseTest.php  SX  :0      )   vendor/sabre/http/tests/HTTP/SapiTest.php  SX  U"      ,   vendor/sabre/http/tests/HTTP/URLUtilTest.php  SX        )   vendor/sabre/http/tests/HTTP/UtilTest.php  SX  %*         vendor/sabre/http/tests/phpcs    SX              )   vendor/sabre/http/tests/phpcs/ruleset.xml  SX  ;.      #   vendor/sabre/http/tests/phpunit.xml  SX  ID          vendor/sabre/uri    SX                 vendor/sabre/uri/.gitignoren   SXn   #         vendor/sabre/uri/.travis.yml   SX   ~nX         vendor/sabre/uri/CHANGELOG.md  SX  dJ         vendor/sabre/uri/composer.json  SX  yɶ         vendor/sabre/uri/lib    SX              "   vendor/sabre/uri/lib/functions.php(  SX(  w.      ,   vendor/sabre/uri/lib/InvalidUriException.phpX  SXX            vendor/sabre/uri/lib/Version.phpM  SXM  <         vendor/sabre/uri/LICENSE!  SX!  W         vendor/sabre/uri/README.md<  SX<  l         vendor/sabre/uri/tests    SX              $   vendor/sabre/uri/tests/BuildTest.php  SX  @      (   vendor/sabre/uri/tests/NormalizeTest.php  SX  P      $   vendor/sabre/uri/tests/ParseTest.php  SX  :A׶         vendor/sabre/uri/tests/phpcs    SX              (   vendor/sabre/uri/tests/phpcs/ruleset.xml  SX  ;.      '   vendor/sabre/uri/tests/phpunit.xml.dist  SX  AX      &   vendor/sabre/uri/tests/ResolveTest.php/
  SX/
  sZ;۶      $   vendor/sabre/uri/tests/SplitTest.php  SX  (Q߶         vendor/sabre/vobject    SX                 vendor/sabre/vobject/.gitignore   SX   ZW          vendor/sabre/vobject/.travis.yml%  SX%  ;         vendor/sabre/vobject/bin    SX              "   vendor/sabre/vobject/bin/bench.php   SX   =51      4   vendor/sabre/vobject/bin/bench_freebusygenerator.php^  SX^  0Z      2   vendor/sabre/vobject/bin/bench_manipulatevcard.php  SX  0V      0   vendor/sabre/vobject/bin/fetch_windows_zones.php  SX  #R      2   vendor/sabre/vobject/bin/generateicalendardata.php  SX        ,   vendor/sabre/vobject/bin/mergeduplicates.php  SX  ,      '   vendor/sabre/vobject/bin/rrulebench.php  SX  d-      !   vendor/sabre/vobject/CHANGELOG.mdq  SXq        "   vendor/sabre/vobject/composer.jsonJ  SXJ  h         vendor/sabre/vobject/lib    SX              6   vendor/sabre/vobject/lib/BirthdayCalendarGenerator.php  SX  mb          vendor/sabre/vobject/lib/Cli.phpS  SXS  ˶      "   vendor/sabre/vobject/lib/Component    SX              0   vendor/sabre/vobject/lib/Component/Available.phpu
  SXu
  @      -   vendor/sabre/vobject/lib/Component/VAlarm.phpZ  SXZ  B      4   vendor/sabre/vobject/lib/Component/VAvailability.php  SX  /3      0   vendor/sabre/vobject/lib/Component/VCalendar.phpZK  SXZK  KO      ,   vendor/sabre/vobject/lib/Component/VCard.phpE  SXE        -   vendor/sabre/vobject/lib/Component/VEvent.phpK  SXK  &7      0   vendor/sabre/vobject/lib/Component/VFreeBusy.phpA  SXA  @\      /   vendor/sabre/vobject/lib/Component/VJournal.php#  SX#  hw      0   vendor/sabre/vobject/lib/Component/VTimeZone.phpP  SXP  ̽X      ,   vendor/sabre/vobject/lib/Component/VTodo.phpF  SXF  _^	      &   vendor/sabre/vobject/lib/Component.php`N  SX`N  )      +   vendor/sabre/vobject/lib/DateTimeParser.php>  SX>  ֑      %   vendor/sabre/vobject/lib/Document.php   SX   !,      (   vendor/sabre/vobject/lib/ElementList.phpb  SXb  ot      )   vendor/sabre/vobject/lib/EofException.phpe  SXe  6gu      )   vendor/sabre/vobject/lib/FreeBusyData.php8  SX8  |      .   vendor/sabre/vobject/lib/FreeBusyGenerator.php#K  SX#K  5      1   vendor/sabre/vobject/lib/InvalidDataException.phpk  SXk  ye̶         vendor/sabre/vobject/lib/ITip    SX              (   vendor/sabre/vobject/lib/ITip/Broker.php  SX  ֓s_      /   vendor/sabre/vobject/lib/ITip/ITipException.phpX  SXX  `      )   vendor/sabre/vobject/lib/ITip/Message.php  SX  @k"r      H   vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php  SX  >      !   vendor/sabre/vobject/lib/Node.php  SX  $7      &   vendor/sabre/vobject/lib/Parameter.php$  SX$  J      +   vendor/sabre/vobject/lib/ParseException.phpI  SXI           vendor/sabre/vobject/lib/Parser    SX              (   vendor/sabre/vobject/lib/Parser/Json.php  SX  GT      +   vendor/sabre/vobject/lib/Parser/MimeDir.phpU  SXU  s'      *   vendor/sabre/vobject/lib/Parser/Parser.phpc  SXc  3'      #   vendor/sabre/vobject/lib/Parser/XML    SX              +   vendor/sabre/vobject/lib/Parser/XML/Element    SX              8   vendor/sabre/vobject/lib/Parser/XML/Element/KeyValue.php7  SX7  *      '   vendor/sabre/vobject/lib/Parser/XML.phph.  SXh.  Շ%      .   vendor/sabre/vobject/lib/PHPUnitAssertions.php	  SX	  UW      !   vendor/sabre/vobject/lib/Property    SX              ,   vendor/sabre/vobject/lib/Property/Binary.php
  SX
  nt      -   vendor/sabre/vobject/lib/Property/Boolean.php  SX  c'N      .   vendor/sabre/vobject/lib/Property/FlatText.php  SX  j
      0   vendor/sabre/vobject/lib/Property/FloatValue.php&  SX&  Ld      +   vendor/sabre/vobject/lib/Property/ICalendar    SX              :   vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.phpe  SXe  XҶ      4   vendor/sabre/vobject/lib/Property/ICalendar/Date.php  SX  N      8   vendor/sabre/vobject/lib/Property/ICalendar/DateTime.phpc(  SXc(  I      8   vendor/sabre/vobject/lib/Property/ICalendar/Duration.phpD  SXD  
춶      6   vendor/sabre/vobject/lib/Property/ICalendar/Period.php
  SX
  CX      5   vendor/sabre/vobject/lib/Property/ICalendar/Recur.phpC)  SXC)  |      2   vendor/sabre/vobject/lib/Property/IntegerValue.php  SX  {      *   vendor/sabre/vobject/lib/Property/Text.php",  SX",  q?      *   vendor/sabre/vobject/lib/Property/Time.php`  SX`  9	6      -   vendor/sabre/vobject/lib/Property/Unknown.php  SX  B      )   vendor/sabre/vobject/lib/Property/Uri.phpI
  SXI
  O𧂶      /   vendor/sabre/vobject/lib/Property/UtcOffset.phpO  SXO  5z      '   vendor/sabre/vobject/lib/Property/VCard    SX              0   vendor/sabre/vobject/lib/Property/VCard/Date.php*  SX*  $y      9   vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.phpP+  SXP+  8HnU      4   vendor/sabre/vobject/lib/Property/VCard/DateTime.phpT  SXT  X	      7   vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php  SX  8wX      5   vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php  SX  Q[      %   vendor/sabre/vobject/lib/Property.php.?  SX.?  ]kc      #   vendor/sabre/vobject/lib/Reader.php	
  SX	
  P
u         vendor/sabre/vobject/lib/Recur    SX              0   vendor/sabre/vobject/lib/Recur/EventIterator.php6  SX6  8<      @   vendor/sabre/vobject/lib/Recur/MaxInstancesExceededException.php  SX  3z      7   vendor/sabre/vobject/lib/Recur/NoInstancesException.php  SX  {A      0   vendor/sabre/vobject/lib/Recur/RDateIterator.php
  SX
  Pԕ      0   vendor/sabre/vobject/lib/Recur/RRuleIterator.phpw  SXw  ar      %   vendor/sabre/vobject/lib/Settings.phpA  SXA  $      !   vendor/sabre/vobject/lib/Splitter    SX              /   vendor/sabre/vobject/lib/Splitter/ICalendar.phpm  SXm  ؀      7   vendor/sabre/vobject/lib/Splitter/SplitterInterface.php  SX  Y      +   vendor/sabre/vobject/lib/Splitter/VCard.php  SX  y      '   vendor/sabre/vobject/lib/StringUtil.phpj  SXj  &HŶ      %   vendor/sabre/vobject/lib/timezonedata    SX              7   vendor/sabre/vobject/lib/timezonedata/exchangezones.phpz  SXz  Hz      4   vendor/sabre/vobject/lib/timezonedata/lotuszones.php  SX  $Pi      0   vendor/sabre/vobject/lib/timezonedata/php-bc.php
  SX
  nm      8   vendor/sabre/vobject/lib/timezonedata/php-workaround.php  SX  S      6   vendor/sabre/vobject/lib/timezonedata/windowszones.php  SX  z      )   vendor/sabre/vobject/lib/TimeZoneUtil.phpS'  SXS'  xβ      %   vendor/sabre/vobject/lib/UUIDUtil.php  SX  ql      +   vendor/sabre/vobject/lib/VCardConverter.php?  SX?  .䵶      $   vendor/sabre/vobject/lib/Version.phpn  SXn  }?      #   vendor/sabre/vobject/lib/Writer.php  SX  
         vendor/sabre/vobject/LICENSE!  SX!  uh         vendor/sabre/vobject/README.mdc  SXc  ?1         vendor/sabre/vobject/resources    SX              %   vendor/sabre/vobject/resources/schema    SX              .   vendor/sabre/vobject/resources/schema/xcal.rngG  SXG        /   vendor/sabre/vobject/resources/schema/xcard.rng'  SX'  >         vendor/sabre/xml    SX                 vendor/sabre/xml/.gitignorek   SXk   XU
         vendor/sabre/xml/.travis.ymlY  SXY  :W
          vendor/sabre/xml/bin    SX                 vendor/sabre/xml/bin/.empty    SX                 vendor/sabre/xml/CHANGELOG.mda  SXa  A         vendor/sabre/xml/composer.jsonv  SXv           vendor/sabre/xml/lib    SX              *   vendor/sabre/xml/lib/ContextStackTrait.php  SX  Շ5`      !   vendor/sabre/xml/lib/Deserializer    SX              /   vendor/sabre/xml/lib/Deserializer/functions.php  SX  ^b         vendor/sabre/xml/lib/Element    SX              %   vendor/sabre/xml/lib/Element/Base.php	  SX	  szȶ      &   vendor/sabre/xml/lib/Element/Cdata.php  SX  ȶ      )   vendor/sabre/xml/lib/Element/Elements.php
  SX
  s:;       )   vendor/sabre/xml/lib/Element/KeyValue.php  SX  \f      $   vendor/sabre/xml/lib/Element/Uri.php
  SX
  7q      ,   vendor/sabre/xml/lib/Element/XmlFragment.php^  SX^  "          vendor/sabre/xml/lib/Element.php  SX  q      (   vendor/sabre/xml/lib/LibXMLException.php  SX  3r      '   vendor/sabre/xml/lib/ParseException.phpa  SXa           vendor/sabre/xml/lib/Reader.phpP%  SXP%  Pm         vendor/sabre/xml/lib/Serializer    SX              -   vendor/sabre/xml/lib/Serializer/functions.php
  SX
  _>Ҿ          vendor/sabre/xml/lib/Service.php#  SX#  ǁ誶          vendor/sabre/xml/lib/Version.phpX  SXX  G         vendor/sabre/xml/lib/Writer.php<  SX<   ɶ      *   vendor/sabre/xml/lib/XmlDeserializable.php  SX  )       (   vendor/sabre/xml/lib/XmlSerializable.php  SX  Ǭi         vendor/sabre/xml/LICENSE!  SX!  |         vendor/sabre/xml/README.md  SX  (         vendor/sabre/xml/tests    SX                 vendor/sabre/xml/tests/phpcs    SX              (   vendor/sabre/xml/tests/phpcs/ruleset.xmlp  SXp  슶      '   vendor/sabre/xml/tests/phpunit.xml.dist  SX  z;         vendor/sabre/xml/tests/Sabre    SX                  vendor/sabre/xml/tests/Sabre/Xml    SX              5   vendor/sabre/xml/tests/Sabre/Xml/ContextStackTest.php  SX  ?      -   vendor/sabre/xml/tests/Sabre/Xml/Deserializer    SX              :   vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php#  SX#  ?L0      >   vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php
  SX
  lm      G   vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php  SX  `	H      A   vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php(  SX(  *n      (   vendor/sabre/xml/tests/Sabre/Xml/Element    SX              6   vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php  SX  U(Ӷ      2   vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php  SX  o϶      9   vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php
  SX
  j^8U      9   vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php  SX  G_w      1   vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php  SX        4   vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php  SX  0      <   vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php{  SX{        3   vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php  SX  ΂      /   vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php3  SX3  E۶      +   vendor/sabre/xml/tests/Sabre/Xml/Serializer    SX              8   vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.phph  SXh  )^fD      E   vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php  SX  hH      0   vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php  SX  4      /   vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php%  SX%  ¹         vendor/splitbrain    SX                 vendor/splitbrain/php-archive    SX              '   vendor/splitbrain/php-archive/.DS_Store  SX  ]      (   vendor/splitbrain/php-archive/.gitignoreD   SXD         )   vendor/splitbrain/php-archive/.travis.yml  SX  ms      )   vendor/splitbrain/php-archive/apigen.neon;   SX;   _-)      +   vendor/splitbrain/php-archive/composer.json/  SX/  7`j      -   vendor/splitbrain/php-archive/generate-api.sh  SX  !X      %   vendor/splitbrain/php-archive/LICENSE3  SX3  VZ      )   vendor/splitbrain/php-archive/phpunit.xml0  SX0  Q      '   vendor/splitbrain/php-archive/README.mdq  SXq  Ȝ      !   vendor/splitbrain/php-archive/src    SX              -   vendor/splitbrain/php-archive/src/Archive.php  SX  9>      .   vendor/splitbrain/php-archive/src/FileInfo.php  SX  
bԶ      )   vendor/splitbrain/php-archive/src/Tar.phpg  SXg        )   vendor/splitbrain/php-archive/src/Zip.phpb  SXb  M      #   vendor/splitbrain/php-archive/tests    SX              -   vendor/splitbrain/php-archive/tests/.DS_Store  SX  !       
   vendor/vakata    SX                 vendor/vakata/jstree    SX                 vendor/vakata/jstree/.gitignore   SX   8а         vendor/vakata/jstree/bower.json  SX  
      #   vendor/vakata/jstree/component.json  SX  O      "   vendor/vakata/jstree/composer.json)  SX)  z"         vendor/vakata/jstree/dist    SX              #   vendor/vakata/jstree/dist/jstree.js{ SX{ ʶ      '   vendor/vakata/jstree/dist/jstree.min.jsK SXK {\          vendor/vakata/jstree/dist/themes    SX              (   vendor/vakata/jstree/dist/themes/default    SX              1   vendor/vakata/jstree/dist/themes/default/32px.png1  SX1  h      1   vendor/vakata/jstree/dist/themes/default/40px.pngX  SXX  l
      2   vendor/vakata/jstree/dist/themes/default/style.css{  SX{  <b+      6   vendor/vakata/jstree/dist/themes/default/style.min.cssqi  SXqi  ؋$      5   vendor/vakata/jstree/dist/themes/default/throbber.gif  SX  JKR      -   vendor/vakata/jstree/dist/themes/default-dark    SX              6   vendor/vakata/jstree/dist/themes/default-dark/32px.png  SX  <޶      6   vendor/vakata/jstree/dist/themes/default-dark/40px.pngU  SXU  h5      7   vendor/vakata/jstree/dist/themes/default-dark/style.css  SX  'ٶ      ;   vendor/vakata/jstree/dist/themes/default-dark/style.min.csss  SXs  G5C      :   vendor/vakata/jstree/dist/themes/default-dark/throbber.gif  SX  0ȶ      !   vendor/vakata/jstree/gruntfile.js  SX  2@      '   vendor/vakata/jstree/jstree.jquery.json  SX            vendor/vakata/jstree/LICENSE-MIT7  SX7  R      !   vendor/vakata/jstree/package.json=  SX=  P      <?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit571f9d19802717f7be61d57b40d60b28::getLoader();
   Bud1                                                                     spblob   bp                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          @                                              @                                                @                                                @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  s r cbwspblob   bplist00	



]ShowStatusBar[ShowSidebar[ShowPathbar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds\SidebarWidth_PreviewPaneVisibility			_{{100, 79}, {1584, 894}})5AMYp}                                s r clsvPblob  obplist00	
DE
_viewOptionsVersion_showIconPreviewWcolumns_calculateAllSizesXtextSizeZsortColumnXiconSize_useRelativeDates	#(-27<


ZidentifierUwidthYascendingWvisibleTname		

\dateModified	
[dateCreated
 
Tsizea	
$%

Tkinds		
)*
Ulabeld	
./
WversionK	
34
Xcomments,	
89^dateLastOpened=>?
BWvisibleUwidthYascendingYdateAdded#@(      #@0      	   . @ H \ e p y                     
!#$%.4678AIKLMV_bcdm|~             G                  s r clsvpblob  Wbplist00	
EF
_viewOptionsVersion_showIconPreviewWcolumns_calculateAllSizesXtextSizeZsortColumnXiconSize_useRelativeDates	
#(-27<AXcomments^dateLastOpened[dateCreatedTsizeUlabelTkindWversionTname\dateModified
UindexUwidthYascendingWvisible,	 $%)*
a	./
d	34

s		89
K	=>

 			%
	#@(      #@0      	   . @ H \ e p y               ')+,-68:;<EGIJKTVXYZceghirtvwx             H                  s r cvSrnlong                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                E                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         DSDB                                 `                                                  @                                                @                                                @       a	./
d	34

s		89
K	=>

 			%
	#@(      #@0      	   . @ H \ e p y               ')+,-68:;<EGIJKTVXYZceghirtvwx             H                  s r cvSrnlong                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    {
    "name": "aws/aws-sdk-php",
    "homepage": "http://aws.amazon.com/sdkforphp",
    "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
    "keywords": ["aws","amazon","sdk","s3","ec2","dynamodb","cloud","glacier"],
    "type": "library",
    "license": "Apache-2.0",
    "authors": [
        {
            "name": "Amazon Web Services",
            "homepage": "http://aws.amazon.com"
        }
    ],
    "support": {
        "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
        "issues": "https://github.com/aws/aws-sdk-php/issues"
    },
    "require": {
        "php": ">=5.5",
        "guzzlehttp/guzzle": "^5.3.1|^6.2.1",
        "guzzlehttp/psr7": "~1.3.1",
        "guzzlehttp/promises": "~1.0",
        "mtdowling/jmespath.php": "~2.2"
    },
    "require-dev": {
        "ext-openssl": "*",
        "ext-pcre": "*",
        "ext-spl": "*",
        "ext-json": "*",
        "ext-dom": "*",
        "ext-simplexml": "*",
        "phpunit/phpunit": "~4.0|~5.0",
        "behat/behat": "~3.0",
        "doctrine/cache": "~1.4",
        "aws/aws-php-sns-message-validator": "~1.0",
        "nette/neon": "^2.3",
        "andrewsville/php-token-reflection": "^1.4",
        "psr/cache": "^1.0"
    },
    "suggest": {
        "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages",
        "ext-curl": "To send requests using cURL",
        "doctrine/cache": "To use the DoctrineCacheAdapter",
        "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications"
    },
    "autoload": {
        "psr-4": {
            "Aws\\": "src/"
        },
        "files": ["src/functions.php"]
    },
    "autoload-dev": {
        "psr-4": {
            "Aws\\Test\\": "tests/"
        },
        "classmap": ["build/"]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "3.0-dev"
        }
    }
}
# Apache License
Version 2.0, January 2004

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

## 1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1
through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the
License.

"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled
by, or are under common control with that entity. For the purposes of this definition, "control" means
(i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract
or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including but not limited to software
source code, documentation source, and configuration files.

"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form,
including but not limited to compiled object code, generated documentation, and conversions to other media
types.

"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License,
as indicated by a copyright notice that is included in or attached to the work (an example is provided in the
Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from)
the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent,
as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not
include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work
and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version of the Work and any
modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to
Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to
submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of
electronic, verbal, or written communication sent to the Licensor or its representatives, including but not
limited to communication on electronic mailing lists, source code control systems, and issue tracking systems
that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been
received by Licensor and subsequently incorporated within the Work.

## 2. Grant of Copyright License.

Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare
Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.

## 3. Grant of Patent License.

Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent
license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such
license applies only to those patent claims licensable by such Contributor that are necessarily infringed by
their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such
Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim
or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work
constitutes direct or contributory patent infringement, then any patent licenses granted to You under this
License for that Work shall terminate as of the date such litigation is filed.

## 4. Redistribution.

You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You meet the following conditions:

   1. You must give any other recipients of the Work or Derivative Works a copy of this License; and

   2. You must cause any modified files to carry prominent notices stating that You changed the files; and

   3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent,
	  trademark, and attribution notices from the Source form of the Work, excluding those notices that do
	  not pertain to any part of the Derivative Works; and

   4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that
	  You distribute must include a readable copy of the attribution notices contained within such NOTICE
	  file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one
	  of the following places: within a NOTICE text file distributed as part of the Derivative Works; within
	  the Source form or documentation, if provided along with the Derivative Works; or, within a display
	  generated by the Derivative Works, if and wherever such third-party notices normally appear. The
	  contents of the NOTICE file are for informational purposes only and do not modify the License. You may
	  add Your own attribution notices within Derivative Works that You distribute, alongside or as an
	  addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be
	  construed as modifying the License.

You may add Your own copyright statement to Your modifications and may provide additional or different license
terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative
Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the
conditions stated in this License.

## 5. Submission of Contributions.

Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by
You to the Licensor shall be under the terms and conditions of this License, without any additional terms or
conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate
license agreement you may have executed with Licensor regarding such Contributions.

## 6. Trademarks.

This License does not grant permission to use the trade names, trademarks, service marks, or product names of
the Licensor, except as required for reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.

## 7. Disclaimer of Warranty.

Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor
provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT,
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.

## 8. Limitation of Liability.

In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless
required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any
Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential
damages of any character arising as a result of this License or out of the use or inability to use the Work
(including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has been advised of the possibility
of such damages.

## 9. Accepting Warranty or Additional Liability.

While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole
responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold
each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS
# AWS SDK for PHP

<http://aws.amazon.com/php>

Copyright 2010-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
A copy of the License is located at

<http://aws.amazon.com/apache2.0>

or in the "license" file accompanying this file. This file is distributed
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language governing
permissions and limitations under the License.

# Guzzle

<https://github.com/guzzle/guzzle>

Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling

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.

# jmespath.php

<https://github.com/mtdowling/jmespath.php>

Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling

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.
   Bud1                                                                 )      ocblob                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               @                                              @                                                @                                                @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              )    A p iIlocblob      A   (     
 A w s C l i e n t . p h pIlocblob         (      A w s C l i e n t I n t e r f a c e . p h pIlocblob     9   (      A w s C l i e n t T r a i t . p h pIlocblob        (      C a c h e I n t e r f a c e . p h pIlocblob     1   (      C l i e n t R e s o l v e r . p h pIlocblob        (      C o m m a n d . p h pIlocblob     )   (      C o m m a n d I n t e r f a c e . p h pIlocblob        (      C o m m a n d P o o l . p h pIlocblob     !   (      C r e d e n t i a l sIlocblob        (      d a t aIlocblob        (      D o c t r i n e C a c h e A d a p t e r . p h pIlocblob      A         E n d p o i n tIlocblob              	 E x c e p t i o nIlocblob     9        
 f u n c t i o n s . p h pIlocblob              H a n d l e rIlocblob     1         H a n d l e r L i s t . p h pIlocblob              H a s D a t a T r a i t . p h pIlocblob     )         H a s h i n g S t r e a m . p h pIlocblob              H a s h I n t e r f a c e . p h pIlocblob     !         H i s t o r y . p h pIlocblob              I d e m p o t e n c y T o k e n M i d d l e w a r e . p h pIlocblob              J s o n C o m p i l e r . p h pIlocblob      A         L r u A r r a y C a c h e . p h pIlocblob               M i d d l e w a r e . p h pIlocblob     9         M o c k H a n d l e r . p h pIlocblob             	 M u l t i p a r tIlocblob       l      M u l t i R e g i o n C l i e n t . p h pIlocblob     1         P h p H a s h . p h pIlocblob              P r e s i g n U r l M i d d l e w a r e . p h pIlocblob     )         P s r C a c h e A d a p t e r . p h pIlocblob             
 R e s u l t . p h pIlocblob     !         R e s u l t I n t e r f a c e . p h pIlocblob              R e s u l t P a g i n a t o r . p h pIlocblob              R e t r y M i d d l e w a r e . p h pIlocblob      A  l      S 3Ilocblob        l      S d k . p h pIlocblob     9  l     	 S i g n a t u r eIlocblob       l      T r a c e M i d d l e w a r e . p h pIlocblob     1  l     
 W a i t e r . p h pIlocblob       l      W r a p p e d H t t p H a n d l e r . p h pIlocblob     )  l                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   E                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         DSDB                                 `                                    (      0          @                                                @                                                @       t e n c y T o k e n M i d d l e w a r e . p h pIlocblob              J s o n C o m p i l e r . p h pIlocblob      A         L r u A r r a y C a c h e . p h pIlocblob               M i d d l e w a r e . p h pIlocblob     9         M o c k H a n d l e r . p h pIlocblob             	 M u l t i p a r tIlocblob       l      M u l t i R e g i o n C l i e n t . p h pIlocblob     1         P h p H a s h . p h pIlocblob              P r e s i g n U r l M i d d l e w a r e . p h pIlocblob     )         P s r C a c h e A d a p t e r . p h pIlocblob             
 R e s u l t . p h pIlocblob     !         R e s u l t I n t e r f a c e . p h pIlocblob        <?php
namespace Aws\Api;

/**
 * Base class that is used by most API shapes
 */
abstract class AbstractModel implements \ArrayAccess
{
    /** @var array */
    protected $definition;

    /** @var ShapeMap */
    protected $shapeMap;

    /**
     * @param array    $definition Service description
     * @param ShapeMap $shapeMap   Shapemap used for creating shapes
     */
    public function __construct(array $definition, ShapeMap $shapeMap)
    {
        $this->definition = $definition;
        $this->shapeMap = $shapeMap;
    }

    public function toArray()
    {
        return $this->definition;
    }

    public function offsetGet($offset)
    {
        return isset($this->definition[$offset])
            ? $this->definition[$offset] : null;
    }

    public function offsetSet($offset, $value)
    {
        $this->definition[$offset] = $value;
    }

    public function offsetExists($offset)
    {
        return isset($this->definition[$offset]);
    }

    public function offsetUnset($offset)
    {
        unset($this->definition[$offset]);
    }

    protected function shapeAt($key)
    {
        if (!isset($this->definition[$key])) {
            throw new \InvalidArgumentException('Expected shape definition at '
                . $key);
        }

        return $this->shapeFor($this->definition[$key]);
    }

    protected function shapeFor(array $definition)
    {
        return isset($definition['shape'])
            ? $this->shapeMap->resolve($definition)
            : Shape::create($definition, $this->shapeMap);
    }
}
<?php
namespace Aws\Api;

use Aws\Exception\UnresolvedApiException;

/**
 * API providers.
 *
 * An API provider is a function that accepts a type, service, and version and
 * returns an array of API data on success or NULL if no API data can be created
 * for the provided arguments.
 *
 * You can wrap your calls to an API provider with the
 * {@see ApiProvider::resolve} method to ensure that API data is created. If the
 * API data is not created, then the resolve() method will throw a
 * {@see Aws\Exception\UnresolvedApiException}.
 *
 *     use Aws\Api\ApiProvider;
 *     $provider = ApiProvider::defaultProvider();
 *     // Returns an array or NULL.
 *     $data = $provider('api', 's3', '2006-03-01');
 *     // Returns an array or throws.
 *     $data = ApiProvider::resolve($provider, 'api', 'elasticfood', '2020-01-01');
 *
 * You can compose multiple providers into a single provider using
 * {@see Aws\or_chain}. This method accepts providers as arguments and
 * returns a new function that will invoke each provider until a non-null value
 * is returned.
 *
 *     $a = ApiProvider::filesystem(sys_get_temp_dir() . '/aws-beta-models');
 *     $b = ApiProvider::manifest();
 *
 *     $c = \Aws\or_chain($a, $b);
 *     $data = $c('api', 'betaservice', '2015-08-08'); // $a handles this.
 *     $data = $c('api', 's3', '2006-03-01');          // $b handles this.
 *     $data = $c('api', 'invalid', '2014-12-15');     // Neither handles this.
 */
class ApiProvider
{
    /** @var array A map of public API type names to their file suffix. */
    private static $typeMap = [
        'api'       => 'api-2',
        'paginator' => 'paginators-1',
        'waiter'    => 'waiters-2',
        'docs'      => 'docs-2',
    ];

    /** @var array API manifest */
    private $manifest;

    /** @var string The directory containing service models. */
    private $modelsDir;

    /**
     * Resolves an API provider and ensures a non-null return value.
     *
     * @param callable $provider Provider function to invoke.
     * @param string   $type     Type of data ('api', 'waiter', 'paginator').
     * @param string   $service  Service name.
     * @param string   $version  API version.
     *
     * @return array
     * @throws UnresolvedApiException
     */
    public static function resolve(callable $provider, $type, $service, $version)
    {
        // Execute the provider and return the result, if there is one.
        $result = $provider($type, $service, $version);
        if (is_array($result)) {
            if (!isset($result['metadata']['serviceIdentifier'])) {
                $result['metadata']['serviceIdentifier'] = $service;
            }
            return $result;
        }

        // Throw an exception with a message depending on the inputs.
        if (!isset(self::$typeMap[$type])) {
            $msg = "The type must be one of: " . implode(', ', self::$typeMap);
        } elseif ($service) {
            $msg = "The {$service} service does not have version: {$version}.";
        } else {
            $msg = "You must specify a service name to retrieve its API data.";
        }

        throw new UnresolvedApiException($msg);
    }

    /**
     * Default SDK API provider.
     *
     * This provider loads pre-built manifest data from the `data` directory.
     *
     * @return self
     */
    public static function defaultProvider()
    {
        return new self(__DIR__ . '/../data', \Aws\manifest());
    }

    /**
     * Loads API data after resolving the version to the latest, compatible,
     * available version based on the provided manifest data.
     *
     * Manifest data is essentially an associative array of service names to
     * associative arrays of API version aliases.
     *
     * [
     *   ...
     *   'ec2' => [
     *     'latest'     => '2014-10-01',
     *     '2014-10-01' => '2014-10-01',
     *     '2014-09-01' => '2014-10-01',
     *     '2014-06-15' => '2014-10-01',
     *     ...
     *   ],
     *   'ecs' => [...],
     *   'elasticache' => [...],
     *   ...
     * ]
     *
     * @param string $dir      Directory containing service models.
     * @param array  $manifest The API version manifest data.
     *
     * @return self
     */
    public static function manifest($dir, array $manifest)
    {
        return new self($dir, $manifest);
    }

    /**
     * Loads API data from the specified directory.
     *
     * If "latest" is specified as the version, this provider must glob the
     * directory to find which is the latest available version.
     *
     * @param string $dir Directory containing service models.
     *
     * @return self
     * @throws \InvalidArgumentException if the provided `$dir` is invalid.
     */
    public static function filesystem($dir)
    {
        return new self($dir);
    }

    /**
     * Retrieves a list of valid versions for the specified service.
     *
     * @param string $service Service name
     *
     * @return array
     */
    public function getVersions($service)
    {
        if (!isset($this->manifest)) {
            $this->buildVersionsList($service);
        }

        if (!isset($this->manifest[$service]['versions'])) {
            return [];
        }

        return array_values(array_unique($this->manifest[$service]['versions']));
    }

    /**
     * Execute the the provider.
     *
     * @param string $type    Type of data ('api', 'waiter', 'paginator').
     * @param string $service Service name.
     * @param string $version API version.
     *
     * @return array|null
     */
    public function __invoke($type, $service, $version)
    {
        // Resolve the type or return null.
        if (isset(self::$typeMap[$type])) {
            $type = self::$typeMap[$type];
        } else {
            return null;
        }

        // Resolve the version or return null.
        if (!isset($this->manifest)) {
            $this->buildVersionsList($service);
        }

        if (!isset($this->manifest[$service]['versions'][$version])) {
            return null;
        }

        $version = $this->manifest[$service]['versions'][$version];
        $path = "{$this->modelsDir}/{$service}/{$version}/{$type}.json";

        try {
            return \Aws\load_compiled_json($path);
        } catch (\InvalidArgumentException $e) {
            return null;
        }
    }

    /**
     * @param string $modelsDir Directory containing service models.
     * @param array  $manifest  The API version manifest data.
     */
    private function __construct($modelsDir, array $manifest = null)
    {
        $this->manifest = $manifest;
        $this->modelsDir = rtrim($modelsDir, '/');
        if (!is_dir($this->modelsDir)) {
            throw new \InvalidArgumentException(
                "The specified models directory, {$modelsDir}, was not found."
            );
        }
    }

    /**
     * Build the versions list for the specified service by globbing the dir.
     */
    private function buildVersionsList($service)
    {
        $dir = "{$this->modelsDir}/{$service}/";

        if (!is_dir($dir)) {
            return;
        }

        // Get versions, remove . and .., and sort in descending order.
        $results = array_diff(scandir($dir, SCANDIR_SORT_DESCENDING), ['..', '.']);

        if (!$results) {
            $this->manifest[$service] = ['versions' => []];
        } else {
            $this->manifest[$service] = [
                'versions' => [
                    'latest' => $results[0]
                ]
            ];
            $this->manifest[$service]['versions'] += array_combine($results, $results);
        }
    }
}
<?php
namespace Aws\Api;

/**
 * DateTime overrides that make DateTime work more seamlessly as a string,
 * with JSON documents, and with JMESPath.
 */
class DateTimeResult extends \DateTime implements \JsonSerializable
{
    /**
     * Create a new DateTimeResult from a unix timestamp.
     *
     * @param $unixTimestamp
     *
     * @return DateTimeResult
     */
    public static function fromEpoch($unixTimestamp)
    {
        return new self(gmdate('c', $unixTimestamp));
    }

    /**
     * Serialize the DateTimeResult as an ISO 8601 date string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->format('c');
    }

    /**
     * Serialize the date as an ISO 8601 date when serializing as JSON.
     *
     * @return mixed|string
     */
    public function jsonSerialize()
    {
        return (string) $this;
    }
}
<?php
namespace Aws\Api;

/**
 * Encapsulates the documentation strings for a given service-version and
 * provides methods for extracting the desired parts related to a service,
 * operation, error, or shape (i.e., parameter).
 */
class DocModel
{
    /** @var array */
    private $docs;

    /**
     * @param array $docs
     *
     * @throws \RuntimeException
     */
    public function __construct(array $docs)
    {
        if (!extension_loaded('tidy')) {
            throw new \RuntimeException('The "tidy" PHP extension is required.');
        }

        $this->docs = $docs;
    }

    /**
     * Convert the doc model to an array.
     *
     * @return array
     */
    public function toArray()
    {
        return $this->docs;
    }

    /**
     * Retrieves documentation about the service.
     *
     * @return null|string
     */
    public function getServiceDocs()
    {
        return isset($this->docs['service']) ? $this->docs['service'] : null;
    }

    /**
     * Retrieves documentation about an operation.
     *
     * @param string $operation Name of the operation
     *
     * @return null|string
     */
    public function getOperationDocs($operation)
    {
        return isset($this->docs['operations'][$operation])
            ? $this->docs['operations'][$operation]
            : null;
    }

    /**
     * Retrieves documentation about an error.
     *
     * @param string $error Name of the error
     *
     * @return null|string
     */
    public function getErrorDocs($error)
    {
        return isset($this->docs['shapes'][$error]['base'])
            ? $this->docs['shapes'][$error]['base']
            : null;
    }

    /**
     * Retrieves documentation about a shape, specific to the context.
     *
     * @param string $shapeName  Name of the shape.
     * @param string $parentName Name of the parent/context shape.
     * @param string $ref        Name used by the context to reference the shape.
     *
     * @return null|string
     */
    public function getShapeDocs($shapeName, $parentName, $ref)
    {
        if (!isset($this->docs['shapes'][$shapeName])) {
            return '';
        }

        $result = '';
        $d = $this->docs['shapes'][$shapeName];
        if (isset($d['refs']["{$parentName}\$${ref}"])) {
            $result = $d['refs']["{$parentName}\$${ref}"];
        } elseif (isset($d['base'])) {
            $result = $d['base'];
        }

        if (isset($d['append'])) {
            $result .= $d['append'];
        }

        return $this->clean($result);
    }

    private function clean($content)
    {
        if (!$content) {
            return '';
        }

        $tidy = new \Tidy();
        $tidy->parseString($content, [
            'indent' => true,
            'doctype' => 'omit',
            'output-html' => true,
            'show-body-only' => true,
            'drop-empty-paras' => true,
            'drop-font-tags' => true,
            'drop-proprietary-attributes' => true,
            'hide-comments' => true,
            'logical-emphasis' => true
        ]);
        $tidy->cleanRepair();

        return (string) $content;
    }
}
<?php
namespace Aws\Api\ErrorParser;

use Aws\Api\Parser\PayloadParserTrait;
use Psr\Http\Message\ResponseInterface;

/**
 * Provides basic JSON error parsing functionality.
 */
trait JsonParserTrait
{
    use PayloadParserTrait;

    private function genericHandler(ResponseInterface $response)
    {
        $code = (string) $response->getStatusCode();

        return [
            'request_id'  => (string) $response->getHeaderLine('x-amzn-requestid'),
            'code'        => null,
            'message'     => null,
            'type'        => $code[0] == '4' ? 'client' : 'server',
            'parsed'      => $this->parseJson($response->getBody())
        ];
    }
}
<?php
namespace Aws\Api\ErrorParser;

use Psr\Http\Message\ResponseInterface;

/**
 * Parsers JSON-RPC errors.
 */
class JsonRpcErrorParser
{
    use JsonParserTrait;

    public function __invoke(ResponseInterface $response)
    {
        $data = $this->genericHandler($response);
        // Make the casing consistent across services.
        if ($data['parsed']) {
            $data['parsed'] = array_change_key_case($data['parsed']);
        }

        if (isset($data['parsed']['__type'])) {
            $parts = explode('#', $data['parsed']['__type']);
            $data['code'] = isset($parts[1]) ? $parts[1] : $parts[0];
            $data['message'] = isset($data['parsed']['message'])
                ? $data['parsed']['message']
                : null;
        }

        return $data;
    }
}
<?php
namespace Aws\Api\ErrorParser;

use Psr\Http\Message\ResponseInterface;

/**
 * Parses JSON-REST errors.
 */
class RestJsonErrorParser
{
    use JsonParserTrait;

    public function __invoke(ResponseInterface $response)
    {
        $data = $this->genericHandler($response);

        // Merge in error data from the JSON body
        if ($json = $data['parsed']) {
            $data = array_replace($data, $json);
        }

        // Correct error type from services like Amazon Glacier
        if (!empty($data['type'])) {
            $data['type'] = strtolower($data['type']);
        }

        // Retrieve the error code from services like Amazon Elastic Transcoder
        if ($code = $response->getHeaderLine('x-amzn-errortype')) {
            $colon = strpos($code, ':');
            $data['code'] = $colon ? substr($code, 0, $colon) : $code;
        }

        return $data;
    }
}
<?php
namespace Aws\Api\ErrorParser;

use Aws\Api\Parser\PayloadParserTrait;
use Psr\Http\Message\ResponseInterface;

/**
 * Parses XML errors.
 */
class XmlErrorParser
{
    use PayloadParserTrait;

    public function __invoke(ResponseInterface $response)
    {
        $code = (string) $response->getStatusCode();

        $data = [
            'type'        => $code[0] == '4' ? 'client' : 'server',
            'request_id'  => null,
            'code'        => null,
            'message'     => null,
            'parsed'      => null
        ];

        $body = $response->getBody();
        if ($body->getSize() > 0) {
            $this->parseBody($this->parseXml($body), $data);
        } else {
            $this->parseHeaders($response, $data);
        }

        return $data;
    }

    private function parseHeaders(ResponseInterface $response, array &$data)
    {
        if ($response->getStatusCode() == '404') {
            $data['code'] = 'NotFound';
        }

        $data['message'] = $response->getStatusCode() . ' '
            . $response->getReasonPhrase();

        if ($requestId = $response->getHeaderLine('x-amz-request-id')) {
            $data['request_id'] = $requestId;
            $data['message'] .= " (Request-ID: $requestId)";
        }
    }

    private function parseBody(\SimpleXMLElement $body, array &$data)
    {
        $data['parsed'] = $body;

        $namespaces = $body->getDocNamespaces();
        if (!isset($namespaces[''])) {
            $prefix = '';
        } else {
            // Account for the default namespace being defined and PHP not
            // being able to handle it :(.
            $body->registerXPathNamespace('ns', $namespaces['']);
            $prefix = 'ns:';
        }

        if ($tempXml = $body->xpath("//{$prefix}Code[1]")) {
            $data['code'] = (string) $tempXml[0];
        }

        if ($tempXml = $body->xpath("//{$prefix}Message[1]")) {
            $data['message'] = (string) $tempXml[0];
        }

        $tempXml = $body->xpath("//{$prefix}RequestId[1]");
        if (empty($tempXml)) {
            $tempXml = $body->xpath("//{$prefix}RequestID[1]");
        }

        if (isset($tempXml[0])) {
            $data['request_id'] = (string) $tempXml[0];
        }
    }
}
<?php
namespace Aws\Api;

/**
 * Represents a list shape.
 */
class ListShape extends Shape
{
    private $member;

    public function __construct(array $definition, ShapeMap $shapeMap)
    {
        $definition['type'] = 'list';
        parent::__construct($definition, $shapeMap);
    }

    /**
     * @return Shape
     * @throws \RuntimeException if no member is specified
     */
    public function getMember()
    {
        if (!$this->member) {
            if (!isset($this->definition['member'])) {
                throw new \RuntimeException('No member attribute specified');
            }
            $this->member = Shape::create(
                $this->definition['member'],
                $this->shapeMap
            );
        }

        return $this->member;
    }
}
<?php
namespace Aws\Api;

/**
 * Represents a map shape.
 */
class MapShape extends Shape
{
    /** @var Shape */
    private $value;

    /** @var Shape */
    private $key;

    public function __construct(array $definition, ShapeMap $shapeMap)
    {
        $definition['type'] = 'map';
        parent::__construct($definition, $shapeMap);
    }

    /**
     * @return Shape
     * @throws \RuntimeException if no value is specified
     */
    public function getValue()
    {
        if (!$this->value) {
            if (!isset($this->definition['value'])) {
                throw new \RuntimeException('No value specified');
            }

            $this->value = Shape::create(
                $this->definition['value'],
                $this->shapeMap
            );
        }

        return $this->value;
    }

    /**
     * @return Shape
     */
    public function getKey()
    {
        if (!$this->key) {
            $this->key = isset($this->definition['key'])
                ? Shape::create($this->definition['key'], $this->shapeMap)
                : new Shape(['type' => 'string'], $this->shapeMap);
        }

        return $this->key;
    }
}
<?php
namespace Aws\Api;

/**
 * Represents an API operation.
 */
class Operation extends AbstractModel
{
    private $input;
    private $output;
    private $errors;

    public function __construct(array $definition, ShapeMap $shapeMap)
    {
        $definition['type'] = 'structure';

        if (!isset($definition['http']['method'])) {
            $definition['http']['method'] = 'POST';
        }

        if (!isset($definition['http']['requestUri'])) {
            $definition['http']['requestUri'] = '/';
        }

        parent::__construct($definition, $shapeMap);
    }

    /**
     * Returns an associative array of the HTTP attribute of the operation:
     *
     * - method: HTTP method of the operation
     * - requestUri: URI of the request (can include URI template placeholders)
     *
     * @return array
     */
    public function getHttp()
    {
        return $this->definition['http'];
    }

    /**
     * Get the input shape of the operation.
     *
     * @return StructureShape
     */
    public function getInput()
    {
        if (!$this->input) {
            if ($input = $this['input']) {
                $this->input = $this->shapeFor($input);
            } else {
                $this->input = new StructureShape([], $this->shapeMap);
            }
        }

        return $this->input;
    }

    /**
     * Get the output shape of the operation.
     *
     * @return StructureShape
     */
    public function getOutput()
    {
        if (!$this->output) {
            if ($output = $this['output']) {
                $this->output = $this->shapeFor($output);
            } else {
                $this->output = new StructureShape([], $this->shapeMap);
            }
        }

        return $this->output;
    }

    /**
     * Get an array of operation error shapes.
     *
     * @return Shape[]
     */
    public function getErrors()
    {
        if ($this->errors === null) {
            if ($errors = $this['errors']) {
                foreach ($errors as &$error) {
                    $error = $this->shapeFor($error);
                }
                $this->errors = $errors;
            } else {
                $this->errors = [];
            }
        }

        return $this->errors;
    }
}
<?php
namespace Aws\Api\Parser;

use Aws\Api\Service;
use Aws\CommandInterface;
use Aws\ResultInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal
 */
abstract class AbstractParser
{
    /** @var \Aws\Api\Service Representation of the service API*/
    protected $api;

    /**
     * @param Service $api Service description.
     */
    public function __construct(Service $api)
    {
        $this->api = $api;
    }

    /**
     * @param CommandInterface  $command  Command that was executed.
     * @param ResponseInterface $response Response that was received.
     *
     * @return ResultInterface
     */
    abstract public function __invoke(
        CommandInterface $command,
        ResponseInterface $response
    );
}
<?php
namespace Aws\Api\Parser;

use Aws\Api\DateTimeResult;
use Aws\Api\Shape;
use Aws\Api\StructureShape;
use Aws\Result;
use Aws\CommandInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal
 */
abstract class AbstractRestParser extends AbstractParser
{
    /**
     * Parses a payload from a response.
     *
     * @param ResponseInterface $response Response to parse.
     * @param StructureShape    $member   Member to parse
     * @param array             $result   Result value
     *
     * @return mixed
     */
    abstract protected function payload(
        ResponseInterface $response,
        StructureShape $member,
        array &$result
    );

    public function __invoke(
        CommandInterface $command,
        ResponseInterface $response
    ) {
        $output = $this->api->getOperation($command->getName())->getOutput();
        $result = [];

        if ($payload = $output['payload']) {
            $this->extractPayload($payload, $output, $response, $result);
        }

        foreach ($output->getMembers() as $name => $member) {
            switch ($member['location']) {
                case 'header':
                    $this->extractHeader($name, $member, $response, $result);
                    break;
                case 'headers':
                    $this->extractHeaders($name, $member, $response, $result);
                    break;
                case 'statusCode':
                    $this->extractStatus($name, $response, $result);
                    break;
            }
        }

        if (!$payload
            && $response->getBody()->getSize() > 0
            && count($output->getMembers()) > 0
        ) {
            // if no payload was found, then parse the contents of the body
            $this->payload($response, $output, $result);
        }

        return new Result($result);
    }

    private function extractPayload(
        $payload,
        StructureShape $output,
        ResponseInterface $response,
        array &$result
    ) {
        $member = $output->getMember($payload);

        if ($member instanceof StructureShape) {
            // Structure members parse top-level data into a specific key.
            $result[$payload] = [];
            $this->payload($response, $member, $result[$payload]);
        } else {
            // Streaming data is just the stream from the response body.
            $result[$payload] = $response->getBody();
        }
    }

    /**
     * Extract a single header from the response into the result.
     */
    private function extractHeader(
        $name,
        Shape $shape,
        ResponseInterface $response,
        &$result
    ) {
        $value = $response->getHeaderLine($shape['locationName'] ?: $name);

        switch ($shape->getType()) {
            case 'float':
            case 'double':
                $value = (float) $value;
                break;
            case 'long':
                $value = (int) $value;
                break;
            case 'boolean':
                $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
                break;
            case 'blob':
                $value = base64_decode($value);
                break;
            case 'timestamp':
                try {
                    $value = new DateTimeResult($value);
                    break;
                } catch (\Exception $e) {
                    // If the value cannot be parsed, then do not add it to the
                    // output structure.
                    return;
                }
        }

        $result[$name] = $value;
    }

    /**
     * Extract a map of headers with an optional prefix from the response.
     */
    private function extractHeaders(
        $name,
        Shape $shape,
        ResponseInterface $response,
        &$result
    ) {
        // Check if the headers are prefixed by a location name
        $result[$name] = [];
        $prefix = $shape['locationName'];
        $prefixLen = strlen($prefix);

        foreach ($response->getHeaders() as $k => $values) {
            if (!$prefixLen) {
                $result[$name][$k] = implode(', ', $values);
            } elseif (stripos($k, $prefix) === 0) {
                $result[$name][substr($k, $prefixLen)] = implode(', ', $values);
            }
        }
    }

    /**
     * Places the status code of the response into the result array.
     */
    private function extractStatus(
        $name,
        ResponseInterface $response,
        array &$result
    ) {
        $result[$name] = (int) $response->getStatusCode();
    }
}
<?php
namespace Aws\Api\Parser;

use Aws\CommandInterface;
use Aws\Exception\AwsException;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Psr7;

/**
 * @internal Decorates a parser and validates the x-amz-crc32 header.
 */
class Crc32ValidatingParser extends AbstractParser
{
    /** @var callable */
    private $parser;

    /**
     * @param callable $parser Parser to wrap.
     */
    public function __construct(callable $parser)
    {
        $this->parser = $parser;
    }

    public function __invoke(
        CommandInterface $command,
        ResponseInterface $response
    ) {
        if ($expected = $response->getHeaderLine('x-amz-crc32')) {
            $hash = hexdec(Psr7\hash($response->getBody(), 'crc32b'));
            if ($expected != $hash) {
                throw new AwsException(
                    "crc32 mismatch. Expected {$expected}, found {$hash}.",
                    $command,
                    [
                        'code'             => 'ClientChecksumMismatch',
                        'connection_error' => true,
                        'response'         => $response
                    ]
                );
            }
        }

        $fn = $this->parser;
        return $fn($command, $response);
    }
}
<?php
namespace Aws\Api\Parser\Exception;

class ParserException extends \RuntimeException {}
<?php
namespace Aws\Api\Parser;

use Aws\Api\DateTimeResult;
use Aws\Api\Shape;

/**
 * @internal Implements standard JSON parsing.
 */
class JsonParser
{
    public function parse(Shape $shape, $value)
    {
        if ($value === null) {
            return $value;
        }

        switch ($shape['type']) {
            case 'structure':
                $target = [];
                foreach ($shape->getMembers() as $name => $member) {
                    $locationName = $member['locationName'] ?: $name;
                    if (isset($value[$locationName])) {
                        $target[$name] = $this->parse($member, $value[$locationName]);
                    }
                }
                return $target;

            case 'list':
                $member = $shape->getMember();
                $target = [];
                foreach ($value as $v) {
                    $target[] = $this->parse($member, $v);
                }
                return $target;

            case 'map':
                $values = $shape->getValue();
                $target = [];
                foreach ($value as $k => $v) {
                    $target[$k] = $this->parse($values, $v);
                }
                return $target;

            case 'timestamp':
                // The Unix epoch (or Unix time or POSIX time or Unix
                // timestamp) is the number of seconds that have elapsed since
                // January 1, 1970 (midnight UTC/GMT).
                return DateTimeResult::fromEpoch($value);

            case 'blob':
                return base64_decode($value);

            default:
                return $value;
        }
    }
}
<?php
namespace Aws\Api\Parser;

use Aws\Api\Service;
use Aws\Result;
use Aws\CommandInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal Implements JSON-RPC parsing (e.g., DynamoDB)
 */
class JsonRpcParser extends AbstractParser
{
    use PayloadParserTrait;

    private $parser;

    /**
     * @param Service    $api    Service description
     * @param JsonParser $parser JSON body builder
     */
    public function __construct(Service $api, JsonParser $parser = null)
    {
        parent::__construct($api);
        $this->parser = $parser ?: new JsonParser();
    }

    public function __invoke(
        CommandInterface $command,
        ResponseInterface $response
    ) {
        $operation = $this->api->getOperation($command->getName());
        $result = null === $operation['output']
            ? null
            : $this->parser->parse(
                $operation->getOutput(),
                $this->parseJson($response->getBody())
            );

        return new Result($result ?: []);
    }
}
<?php
namespace Aws\Api\Parser;

use Aws\Api\Parser\Exception\ParserException;

trait PayloadParserTrait
{
    /**
     * @param string $json
     *
     * @throws ParserException
     *
     * @return array
     */
    private function parseJson($json)
    {
        $jsonPayload = json_decode($json, true);

        if (JSON_ERROR_NONE !== json_last_error()) {
            throw new ParserException('Error parsing JSON: '
                . json_last_error_msg());
        }

        return $jsonPayload;
    }

    /**
     * @param string $xml
     *
     * @throws ParserException
     *
     * @return \SimpleXMLElement
     */
    private function parseXml($xml)
    {
        $priorSetting = libxml_use_internal_errors(true);
        try {
            libxml_clear_errors();
            $xmlPayload = new \SimpleXMLElement($xml);
            if ($error = libxml_get_last_error()) {
                throw new \RuntimeException($error->message);
            }
        } catch (\Exception $e) {
            throw new ParserException("Error parsing XML: {$e->getMessage()}", 0, $e);
        } finally {
            libxml_use_internal_errors($priorSetting);
        }

        return $xmlPayload;
    }
}
<?php
namespace Aws\Api\Parser;

use Aws\Api\Service;
use Aws\Result;
use Aws\CommandInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal Parses query (XML) responses (e.g., EC2, SQS, and many others)
 */
class QueryParser extends AbstractParser
{
    use PayloadParserTrait;

    /** @var XmlParser */
    private $xmlParser;

    /** @var bool */
    private $honorResultWrapper;

    /**
     * @param Service   $api                Service description
     * @param XmlParser $xmlParser          Optional XML parser
     * @param bool      $honorResultWrapper Set to false to disable the peeling
     *                                      back of result wrappers from the
     *                                      output structure.
     */
    public function __construct(
        Service $api,
        XmlParser $xmlParser = null,
        $honorResultWrapper = true
    ) {
        parent::__construct($api);
        $this->xmlParser = $xmlParser ?: new XmlParser();
        $this->honorResultWrapper = $honorResultWrapper;
    }

    public function __invoke(
        CommandInterface $command,
        ResponseInterface $response
    ) {
        $output = $this->api->getOperation($command->getName())->getOutput();
        $xml = $this->parseXml($response->getBody());

        if ($this->honorResultWrapper && $output['resultWrapper']) {
            $xml = $xml->{$output['resultWrapper']};
        }

        return new Result($this->xmlParser->parse($output, $xml));
    }
}
<?php
namespace Aws\Api\Parser;

use Aws\Api\Service;
use Aws\Api\StructureShape;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal Implements REST-JSON parsing (e.g., Glacier, Elastic Transcoder)
 */
class RestJsonParser extends AbstractRestParser
{
    use PayloadParserTrait;

    /** @var JsonParser */
    private $parser;

    /**
     * @param Service    $api    Service description
     * @param JsonParser $parser JSON body builder
     */
    public function __construct(Service $api, JsonParser $parser = null)
    {
        parent::__construct($api);
        $this->parser = $parser ?: new JsonParser();
    }

    protected function payload(
        ResponseInterface $response,
        StructureShape $member,
        array &$result
    ) {
        $jsonBody = $this->parseJson($response->getBody());

        if ($jsonBody) {
            $result += $this->parser->parse($member, $jsonBody);
        }
    }
}
<?php
namespace Aws\Api\Parser;

use Aws\Api\StructureShape;
use Aws\Api\Service;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal Implements REST-XML parsing (e.g., S3, CloudFront, etc...)
 */
class RestXmlParser extends AbstractRestParser
{
    use PayloadParserTrait;

    /** @var XmlParser */
    private $parser;

    /**
     * @param Service   $api    Service description
     * @param XmlParser $parser XML body parser
     */
    public function __construct(Service $api, XmlParser $parser = null)
    {
        parent::__construct($api);
        $this->parser = $parser ?: new XmlParser();
    }

    protected function payload(
        ResponseInterface $response,
        StructureShape $member,
        array &$result
    ) {
        $xml = $this->parseXml($response->getBody());
        $result += $this->parser->parse($member, $xml);
    }
}
<?php
namespace Aws\Api\Parser;

use Aws\Api\DateTimeResult;
use Aws\Api\ListShape;
use Aws\Api\MapShape;
use Aws\Api\Shape;
use Aws\Api\StructureShape;

/**
 * @internal Implements standard XML parsing for REST-XML and Query protocols.
 */
class XmlParser
{
    public function parse(StructureShape $shape, \SimpleXMLElement $value)
    {
        return $this->dispatch($shape, $value);
    }

    private function dispatch($shape, \SimpleXMLElement $value)
    {
        static $methods = [
            'structure' => 'parse_structure',
            'list'      => 'parse_list',
            'map'       => 'parse_map',
            'blob'      => 'parse_blob',
            'boolean'   => 'parse_boolean',
            'integer'   => 'parse_integer',
            'float'     => 'parse_float',
            'double'    => 'parse_float',
            'timestamp' => 'parse_timestamp',
        ];

        $type = $shape['type'];
        if (isset($methods[$type])) {
            return $this->{$methods[$type]}($shape, $value);
        }

        return (string) $value;
    }

    private function parse_structure(
        StructureShape $shape,
        \SimpleXMLElement $value
    ) {
        $target = [];

        foreach ($shape->getMembers() as $name => $member) {
            // Extract the name of the XML node
            $node = $this->memberKey($member, $name);
            if (isset($value->{$node})) {
                $target[$name] = $this->dispatch($member, $value->{$node});
            }
        }

        return $target;
    }

    private function memberKey(Shape $shape, $name)
    {
        if (null !== $shape['locationName']) {
            return $shape['locationName'];
        }

        if ($shape instanceof ListShape && $shape['flattened']) {
            return $shape->getMember()['locationName'] ?: $name;
        }

        return $name;
    }

    private function parse_list(ListShape $shape, \SimpleXMLElement  $value)
    {
        $target = [];
        $member = $shape->getMember();

        if (!$shape['flattened']) {
            $value = $value->{$member['locationName'] ?: 'member'};
        }

        foreach ($value as $v) {
            $target[] = $this->dispatch($member, $v);
        }

        return $target;
    }

    private function parse_map(MapShape $shape, \SimpleXMLElement $value)
    {
        $target = [];

        if (!$shape['flattened']) {
            $value = $value->entry;
        }

        $mapKey = $shape->getKey();
        $mapValue = $shape->getValue();
        $keyName = $shape->getKey()['locationName'] ?: 'key';
        $valueName = $shape->getValue()['locationName'] ?: 'value';

        foreach ($value as $node) {
            $key = $this->dispatch($mapKey, $node->{$keyName});
            $value = $this->dispatch($mapValue, $node->{$valueName});
            $target[$key] = $value;
        }

        return $target;
    }

    private function parse_blob(Shape $shape, $value)
    {
        return base64_decode((string) $value);
    }

    private function parse_float(Shape $shape, $value)
    {
        return (float) (string) $value;
    }

    private function parse_integer(Shape $shape, $value)
    {
        return (int) (string) $value;
    }

    private function parse_boolean(Shape $shape, $value)
    {
        return $value == 'true' ? true : false;
    }

    private function parse_timestamp(Shape $shape, $value)
    {
        return new DateTimeResult($value);
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\Shape;
use Aws\Api\ListShape;

/**
 * @internal
 */
class Ec2ParamBuilder extends QueryParamBuilder
{
    protected function queryName(Shape $shape, $default = null)
    {
        return ($shape['queryName']
            ?: ucfirst($shape['locationName']))
                ?: $default;
    }

    protected function isFlat(Shape $shape)
    {
        return false;
    }

    protected function format_list(
        ListShape $shape,
        array $value,
        $prefix,
        &$query
    ) {
        // Handle empty list serialization
        if (!$value) {
            $query[$prefix] = false;
        } else {
            $items = $shape->getMember();
            foreach ($value as $k => $v) {
                $this->format($items, $v, $prefix . '.' . ($k + 1), $query);
            }
        }
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\Service;
use Aws\Api\Shape;
use Aws\Api\TimestampShape;

/**
 * Formats the JSON body of a JSON-REST or JSON-RPC operation.
 * @internal
 */
class JsonBody
{
    private $api;

    public function __construct(Service $api)
    {
        $this->api = $api;
    }

    /**
     * Gets the JSON Content-Type header for a service API
     *
     * @param Service $service
     *
     * @return string
     */
    public static function getContentType(Service $service)
    {
        return 'application/x-amz-json-'
            . number_format($service->getMetadata('jsonVersion'), 1);
    }

    /**
     * Builds the JSON body based on an array of arguments.
     *
     * @param Shape $shape Operation being constructed
     * @param array $args  Associative array of arguments
     *
     * @return string
     */
    public function build(Shape $shape, array $args)
    {
        $result = json_encode($this->format($shape, $args));

        return $result == '[]' ? '{}' : $result;
    }

    private function format(Shape $shape, $value)
    {
        switch ($shape['type']) {
            case 'structure':
                $data = [];
                foreach ($value as $k => $v) {
                    if ($v !== null && $shape->hasMember($k)) {
                        $valueShape = $shape->getMember($k);
                        $data[$valueShape['locationName'] ?: $k]
                            = $this->format($valueShape, $v);
                    }
                }
                return $data;

            case 'list':
                $items = $shape->getMember();
                foreach ($value as &$v) {
                    $v = $this->format($items, $v);
                }
                return $value;

            case 'map':
                if (empty($value)) {
                    return new \stdClass;
                }
                $values = $shape->getValue();
                foreach ($value as &$v) {
                    $v = $this->format($values, $v);
                }
                return $value;

            case 'blob':
                return base64_encode($value);

            case 'timestamp':
                return TimestampShape::format($value, 'unixTimestamp');

            default:
                return $value;
        }
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\Service;
use Aws\CommandInterface;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;

/**
 * Prepares a JSON-RPC request for transfer.
 * @internal
 */
class JsonRpcSerializer
{
    /** @var JsonBody */
    private $jsonFormatter;

    /** @var string */
    private $endpoint;

    /** @var Service */
    private $api;

    /** @var string */
    private $contentType;

    /**
     * @param Service  $api           Service description
     * @param string   $endpoint      Endpoint to connect to
     * @param JsonBody $jsonFormatter Optional JSON formatter to use
     */
    public function __construct(
        Service $api,
        $endpoint,
        JsonBody $jsonFormatter = null
    ) {
        $this->endpoint = $endpoint;
        $this->api = $api;
        $this->jsonFormatter = $jsonFormatter ?: new JsonBody($this->api);
        $this->contentType = JsonBody::getContentType($api);
    }

    /**
     * When invoked with an AWS command, returns a serialization array
     * containing "method", "uri", "headers", and "body" key value pairs.
     *
     * @param CommandInterface $command
     *
     * @return RequestInterface
     */
    public function __invoke(CommandInterface $command)
    {
        $name = $command->getName();
        $operation = $this->api->getOperation($name);

        return new Request(
            $operation['http']['method'],
            $this->endpoint,
            [
                'X-Amz-Target' => $this->api->getMetadata('targetPrefix') . '.' . $name,
                'Content-Type' => $this->contentType
            ],
            $this->jsonFormatter->build(
                $operation->getInput(),
                $command->toArray()
            )
        );
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\StructureShape;
use Aws\Api\ListShape;
use Aws\Api\MapShape;
use Aws\Api\Shape;
use Aws\Api\TimestampShape;

/**
 * @internal
 */
class QueryParamBuilder
{
    private $methods;

    protected function queryName(Shape $shape, $default = null)
    {
        if (null !== $shape['queryName']) {
            return $shape['queryName'];
        }

        if (null !== $shape['locationName']) {
            return $shape['locationName'];
        }

        if ($this->isFlat($shape) && !empty($shape['member']['locationName'])) {
            return $shape['member']['locationName'];
        }

        return $default;
    }

    protected function isFlat(Shape $shape)
    {
        return $shape['flattened'] === true;
    }

    public function __invoke(StructureShape $shape, array $params)
    {
        if (!$this->methods) {
            $this->methods = array_fill_keys(get_class_methods($this), true);
        }

        $query = [];
        $this->format_structure($shape, $params, '', $query);

        return $query;
    }

    protected function format(Shape $shape, $value, $prefix, array &$query)
    {
        $type = 'format_' . $shape['type'];
        if (isset($this->methods[$type])) {
            $this->{$type}($shape, $value, $prefix, $query);
        } else {
            $query[$prefix] = (string) $value;
        }
    }

    protected function format_structure(
        StructureShape $shape,
        array $value,
        $prefix,
        &$query
    ) {
        if ($prefix) {
            $prefix .= '.';
        }

        foreach ($value as $k => $v) {
            if ($shape->hasMember($k)) {
                $member = $shape->getMember($k);
                $this->format(
                    $member,
                    $v,
                    $prefix . $this->queryName($member, $k),
                    $query
                );
            }
        }
    }

    protected function format_list(
        ListShape $shape,
        array $value,
        $prefix,
        &$query
    ) {
        // Handle empty list serialization
        if (!$value) {
            $query[$prefix] = '';
            return;
        }

        $items = $shape->getMember();

        if (!$this->isFlat($shape)) {
            $locationName = $shape->getMember()['locationName'] ?: 'member';
            $prefix .= ".$locationName";
        } elseif ($name = $this->queryName($items)) {
            $parts = explode('.', $prefix);
            $parts[count($parts) - 1] = $name;
            $prefix = implode('.', $parts);
        }

        foreach ($value as $k => $v) {
            $this->format($items, $v, $prefix . '.' . ($k + 1), $query);
        }
    }

    protected function format_map(
        MapShape $shape,
        array $value,
        $prefix,
        array &$query
    ) {
        $vals = $shape->getValue();
        $keys = $shape->getKey();

        if (!$this->isFlat($shape)) {
            $prefix .= '.entry';
        }

        $i = 0;
        $keyName = '%s.%d.' . $this->queryName($keys, 'key');
        $valueName = '%s.%s.' . $this->queryName($vals, 'value');

        foreach ($value as $k => $v) {
            $i++;
            $this->format($keys, $k, sprintf($keyName, $prefix, $i), $query);
            $this->format($vals, $v, sprintf($valueName, $prefix, $i), $query);
        }
    }

    protected function format_blob(Shape $shape, $value, $prefix, array &$query)
    {
        $query[$prefix] = base64_encode($value);
    }

    protected function format_timestamp(
        TimestampShape $shape,
        $value,
        $prefix,
        array &$query
    ) {
        $query[$prefix] = TimestampShape::format($value, 'iso8601');
    }

    protected function format_boolean(Shape $shape, $value, $prefix, array &$query)
    {
        $query[$prefix] = ($value) ? 'true' : 'false';
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\Service;
use Aws\CommandInterface;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;

/**
 * Serializes a query protocol request.
 * @internal
 */
class QuerySerializer
{
    private $endpoint;
    private $api;
    private $paramBuilder;

    public function __construct(
        Service $api,
        $endpoint,
        callable $paramBuilder = null
    ) {
        $this->api = $api;
        $this->endpoint = $endpoint;
        $this->paramBuilder = $paramBuilder ?: new QueryParamBuilder();
    }

    /**
     * When invoked with an AWS command, returns a serialization array
     * containing "method", "uri", "headers", and "body" key value pairs.
     *
     * @param CommandInterface $command
     *
     * @return RequestInterface
     */
    public function __invoke(CommandInterface $command)
    {
        $operation = $this->api->getOperation($command->getName());

        $body = [
            'Action'  => $command->getName(),
            'Version' => $this->api->getMetadata('apiVersion')
        ];

        $params = $command->toArray();

        // Only build up the parameters when there are parameters to build
        if ($params) {
            $body += call_user_func(
                $this->paramBuilder,
                $operation->getInput(),
                $params
            );
        }

        $body = http_build_query($body, null, '&', PHP_QUERY_RFC3986);

        return new Request(
            'POST',
            $this->endpoint,
            [
                'Content-Length' => strlen($body),
                'Content-Type'   => 'application/x-www-form-urlencoded'
            ],
            $body
        );
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\Service;
use Aws\Api\StructureShape;

/**
 * Serializes requests for the REST-JSON protocol.
 * @internal
 */
class RestJsonSerializer extends RestSerializer
{
    /** @var JsonBody */
    private $jsonFormatter;

    /** @var string */
    private $contentType;

    /**
     * @param Service  $api           Service API description
     * @param string   $endpoint      Endpoint to connect to
     * @param JsonBody $jsonFormatter Optional JSON formatter to use
     */
    public function __construct(
        Service $api,
        $endpoint,
        JsonBody $jsonFormatter = null
    ) {
        parent::__construct($api, $endpoint);
        $this->contentType = JsonBody::getContentType($api);
        $this->jsonFormatter = $jsonFormatter ?: new JsonBody($api);
    }

    protected function payload(StructureShape $member, array $value, array &$opts)
    {
        $opts['headers']['Content-Type'] = $this->contentType;
        $opts['body'] = (string) $this->jsonFormatter->build($member, $value);
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\MapShape;
use Aws\Api\Service;
use Aws\Api\Operation;
use Aws\Api\Shape;
use Aws\Api\StructureShape;
use Aws\Api\TimestampShape;
use Aws\CommandInterface;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Serializes HTTP locations like header, uri, payload, etc...
 * @internal
 */
abstract class RestSerializer
{
    /** @var Service */
    private $api;

    /** @var Psr7\Uri */
    private $endpoint;

    /**
     * @param Service $api      Service API description
     * @param string  $endpoint Endpoint to connect to
     */
    public function __construct(Service $api, $endpoint)
    {
        $this->api = $api;
        $this->endpoint = Psr7\uri_for($endpoint);
    }

    /**
     * @param CommandInterface $command Command to serialized
     *
     * @return RequestInterface
     */
    public function __invoke(CommandInterface $command)
    {
        $operation = $this->api->getOperation($command->getName());
        $args = $command->toArray();
        $opts = $this->serialize($operation, $args);
        $uri = $this->buildEndpoint($operation, $args, $opts);

        return new Psr7\Request(
            $operation['http']['method'],
            $uri,
            isset($opts['headers']) ? $opts['headers'] : [],
            isset($opts['body']) ? $opts['body'] : null
        );
    }

    /**
     * Modifies a hash of request options for a payload body.
     *
     * @param StructureShape   $member  Member to serialize
     * @param array            $value   Value to serialize
     * @param array            $opts    Request options to modify.
     */
    abstract protected function payload(
        StructureShape $member,
        array $value,
        array &$opts
    );

    private function serialize(Operation $operation, array $args)
    {
        $opts = [];
        $input = $operation->getInput();

        // Apply the payload trait if present
        if ($payload = $input['payload']) {
            $this->applyPayload($input, $payload, $args, $opts);
        }

        foreach ($args as $name => $value) {
            if ($input->hasMember($name)) {
                $member = $input->getMember($name);
                $location = $member['location'];
                if (!$payload && !$location) {
                    $bodyMembers[$name] = $value;
                } elseif ($location == 'header') {
                    $this->applyHeader($name, $member, $value, $opts);
                } elseif ($location == 'querystring') {
                    $this->applyQuery($name, $member, $value, $opts);
                } elseif ($location == 'headers') {
                    $this->applyHeaderMap($name, $member, $value, $opts);
                }
            }
        }

        if (isset($bodyMembers)) {
            $this->payload($operation->getInput(), $bodyMembers, $opts);
        }

        return $opts;
    }

    private function applyPayload(StructureShape $input, $name, array $args, array &$opts)
    {
        if (!isset($args[$name])) {
            return;
        }

        $m = $input->getMember($name);

        if ($m['streaming'] ||
           ($m['type'] == 'string' || $m['type'] == 'blob')
        ) {
            // Streaming bodies or payloads that are strings are
            // always just a stream of data.
            $opts['body'] = Psr7\stream_for($args[$name]);
            return;
        }

        $this->payload($m, $args[$name], $opts);
    }

    private function applyHeader($name, Shape $member, $value, array &$opts)
    {
        if ($member->getType() == 'timestamp') {
            $value = TimestampShape::format($value, 'rfc822');
        }

        $opts['headers'][$member['locationName'] ?: $name] = $value;
    }

    /**
     * Note: This is currently only present in the Amazon S3 model.
     */
    private function applyHeaderMap($name, Shape $member, array $value, array &$opts)
    {
        $prefix = $member['locationName'];
        foreach ($value as $k => $v) {
            $opts['headers'][$prefix . $k] = $v;
        }
    }

    private function applyQuery($name, Shape $member, $value, array &$opts)
    {
        if ($member instanceof MapShape) {
            $opts['query'] = isset($opts['query']) && is_array($opts['query'])
                ? $opts['query'] + $value
                : $value;
        } elseif ($value !== null) {
            if ($member->getType() === 'boolean') {
                $value = $value ? 'true' : 'false';
            }
            
            $opts['query'][$member['locationName'] ?: $name] = $value;
        }
    }

    private function buildEndpoint(Operation $operation, array $args, array $opts)
    {
        $varspecs = [];

        // Create an associative array of varspecs used in expansions
        foreach ($operation->getInput()->getMembers() as $name => $member) {
            if ($member['location'] == 'uri') {
                $varspecs[$member['locationName'] ?: $name] =
                    isset($args[$name])
                        ? $args[$name]
                        : null;
            }
        }

        $relative = preg_replace_callback(
            '/\{([^\}]+)\}/',
            function (array $matches) use ($varspecs) {
                $isGreedy = substr($matches[1], -1, 1) == '+';
                $k = $isGreedy ? substr($matches[1], 0, -1) : $matches[1];
                if (!isset($varspecs[$k])) {
                    return '';
                } elseif ($isGreedy) {
                    return str_replace('%2F', '/', rawurlencode($varspecs[$k]));
                } else {
                    return rawurlencode($varspecs[$k]);
                }
            },
            $operation['http']['requestUri']
        );

        // Add the query string variables or appending to one if needed.
        if (!empty($opts['query'])) {
            $append = Psr7\build_query($opts['query']);
            $relative .= strpos($relative, '?') ? "&{$append}" : "?$append";
        }

        // Expand path place holders using Amazon's slightly different URI
        // template syntax.
        return Psr7\Uri::resolve($this->endpoint, $relative);
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\StructureShape;
use Aws\Api\Service;

/**
 * @internal
 */
class RestXmlSerializer extends RestSerializer
{
    /** @var XmlBody */
    private $xmlBody;

    /**
     * @param Service $api      Service API description
     * @param string  $endpoint Endpoint to connect to
     * @param XmlBody $xmlBody  Optional XML formatter to use
     */
    public function __construct(
        Service $api,
        $endpoint,
        XmlBody $xmlBody = null
    ) {
        parent::__construct($api, $endpoint);
        $this->xmlBody = $xmlBody ?: new XmlBody($api);
    }

    protected function payload(StructureShape $member, array $value, array &$opts)
    {
        $opts['headers']['Content-Type'] = 'application/xml';
        $opts['body'] = (string) $this->xmlBody->build($member, $value);
    }
}
<?php
namespace Aws\Api\Serializer;

use Aws\Api\MapShape;
use Aws\Api\Service;
use Aws\Api\Shape;
use Aws\Api\StructureShape;
use Aws\Api\ListShape;
use Aws\Api\TimestampShape;
use XMLWriter;

/**
 * @internal Formats the XML body of a REST-XML services.
 */
class XmlBody
{
    /** @var \Aws\Api\Service */
    private $api;

    /**
     * @param Service $api API being used to create the XML body.
     */
    public function __construct(Service $api)
    {
        $this->api = $api;
    }

    /**
     * Builds the XML body based on an array of arguments.
     *
     * @param Shape $shape Operation being constructed
     * @param array $args  Associative array of arguments
     *
     * @return string
     */
    public function build(Shape $shape, array $args)
    {
        $xml = new XMLWriter();
        $xml->openMemory();
        $xml->startDocument('1.0', 'UTF-8');
        $this->format($shape, $shape['locationName'], $args, $xml);
        $xml->endDocument();

        return $xml->outputMemory();
    }

    private function startElement(Shape $shape, $name, XMLWriter $xml)
    {
        $xml->startElement($name);

        if ($ns = $shape['xmlNamespace']) {
            $xml->writeAttribute(
                isset($ns['prefix']) ? "xmlns:{$ns['prefix']}" : 'xmlns',
                $shape['xmlNamespace']['uri']
            );
        }
    }

    private function format(Shape $shape, $name, $value, XMLWriter $xml)
    {
        // Any method mentioned here has a custom serialization handler.
        static $methods = [
            'add_structure' => true,
            'add_list'      => true,
            'add_blob'      => true,
            'add_timestamp' => true,
            'add_boolean'   => true,
            'add_map'       => true,
            'add_string'    => true
        ];

        $type = 'add_' . $shape['type'];
        if (isset($methods[$type])) {
            $this->{$type}($shape, $name, $value, $xml);
        } else {
            $this->defaultShape($shape, $name, $value, $xml);
        }
    }

    private function defaultShape(Shape $shape, $name, $value, XMLWriter $xml)
    {
        $this->startElement($shape, $name, $xml);
        $xml->writeRaw($value);
        $xml->endElement();
    }

    private function add_structure(
        StructureShape $shape,
        $name,
        array $value,
        \XMLWriter $xml
    ) {
        $this->startElement($shape, $name, $xml);

        foreach ($this->getStructureMembers($shape, $value) as $k => $definition) {
            $this->format(
                $definition['member'],
                $definition['member']['locationName'] ?: $k,
                $definition['value'],
                $xml
            );
        }

        $xml->endElement();
    }

    private function getStructureMembers(StructureShape $shape, array $value)
    {
        $members = [];

        foreach ($value as $k => $v) {
            if ($v !== null && $shape->hasMember($k)) {
                $definition = [
                    'member' => $shape->getMember($k),
                    'value'  => $v,
                ];

                if ($definition['member']['xmlAttribute']) {
                    // array_unshift_associative
                    $members = [$k => $definition] + $members;
                } else {
                    $members[$k] = $definition;
                }
            }
        }

        return $members;
    }

    private function add_list(
        ListShape $shape,
        $name,
        array $value,
        XMLWriter $xml
    ) {
        $items = $shape->getMember();

        if ($shape['flattened']) {
            $elementName = $name;
        } else {
            $this->startElement($shape, $name, $xml);
            $elementName = $items['locationName'] ?: 'member';
        }

        foreach ($value as &$v) {
            $this->format($items, $elementName, $v, $xml);
        }

        if (!$shape['flattened']) {
            $xml->endElement();
        }
    }

    private function add_map(
        MapShape $shape,
        $name,
        array $value,
        XMLWriter $xml
    ) {
        $xmlEntry = $shape['flattened'] ? $shape['locationName'] : 'entry';
        $xmlKey = $shape->getKey()['locationName'] ?: 'key';
        $xmlValue = $shape->getValue()['locationName'] ?: 'value';

        $this->startElement($shape, $name, $xml);

        foreach ($value as $key => $v) {
            $this->startElement($shape, $xmlEntry, $xml);
            $this->format($shape->getKey(), $xmlKey, $key, $xml);
            $this->format($shape->getValue(), $xmlValue, $v, $xml);
            $xml->endElement();
        }

        $xml->endElement();
    }

    private function add_blob(Shape $shape, $name, $value, XMLWriter $xml)
    {
        $this->startElement($shape, $name, $xml);
        $xml->writeRaw(base64_encode($value));
        $xml->endElement();
    }

    private function add_timestamp(
        TimestampShape $shape,
        $name,
        $value,
        XMLWriter $xml
    ) {
        $this->startElement($shape, $name, $xml);
        $xml->writeRaw(TimestampShape::format($value, 'iso8601'));
        $xml->endElement();
    }

    private function add_boolean(
        Shape $shape,
        $name,
        $value,
        XMLWriter $xml
    ) {
        $this->startElement($shape, $name, $xml);
        $xml->writeRaw($value ? 'true' : 'false');
        $xml->endElement();
    }

    private function add_string(
        Shape $shape,
        $name,
        $value,
        XMLWriter $xml
    ) {
        if ($shape['xmlAttribute']) {
            $xml->writeAttribute($shape['locationName'] ?: $name, $value);
        } else {
            $this->defaultShape($shape, $name, $value, $xml);
        }
    }
}
<?php
namespace Aws\Api;

use Aws\Api\Serializer\QuerySerializer;
use Aws\Api\Serializer\Ec2ParamBuilder;
use Aws\Api\Parser\QueryParser;

/**
 * Represents a web service API model.
 */
class Service extends AbstractModel
{
    /** @var callable */
    private $apiProvider;

    /** @var string */
    private $serviceName;

    /** @var string */
    private $apiVersion;

    /** @var Operation[] */
    private $operations = [];

    /** @var array */
    private $paginators = null;

    /** @var array */
    private $waiters = null;

    /**
     * @param array    $definition
     * @param callable $provider
     *
     * @internal param array $definition Service description
     */
    public function __construct(array $definition, callable $provider)
    {
        static $defaults = [
            'operations' => [],
            'shapes'     => [],
            'metadata'   => []
        ], $defaultMeta = [
            'apiVersion'       => null,
            'serviceFullName'  => null,
            'endpointPrefix'   => null,
            'signingName'      => null,
            'signatureVersion' => null,
            'protocol'         => null,
            'uid'              => null
        ];

        $definition += $defaults;
        $definition['metadata'] += $defaultMeta;
        $this->definition = $definition;
        $this->apiProvider = $provider;
        parent::__construct($definition, new ShapeMap($definition['shapes']));

        if (isset($definition['metadata']['serviceIdentifier'])) {
            $this->serviceName = $this->getServiceName();
        } else {
            $this->serviceName = $this->getEndpointPrefix();
        }

        $this->apiVersion = $this->getApiVersion();
    }

    /**
     * Creates a request serializer for the provided API object.
     *
     * @param Service $api      API that contains a protocol.
     * @param string  $endpoint Endpoint to send requests to.
     *
     * @return callable
     * @throws \UnexpectedValueException
     */
    public static function createSerializer(Service $api, $endpoint)
    {
        static $mapping = [
            'json'      => 'Aws\Api\Serializer\JsonRpcSerializer',
            'query'     => 'Aws\Api\Serializer\QuerySerializer',
            'rest-json' => 'Aws\Api\Serializer\RestJsonSerializer',
            'rest-xml'  => 'Aws\Api\Serializer\RestXmlSerializer'
        ];

        $proto = $api->getProtocol();

        if (isset($mapping[$proto])) {
            return new $mapping[$proto]($api, $endpoint);
        } elseif ($proto == 'ec2') {
            return new QuerySerializer($api, $endpoint, new Ec2ParamBuilder());
        }

        throw new \UnexpectedValueException(
            'Unknown protocol: ' . $api->getProtocol()
        );
    }

    /**
     * Creates an error parser for the given protocol.
     *
     * @param string $protocol Protocol to parse (e.g., query, json, etc.)
     *
     * @return callable
     * @throws \UnexpectedValueException
     */
    public static function createErrorParser($protocol)
    {
        static $mapping = [
            'json'      => 'Aws\Api\ErrorParser\JsonRpcErrorParser',
            'query'     => 'Aws\Api\ErrorParser\XmlErrorParser',
            'rest-json' => 'Aws\Api\ErrorParser\RestJsonErrorParser',
            'rest-xml'  => 'Aws\Api\ErrorParser\XmlErrorParser',
            'ec2'       => 'Aws\Api\ErrorParser\XmlErrorParser'
        ];

        if (isset($mapping[$protocol])) {
            return new $mapping[$protocol]();
        }

        throw new \UnexpectedValueException("Unknown protocol: $protocol");
    }

    /**
     * Applies the listeners needed to parse client models.
     *
     * @param Service $api API to create a parser for
     * @return callable
     * @throws \UnexpectedValueException
     */
    public static function createParser(Service $api)
    {
        static $mapping = [
            'json'      => 'Aws\Api\Parser\JsonRpcParser',
            'query'     => 'Aws\Api\Parser\QueryParser',
            'rest-json' => 'Aws\Api\Parser\RestJsonParser',
            'rest-xml'  => 'Aws\Api\Parser\RestXmlParser'
        ];

        $proto = $api->getProtocol();
        if (isset($mapping[$proto])) {
            return new $mapping[$proto]($api);
        } elseif ($proto == 'ec2') {
            return new QueryParser($api, null, false);
        }

        throw new \UnexpectedValueException(
            'Unknown protocol: ' . $api->getProtocol()
        );
    }

    /**
     * Get the full name of the service
     *
     * @return string
     */
    public function getServiceFullName()
    {
        return $this->definition['metadata']['serviceFullName'];
    }

    /**
     * Get the API version of the service
     *
     * @return string
     */
    public function getApiVersion()
    {
        return $this->definition['metadata']['apiVersion'];
    }

    /**
     * Get the API version of the service
     *
     * @return string
     */
    public function getEndpointPrefix()
    {
        return $this->definition['metadata']['endpointPrefix'];
    }

    /**
     * Get the signing name used by the service.
     *
     * @return string
     */
    public function getSigningName()
    {
        return $this->definition['metadata']['signingName']
            ?: $this->definition['metadata']['endpointPrefix'];
    }

    /**
     * Get the service name.
     *
     * @return string
     */
    public function getServiceName()
    {
        return $this->definition['metadata']['serviceIdentifier'];
    }

    /**
     * Get the default signature version of the service.
     *
     * Note: this method assumes "v4" when not specified in the model.
     *
     * @return string
     */
    public function getSignatureVersion()
    {
        return $this->definition['metadata']['signatureVersion'] ?: 'v4';
    }

    /**
     * Get the protocol used by the service.
     *
     * @return string
     */
    public function getProtocol()
    {
        return $this->definition['metadata']['protocol'];
    }

    /**
     * Get the uid string used by the service
     *
     * @return string
     */
    public function getUid()
    {
        return $this->definition['metadata']['uid'];
    }

    /**
     * Check if the description has a specific operation by name.
     *
     * @param string $name Operation to check by name
     *
     * @return bool
     */
    public function hasOperation($name)
    {
        return isset($this['operations'][$name]);
    }

    /**
     * Get an operation by name.
     *
     * @param string $name Operation to retrieve by name
     *
     * @return Operation
     * @throws \InvalidArgumentException If the operation is not found
     */
    public function getOperation($name)
    {
        if (!isset($this->operations[$name])) {
            if (!isset($this->definition['operations'][$name])) {
                throw new \InvalidArgumentException("Unknown operation: $name");
            }
            $this->operations[$name] = new Operation(
                $this->definition['operations'][$name],
                $this->shapeMap
            );
        }

        return $this->operations[$name];
    }

    /**
     * Get all of the operations of the description.
     *
     * @return Operation[]
     */
    public function getOperations()
    {
        $result = [];
        foreach ($this->definition['operations'] as $name => $definition) {
            $result[$name] = $this->getOperation($name);
        }

        return $result;
    }

    /**
     * Get all of the service metadata or a specific metadata key value.
     *
     * @param string|null $key Key to retrieve or null to retrieve all metadata
     *
     * @return mixed Returns the result or null if the key is not found
     */
    public function getMetadata($key = null)
    {
        if (!$key) {
            return $this['metadata'];
        } elseif (isset($this->definition['metadata'][$key])) {
            return $this->definition['metadata'][$key];
        }

        return null;
    }

    /**
     * Gets an associative array of available paginator configurations where
     * the key is the name of the paginator, and the value is the paginator
     * configuration.
     *
     * @return array
     * @unstable The configuration format of paginators may change in the future
     */
    public function getPaginators()
    {
        if (!isset($this->paginators)) {
            $res = call_user_func(
                $this->apiProvider,
                'paginator',
                $this->serviceName,
                $this->apiVersion
            );
            $this->paginators = isset($res['pagination'])
                ? $res['pagination']
                : [];
        }

        return $this->paginators;
    }

    /**
     * Determines if the service has a paginator by name.
     *
     * @param string $name Name of the paginator.
     *
     * @return bool
     */
    public function hasPaginator($name)
    {
        return isset($this->getPaginators()[$name]);
    }

    /**
     * Retrieve a paginator by name.
     *
     * @param string $name Paginator to retrieve by name. This argument is
     *                     typically the operation name.
     * @return array
     * @throws \UnexpectedValueException if the paginator does not exist.
     * @unstable The configuration format of paginators may change in the future
     */
    public function getPaginatorConfig($name)
    {
        static $defaults = [
            'input_token'  => null,
            'output_token' => null,
            'limit_key'    => null,
            'result_key'   => null,
            'more_results' => null,
        ];

        if ($this->hasPaginator($name)) {
            return $this->paginators[$name] + $defaults;
        }

        throw new \UnexpectedValueException("There is no {$name} "
            . "paginator defined for the {$this->serviceName} service.");
    }

    /**
     * Gets an associative array of available waiter configurations where the
     * key is the name of the waiter, and the value is the waiter
     * configuration.
     *
     * @return array
     */
    public function getWaiters()
    {
        if (!isset($this->waiters)) {
            $res = call_user_func(
                $this->apiProvider,
                'waiter',
                $this->serviceName,
                $this->apiVersion
            );
            $this->waiters = isset($res['waiters'])
                ? $res['waiters']
                : [];
        }

        return $this->waiters;
    }

    /**
     * Determines if the service has a waiter by name.
     *
     * @param string $name Name of the waiter.
     *
     * @return bool
     */
    public function hasWaiter($name)
    {
        return isset($this->getWaiters()[$name]);
    }

    /**
     * Get a waiter configuration by name.
     *
     * @param string $name Name of the waiter by name.
     *
     * @return array
     * @throws \UnexpectedValueException if the waiter does not exist.
     */
    public function getWaiterConfig($name)
    {
        // Error if the waiter is not defined
        if ($this->hasWaiter($name)) {
            return $this->waiters[$name];
        }

        throw new \UnexpectedValueException("There is no {$name} waiter "
            . "defined for the {$this->serviceName} service.");
    }

    /**
     * Get the shape map used by the API.
     *
     * @return ShapeMap
     */
    public function getShapeMap()
    {
        return $this->shapeMap;
    }
}
<?php
namespace Aws\Api;

/**
 * Base class representing a modeled shape.
 */
class Shape extends AbstractModel
{
    /**
     * Get a concrete shape for the given definition.
     *
     * @param array    $definition
     * @param ShapeMap $shapeMap
     *
     * @return mixed
     * @throws \RuntimeException if the type is invalid
     */
    public static function create(array $definition, ShapeMap $shapeMap)
    {
        static $map = [
            'structure' => 'Aws\Api\StructureShape',
            'map'       => 'Aws\Api\MapShape',
            'list'      => 'Aws\Api\ListShape',
            'timestamp' => 'Aws\Api\TimestampShape',
            'integer'   => 'Aws\Api\Shape',
            'double'    => 'Aws\Api\Shape',
            'float'     => 'Aws\Api\Shape',
            'long'      => 'Aws\Api\Shape',
            'string'    => 'Aws\Api\Shape',
            'byte'      => 'Aws\Api\Shape',
            'character' => 'Aws\Api\Shape',
            'blob'      => 'Aws\Api\Shape',
            'boolean'   => 'Aws\Api\Shape'
        ];

        if (isset($definition['shape'])) {
            return $shapeMap->resolve($definition);
        }

        if (!isset($map[$definition['type']])) {
            throw new \RuntimeException('Invalid type: '
                . print_r($definition, true));
        }

        $type = $map[$definition['type']];

        return new $type($definition, $shapeMap);
    }

    /**
     * Get the type of the shape
     *
     * @return string
     */
    public function getType()
    {
        return $this->definition['type'];
    }

    /**
     * Get the name of the shape
     *
     * @return string
     */
    public function getName()
    {
        return $this->definition['name'];
    }
}
<?php
namespace Aws\Api;

/**
 * Builds shape based on shape references.
 */
class ShapeMap
{
    /** @var array */
    private $definitions;

    /** @var Shape[] */
    private $simple;

    /**
     * @param array $shapeModels Associative array of shape definitions.
     */
    public function __construct(array $shapeModels)
    {
        $this->definitions = $shapeModels;
    }

    /**
     * Get an array of shape names.
     *
     * @return array
     */
    public function getShapeNames()
    {
        return array_keys($this->definitions);
    }

    /**
     * Resolve a shape reference
     *
     * @param array $shapeRef Shape reference shape
     *
     * @return Shape
     * @throws \InvalidArgumentException
     */
    public function resolve(array $shapeRef)
    {
        $shape = $shapeRef['shape'];

        if (!isset($this->definitions[$shape])) {
            throw new \InvalidArgumentException('Shape not found: ' . $shape);
        }

        $isSimple = count($shapeRef) == 1;
        if ($isSimple && isset($this->simple[$shape])) {
            return $this->simple[$shape];
        }

        $definition = $shapeRef + $this->definitions[$shape];
        $definition['name'] = $definition['shape'];
        unset($definition['shape']);

        $result = Shape::create($definition, $this);

        if ($isSimple) {
            $this->simple[$shape] = $result;
        }

        return $result;
    }
}
<?php
namespace Aws\Api;

/**
 * Represents a structure shape and resolve member shape references.
 */
class StructureShape extends Shape
{
    /**
     * @var Shape[]
     */
    private $members;

    public function __construct(array $definition, ShapeMap $shapeMap)
    {
        $definition['type'] = 'structure';

        if (!isset($definition['members'])) {
            $definition['members'] = [];
        }

        parent::__construct($definition, $shapeMap);
    }

    /**
     * Gets a list of all members
     *
     * @return Shape[]
     */
    public function getMembers()
    {
        if (empty($this->members)) {
            $this->generateMembersHash();
        }

        return $this->members;
    }

    /**
     * Check if a specific member exists by name.
     *
     * @param string $name Name of the member to check
     *
     * @return bool
     */
    public function hasMember($name)
    {
        return isset($this->definition['members'][$name]);
    }

    /**
     * Retrieve a member by name.
     *
     * @param string $name Name of the member to retrieve
     *
     * @return Shape
     * @throws \InvalidArgumentException if the member is not found.
     */
    public function getMember($name)
    {
        $members = $this->getMembers();

        if (!isset($members[$name])) {
            throw new \InvalidArgumentException('Unknown member ' . $name);
        }

        return $members[$name];
    }


    private function generateMembersHash()
    {
        $this->members = [];

        foreach ($this->definition['members'] as $name => $definition) {
            $this->members[$name] = $this->shapeFor($definition);
        }
    }
}
<?php
namespace Aws\Api;

/**
 * Represents a timestamp shape.
 */
class TimestampShape extends Shape
{
    public function __construct(array $definition, ShapeMap $shapeMap)
    {
        $definition['type'] = 'timestamp';
        parent::__construct($definition, $shapeMap);
    }

    /**
     * Formats a timestamp value for a service.
     *
     * @param mixed  $value  Value to format
     * @param string $format Format used to serialize the value
     *
     * @return int|string
     * @throws \UnexpectedValueException if the format is unknown.
     * @throws \InvalidArgumentException if the value is an unsupported type.
     */
    public static function format($value, $format)
    {
        if ($value instanceof \DateTime) {
            $value = $value->getTimestamp();
        } elseif (is_string($value)) {
            $value = strtotime($value);
        } elseif (!is_int($value)) {
            throw new \InvalidArgumentException('Unable to handle the provided'
                . ' timestamp type: ' . gettype($value));
        }

        switch ($format) {
            case 'iso8601':
                return gmdate('Y-m-d\TH:i:s\Z', $value);
            case 'rfc822':
                return gmdate('D, d M Y H:i:s \G\M\T', $value);
            case 'unixTimestamp':
                return $value;
            default:
                throw new \UnexpectedValueException('Unknown timestamp format: '
                    . $format);
        }
    }
}
<?php
namespace Aws\Api;

use Aws;

/**
 * Validates a schema against a hash of input.
 */
class Validator
{
    private $path = [];
    private $errors = [];
    private $constraints = [];

    private static $defaultConstraints = [
        'required' => true,
        'min'      => true,
        'max'      => false,
        'pattern'  => false
    ];

    /**
     * @param array $constraints Associative array of constraints to enforce.
     *                           Accepts the following keys: "required", "min",
     *                           "max", and "pattern". If a key is not
     *                           provided, the constraint will assume false.
     */
    public function __construct(array $constraints = null)
    {
        static $assumedFalseValues = [
            'required' => false,
            'min'      => false,
            'max'      => false,
            'pattern'  => false
        ];
        $this->constraints = empty($constraints)
            ? self::$defaultConstraints
            : $constraints + $assumedFalseValues;
    }

    /**
     * Validates the given input against the schema.
     *
     * @param string $name  Operation name
     * @param Shape  $shape Shape to validate
     * @param array  $input Input to validate
     *
     * @throws \InvalidArgumentException if the input is invalid.
     */
    public function validate($name, Shape $shape, array $input)
    {
        $this->dispatch($shape, $input);

        if ($this->errors) {
            $message = sprintf(
                "Found %d error%s while validating the input provided for the "
                    . "%s operation:\n%s",
                count($this->errors),
                count($this->errors) > 1 ? 's' : '',
                $name,
                implode("\n", $this->errors)
            );
            $this->errors = [];

            throw new \InvalidArgumentException($message);
        }
    }

    private function dispatch(Shape $shape, $value)
    {
        static $methods = [
            'structure' => 'check_structure',
            'list'      => 'check_list',
            'map'       => 'check_map',
            'blob'      => 'check_blob',
            'boolean'   => 'check_boolean',
            'integer'   => 'check_numeric',
            'float'     => 'check_numeric',
            'long'      => 'check_numeric',
            'string'    => 'check_string',
            'byte'      => 'check_string',
            'char'      => 'check_string'
        ];

        $type = $shape->getType();
        if (isset($methods[$type])) {
            $this->{$methods[$type]}($shape, $value);
        }
    }

    private function check_structure(StructureShape $shape, $value)
    {
        if (!$this->checkAssociativeArray($value)) {
            return;
        }

        if ($this->constraints['required'] && $shape['required']) {
            foreach ($shape['required'] as $req) {
                if (!isset($value[$req])) {
                    $this->path[] = $req;
                    $this->addError('is missing and is a required parameter');
                    array_pop($this->path);
                }
            }
        }

        foreach ($value as $name => $v) {
            if ($shape->hasMember($name)) {
                $this->path[] = $name;
                $this->dispatch(
                    $shape->getMember($name),
                    isset($value[$name]) ? $value[$name] : null
                );
                array_pop($this->path);
            }
        }
    }

    private function check_list(ListShape $shape, $value)
    {
        if (!is_array($value)) {
            $this->addError('must be an array. Found '
                . Aws\describe_type($value));
            return;
        }

        $this->validateRange($shape, count($value), "list element count");

        $items = $shape->getMember();
        foreach ($value as $index => $v) {
            $this->path[] = $index;
            $this->dispatch($items, $v);
            array_pop($this->path);
        }
    }

    private function check_map(MapShape $shape, $value)
    {
        if (!$this->checkAssociativeArray($value)) {
            return;
        }

        $values = $shape->getValue();
        foreach ($value as $key => $v) {
            $this->path[] = $key;
            $this->dispatch($values, $v);
            array_pop($this->path);
        }
    }

    private function check_blob(Shape $shape, $value)
    {
        static $valid = [
            'string' => true,
            'integer' => true,
            'double' => true,
            'resource' => true
        ];

        $type = gettype($value);
        if (!isset($valid[$type])) {
            if ($type != 'object' || !method_exists($value, '__toString')) {
                $this->addError('must be an fopen resource, a '
                    . 'GuzzleHttp\Stream\StreamInterface object, or something '
                    . 'that can be cast to a string. Found '
                    . Aws\describe_type($value));
            }
        }
    }

    private function check_numeric(Shape $shape, $value)
    {
        if (!is_numeric($value)) {
            $this->addError('must be numeric. Found '
                . Aws\describe_type($value));
            return;
        }

        $this->validateRange($shape, $value, "numeric value");
    }

    private function check_boolean(Shape $shape, $value)
    {
        if (!is_bool($value)) {
            $this->addError('must be a boolean. Found '
                . Aws\describe_type($value));
        }
    }

    private function check_string(Shape $shape, $value)
    {
        if (!$this->checkCanString($value)) {
            $this->addError('must be a string or an object that implements '
                . '__toString(). Found ' . Aws\describe_type($value));
            return;
        }

        $this->validateRange($shape, strlen($value), "string length");

        if ($this->constraints['pattern']) {
            $pattern = $shape['pattern'];
            if ($pattern && !preg_match("/$pattern/", $value)) {
                $this->addError("Pattern /$pattern/ failed to match '$value'");
            }
        }
    }

    private function validateRange(Shape $shape, $length, $descriptor)
    {
        if ($this->constraints['min']) {
            $min = $shape['min'];
            if ($min && $length < $min) {
                $this->addError("expected $descriptor to be >= $min, but "
                    . "found $descriptor of $length");
            }
        }

        if ($this->constraints['max']) {
            $max = $shape['max'];
            if ($max && $length > $max) {
                $this->addError("expected $descriptor to be <= $max, but "
                    . "found $descriptor of $length");
            }
        }
    }

    private function checkCanString($value)
    {
        static $valid = [
            'string'  => true,
            'integer' => true,
            'double'  => true,
            'NULL'    => true,
        ];

        $type = gettype($value);

        return isset($valid[$type]) ||
            ($type == 'object' && method_exists($value, '__toString'));
    }

    private function checkAssociativeArray($value)
    {
        if (!is_array($value) || isset($value[0])) {
            $this->addError('must be an associative array. Found '
                . Aws\describe_type($value));
            return false;
        }

        return true;
    }

    private function addError($message)
    {
        $this->errors[] =
            implode('', array_map(function ($s) { return "[{$s}]"; }, $this->path))
            . ' '
            . $message;
    }
}
<?php
namespace Aws;

use Aws\Api\ApiProvider;
use Aws\Api\DocModel;
use Aws\Api\Service;
use Aws\Signature\SignatureProvider;
use GuzzleHttp\Psr7\Uri;

/**
 * Default AWS client implementation
 */
class AwsClient implements AwsClientInterface
{
    use AwsClientTrait;

    /** @var array */
    private $config;

    /** @var string */
    private $region;

    /** @var string */
    private $endpoint;

    /** @var Service */
    private $api;

    /** @var callable */
    private $signatureProvider;

    /** @var callable */
    private $credentialProvider;

    /** @var HandlerList */
    private $handlerList;

    /** @var array*/
    private $defaultRequestOptions;

    /**
     * Get an array of client constructor arguments used by the client.
     *
     * @return array
     */
    public static function getArguments()
    {
        return ClientResolver::getDefaultArguments();
    }

    /**
     * The client constructor accepts the following options:
     *
     * - api_provider: (callable) An optional PHP callable that accepts a
     *   type, service, and version argument, and returns an array of
     *   corresponding configuration data. The type value can be one of api,
     *   waiter, or paginator.
     * - credentials:
     *   (Aws\Credentials\CredentialsInterface|array|bool|callable) Specifies
     *   the credentials used to sign requests. Provide an
     *   Aws\Credentials\CredentialsInterface object, an associative array of
     *   "key", "secret", and an optional "token" key, `false` to use null
     *   credentials, or a callable credentials provider used to create
     *   credentials or return null. See Aws\Credentials\CredentialProvider for
     *   a list of built-in credentials providers. If no credentials are
     *   provided, the SDK will attempt to load them from the environment.
     * - debug: (bool|array) Set to true to display debug information when
     *   sending requests. Alternatively, you can provide an associative array
     *   with the following keys: logfn: (callable) Function that is invoked
     *   with log messages; stream_size: (int) When the size of a stream is
     *   greater than this number, the stream data will not be logged (set to
     *   "0" to not log any stream data); scrub_auth: (bool) Set to false to
     *   disable the scrubbing of auth data from the logged messages; http:
     *   (bool) Set to false to disable the "debug" feature of lower level HTTP
     *   adapters (e.g., verbose curl output).
     * - stats: (bool|array) Set to true to gather transfer statistics on
     *   requests sent. Alternatively, you can provide an associative array with
     *   the following keys: retries: (bool) Set to false to disable reporting
     *   on retries attempted; http: (bool) Set to true to enable collecting
     *   statistics from lower level HTTP adapters (e.g., values returned in
     *   GuzzleHttp\TransferStats). HTTP handlers must support an
     *   `http_stats_receiver` option for this to have an effect; timer: (bool)
     *   Set to true to enable a command timer that reports the total wall clock
     *   time spent on an operation in seconds.
     * - endpoint: (string) The full URI of the webservice. This is only
     *   required when connecting to a custom endpoint (e.g., a local version
     *   of S3).
     * - endpoint_provider: (callable) An optional PHP callable that
     *   accepts a hash of options including a "service" and "region" key and
     *   returns NULL or a hash of endpoint data, of which the "endpoint" key
     *   is required. See Aws\Endpoint\EndpointProvider for a list of built-in
     *   providers.
     * - handler: (callable) A handler that accepts a command object,
     *   request object and returns a promise that is fulfilled with an
     *   Aws\ResultInterface object or rejected with an
     *   Aws\Exception\AwsException. A handler does not accept a next handler
     *   as it is terminal and expected to fulfill a command. If no handler is
     *   provided, a default Guzzle handler will be utilized.
     * - http: (array, default=array(0)) Set to an array of SDK request
     *   options to apply to each request (e.g., proxy, verify, etc.).
     * - http_handler: (callable) An HTTP handler is a function that
     *   accepts a PSR-7 request object and returns a promise that is fulfilled
     *   with a PSR-7 response object or rejected with an array of exception
     *   data. NOTE: This option supersedes any provided "handler" option.
     * - profile: (string) Allows you to specify which profile to use when
     *   credentials are created from the AWS credentials file in your HOME
     *   directory. This setting overrides the AWS_PROFILE environment
     *   variable. Note: Specifying "profile" will cause the "credentials" key
     *   to be ignored.
     * - region: (string, required) Region to connect to. See
     *   http://docs.aws.amazon.com/general/latest/gr/rande.html for a list of
     *   available regions.
     * - retries: (int, default=int(3)) Configures the maximum number of
     *   allowed retries for a client (pass 0 to disable retries).
     * - scheme: (string, default=string(5) "https") URI scheme to use when
     *   connecting connect. The SDK will utilize "https" endpoints (i.e.,
     *   utilize SSL/TLS connections) by default. You can attempt to connect to
     *   a service over an unencrypted "http" endpoint by setting ``scheme`` to
     *   "http".
     * - signature_provider: (callable) A callable that accepts a signature
     *   version name (e.g., "v4"), a service name, and region, and
     *   returns a SignatureInterface object or null. This provider is used to
     *   create signers utilized by the client. See
     *   Aws\Signature\SignatureProvider for a list of built-in providers
     * - signature_version: (string) A string representing a custom
     *   signature version to use with a service (e.g., v4). Note that
     *   per/operation signature version MAY override this requested signature
     *   version.
     * - validate: (bool, default=bool(true)) Set to false to disable
     *   client-side parameter validation.
     * - version: (string, required) The version of the webservice to
     *   utilize (e.g., 2006-03-01).
     *
     * @param array $args Client configuration arguments.
     *
     * @throws \InvalidArgumentException if any required options are missing or
     *                                   the service is not supported.
     */
    public function __construct(array $args)
    {
        list($service, $exceptionClass) = $this->parseClass();
        if (!isset($args['service'])) {
            $args['service'] = manifest($service)['endpoint'];
        }
        if (!isset($args['exception_class'])) {
            $args['exception_class'] = $exceptionClass;
        }

        $this->handlerList = new HandlerList();
        $resolver = new ClientResolver(static::getArguments());
        $config = $resolver->resolve($args, $this->handlerList);
        $this->api = $config['api'];
        $this->signatureProvider = $config['signature_provider'];
        $this->endpoint = new Uri($config['endpoint']);
        $this->credentialProvider = $config['credentials'];
        $this->region = isset($config['region']) ? $config['region'] : null;
        $this->config = $config['config'];
        $this->defaultRequestOptions = $config['http'];
        $this->addSignatureMiddleware();
        $this->addInvocationId();

        if (isset($args['with_resolved'])) {
            $args['with_resolved']($config);
        }
    }

    public function getHandlerList()
    {
        return $this->handlerList;
    }

    public function getConfig($option = null)
    {
        return $option === null
            ? $this->config
            : (isset($this->config[$option])
                ? $this->config[$option]
                : null);
    }

    public function getCredentials()
    {
        $fn = $this->credentialProvider;
        return $fn();
    }

    public function getEndpoint()
    {
        return $this->endpoint;
    }

    public function getRegion()
    {
        return $this->region;
    }

    public function getApi()
    {
        return $this->api;
    }

    public function getCommand($name, array $args = [])
    {
        // Fail fast if the command cannot be found in the description.
        if (!isset($this->getApi()['operations'][$name])) {
            $name = ucfirst($name);
            if (!isset($this->getApi()['operations'][$name])) {
                throw new \InvalidArgumentException("Operation not found: $name");
            }
        }

        if (!isset($args['@http'])) {
            $args['@http'] = $this->defaultRequestOptions;
        } else {
            $args['@http'] += $this->defaultRequestOptions;
        }

        return new Command($name, $args, clone $this->getHandlerList());
    }

    public function __sleep()
    {
        throw new \RuntimeException('Instances of ' . static::class
            . ' cannot be serialized');
    }

    /**
     * Get the signature_provider function of the client.
     *
     * @return callable
     */
    final protected function getSignatureProvider()
    {
        return $this->signatureProvider;
    }

    /**
     * Parse the class name and setup the custom exception class of the client
     * and return the "service" name of the client and "exception_class".
     *
     * @return array
     */
    private function parseClass()
    {
        $klass = get_class($this);

        if ($klass === __CLASS__) {
            return ['', 'Aws\Exception\AwsException'];
        }

        $service = substr($klass, strrpos($klass, '\\') + 1, -6);

        return [
            strtolower($service),
            "Aws\\{$service}\\Exception\\{$service}Exception"
        ];
    }

    private function addSignatureMiddleware()
    {
        $api = $this->getApi();
        $provider = $this->signatureProvider;
        $version = $this->config['signature_version'];
        $name = $this->config['signing_name'];
        $region = $this->config['signing_region'];

        $resolver = static function (
            CommandInterface $c
        ) use ($api, $provider, $name, $region, $version) {
            if ('none' === $api->getOperation($c->getName())['authtype']) {
                $version = 'anonymous';
            }

            return SignatureProvider::resolve($provider, $version, $name, $region);
        };

        $this->handlerList->appendSign(
            Middleware::signer($this->credentialProvider, $resolver),
            'signer'
        );
    }

    private function addInvocationId()
    {
        // Add invocation id to each request
        $this->handlerList->prependSign(Middleware::invocationId(), 'invocation-id');
    }

    /**
     * Returns a service model and doc model with any necessary changes
     * applied.
     *
     * @param array $api  Array of service data being documented.
     * @param array $docs Array of doc model data.
     *
     * @return array Tuple containing a [Service, DocModel]
     *
     * @internal This should only used to document the service API.
     * @codeCoverageIgnore
     */
    public static function applyDocFilters(array $api, array $docs)
    {
        return [
            new Service($api, ApiProvider::defaultProvider()),
            new DocModel($docs)
        ];
    }

    /**
     * @deprecated
     * @return static
     */
    public static function factory(array $config = [])
    {
        return new static($config);
    }
}
<?php
namespace Aws;

use Psr\Http\Message\UriInterface;
use GuzzleHttp\Promise\PromiseInterface;

/**
 * Represents an AWS client.
 */
interface AwsClientInterface
{
    /**
     * Creates and executes a command for an operation by name.
     *
     * Suffixing an operation name with "Async" will return a
     * promise that can be used to execute commands asynchronously.
     *
     * @param string $name      Name of the command to execute.
     * @param array  $arguments Arguments to pass to the getCommand method.
     *
     * @return ResultInterface
     * @throws \Exception
     */
    public function __call($name, array $arguments);

    /**
     * Create a command for an operation name.
     *
     * Special keys may be set on the command to control how it behaves,
     * including:
     *
     * - @http: Associative array of transfer specific options to apply to the
     *   request that is serialized for this command. Available keys include
     *   "proxy", "verify", "timeout", "connect_timeout", "debug", "delay", and
     *   "headers".
     *
     * @param string $name Name of the operation to use in the command
     * @param array  $args Arguments to pass to the command
     *
     * @return CommandInterface
     * @throws \InvalidArgumentException if no command can be found by name
     */
    public function getCommand($name, array $args = []);

    /**
     * Execute a single command.
     *
     * @param CommandInterface $command Command to execute
     *
     * @return ResultInterface
     * @throws \Exception
     */
    public function execute(CommandInterface $command);

    /**
     * Execute a command asynchronously.
     *
     * @param CommandInterface $command Command to execute
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function executeAsync(CommandInterface $command);

    /**
     * Returns a promise that is fulfilled with an
     * {@see \Aws\Credentials\CredentialsInterface} object.
     *
     * If you need the credentials synchronously, then call the wait() method
     * on the returned promise.
     *
     * @return PromiseInterface
     */
    public function getCredentials();

    /**
     * Get the region to which the client is configured to send requests.
     *
     * @return string
     */
    public function getRegion();

    /**
     * Gets the default endpoint, or base URL, used by the client.
     *
     * @return UriInterface
     */
    public function getEndpoint();

    /**
     * Get the service description associated with the client.
     *
     * @return \Aws\Api\Service
     */
    public function getApi();

    /**
     * Get a client configuration value.
     *
     * @param string|null $option The option to retrieve. Pass null to retrieve
     *                            all options.
     * @return mixed|null
     */
    public function getConfig($option = null);

    /**
     * Get the handler list used to transfer commands.
     *
     * This list can be modified to add middleware or to change the underlying
     * handler used to send HTTP requests.
     *
     * @return HandlerList
     */
    public function getHandlerList();

    /**
     * Get a resource iterator for the specified operation.
     *
     * @param string $name Name of the iterator to retrieve.
     * @param array  $args Command arguments to use with each command.
     *
     * @return \Iterator
     * @throws \UnexpectedValueException if the iterator config is invalid.
     */
    public function getIterator($name, array $args = []);

    /**
     * Get a result paginator for the specified operation.
     *
     * @param string $name   Name of the operation used for iterator
     * @param array  $args   Command args to be used with each command
     *
     * @return \Aws\ResultPaginator
     * @throws \UnexpectedValueException if the iterator config is invalid.
     */
    public function getPaginator($name, array $args = []);

    /**
     * Wait until a resource is in a particular state.
     *
     * @param string|callable $name Name of the waiter that defines the wait
     *                              configuration and conditions.
     * @param array  $args          Args to be used with each command executed
     *                              by the waiter. Waiter configuration options
     *                              can be provided in an associative array in
     *                              the @waiter key.
     * @return void
     * @throws \UnexpectedValueException if the waiter is invalid.
     */
    public function waitUntil($name, array $args = []);

    /**
     * Get a waiter that waits until a resource is in a particular state.
     *
     * Retrieving a waiter can be useful when you wish to wait asynchronously:
     *
     *     $waiter = $client->getWaiter('foo', ['bar' => 'baz']);
     *     $waiter->promise()->then(function () { echo 'Done!'; });
     *
     * @param string|callable $name Name of the waiter that defines the wait
     *                              configuration and conditions.
     * @param array  $args          Args to be used with each command executed
     *                              by the waiter. Waiter configuration options
     *                              can be provided in an associative array in
     *                              the @waiter key.
     * @return \Aws\Waiter
     * @throws \UnexpectedValueException if the waiter is invalid.
     */
    public function getWaiter($name, array $args = []);
}
<?php
namespace Aws;

use Aws\Api\Service;
use GuzzleHttp\Promise\Promise;

/**
 * A trait providing generic functionality for interacting with Amazon Web
 * Services. This is meant to be used in classes implementing
 * \Aws\AwsClientInterface
 */
trait AwsClientTrait
{
    public function getPaginator($name, array $args = [])
    {
        $config = $this->getApi()->getPaginatorConfig($name);

        return new ResultPaginator($this, $name, $args, $config);
    }

    public function getIterator($name, array $args = [])
    {
        $config = $this->getApi()->getPaginatorConfig($name);
        if (!$config['result_key']) {
            throw new \UnexpectedValueException(sprintf(
                'There are no resources to iterate for the %s operation of %s',
                $name, $this->getApi()['serviceFullName']
            ));
        }

        $key = is_array($config['result_key'])
            ? $config['result_key'][0]
            : $config['result_key'];

        if ($config['output_token'] && $config['input_token']) {
            return $this->getPaginator($name, $args)->search($key);
        }

        $result = $this->execute($this->getCommand($name, $args))->search($key);

        return new \ArrayIterator((array) $result);
    }

    public function waitUntil($name, array $args = [])
    {
        return $this->getWaiter($name, $args)->promise()->wait();
    }

    public function getWaiter($name, array $args = [])
    {
        $config = isset($args['@waiter']) ? $args['@waiter'] : [];
        $config += $this->getApi()->getWaiterConfig($name);

        return new Waiter($this, $name, $args, $config);
    }

    public function execute(CommandInterface $command)
    {
        return $this->executeAsync($command)->wait();
    }

    public function executeAsync(CommandInterface $command)
    {
        $handler = $command->getHandlerList()->resolve();
        return $handler($command);
    }

    public function __call($name, array $args)
    {
        $params = isset($args[0]) ? $args[0] : [];

        if (substr($name, -5) === 'Async') {
            return $this->executeAsync(
                $this->getCommand(substr($name, 0, -5), $params)
            );
        }

        return $this->execute($this->getCommand($name, $params));
    }

    /**
     * @param string $name
     * @param array $args
     *
     * @return CommandInterface
     */
    abstract public function getCommand($name, array $args = []);

    /**
     * @return Service
     */
    abstract public function getApi();
}
<?php
namespace Aws;

/**
 * Represents a simple cache interface.
 */
interface CacheInterface
{
    /**
     * Get a cache item by key.
     *
     * @param string $key Key to retrieve.
     *
     * @return mixed|null Returns the value or null if not found.
     */
    public function get($key);

    /**
     * Set a cache key value.
     *
     * @param string $key   Key to set
     * @param mixed  $value Value to set.
     * @param int    $ttl   Number of seconds the item is allowed to live. Set
     *                      to 0 to allow an unlimited lifetime.
     */
    public function set($key, $value, $ttl = 0);

    /**
     * Remove a cache key.
     *
     * @param string $key Key to remove.
     */
    public function remove($key);
}
<?php
namespace Aws;

use Aws\Api\Validator;
use Aws\Api\ApiProvider;
use Aws\Api\Service;
use Aws\Credentials\Credentials;
use Aws\Credentials\CredentialsInterface;
use Aws\Endpoint\Partition;
use Aws\Endpoint\PartitionEndpointProvider;
use Aws\Endpoint\PartitionProviderInterface;
use Aws\Signature\SignatureProvider;
use Aws\Endpoint\EndpointProvider;
use Aws\Credentials\CredentialProvider;
use GuzzleHttp\Promise;
use InvalidArgumentException as IAE;
use Psr\Http\Message\RequestInterface;

/**
 * @internal Resolves a hash of client arguments to construct a client.
 */
class ClientResolver
{
    /** @var array */
    private $argDefinitions;

    /** @var array Map of types to a corresponding function */
    private static $typeMap = [
        'resource' => 'is_resource',
        'callable' => 'is_callable',
        'int'      => 'is_int',
        'bool'     => 'is_bool',
        'string'   => 'is_string',
        'object'   => 'is_object',
        'array'    => 'is_array',
    ];

    private static $defaultArgs = [
        'service' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'doc'      => 'Name of the service to utilize. This value will be supplied by default when using one of the SDK clients (e.g., Aws\\S3\\S3Client).',
            'required' => true,
            'internal' => true
        ],
        'exception_class' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'doc'      => 'Exception class to create when an error occurs.',
            'default'  => 'Aws\Exception\AwsException',
            'internal' => true
        ],
        'scheme' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'default'  => 'https',
            'doc'      => 'URI scheme to use when connecting connect. The SDK will utilize "https" endpoints (i.e., utilize SSL/TLS connections) by default. You can attempt to connect to a service over an unencrypted "http" endpoint by setting ``scheme`` to "http".',
        ],
        'endpoint' => [
            'type'  => 'value',
            'valid' => ['string'],
            'doc'   => 'The full URI of the webservice. This is only required when connecting to a custom endpoint (e.g., a local version of S3).',
            'fn'    => [__CLASS__, '_apply_endpoint'],
        ],
        'region' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'required' => [__CLASS__, '_missing_region'],
            'doc'      => 'Region to connect to. See http://docs.aws.amazon.com/general/latest/gr/rande.html for a list of available regions.',
        ],
        'version' => [
            'type'     => 'value',
            'valid'    => ['string'],
            'required' => [__CLASS__, '_missing_version'],
            'doc'      => 'The version of the webservice to utilize (e.g., 2006-03-01).',
        ],
        'signature_provider' => [
            'type'    => 'value',
            'valid'   => ['callable'],
            'doc'     => 'A callable that accepts a signature version name (e.g., "v4"), a service name, and region, and  returns a SignatureInterface object or null. This provider is used to create signers utilized by the client. See Aws\\Signature\\SignatureProvider for a list of built-in providers',
            'default' => [__CLASS__, '_default_signature_provider'],
        ],
        'endpoint_provider' => [
            'type'     => 'value',
            'valid'    => ['callable'],
            'fn'       => [__CLASS__, '_apply_endpoint_provider'],
            'doc'      => 'An optional PHP callable that accepts a hash of options including a "service" and "region" key and returns NULL or a hash of endpoint data, of which the "endpoint" key is required. See Aws\\Endpoint\\EndpointProvider for a list of built-in providers.',
            'default' => [__CLASS__, '_default_endpoint_provider'],
        ],
        'api_provider' => [
            'type'     => 'value',
            'valid'    => ['callable'],
            'doc'      => 'An optional PHP callable that accepts a type, service, and version argument, and returns an array of corresponding configuration data. The type value can be one of api, waiter, or paginator.',
            'fn'       => [__CLASS__, '_apply_api_provider'],
            'default'  => [ApiProvider::class, 'defaultProvider'],
        ],
        'signature_version' => [
            'type'    => 'config',
            'valid'   => ['string'],
            'doc'     => 'A string representing a custom signature version to use with a service (e.g., v4). Note that per/operation signature version MAY override this requested signature version.',
            'default' => [__CLASS__, '_default_signature_version'],
        ],
        'signing_name' => [
            'type'    => 'config',
            'valid'   => ['string'],
            'doc'     => 'A string representing a custom service name to be used when calculating a request signature.',
            'default' => [__CLASS__, '_default_signing_name'],
        ],
        'signing_region' => [
            'type'    => 'config',
            'valid'   => ['string'],
            'doc'     => 'A string representing a custom region name to be used when calculating a request signature.',
            'default' => [__CLASS__, '_default_signing_region'],
        ],
        'profile' => [
            'type'  => 'config',
            'valid' => ['string'],
            'doc'   => 'Allows you to specify which profile to use when credentials are created from the AWS credentials file in your HOME directory. This setting overrides the AWS_PROFILE environment variable. Note: Specifying "profile" will cause the "credentials" key to be ignored.',
            'fn'    => [__CLASS__, '_apply_profile'],
        ],
        'credentials' => [
            'type'    => 'value',
            'valid'   => [CredentialsInterface::class, CacheInterface::class, 'array', 'bool', 'callable'],
            'doc'     => 'Specifies the credentials used to sign requests. Provide an Aws\Credentials\CredentialsInterface object, an associative array of "key", "secret", and an optional "token" key, `false` to use null credentials, or a callable credentials provider used to create credentials or return null. See Aws\\Credentials\\CredentialProvider for a list of built-in credentials providers. If no credentials are provided, the SDK will attempt to load them from the environment.',
            'fn'      => [__CLASS__, '_apply_credentials'],
            'default' => [CredentialProvider::class, 'defaultProvider'],
        ],
        'stats' => [
            'type'  => 'value',
            'valid' => ['bool', 'array'],
            'default' => false,
            'doc'   => 'Set to true to gather transfer statistics on requests sent. Alternatively, you can provide an associative array with the following keys: retries: (bool) Set to false to disable reporting on retries attempted; http: (bool) Set to true to enable collecting statistics from lower level HTTP adapters (e.g., values returned in GuzzleHttp\TransferStats). HTTP handlers must support an http_stats_receiver option for this to have an effect; timer: (bool) Set to true to enable a command timer that reports the total wall clock time spent on an operation in seconds.',
            'fn'    => [__CLASS__, '_apply_stats'],
        ],
        'retries' => [
            'type'    => 'value',
            'valid'   => ['int'],
            'doc'     => 'Configures the maximum number of allowed retries for a client (pass 0 to disable retries). ',
            'fn'      => [__CLASS__, '_apply_retries'],
            'default' => 3,
        ],
        'validate' => [
            'type'    => 'value',
            'valid'   => ['bool', 'array'],
            'default' => true,
            'doc'     => 'Set to false to disable client-side parameter validation. Set to true to utilize default validation constraints. Set to an associative array of validation options to enable specific validation constraints.',
            'fn'      => [__CLASS__, '_apply_validate'],
        ],
        'debug' => [
            'type'  => 'value',
            'valid' => ['bool', 'array'],
            'doc'   => 'Set to true to display debug information when sending requests. Alternatively, you can provide an associative array with the following keys: logfn: (callable) Function that is invoked with log messages; stream_size: (int) When the size of a stream is greater than this number, the stream data will not be logged (set to "0" to not log any stream data); scrub_auth: (bool) Set to false to disable the scrubbing of auth data from the logged messages; http: (bool) Set to false to disable the "debug" feature of lower level HTTP adapters (e.g., verbose curl output).',
            'fn'    => [__CLASS__, '_apply_debug'],
        ],
        'http' => [
            'type'    => 'value',
            'valid'   => ['array'],
            'default' => [],
            'doc'     => 'Set to an array of SDK request options to apply to each request (e.g., proxy, verify, etc.).',
        ],
        'http_handler' => [
            'type'    => 'value',
            'valid'   => ['callable'],
            'doc'     => 'An HTTP handler is a function that accepts a PSR-7 request object and returns a promise that is fulfilled with a PSR-7 response object or rejected with an array of exception data. NOTE: This option supersedes any provided "handler" option.',
            'fn'      => [__CLASS__, '_apply_http_handler']
        ],
        'handler' => [
            'type'     => 'value',
            'valid'    => ['callable'],
            'doc'      => 'A handler that accepts a command object, request object and returns a promise that is fulfilled with an Aws\ResultInterface object or rejected with an Aws\Exception\AwsException. A handler does not accept a next handler as it is terminal and expected to fulfill a command. If no handler is provided, a default Guzzle handler will be utilized.',
            'fn'       => [__CLASS__, '_apply_handler'],
            'default'  => [__CLASS__, '_default_handler']
        ],
        'ua_append' => [
            'type'     => 'value',
            'valid'    => ['string', 'array'],
            'doc'      => 'Provide a string or array of strings to send in the User-Agent header.',
            'fn'       => [__CLASS__, '_apply_user_agent'],
            'default'  => [],
        ],
    ];

    /**
     * Gets an array of default client arguments, each argument containing a
     * hash of the following:
     *
     * - type: (string, required) option type described as follows:
     *   - value: The default option type.
     *   - config: The provided value is made available in the client's
     *     getConfig() method.
     * - valid: (array, required) Valid PHP types or class names. Note: null
     *   is not an allowed type.
     * - required: (bool, callable) Whether or not the argument is required.
     *   Provide a function that accepts an array of arguments and returns a
     *   string to provide a custom error message.
     * - default: (mixed) The default value of the argument if not provided. If
     *   a function is provided, then it will be invoked to provide a default
     *   value. The function is provided the array of options and is expected
     *   to return the default value of the option.
     * - doc: (string) The argument documentation string.
     * - fn: (callable) Function used to apply the argument. The function
     *   accepts the provided value, array of arguments by reference, and an
     *   event emitter.
     *
     * Note: Order is honored and important when applying arguments.
     *
     * @return array
     */
    public static function getDefaultArguments()
    {
        return self::$defaultArgs;
    }

    /**
     * @param array $argDefinitions Client arguments.
     */
    public function __construct(array $argDefinitions)
    {
        $this->argDefinitions = $argDefinitions;
    }

    /**
     * Resolves client configuration options and attached event listeners.
     *
     * @param array       $args Provided constructor arguments.
     * @param HandlerList $list Handler list to augment.
     *
     * @return array Returns the array of provided options.
     * @throws \InvalidArgumentException
     * @see Aws\AwsClient::__construct for a list of available options.
     */
    public function resolve(array $args, HandlerList $list)
    {
        $args['config'] = [];
        foreach ($this->argDefinitions as $key => $a) {
            // Add defaults, validate required values, and skip if not set.
            if (!isset($args[$key])) {
                if (isset($a['default'])) {
                    // Merge defaults in when not present.
                    $args[$key] = is_callable($a['default'])
                        ? $a['default']($args)
                        : $a['default'];
                } elseif (empty($a['required'])) {
                    continue;
                } else {
                    $this->throwRequired($args);
                }
            }

            // Validate the types against the provided value.
            foreach ($a['valid'] as $check) {
                if (isset(self::$typeMap[$check])) {
                    $fn = self::$typeMap[$check];
                    if ($fn($args[$key])) {
                        goto is_valid;
                    }
                } elseif ($args[$key] instanceof $check) {
                    goto is_valid;
                }
            }

            $this->invalidType($key, $args[$key]);

            // Apply the value
            is_valid:
            if (isset($a['fn'])) {
                $a['fn']($args[$key], $args, $list);
            }

            if ($a['type'] === 'config') {
                $args['config'][$key] = $args[$key];
            }
        }

        return $args;
    }

    /**
     * Creates a verbose error message for an invalid argument.
     *
     * @param string $name        Name of the argument that is missing.
     * @param array  $args        Provided arguments
     * @param bool   $useRequired Set to true to show the required fn text if
     *                            available instead of the documentation.
     * @return string
     */
    private function getArgMessage($name, $args = [], $useRequired = false)
    {
        $arg = $this->argDefinitions[$name];
        $msg = '';
        $modifiers = [];
        if (isset($arg['valid'])) {
            $modifiers[] = implode('|', $arg['valid']);
        }
        if (isset($arg['choice'])) {
            $modifiers[] = 'One of ' . implode(', ', $arg['choice']);
        }
        if ($modifiers) {
            $msg .= '(' . implode('; ', $modifiers) . ')';
        }
        $msg = wordwrap("{$name}: {$msg}", 75, "\n  ");

        if ($useRequired && is_callable($arg['required'])) {
            $msg .= "\n\n  ";
            $msg .= str_replace("\n", "\n  ", call_user_func($arg['required'], $args));
        } elseif (isset($arg['doc'])) {
            $msg .= wordwrap("\n\n  {$arg['doc']}", 75, "\n  ");
        }

        return $msg;
    }

    /**
     * Throw when an invalid type is encountered.
     *
     * @param string $name     Name of the value being validated.
     * @param mixed  $provided The provided value.
     * @throws \InvalidArgumentException
     */
    private function invalidType($name, $provided)
    {
        $expected = implode('|', $this->argDefinitions[$name]['valid']);
        $msg = "Invalid configuration value "
            . "provided for \"{$name}\". Expected {$expected}, but got "
            . describe_type($provided) . "\n\n"
            . $this->getArgMessage($name);
        throw new IAE($msg);
    }

    /**
     * Throws an exception for missing required arguments.
     *
     * @param array $args Passed in arguments.
     * @throws \InvalidArgumentException
     */
    private function throwRequired(array $args)
    {
        $missing = [];
        foreach ($this->argDefinitions as $k => $a) {
            if (empty($a['required'])
                || isset($a['default'])
                || array_key_exists($k, $args)
            ) {
                continue;
            }
            $missing[] = $this->getArgMessage($k, $args, true);
        }
        $msg = "Missing required client configuration options: \n\n";
        $msg .= implode("\n\n", $missing);
        throw new IAE($msg);
    }

    public static function _apply_retries($value, array &$args, HandlerList $list)
    {
        if ($value) {
            $decider = RetryMiddleware::createDefaultDecider($value);
            $list->appendSign(
                Middleware::retry($decider, null, $args['stats']['retries']),
                'retry'
            );
        }
    }

    public static function _apply_credentials($value, array &$args)
    {
        if (is_callable($value)) {
            return;
        } elseif ($value instanceof CredentialsInterface) {
            $args['credentials'] = CredentialProvider::fromCredentials($value);
        } elseif (is_array($value)
            && isset($value['key'])
            && isset($value['secret'])
        ) {
            $args['credentials'] = CredentialProvider::fromCredentials(
                new Credentials(
                    $value['key'],
                    $value['secret'],
                    isset($value['token']) ? $value['token'] : null,
                    isset($value['expires']) ? $value['expires'] : null
                )
            );
        } elseif ($value === false) {
            $args['credentials'] = CredentialProvider::fromCredentials(
                new Credentials('', '')
            );
            $args['config']['signature_version'] = 'anonymous';
        } elseif ($value instanceof CacheInterface) {
            $args['credentials'] = CredentialProvider::defaultProvider($args);
        } else {
            throw new IAE('Credentials must be an instance of '
                . 'Aws\Credentials\CredentialsInterface, an associative '
                . 'array that contains "key", "secret", and an optional "token" '
                . 'key-value pairs, a credentials provider function, or false.');
        }
    }

    public static function _apply_api_provider(callable $value, array &$args, HandlerList $list)
    {
        $api = new Service(
            ApiProvider::resolve(
                $value,
                'api',
                $args['service'],
                $args['version']
            ),
            $value
        );
        $args['api'] = $api;
        $args['serializer'] = Service::createSerializer($api, $args['endpoint']);
        $args['parser'] = Service::createParser($api);
        $args['error_parser'] = Service::createErrorParser($api->getProtocol());
        $list->prependBuild(Middleware::requestBuilder($args['serializer']), 'builder');
    }

    public static function _apply_endpoint_provider(callable $value, array &$args)
    {
        if (!isset($args['endpoint'])) {
            // Invoke the endpoint provider and throw if it does not resolve.
            $result = EndpointProvider::resolve($value, [
                'service' => $args['service'],
                'region'  => $args['region'],
                'scheme'  => $args['scheme']
            ]);

            $args['endpoint'] = $result['endpoint'];

            if (isset($result['signatureVersion'])) {
                $args['config']['signature_version'] = $result['signatureVersion'];
            }
            if (isset($result['signingRegion'])) {
                $args['config']['signing_region'] = $result['signingRegion'];
            }
            if (isset($result['signingName'])) {
                $args['config']['signing_name'] = $result['signingName'];
            }
        }
    }

    public static function _apply_debug($value, array &$args, HandlerList $list)
    {
        if ($value !== false) {
            $list->interpose(new TraceMiddleware($value === true ? [] : $value));
        }
    }

    public static function _apply_stats($value, array &$args, HandlerList $list)
    {
        // Create an array of stat collectors that are disabled (set to false)
        // by default. If the user has passed in true, enable all stat
        // collectors.
        $defaults = array_fill_keys(
            ['http', 'retries', 'timer'],
            $value === true
        );
        $args['stats'] = is_array($value)
            ? array_replace($defaults, $value)
            : $defaults;

        if ($args['stats']['timer']) {
            $list->prependInit(Middleware::timer(), 'timer');
        }
    }

    public static function _apply_profile($_, array &$args)
    {
        $args['credentials'] = CredentialProvider::ini($args['profile']);
    }

    public static function _apply_validate($value, array &$args, HandlerList $list)
    {
        if ($value === false) {
            return;
        }

        $validator = $value === true
            ? new Validator()
            : new Validator($value);
        $list->appendValidate(
            Middleware::validation($args['api'], $validator),
            'validation'
        );
    }

    public static function _apply_handler($value, array &$args, HandlerList $list)
    {
        $list->setHandler($value);
    }

    public static function _default_handler(array &$args)
    {
        return new WrappedHttpHandler(
            default_http_handler(),
            $args['parser'],
            $args['error_parser'],
            $args['exception_class'],
            $args['stats']['http']
        );
    }

    public static function _apply_http_handler($value, array &$args, HandlerList $list)
    {
        $args['handler'] = new WrappedHttpHandler(
            $value,
            $args['parser'],
            $args['error_parser'],
            $args['exception_class'],
            $args['stats']['http']
        );
    }

    public static function _apply_user_agent($value, array &$args, HandlerList $list)
    {
        if (!is_array($value)) {
            $value = [$value];
        }

        $value = array_map('strval', $value);

        array_unshift($value, 'aws-sdk-php/' . Sdk::VERSION);
        $args['ua_append'] = $value;

        $list->appendBuild(static function (callable $handler) use ($value) {
            return function (
                CommandInterface $command,
                RequestInterface $request
            ) use ($handler, $value) {
                return $handler($command, $request->withHeader(
                    'User-Agent',
                    implode(' ', array_merge(
                        $value,
                        $request->getHeader('User-Agent')
                    ))
                ));
            };
        });
    }

    public static function _apply_endpoint($value, array &$args, HandlerList $list)
    {
        $parts = parse_url($value);
        if (empty($parts['scheme']) || empty($parts['host'])) {
            throw new IAE(
                'Endpoints must be full URIs and include a scheme and host'
            );
        }

        $args['endpoint'] = $value;
    }

    public static function _default_endpoint_provider(array $args)
    {
        return PartitionEndpointProvider::defaultProvider()
            ->getPartition($args['region'], $args['service']);
    }

    public static function _default_signature_provider()
    {
        return SignatureProvider::defaultProvider();
    }

    public static function _default_signature_version(array &$args)
    {
        if (isset($args['config']['signature_version'])) {
            return $args['config']['signature_version'];
        }

        $args['__partition_result'] = isset($args['__partition_result'])
            ? isset($args['__partition_result'])
            : call_user_func(PartitionEndpointProvider::defaultProvider(), [
                'service' => $args['service'],
                'region' => $args['region'],
            ]);

        return isset($args['__partition_result']['signatureVersion'])
            ? $args['__partition_result']['signatureVersion']
            : $args['api']->getSignatureVersion();
    }

    public static function _default_signing_name(array &$args)
    {
        if (isset($args['config']['signing_name'])) {
            return $args['config']['signing_name'];
        }

        $args['__partition_result'] = isset($args['__partition_result'])
            ? isset($args['__partition_result'])
            : call_user_func(PartitionEndpointProvider::defaultProvider(), [
                'service' => $args['service'],
                'region' => $args['region'],
            ]);

        if (isset($args['__partition_result']['signingName'])) {
            return $args['__partition_result']['signingName'];
        }

        if ($signingName = $args['api']->getSigningName()) {
            return $signingName;
        }

        return $args['service'];
    }

    public static function _default_signing_region(array &$args)
    {
        if (isset($args['config']['signing_region'])) {
            return $args['config']['signing_region'];
        }

        $args['__partition_result'] = isset($args['__partition_result'])
            ? isset($args['__partition_result'])
            : call_user_func(PartitionEndpointProvider::defaultProvider(), [
                'service' => $args['service'],
                'region' => $args['region'],
            ]);

        return isset($args['__partition_result']['signingRegion'])
            ? $args['__partition_result']['signingRegion']
            : $args['region'];
    }

    public static function _missing_version(array $args)
    {
        $service = isset($args['service']) ? $args['service'] : '';
        $versions = ApiProvider::defaultProvider()->getVersions($service);
        $versions = implode("\n", array_map(function ($v) {
            return "* \"$v\"";
        }, $versions)) ?: '* (none found)';

        return <<<EOT
A "version" configuration value is required. Specifying a version constraint
ensures that your code will not be affected by a breaking change made to the
service. For example, when using Amazon S3, you can lock your API version to
"2006-03-01".

Your build of the SDK has the following version(s) of "{$service}": {$versions}

You may provide "latest" to the "version" configuration value to utilize the
most recent available API version that your client's API provider can find.
Note: Using 'latest' in a production application is not recommended.

A list of available API versions can be found on each client's API documentation
page: http://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html. If you are
unable to load a specific API version, then you may need to update your copy of
the SDK.
EOT;
    }

    public static function _missing_region(array $args)
    {
        $service = isset($args['service']) ? $args['service'] : '';

        return <<<EOT
A "region" configuration value is required for the "{$service}" service
(e.g., "us-west-2"). A list of available public regions and endpoints can be
found at http://docs.aws.amazon.com/general/latest/gr/rande.html.
EOT;
    }
}
<?php
namespace Aws;

/**
 * AWS command object.
 */
class Command implements CommandInterface
{
    use HasDataTrait;

    /** @var string */
    private $name;

    /** @var HandlerList */
    private $handlerList;

    /**
     * Accepts an associative array of command options, including:
     *
     * - @http: (array) Associative array of transfer options.
     *
     * @param string      $name           Name of the command
     * @param array       $args           Arguments to pass to the command
     * @param HandlerList $list           Handler list
     */
    public function __construct($name, array $args = [], HandlerList $list = null)
    {
        $this->name = $name;
        $this->data = $args;
        $this->handlerList = $list ?: new HandlerList();

        if (!isset($this->data['@http'])) {
            $this->data['@http'] = [];
        }
    }

    public function __clone()
    {
        $this->handlerList = clone $this->handlerList;
    }

    public function getName()
    {
        return $this->name;
    }

    public function hasParam($name)
    {
        return array_key_exists($name, $this->data);
    }

    public function getHandlerList()
    {
        return $this->handlerList;
    }

    /** @deprecated */
    public function get($name)
    {
        return $this[$name];
    }
}
<?php
namespace Aws;

/**
 * A command object encapsulates the input parameters used to control the
 * creation of a HTTP request and processing of a HTTP response.
 *
 * Using the toArray() method will return the input parameters of the command
 * as an associative array.
 */
interface CommandInterface extends \ArrayAccess, \Countable, \IteratorAggregate
{
    /**
     * Converts the command parameters to an array
     *
     * @return array
     */
    public function toArray();

    /**
     * Get the name of the command
     *
     * @return string
     */
    public function getName();

    /**
     * Check if the command has a parameter by name.
     *
     * @param string $name Name of the parameter to check
     *
     * @return bool
     */
    public function hasParam($name);

    /**
     * Get the handler list used to transfer the command.
     *
     * @return HandlerList
     */
    public function getHandlerList();
}
<?php
namespace Aws;

use GuzzleHttp\Promise\PromisorInterface;
use GuzzleHttp\Promise\EachPromise;

/**
 * Sends and iterator of commands concurrently using a capped pool size.
 *
 * The pool will read command objects from an iterator until it is cancelled or
 * until the iterator is consumed.
 */
class CommandPool implements PromisorInterface
{
    /** @var EachPromise */
    private $each;

    /**
     * The CommandPool constructor accepts a hash of configuration options:
     *
     * - concurrency: (callable|int) Maximum number of commands to execute
     *   concurrently. Provide a function to resize the pool dynamically. The
     *   function will be provided the current number of pending requests and
     *   is expected to return an integer representing the new pool size limit.
     * - before: (callable) function to invoke before sending each command. The
     *   before function accepts the command and the key of the iterator of the
     *   command. You can mutate the command as needed in the before function
     *   before sending the command.
     * - fulfilled: (callable) Function to invoke when a promise is fulfilled.
     *   The function is provided the result object, id of the iterator that the
     *   result came from, and the aggregate promise that can be resolved/rejected
     *   if you need to short-circuit the pool.
     * - rejected: (callable) Function to invoke when a promise is rejected.
     *   The function is provided an AwsException object, id of the iterator that
     *   the exception came from, and the aggregate promise that can be
     *   resolved/rejected if you need to short-circuit the pool.
     *
     * @param AwsClientInterface $client   Client used to execute commands.
     * @param array|\Iterator    $commands Iterable that yields commands.
     * @param array              $config   Associative array of options.
     */
    public function __construct(
        AwsClientInterface $client,
        $commands,
        array $config = []
    ) {
        if (!isset($config['concurrency'])) {
            $config['concurrency'] = 25;
        }

        $before = $this->getBefore($config);
        $mapFn = function ($commands) use ($client, $before) {
            foreach ($commands as $key => $command) {
                if (!($command instanceof CommandInterface)) {
                    throw new \InvalidArgumentException('Each value yielded by '
                        . 'the iterator must be an Aws\CommandInterface.');
                }
                if ($before) {
                    $before($command, $key);
                }
                yield $client->executeAsync($command);
            }
        };

        $this->each = new EachPromise($mapFn($commands), $config);
    }

    /**
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public function promise()
    {
        return $this->each->promise();
    }

    /**
     * Executes a pool synchronously and aggregates the results of the pool
     * into an indexed array in the same order as the passed in array.
     *
     * @param AwsClientInterface $client   Client used to execute commands.
     * @param mixed              $commands Iterable that yields commands.
     * @param array              $config   Configuration options.
     *
     * @return array
     * @see \Aws\CommandPool::__construct for available configuration options.
     */
    public static function batch(
        AwsClientInterface $client,
        $commands,
        array $config = []
    ) {
        $results = [];
        self::cmpCallback($config, 'fulfilled', $results);
        self::cmpCallback($config, 'rejected', $results);

        return (new self($client, $commands, $config))
            ->promise()
            ->then(static function () use (&$results) {
                ksort($results);
                return $results;
            })
            ->wait();
    }

    /**
     * @return callable
     */
    private function getBefore(array $config)
    {
        if (!isset($config['before'])) {
            return null;
        }

        if (is_callable($config['before'])) {
            return $config['before'];
        }

        throw new \InvalidArgumentException('before must be callable');
    }

    /**
     * Adds an onFulfilled or onRejected callback that aggregates results into
     * an array. If a callback is already present, it is replaced with the
     * composed function.
     *
     * @param array $config
     * @param       $name
     * @param array $results
     */
    private static function cmpCallback(array &$config, $name, array &$results)
    {
        if (!isset($config[$name])) {
            $config[$name] = function ($v, $k) use (&$results) {
                $results[$k] = $v;
            };
        } else {
            $currentFn = $config[$name];
            $config[$name] = function ($v, $k) use (&$results, $currentFn) {
                $currentFn($v, $k);
                $results[$k] = $v;
            };
        }
    }
}
<?php
namespace Aws\Credentials;

use Aws\Exception\AwsException;
use Aws\Exception\CredentialsException;
use Aws\Result;
use Aws\Sts\StsClient;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;

/**
 * Credential provider that provides credentials via assuming a role
 * More Information, see: http://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sts-2011-06-15.html#assumerole
 */
class AssumeRoleCredentialProvider
{
    const ERROR_MSG = "Missing required 'AssumeRoleCredentialProvider' configuration option: ";

    /** @var callable */
    private $client;

    /** @var array */
    private $assumeRoleParams;

    /**
     * The constructor requires following configure parameters:
     *  - client: a StsClient
     *  - assume_role_params: Parameters used to make assumeRole call
     *
     * @param array $config Configuration options
     * @throws \InvalidArgumentException
     */
    public function __construct(array $config = [])
    {
        if (!isset($config['assume_role_params'])) {
            throw new \InvalidArgumentException(self::ERROR_MSG . "'assume_role_params'.");
        }

        if (!isset($config['client'])) {
            throw new \InvalidArgumentException(self::ERROR_MSG . "'client'.");
        }

        $this->client = $config['client'];
        $this->assumeRoleParams = $config['assume_role_params'];
    }

    /**
     * Loads assume role credentials.
     *
     * @return PromiseInterface
     */
    public function __invoke()
    {
        $client = $this->client;
        return $client->assumeRoleAsync($this->assumeRoleParams)
            ->then(function (Result $result) {
                return $this->client->createCredentials($result);
            })->otherwise(function (\RuntimeException $exception) {
                throw new CredentialsException(
                    "Error in retrieving assume role credentials.",
                    0,
                    $exception
                );
            });
    }
}
<?php
namespace Aws\Credentials;

use Aws;
use Aws\CacheInterface;
use Aws\Exception\CredentialsException;
use GuzzleHttp\Promise;

/**
 * Credential providers are functions that accept no arguments and return a
 * promise that is fulfilled with an {@see \Aws\Credentials\CredentialsInterface}
 * or rejected with an {@see \Aws\Exception\CredentialsException}.
 *
 * <code>
 * use Aws\Credentials\CredentialProvider;
 * $provider = CredentialProvider::defaultProvider();
 * // Returns a CredentialsInterface or throws.
 * $creds = $provider()->wait();
 * </code>
 *
 * Credential providers can be composed to create credentials using conditional
 * logic that can create different credentials in different environments. You
 * can compose multiple providers into a single provider using
 * {@see Aws\Credentials\CredentialProvider::chain}. This function accepts
 * providers as variadic arguments and returns a new function that will invoke
 * each provider until a successful set of credentials is returned.
 *
 * <code>
 * // First try an INI file at this location.
 * $a = CredentialProvider::ini(null, '/path/to/file.ini');
 * // Then try an INI file at this location.
 * $b = CredentialProvider::ini(null, '/path/to/other-file.ini');
 * // Then try loading from environment variables.
 * $c = CredentialProvider::env();
 * // Combine the three providers together.
 * $composed = CredentialProvider::chain($a, $b, $c);
 * // Returns a promise that is fulfilled with credentials or throws.
 * $promise = $composed();
 * // Wait on the credentials to resolve.
 * $creds = $promise->wait();
 * </code>
 */
class CredentialProvider
{
    const ENV_KEY = 'AWS_ACCESS_KEY_ID';
    const ENV_SECRET = 'AWS_SECRET_ACCESS_KEY';
    const ENV_SESSION = 'AWS_SESSION_TOKEN';
    const ENV_PROFILE = 'AWS_PROFILE';

    /**
     * Create a default credential provider that first checks for environment
     * variables, then checks for the "default" profile in ~/.aws/credentials,
     * then checks for "profile default" profile in ~/.aws/config (which is
     * the default profile of AWS CLI), then tries to make a GET Request to
     * fetch credentials if Ecs environment variable is presented, and finally
     * checks for EC2 instance profile credentials.
     *
     * This provider is automatically wrapped in a memoize function that caches
     * previously provided credentials.
     *
     * @param array $config Optional array of ecs/instance profile credentials
     *                      provider options.
     *
     * @return callable
     */
    public static function defaultProvider(array $config = [])
    {
        $localCredentialProviders = self::localCredentialProviders();
        $remoteCredentialProviders = self::remoteCredentialProviders($config);

        return self::memoize(
            call_user_func_array(
                'self::chain',
                array_merge($localCredentialProviders, $remoteCredentialProviders)
            )
        );
    }

    /**
     * Create a credential provider function from a set of static credentials.
     *
     * @param CredentialsInterface $creds
     *
     * @return callable
     */
    public static function fromCredentials(CredentialsInterface $creds)
    {
        $promise = Promise\promise_for($creds);

        return function () use ($promise) {
            return $promise;
        };
    }

    /**
     * Creates an aggregate credentials provider that invokes the provided
     * variadic providers one after the other until a provider returns
     * credentials.
     *
     * @return callable
     */
    public static function chain()
    {
        $links = func_get_args();
        if (empty($links)) {
            throw new \InvalidArgumentException('No providers in chain');
        }

        return function () use ($links) {
            /** @var callable $parent */
            $parent = array_shift($links);
            $promise = $parent();
            while ($next = array_shift($links)) {
                $promise = $promise->otherwise($next);
            }
            return $promise;
        };
    }

    /**
     * Wraps a credential provider and caches previously provided credentials.
     *
     * Ensures that cached credentials are refreshed when they expire.
     *
     * @param callable $provider Credentials provider function to wrap.
     *
     * @return callable
     */
    public static function memoize(callable $provider)
    {
        return function () use ($provider) {
            static $result;
            static $isConstant;

            // Constant credentials will be returned constantly.
            if ($isConstant) {
                return $result;
            }

            // Create the initial promise that will be used as the cached value
            // until it expires.
            if (null === $result) {
                $result = $provider();
            }

            // Return credentials that could expire and refresh when needed.
            return $result
                ->then(function (CredentialsInterface $creds) use ($provider, &$isConstant, &$result) {
                    // Determine if these are constant credentials.
                    if (!$creds->getExpiration()) {
                        $isConstant = true;
                        return $creds;
                    }

                    // Refresh expired credentials.
                    if (!$creds->isExpired()) {
                        return $creds;
                    }
                    // Refresh the result and forward the promise.
                    return $result = $provider();
                });
        };
    }

    /**
     * Wraps a credential provider and saves provided credentials in an
     * instance of Aws\CacheInterface. Forwards calls when no credentials found
     * in cache and updates cache with the results.
     *
     * Defaults to using a simple file-based cache when none provided.
     *
     * @param callable $provider Credentials provider function to wrap
     * @param CacheInterface $cache Cache to store credentials
     * @param string|null $cacheKey (optional) Cache key to use
     *
     * @return callable
     */
    public static function cache(
        callable $provider,
        CacheInterface $cache,
        $cacheKey = null
    ) {
        $cacheKey = $cacheKey ?: 'aws_cached_credentials';

        return function () use ($provider, $cache, $cacheKey) {
            $found = $cache->get($cacheKey);
            if ($found instanceof CredentialsInterface && !$found->isExpired()) {
                return Promise\promise_for($found);
            }

            return $provider()
                ->then(function (CredentialsInterface $creds) use (
                    $cache,
                    $cacheKey
                ) {
                    $cache->set(
                        $cacheKey,
                        $creds,
                        null === $creds->getExpiration() ?
                            0 : $creds->getExpiration() - time()
                    );

                    return $creds;
                });
        };
    }

    /**
     * Provider that creates credentials from environment variables
     * AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN.
     *
     * @return callable
     */
    public static function env()
    {
        return function () {
            // Use credentials from environment variables, if available
            $key = getenv(self::ENV_KEY);
            $secret = getenv(self::ENV_SECRET);
            if ($key && $secret) {
                return Promise\promise_for(
                    new Credentials($key, $secret, getenv(self::ENV_SESSION))
                );
            }

            return self::reject('Could not find environment variable '
                . 'credentials in ' . self::ENV_KEY . '/' . self::ENV_SECRET);
        };
    }

    /**
     * Credential provider that creates credentials using instance profile
     * credentials.
     *
     * @param array $config Array of configuration data.
     *
     * @return InstanceProfileProvider
     * @see Aws\Credentials\InstanceProfileProvider for $config details.
     */
    public static function instanceProfile(array $config = [])
    {
        return new InstanceProfileProvider($config);
    }

    /**
     * Credential provider that creates credentials using
     * ecs credentials by a GET request, whose uri is specified
     * by environment variable
     *
     * @param array $config Array of configuration data.
     *
     * @return EcsCredentialProvider
     * @see Aws\Credentials\EcsCredentialProvider for $config details.
     */
    public static function ecsCredentials(array $config = [])
    {
        return new EcsCredentialProvider($config);
    }

    /**
     * Credential provider that creates credentials using assume role
     *
     * @param array $config Array of configuration data
     * @return callable
     * @see Aws\Credentials\AssumeRoleCredentialProvider for $config details.
     */
    public static function assumeRole(array $config=[])
    {
        return new AssumeRoleCredentialProvider($config);
    }

    /**
     * Credentials provider that creates credentials using an ini file stored
     * in the current user's home directory.
     *
     * @param string|null $profile  Profile to use. If not specified will use
     *                              the "default" profile in "~/.aws/credentials".
     * @param string|null $filename If provided, uses a custom filename rather
     *                              than looking in the home directory.
     *
     * @return callable
     */
    public static function ini($profile = null, $filename = null)
    {
        $filename = $filename ?: (self::getHomeDir() . '/.aws/credentials');
        $profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');

        return function () use ($profile, $filename) {
            if (!is_readable($filename)) {
                return self::reject("Cannot read credentials from $filename");
            }
            $data = parse_ini_file($filename, true);
            if ($data === false) {
                return self::reject("Invalid credentials file: $filename");
            }
            if (!isset($data[$profile])) {
                return self::reject("'$profile' not found in credentials file");
            }
            if (!isset($data[$profile]['aws_access_key_id'])
                || !isset($data[$profile]['aws_secret_access_key'])
            ) {
                return self::reject("No credentials present in INI profile "
                    . "'$profile' ($filename)");
            }

            if (empty($data[$profile]['aws_session_token'])) {
                $data[$profile]['aws_session_token']
                    = isset($data[$profile]['aws_security_token'])
                        ? $data[$profile]['aws_security_token']
                        : null;
            }

            return Promise\promise_for(
                new Credentials(
                    $data[$profile]['aws_access_key_id'],
                    $data[$profile]['aws_secret_access_key'],
                    $data[$profile]['aws_session_token']
                )
            );
        };
    }

    /**
     * Local credential providers returns a list of local credential providers
     * in following order:
     *  - credentials from environment variables
     *  - 'default' profile in '.aws/credentials' file
     *  - 'profile default' profile in '.aws/config' file
     *
     * @return array
     */
    private static function localCredentialProviders()
    {
        return [
            self::env(),
            self::ini(),
            self::ini('profile default', self::getHomeDir() . '/.aws/config')
        ];
    }

    /**
     * Remote credential providers returns a list of credentials providers
     * for the remote endpoints such as EC2 or ECS Roles.
     *
     * @param array $config Array of configuration data.
     *
     * @return array
     * @see Aws\Credentials\InstanceProfileProvider for $config details.
     * @see Aws\Credentials\EcsCredentialProvider for $config details.
     */
    private static function remoteCredentialProviders(array $config = [])
    {
        if (!empty(getenv(EcsCredentialProvider::ENV_URI))) {
            $providers['ecs'] = self::ecsCredentials($config);
        }
        $providers['instance'] = self::instanceProfile($config);

        if (isset($config['credentials'])
            && $config['credentials'] instanceof CacheInterface
        ) {
            foreach ($providers as $key => $provider) {
                $providers[$key] = self::cache(
                    $provider,
                    $config['credentials'],
                    'aws_cached_' . $key . '_credentials'
                );
            }
        }

        return $providers;
    }

    /**
     * Gets the environment's HOME directory if available.
     *
     * @return null|string
     */
    private static function getHomeDir()
    {
        // On Linux/Unix-like systems, use the HOME environment variable
        if ($homeDir = getenv('HOME')) {
            return $homeDir;
        }

        // Get the HOMEDRIVE and HOMEPATH values for Windows hosts
        $homeDrive = getenv('HOMEDRIVE');
        $homePath = getenv('HOMEPATH');

        return ($homeDrive && $homePath) ? $homeDrive . $homePath : null;
    }

    private static function reject($msg)
    {
        return new Promise\RejectedPromise(new CredentialsException($msg));
    }
}
<?php
namespace Aws\Credentials;

/**
 * Basic implementation of the AWS Credentials interface that allows callers to
 * pass in the AWS Access Key and AWS Secret Access Key in the constructor.
 */
class Credentials implements CredentialsInterface, \Serializable
{
    private $key;
    private $secret;
    private $token;
    private $expires;

    /**
     * Constructs a new BasicAWSCredentials object, with the specified AWS
     * access key and AWS secret key
     *
     * @param string $key     AWS access key ID
     * @param string $secret  AWS secret access key
     * @param string $token   Security token to use
     * @param int    $expires UNIX timestamp for when credentials expire
     */
    public function __construct($key, $secret, $token = null, $expires = null)
    {
        $this->key = trim($key);
        $this->secret = trim($secret);
        $this->token = $token;
        $this->expires = $expires;
    }

    public static function __set_state(array $state)
    {
        return new self(
            $state['key'],
            $state['secret'],
            $state['token'],
            $state['expires']
        );
    }

    public function getAccessKeyId()
    {
        return $this->key;
    }

    public function getSecretKey()
    {
        return $this->secret;
    }

    public function getSecurityToken()
    {
        return $this->token;
    }

    public function getExpiration()
    {
        return $this->expires;
    }

    public function isExpired()
    {
        return $this->expires !== null && time() >= $this->expires;
    }

    public function toArray()
    {
        return [
            'key'     => $this->key,
            'secret'  => $this->secret,
            'token'   => $this->token,
            'expires' => $this->expires
        ];
    }

    public function serialize()
    {
        return json_encode($this->toArray());
    }

    public function unserialize($serialized)
    {
        $data = json_decode($serialized, true);

        $this->key = $data['key'];
        $this->secret = $data['secret'];
        $this->token = $data['token'];
        $this->expires = $data['expires'];
    }
}
<?php
namespace Aws\Credentials;

/**
 * Provides access to the AWS credentials used for accessing AWS services: AWS
 * access key ID, secret access key, and security token. These credentials are
 * used to securely sign requests to AWS services.
 */
interface CredentialsInterface
{
    /**
     * Returns the AWS access key ID for this credentials object.
     *
     * @return string
     */
    public function getAccessKeyId();

    /**
     * Returns the AWS secret access key for this credentials object.
     *
     * @return string
     */
    public function getSecretKey();

    /**
     * Get the associated security token if available
     *
     * @return string|null
     */
    public function getSecurityToken();

    /**
     * Get the UNIX timestamp in which the credentials will expire
     *
     * @return int|null
     */
    public function getExpiration();

    /**
     * Check if the credentials are expired
     *
     * @return bool
     */
    public function isExpired();

    /**
     * Converts the credentials to an associative array.
     *
     * @return array
     */
    public function toArray();
}
<?php
namespace Aws\Credentials;

use Aws\Exception\CredentialsException;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Credential provider that fetches credentials with GET request.
 * ECS environment variable is used in constructing request URI.
 */
class EcsCredentialProvider
{
    const SERVER_URI = 'http://169.254.170.2';
    const ENV_URI = "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI";

    /** @var callable */
    private $client;

    /**
     *  The constructor accepts following options:
     *  - timeout: (optional) Connection timeout, in seconds, default 1.0
     *  - client: An EcsClient to make request from
     *
     * @param array $config Configuration options
     */
    public function __construct(array $config = [])
    {
        $this->timeout = isset($config['timeout']) ? $config['timeout'] : 1.0;
        $this->client = isset($config['client'])
            ? $config['client']
            : \Aws\default_http_handler();
    }

    /**
     * Load ECS credentials
     *
     * @return PromiseInterface
     */
    public function __invoke()
    {
        $client = $this->client;
        $request = new Request('GET', self::getEcsUri());
        return $client(
            $request,
            ['timeout' => $this->timeout]
        )->then(function (ResponseInterface $response) {
            $result = $this->decodeResult((string) $response->getBody());
            return new Credentials(
                $result['AccessKeyId'],
                $result['SecretAccessKey'],
                $result['Token'],
                strtotime($result['Expiration'])
            );
        })->otherwise(function ($reason) {
            $reason = is_array($reason) ? $reason['exception'] : $reason;
            $msg = $reason->getMessage();
            throw new CredentialsException(
                "Error retrieving credential from ECS ($msg)"
            );
        });
    }

    /**
     * Fetch credential URI from ECS environment variable
     *
     * @return string Returns ECS URI
     */
    private function getEcsUri()
    {
        $creds_uri = getenv(self::ENV_URI);
        return self::SERVER_URI . $creds_uri;
    }

    private function decodeResult($response)
    {
        $result = json_decode($response, true);

        if (!isset($result['AccessKeyId'])) {
            throw new CredentialsException('Unexpected ECS credential value');
        }
        return $result;
    }
}
<?php
namespace Aws\Credentials;

use Aws\Exception\CredentialsException;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Credential provider that provides credentials from the EC2 metadata server.
 */
class InstanceProfileProvider
{
    const SERVER_URI = 'http://169.254.169.254/latest/';
    const CRED_PATH = 'meta-data/iam/security-credentials/';

    /** @var string */
    private $profile;

    /** @var callable */
    private $client;

    /**
     * The constructor accepts the following options:
     *
     * - timeout: Connection timeout, in seconds.
     * - profile: Optional EC2 profile name, if known.
     *
     * @param array $config Configuration options.
     */
    public function __construct(array $config = [])
    {
        $this->timeout = isset($config['timeout']) ? $config['timeout'] : 1.0;
        $this->profile = isset($config['profile']) ? $config['profile'] : null;
        $this->client = isset($config['client'])
            ? $config['client'] // internal use only
            : \Aws\default_http_handler();
    }

    /**
     * Loads instance profile credentials.
     *
     * @return PromiseInterface
     */
    public function __invoke()
    {
        return Promise\coroutine(function () {
            if (!$this->profile) {
                $this->profile = (yield $this->request(self::CRED_PATH));
            }
            $json = (yield $this->request(self::CRED_PATH . $this->profile));
            $result = $this->decodeResult($json);
            yield new Credentials(
                $result['AccessKeyId'],
                $result['SecretAccessKey'],
                $result['Token'],
                strtotime($result['Expiration'])
            );
        });
    }

    /**
     * @param string $url
     * @return PromiseInterface Returns a promise that is fulfilled with the
     *                          body of the response as a string.
     */
    private function request($url)
    {
        $fn = $this->client;
        $request = new Request('GET', self::SERVER_URI . $url);

        return $fn($request, ['timeout' => $this->timeout])
            ->then(function (ResponseInterface $response) {
                return (string) $response->getBody();
            })->otherwise(function (array $reason) {
                $reason = $reason['exception'];
                $msg = $reason->getMessage();
                throw new CredentialsException(
                    $this->createErrorMessage($msg, 0, $reason)
                );
            });
    }

    private function createErrorMessage($previous)
    {
        return "Error retrieving credentials from the instance profile "
            . "metadata server. ({$previous})";
    }

    private function decodeResult($response)
    {
        $result = json_decode($response, true);

        if ($result['Code'] !== 'Success') {
            throw new CredentialsException('Unexpected instance profile '
                .  'response code: ' . $result['Code']);
        }

        return $result;
    }
}
<?php
// This file was auto-generated from sdk-root/src/data/endpoints.json
return [ 'partitions' => [ [ 'defaults' => [ 'hostname' => '{service}.{region}.{dnsSuffix}', 'protocols' => [ 'https', ], 'signatureVersions' => [ 'v4', ], ], 'dnsSuffix' => 'amazonaws.com', 'partition' => 'aws', 'partitionName' => 'AWS Standard', 'regionRegex' => '^(us|eu|ap|sa|ca)\\-\\w+\\-\\d+$', 'regions' => [ 'ap-northeast-1' => [ 'description' => 'Asia Pacific (Tokyo)', ], 'ap-northeast-2' => [ 'description' => 'Asia Pacific (Seoul)', ], 'ap-south-1' => [ 'description' => 'Asia Pacific (Mumbai)', ], 'ap-southeast-1' => [ 'description' => 'Asia Pacific (Singapore)', ], 'ap-southeast-2' => [ 'description' => 'Asia Pacific (Sydney)', ], 'ca-central-1' => [ 'description' => 'Canada (Central)', ], 'eu-central-1' => [ 'description' => 'EU (Frankfurt)', ], 'eu-west-1' => [ 'description' => 'EU (Ireland)', ], 'eu-west-2' => [ 'description' => 'EU (London)', ], 'sa-east-1' => [ 'description' => 'South America (Sao Paulo)', ], 'us-east-1' => [ 'description' => 'US East (N. Virginia)', ], 'us-east-2' => [ 'description' => 'US East (Ohio)', ], 'us-west-1' => [ 'description' => 'US West (N. California)', ], 'us-west-2' => [ 'description' => 'US West (Oregon)', ], ], 'services' => [ 'acm' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'apigateway' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'application-autoscaling' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'application-autoscaling', ], 'hostname' => 'autoscaling.{region}.amazonaws.com', 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'appstream2' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'appstream', ], 'protocols' => [ 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'autoscaling' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'batch' => [ 'endpoints' => [ 'us-east-1' => [], ], ], 'budgets' => [ 'endpoints' => [ 'aws-global' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 'budgets.amazonaws.com', ], ], 'isRegionalized' => false, 'partitionEndpoint' => 'aws-global', ], 'clouddirectory' => [ 'endpoints' => [ 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'cloudformation' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'cloudfront' => [ 'endpoints' => [ 'aws-global' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 'cloudfront.amazonaws.com', 'protocols' => [ 'http', 'https', ], ], ], 'isRegionalized' => false, 'partitionEndpoint' => 'aws-global', ], 'cloudhsm' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'cloudsearch' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'cloudtrail' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'codebuild' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'codecommit' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'codedeploy' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'codepipeline' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'cognito-identity' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'cognito-idp' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'cognito-sync' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'config' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'cur' => [ 'endpoints' => [ 'us-east-1' => [], ], ], 'data.iot' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'iotdata', ], 'protocols' => [ 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'datapipeline' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-2' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'devicefarm' => [ 'endpoints' => [ 'us-west-2' => [], ], ], 'directconnect' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'discovery' => [ 'endpoints' => [ 'us-west-2' => [], ], ], 'dms' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'ds' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'dynamodb' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'local' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 'localhost:8000', 'protocols' => [ 'http', ], ], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'ec2' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'ecr' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'ecs' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'elasticache' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'elasticbeanstalk' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'elasticfilesystem' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'elasticloadbalancing' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'elasticmapreduce' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], 'sslCommonName' => '{region}.{service}.{dnsSuffix}', ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [ 'sslCommonName' => '{service}.{region}.{dnsSuffix}', ], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [ 'sslCommonName' => '{service}.{region}.{dnsSuffix}', ], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'elastictranscoder' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'email' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'es' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'events' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'firehose' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'gamelift' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'glacier' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'health' => [ 'endpoints' => [ 'us-east-1' => [], ], ], 'iam' => [ 'endpoints' => [ 'aws-global' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 'iam.amazonaws.com', ], ], 'isRegionalized' => false, 'partitionEndpoint' => 'aws-global', ], 'importexport' => [ 'endpoints' => [ 'aws-global' => [ 'credentialScope' => [ 'region' => 'us-east-1', 'service' => 'IngestionService', ], 'hostname' => 'importexport.amazonaws.com', 'signatureVersions' => [ 'v2', 'v4', ], ], ], 'isRegionalized' => false, 'partitionEndpoint' => 'aws-global', ], 'inspector' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-2' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'iot' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'execute-api', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'kinesis' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'kinesisanalytics' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'kms' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'lambda' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'lightsail' => [ 'endpoints' => [ 'us-east-1' => [], ], ], 'logs' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'machinelearning' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], ], ], 'marketplacecommerceanalytics' => [ 'endpoints' => [ 'us-east-1' => [], ], ], 'metering.marketplace' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'aws-marketplace', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'mobileanalytics' => [ 'endpoints' => [ 'us-east-1' => [], ], ], 'monitoring' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'opsworks' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'opsworks-cm' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'pinpoint' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'mobiletargeting', ], ], 'endpoints' => [ 'us-east-1' => [], ], ], 'polly' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'rds' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [ 'sslCommonName' => '{service}.{dnsSuffix}', ], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'redshift' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'rekognition' => [ 'endpoints' => [ 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'route53' => [ 'endpoints' => [ 'aws-global' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 'route53.amazonaws.com', ], ], 'isRegionalized' => false, 'partitionEndpoint' => 'aws-global', ], 'route53domains' => [ 'endpoints' => [ 'us-east-1' => [], ], ], 'runtime.lex' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'lex', ], ], 'endpoints' => [ 'us-east-1' => [], ], ], 's3' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], 'signatureVersions' => [ 's3v4', ], ], 'endpoints' => [ 'ap-northeast-1' => [ 'hostname' => 's3-ap-northeast-1.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [ 'hostname' => 's3-ap-southeast-1.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], 'ap-southeast-2' => [ 'hostname' => 's3-ap-southeast-2.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [ 'hostname' => 's3-eu-west-1.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], 'eu-west-2' => [], 's3-external-1' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 's3-external-1.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], 'sa-east-1' => [ 'hostname' => 's3-sa-east-1.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], 'us-east-1' => [ 'hostname' => 's3.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], 'us-east-2' => [], 'us-west-1' => [ 'hostname' => 's3-us-west-1.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], 'us-west-2' => [ 'hostname' => 's3-us-west-2.amazonaws.com', 'signatureVersions' => [ 's3', 's3v4', ], ], ], 'isRegionalized' => true, 'partitionEndpoint' => 'us-east-1', ], 'sdb' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-west-1' => [], 'sa-east-1' => [], 'us-east-1' => [ 'hostname' => 'sdb.amazonaws.com', ], 'us-west-1' => [], 'us-west-2' => [], ], ], 'servicecatalog' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'shield' => [ 'defaults' => [ 'protocols' => [ 'https', ], 'sslCommonName' => 'Shield.us-east-1.amazonaws.com', ], 'endpoints' => [ 'us-east-1' => [], ], 'isRegionalized' => false, ], 'sms' => [ 'endpoints' => [ 'ap-southeast-2' => [], 'eu-west-1' => [], 'us-east-1' => [], ], ], 'snowball' => [ 'endpoints' => [ 'ap-south-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'sns' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'sqs' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], 'sslCommonName' => '{region}.queue.{dnsSuffix}', ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [ 'sslCommonName' => 'queue.{dnsSuffix}', ], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'ssm' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'states' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-2' => [], ], ], 'storagegateway' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'streams.dynamodb' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'dynamodb', ], 'protocols' => [ 'http', 'http', 'https', 'https', ], ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'local' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 'localhost:8000', 'protocols' => [ 'http', ], ], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'sts' => [ 'defaults' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 'sts.amazonaws.com', ], 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [ 'credentialScope' => [ 'region' => 'ap-northeast-2', ], 'hostname' => 'sts.ap-northeast-2.amazonaws.com', ], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'aws-global' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], 'partitionEndpoint' => 'aws-global', ], 'support' => [ 'endpoints' => [ 'us-east-1' => [], ], ], 'swf' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'ca-central-1' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'eu-west-2' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], 'waf' => [ 'endpoints' => [ 'aws-global' => [ 'credentialScope' => [ 'region' => 'us-east-1', ], 'hostname' => 'waf.amazonaws.com', ], ], 'isRegionalized' => false, 'partitionEndpoint' => 'aws-global', ], 'waf-regional' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'workspaces' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'us-east-1' => [], 'us-west-2' => [], ], ], 'xray' => [ 'endpoints' => [ 'ap-northeast-1' => [], 'ap-northeast-2' => [], 'ap-south-1' => [], 'ap-southeast-1' => [], 'ap-southeast-2' => [], 'eu-central-1' => [], 'eu-west-1' => [], 'sa-east-1' => [], 'us-east-1' => [], 'us-east-2' => [], 'us-west-1' => [], 'us-west-2' => [], ], ], ], ], [ 'defaults' => [ 'hostname' => '{service}.{region}.{dnsSuffix}', 'protocols' => [ 'https', ], 'signatureVersions' => [ 'v4', ], ], 'dnsSuffix' => 'amazonaws.com.cn', 'partition' => 'aws-cn', 'partitionName' => 'AWS China', 'regionRegex' => '^cn\\-\\w+\\-\\d+$', 'regions' => [ 'cn-north-1' => [ 'description' => 'China (Beijing)', ], ], 'services' => [ 'autoscaling' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'cloudformation' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'cloudtrail' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'config' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'directconnect' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'dynamodb' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'ec2' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'elasticache' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'elasticbeanstalk' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'elasticloadbalancing' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'elasticmapreduce' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'events' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'glacier' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'iam' => [ 'endpoints' => [ 'aws-cn-global' => [ 'credentialScope' => [ 'region' => 'cn-north-1', ], 'hostname' => 'iam.cn-north-1.amazonaws.com.cn', ], ], 'isRegionalized' => false, 'partitionEndpoint' => 'aws-cn-global', ], 'kinesis' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'logs' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'monitoring' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'rds' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'redshift' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 's3' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], 'signatureVersions' => [ 's3v4', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'sns' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'sqs' => [ 'defaults' => [ 'protocols' => [ 'http', 'https', ], 'sslCommonName' => '{region}.queue.{dnsSuffix}', ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'storagegateway' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'streams.dynamodb' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'dynamodb', ], 'protocols' => [ 'http', 'http', 'https', 'https', ], ], 'endpoints' => [ 'cn-north-1' => [], ], ], 'sts' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], 'swf' => [ 'endpoints' => [ 'cn-north-1' => [], ], ], ], ], [ 'defaults' => [ 'hostname' => '{service}.{region}.{dnsSuffix}', 'protocols' => [ 'https', ], 'signatureVersions' => [ 'v4', ], ], 'dnsSuffix' => 'amazonaws.com', 'partition' => 'aws-us-gov', 'partitionName' => 'AWS GovCloud (US)', 'regionRegex' => '^us\\-gov\\-\\w+\\-\\d+$', 'regions' => [ 'us-gov-west-1' => [ 'description' => 'AWS GovCloud (US)', ], ], 'services' => [ 'autoscaling' => [ 'endpoints' => [ 'us-gov-west-1' => [ 'protocols' => [ 'http', 'https', ], ], ], ], 'cloudformation' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'cloudhsm' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'cloudtrail' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'config' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'directconnect' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'dynamodb' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'ec2' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'elasticache' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'elasticloadbalancing' => [ 'endpoints' => [ 'us-gov-west-1' => [ 'protocols' => [ 'http', 'https', ], ], ], ], 'elasticmapreduce' => [ 'endpoints' => [ 'us-gov-west-1' => [ 'protocols' => [ 'http', 'https', ], ], ], ], 'glacier' => [ 'endpoints' => [ 'us-gov-west-1' => [ 'protocols' => [ 'http', 'https', ], ], ], ], 'iam' => [ 'endpoints' => [ 'aws-us-gov-global' => [ 'credentialScope' => [ 'region' => 'us-gov-west-1', ], 'hostname' => 'iam.us-gov.amazonaws.com', ], ], 'isRegionalized' => false, 'partitionEndpoint' => 'aws-us-gov-global', ], 'kms' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'logs' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'monitoring' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'rds' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'redshift' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 's3' => [ 'defaults' => [ 'signatureVersions' => [ 's3', 's3v4', ], ], 'endpoints' => [ 'fips-us-gov-west-1' => [ 'credentialScope' => [ 'region' => 'us-gov-west-1', ], 'hostname' => 's3-fips-us-gov-west-1.amazonaws.com', ], 'us-gov-west-1' => [ 'hostname' => 's3-us-gov-west-1.amazonaws.com', 'protocols' => [ 'http', 'https', ], ], ], ], 'snowball' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'sns' => [ 'endpoints' => [ 'us-gov-west-1' => [ 'protocols' => [ 'http', 'https', ], ], ], ], 'sqs' => [ 'endpoints' => [ 'us-gov-west-1' => [ 'protocols' => [ 'http', 'https', ], 'sslCommonName' => '{region}.queue.{dnsSuffix}', ], ], ], 'streams.dynamodb' => [ 'defaults' => [ 'credentialScope' => [ 'service' => 'dynamodb', ], ], 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'sts' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], 'swf' => [ 'endpoints' => [ 'us-gov-west-1' => [], ], ], ], ], ], 'version' => 3,];
<?php
// This file was auto-generated from sdk-root/src/data/manifest.json
return [ 'acm' => [ 'namespace' => 'Acm', 'versions' => [ 'latest' => '2015-12-08', '2015-12-08' => '2015-12-08', ], ], 'apigateway' => [ 'namespace' => 'ApiGateway', 'versions' => [ 'latest' => '2015-07-09', '2015-07-09' => '2015-07-09', '2015-06-01' => '2015-07-09', ], ], 'application-autoscaling' => [ 'namespace' => 'ApplicationAutoScaling', 'versions' => [ 'latest' => '2016-02-06', '2016-02-06' => '2016-02-06', ], ], 'appstream' => [ 'namespace' => 'Appstream', 'versions' => [ 'latest' => '2016-12-01', '2016-12-01' => '2016-12-01', ], ], 'autoscaling' => [ 'namespace' => 'AutoScaling', 'versions' => [ 'latest' => '2011-01-01', '2011-01-01' => '2011-01-01', ], ], 'batch' => [ 'namespace' => 'Batch', 'versions' => [ 'latest' => '2016-08-10', '2016-08-10' => '2016-08-10', ], ], 'budgets' => [ 'namespace' => 'Budgets', 'versions' => [ 'latest' => '2016-10-20', '2016-10-20' => '2016-10-20', ], ], 'clouddirectory' => [ 'namespace' => 'CloudDirectory', 'versions' => [ 'latest' => '2016-05-10', '2016-05-10' => '2016-05-10', ], ], 'cloudformation' => [ 'namespace' => 'CloudFormation', 'versions' => [ 'latest' => '2010-05-15', '2010-05-15' => '2010-05-15', ], ], 'cloudfront' => [ 'namespace' => 'CloudFront', 'versions' => [ 'latest' => '2016-11-25', '2016-11-25' => '2016-11-25', '2016-09-29' => '2016-09-29', '2016-09-07' => '2016-09-07', '2016-08-20' => '2016-08-20', '2016-08-01' => '2016-08-01', '2016-01-28' => '2016-01-28', '2016-01-13' => '2016-11-25', '2015-09-17' => '2016-11-25', '2015-07-27' => '2015-07-27', '2015-04-17' => '2015-07-27', '2014-11-06' => '2015-07-27', ], ], 'cloudhsm' => [ 'namespace' => 'CloudHsm', 'versions' => [ 'latest' => '2014-05-30', '2014-05-30' => '2014-05-30', ], ], 'cloudsearch' => [ 'namespace' => 'CloudSearch', 'versions' => [ 'latest' => '2013-01-01', '2013-01-01' => '2013-01-01', ], ], 'cloudsearchdomain' => [ 'namespace' => 'CloudSearchDomain', 'versions' => [ 'latest' => '2013-01-01', '2013-01-01' => '2013-01-01', ], ], 'cloudtrail' => [ 'namespace' => 'CloudTrail', 'versions' => [ 'latest' => '2013-11-01', '2013-11-01' => '2013-11-01', ], ], 'codebuild' => [ 'namespace' => 'CodeBuild', 'versions' => [ 'latest' => '2016-10-06', '2016-10-06' => '2016-10-06', ], ], 'codecommit' => [ 'namespace' => 'CodeCommit', 'versions' => [ 'latest' => '2015-04-13', '2015-04-13' => '2015-04-13', ], ], 'codedeploy' => [ 'namespace' => 'CodeDeploy', 'versions' => [ 'latest' => '2014-10-06', '2014-10-06' => '2014-10-06', ], ], 'codepipeline' => [ 'namespace' => 'CodePipeline', 'versions' => [ 'latest' => '2015-07-09', '2015-07-09' => '2015-07-09', ], ], 'cognito-identity' => [ 'namespace' => 'CognitoIdentity', 'versions' => [ 'latest' => '2014-06-30', '2014-06-30' => '2014-06-30', ], ], 'cognito-idp' => [ 'namespace' => 'CognitoIdentityProvider', 'versions' => [ 'latest' => '2016-04-18', '2016-04-18' => '2016-04-18', ], ], 'cognito-sync' => [ 'namespace' => 'CognitoSync', 'versions' => [ 'latest' => '2014-06-30', '2014-06-30' => '2014-06-30', ], ], 'config' => [ 'namespace' => 'ConfigService', 'versions' => [ 'latest' => '2014-11-12', '2014-11-12' => '2014-11-12', ], ], 'cur' => [ 'namespace' => 'CostandUsageReportService', 'versions' => [ 'latest' => '2017-01-06', '2017-01-06' => '2017-01-06', ], ], 'data.iot' => [ 'namespace' => 'IotDataPlane', 'versions' => [ 'latest' => '2015-05-28', '2015-05-28' => '2015-05-28', ], ], 'datapipeline' => [ 'namespace' => 'DataPipeline', 'versions' => [ 'latest' => '2012-10-29', '2012-10-29' => '2012-10-29', ], ], 'devicefarm' => [ 'namespace' => 'DeviceFarm', 'versions' => [ 'latest' => '2015-06-23', '2015-06-23' => '2015-06-23', ], ], 'directconnect' => [ 'namespace' => 'DirectConnect', 'versions' => [ 'latest' => '2012-10-25', '2012-10-25' => '2012-10-25', ], ], 'discovery' => [ 'namespace' => 'ApplicationDiscoveryService', 'versions' => [ 'latest' => '2015-11-01', '2015-11-01' => '2015-11-01', ], ], 'dms' => [ 'namespace' => 'DatabaseMigrationService', 'versions' => [ 'latest' => '2016-01-01', '2016-01-01' => '2016-01-01', ], ], 'ds' => [ 'namespace' => 'DirectoryService', 'versions' => [ 'latest' => '2015-04-16', '2015-04-16' => '2015-04-16', ], ], 'dynamodb' => [ 'namespace' => 'DynamoDb', 'versions' => [ 'latest' => '2012-08-10', '2012-08-10' => '2012-08-10', '2011-12-05' => '2011-12-05', ], ], 'ec2' => [ 'namespace' => 'Ec2', 'versions' => [ 'latest' => '2016-11-15', '2016-11-15' => '2016-11-15', '2016-09-15' => '2016-09-15', '2016-04-01' => '2016-04-01', '2015-10-01' => '2015-10-01', '2015-04-15' => '2016-11-15', ], ], 'ecr' => [ 'namespace' => 'Ecr', 'versions' => [ 'latest' => '2015-09-21', '2015-09-21' => '2015-09-21', ], ], 'ecs' => [ 'namespace' => 'Ecs', 'versions' => [ 'latest' => '2014-11-13', '2014-11-13' => '2014-11-13', ], ], 'elasticache' => [ 'namespace' => 'ElastiCache', 'versions' => [ 'latest' => '2015-02-02', '2015-02-02' => '2015-02-02', ], ], 'elasticbeanstalk' => [ 'namespace' => 'ElasticBeanstalk', 'versions' => [ 'latest' => '2010-12-01', '2010-12-01' => '2010-12-01', ], ], 'elasticfilesystem' => [ 'namespace' => 'Efs', 'versions' => [ 'latest' => '2015-02-01', '2015-02-01' => '2015-02-01', ], ], 'elasticloadbalancing' => [ 'namespace' => 'ElasticLoadBalancing', 'versions' => [ 'latest' => '2012-06-01', '2012-06-01' => '2012-06-01', ], ], 'elasticloadbalancingv2' => [ 'namespace' => 'ElasticLoadBalancingV2', 'versions' => [ 'latest' => '2015-12-01', '2015-12-01' => '2015-12-01', ], ], 'elasticmapreduce' => [ 'namespace' => 'Emr', 'versions' => [ 'latest' => '2009-03-31', '2009-03-31' => '2009-03-31', ], ], 'elastictranscoder' => [ 'namespace' => 'ElasticTranscoder', 'versions' => [ 'latest' => '2012-09-25', '2012-09-25' => '2012-09-25', ], ], 'email' => [ 'namespace' => 'Ses', 'versions' => [ 'latest' => '2010-12-01', '2010-12-01' => '2010-12-01', ], ], 'es' => [ 'namespace' => 'ElasticsearchService', 'versions' => [ 'latest' => '2015-01-01', '2015-01-01' => '2015-01-01', ], ], 'events' => [ 'namespace' => 'CloudWatchEvents', 'versions' => [ 'latest' => '2015-10-07', '2015-10-07' => '2015-10-07', '2014-02-03' => '2015-10-07', ], ], 'firehose' => [ 'namespace' => 'Firehose', 'versions' => [ 'latest' => '2015-08-04', '2015-08-04' => '2015-08-04', ], ], 'gamelift' => [ 'namespace' => 'GameLift', 'versions' => [ 'latest' => '2015-10-01', '2015-10-01' => '2015-10-01', ], ], 'glacier' => [ 'namespace' => 'Glacier', 'versions' => [ 'latest' => '2012-06-01', '2012-06-01' => '2012-06-01', ], ], 'health' => [ 'namespace' => 'Health', 'versions' => [ 'latest' => '2016-08-04', '2016-08-04' => '2016-08-04', ], ], 'iam' => [ 'namespace' => 'Iam', 'versions' => [ 'latest' => '2010-05-08', '2010-05-08' => '2010-05-08', ], ], 'importexport' => [ 'namespace' => 'ImportExport', 'versions' => [ 'latest' => '2010-06-01', '2010-06-01' => '2010-06-01', ], ], 'inspector' => [ 'namespace' => 'Inspector', 'versions' => [ 'latest' => '2016-02-16', '2016-02-16' => '2016-02-16', '2015-08-18' => '2016-02-16', ], ], 'iot' => [ 'namespace' => 'Iot', 'versions' => [ 'latest' => '2015-05-28', '2015-05-28' => '2015-05-28', ], ], 'kinesis' => [ 'namespace' => 'Kinesis', 'versions' => [ 'latest' => '2013-12-02', '2013-12-02' => '2013-12-02', ], ], 'kinesisanalytics' => [ 'namespace' => 'KinesisAnalytics', 'versions' => [ 'latest' => '2015-08-14', '2015-08-14' => '2015-08-14', ], ], 'kms' => [ 'namespace' => 'Kms', 'versions' => [ 'latest' => '2014-11-01', '2014-11-01' => '2014-11-01', ], ], 'lambda' => [ 'namespace' => 'Lambda', 'versions' => [ 'latest' => '2015-03-31', '2015-03-31' => '2015-03-31', ], ], 'lightsail' => [ 'namespace' => 'Lightsail', 'versions' => [ 'latest' => '2016-11-28', '2016-11-28' => '2016-11-28', ], ], 'logs' => [ 'namespace' => 'CloudWatchLogs', 'versions' => [ 'latest' => '2014-03-28', '2014-03-28' => '2014-03-28', ], ], 'machinelearning' => [ 'namespace' => 'MachineLearning', 'versions' => [ 'latest' => '2014-12-12', '2014-12-12' => '2014-12-12', ], ], 'marketplacecommerceanalytics' => [ 'namespace' => 'MarketplaceCommerceAnalytics', 'versions' => [ 'latest' => '2015-07-01', '2015-07-01' => '2015-07-01', ], ], 'metering.marketplace' => [ 'namespace' => 'MarketplaceMetering', 'versions' => [ 'latest' => '2016-01-14', '2016-01-14' => '2016-01-14', ], ], 'monitoring' => [ 'namespace' => 'CloudWatch', 'versions' => [ 'latest' => '2010-08-01', '2010-08-01' => '2010-08-01', ], ], 'opsworks' => [ 'namespace' => 'OpsWorks', 'versions' => [ 'latest' => '2013-02-18', '2013-02-18' => '2013-02-18', ], ], 'opsworkscm' => [ 'namespace' => 'OpsWorksCM', 'versions' => [ 'latest' => '2016-11-01', '2016-11-01' => '2016-11-01', ], ], 'pinpoint' => [ 'namespace' => 'Pinpoint', 'versions' => [ 'latest' => '2016-12-01', '2016-12-01' => '2016-12-01', ], ], 'polly' => [ 'namespace' => 'Polly', 'versions' => [ 'latest' => '2016-06-10', '2016-06-10' => '2016-06-10', ], ], 'rds' => [ 'namespace' => 'Rds', 'versions' => [ 'latest' => '2014-10-31', '2014-10-31' => '2014-10-31', '2014-09-01' => '2014-09-01', ], ], 'redshift' => [ 'namespace' => 'Redshift', 'versions' => [ 'latest' => '2012-12-01', '2012-12-01' => '2012-12-01', ], ], 'rekognition' => [ 'namespace' => 'Rekognition', 'versions' => [ 'latest' => '2016-06-27', '2016-06-27' => '2016-06-27', ], ], 'route53' => [ 'namespace' => 'Route53', 'versions' => [ 'latest' => '2013-04-01', '2013-04-01' => '2013-04-01', ], ], 'route53domains' => [ 'namespace' => 'Route53Domains', 'versions' => [ 'latest' => '2014-05-15', '2014-05-15' => '2014-05-15', ], ], 'runtime.lex' => [ 'namespace' => 'LexRuntimeService', 'versions' => [ 'latest' => '2016-11-28', '2016-11-28' => '2016-11-28', ], ], 's3' => [ 'namespace' => 'S3', 'versions' => [ 'latest' => '2006-03-01', '2006-03-01' => '2006-03-01', ], ], 'servicecatalog' => [ 'namespace' => 'ServiceCatalog', 'versions' => [ 'latest' => '2015-12-10', '2015-12-10' => '2015-12-10', ], ], 'shield' => [ 'namespace' => 'Shield', 'versions' => [ 'latest' => '2016-06-02', '2016-06-02' => '2016-06-02', ], ], 'sms' => [ 'namespace' => 'Sms', 'versions' => [ 'latest' => '2016-10-24', '2016-10-24' => '2016-10-24', ], ], 'snowball' => [ 'namespace' => 'SnowBall', 'versions' => [ 'latest' => '2016-06-30', '2016-06-30' => '2016-06-30', ], ], 'sns' => [ 'namespace' => 'Sns', 'versions' => [ 'latest' => '2010-03-31', '2010-03-31' => '2010-03-31', ], ], 'sqs' => [ 'namespace' => 'Sqs', 'versions' => [ 'latest' => '2012-11-05', '2012-11-05' => '2012-11-05', ], ], 'ssm' => [ 'namespace' => 'Ssm', 'versions' => [ 'latest' => '2014-11-06', '2014-11-06' => '2014-11-06', ], ], 'states' => [ 'namespace' => 'Sfn', 'versions' => [ 'latest' => '2016-11-23', '2016-11-23' => '2016-11-23', ], ], 'storagegateway' => [ 'namespace' => 'StorageGateway', 'versions' => [ 'latest' => '2013-06-30', '2013-06-30' => '2013-06-30', ], ], 'streams.dynamodb' => [ 'namespace' => 'DynamoDbStreams', 'versions' => [ 'latest' => '2012-08-10', '2012-08-10' => '2012-08-10', ], ], 'sts' => [ 'namespace' => 'Sts', 'versions' => [ 'latest' => '2011-06-15', '2011-06-15' => '2011-06-15', ], ], 'support' => [ 'namespace' => 'Support', 'versions' => [ 'latest' => '2013-04-15', '2013-04-15' => '2013-04-15', ], ], 'swf' => [ 'namespace' => 'Swf', 'versions' => [ 'latest' => '2012-01-25', '2012-01-25' => '2012-01-25', ], ], 'waf-regional' => [ 'namespace' => 'WafRegional', 'versions' => [ 'latest' => '2016-11-28', '2016-11-28' => '2016-11-28', ], ], 'waf' => [ 'namespace' => 'Waf', 'versions' => [ 'latest' => '2015-08-24', '2015-08-24' => '2015-08-24', ], ], 'workspaces' => [ 'namespace' => 'WorkSpaces', 'versions' => [ 'latest' => '2015-04-08', '2015-04-08' => '2015-04-08', ], ], 'xray' => [ 'namespace' => 'XRay', 'versions' => [ 'latest' => '2016-04-12', '2016-04-12' => '2016-04-12', ], ],];
<?php
// This file was auto-generated from sdk-root/src/data/s3/2006-03-01/api-2.json
return [ 'version' => '2.0', 'metadata' => [ 'apiVersion' => '2006-03-01', 'checksumFormat' => 'md5', 'endpointPrefix' => 's3', 'globalEndpoint' => 's3.amazonaws.com', 'protocol' => 'rest-xml', 'serviceAbbreviation' => 'Amazon S3', 'serviceFullName' => 'Amazon Simple Storage Service', 'signatureVersion' => 's3', 'timestampFormat' => 'rfc822', 'uid' => 's3-2006-03-01', ], 'operations' => [ 'AbortMultipartUpload' => [ 'name' => 'AbortMultipartUpload', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'AbortMultipartUploadRequest', ], 'output' => [ 'shape' => 'AbortMultipartUploadOutput', ], 'errors' => [ [ 'shape' => 'NoSuchUpload', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadAbort.html', ], 'CompleteMultipartUpload' => [ 'name' => 'CompleteMultipartUpload', 'http' => [ 'method' => 'POST', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'CompleteMultipartUploadRequest', ], 'output' => [ 'shape' => 'CompleteMultipartUploadOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadComplete.html', ], 'CopyObject' => [ 'name' => 'CopyObject', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'CopyObjectRequest', ], 'output' => [ 'shape' => 'CopyObjectOutput', ], 'errors' => [ [ 'shape' => 'ObjectNotInActiveTierError', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectCOPY.html', 'alias' => 'PutObjectCopy', ], 'CreateBucket' => [ 'name' => 'CreateBucket', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}', ], 'input' => [ 'shape' => 'CreateBucketRequest', ], 'output' => [ 'shape' => 'CreateBucketOutput', ], 'errors' => [ [ 'shape' => 'BucketAlreadyExists', ], [ 'shape' => 'BucketAlreadyOwnedByYou', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUT.html', 'alias' => 'PutBucket', ], 'CreateMultipartUpload' => [ 'name' => 'CreateMultipartUpload', 'http' => [ 'method' => 'POST', 'requestUri' => '/{Bucket}/{Key+}?uploads', ], 'input' => [ 'shape' => 'CreateMultipartUploadRequest', ], 'output' => [ 'shape' => 'CreateMultipartUploadOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadInitiate.html', 'alias' => 'InitiateMultipartUpload', ], 'DeleteBucket' => [ 'name' => 'DeleteBucket', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}', ], 'input' => [ 'shape' => 'DeleteBucketRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketDELETE.html', ], 'DeleteBucketAnalyticsConfiguration' => [ 'name' => 'DeleteBucketAnalyticsConfiguration', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?analytics', ], 'input' => [ 'shape' => 'DeleteBucketAnalyticsConfigurationRequest', ], ], 'DeleteBucketCors' => [ 'name' => 'DeleteBucketCors', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?cors', ], 'input' => [ 'shape' => 'DeleteBucketCorsRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketDELETEcors.html', ], 'DeleteBucketInventoryConfiguration' => [ 'name' => 'DeleteBucketInventoryConfiguration', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?inventory', ], 'input' => [ 'shape' => 'DeleteBucketInventoryConfigurationRequest', ], ], 'DeleteBucketLifecycle' => [ 'name' => 'DeleteBucketLifecycle', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?lifecycle', ], 'input' => [ 'shape' => 'DeleteBucketLifecycleRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketDELETElifecycle.html', ], 'DeleteBucketMetricsConfiguration' => [ 'name' => 'DeleteBucketMetricsConfiguration', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?metrics', ], 'input' => [ 'shape' => 'DeleteBucketMetricsConfigurationRequest', ], ], 'DeleteBucketPolicy' => [ 'name' => 'DeleteBucketPolicy', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?policy', ], 'input' => [ 'shape' => 'DeleteBucketPolicyRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketDELETEpolicy.html', ], 'DeleteBucketReplication' => [ 'name' => 'DeleteBucketReplication', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?replication', ], 'input' => [ 'shape' => 'DeleteBucketReplicationRequest', ], ], 'DeleteBucketTagging' => [ 'name' => 'DeleteBucketTagging', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?tagging', ], 'input' => [ 'shape' => 'DeleteBucketTaggingRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketDELETEtagging.html', ], 'DeleteBucketWebsite' => [ 'name' => 'DeleteBucketWebsite', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}?website', ], 'input' => [ 'shape' => 'DeleteBucketWebsiteRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketDELETEwebsite.html', ], 'DeleteObject' => [ 'name' => 'DeleteObject', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'DeleteObjectRequest', ], 'output' => [ 'shape' => 'DeleteObjectOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectDELETE.html', ], 'DeleteObjectTagging' => [ 'name' => 'DeleteObjectTagging', 'http' => [ 'method' => 'DELETE', 'requestUri' => '/{Bucket}/{Key+}?tagging', ], 'input' => [ 'shape' => 'DeleteObjectTaggingRequest', ], 'output' => [ 'shape' => 'DeleteObjectTaggingOutput', ], ], 'DeleteObjects' => [ 'name' => 'DeleteObjects', 'http' => [ 'method' => 'POST', 'requestUri' => '/{Bucket}?delete', ], 'input' => [ 'shape' => 'DeleteObjectsRequest', ], 'output' => [ 'shape' => 'DeleteObjectsOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/multiobjectdeleteapi.html', 'alias' => 'DeleteMultipleObjects', ], 'GetBucketAccelerateConfiguration' => [ 'name' => 'GetBucketAccelerateConfiguration', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?accelerate', ], 'input' => [ 'shape' => 'GetBucketAccelerateConfigurationRequest', ], 'output' => [ 'shape' => 'GetBucketAccelerateConfigurationOutput', ], ], 'GetBucketAcl' => [ 'name' => 'GetBucketAcl', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?acl', ], 'input' => [ 'shape' => 'GetBucketAclRequest', ], 'output' => [ 'shape' => 'GetBucketAclOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETacl.html', ], 'GetBucketAnalyticsConfiguration' => [ 'name' => 'GetBucketAnalyticsConfiguration', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?analytics', ], 'input' => [ 'shape' => 'GetBucketAnalyticsConfigurationRequest', ], 'output' => [ 'shape' => 'GetBucketAnalyticsConfigurationOutput', ], ], 'GetBucketCors' => [ 'name' => 'GetBucketCors', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?cors', ], 'input' => [ 'shape' => 'GetBucketCorsRequest', ], 'output' => [ 'shape' => 'GetBucketCorsOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETcors.html', ], 'GetBucketInventoryConfiguration' => [ 'name' => 'GetBucketInventoryConfiguration', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?inventory', ], 'input' => [ 'shape' => 'GetBucketInventoryConfigurationRequest', ], 'output' => [ 'shape' => 'GetBucketInventoryConfigurationOutput', ], ], 'GetBucketLifecycle' => [ 'name' => 'GetBucketLifecycle', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?lifecycle', ], 'input' => [ 'shape' => 'GetBucketLifecycleRequest', ], 'output' => [ 'shape' => 'GetBucketLifecycleOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETlifecycle.html', 'deprecated' => true, ], 'GetBucketLifecycleConfiguration' => [ 'name' => 'GetBucketLifecycleConfiguration', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?lifecycle', ], 'input' => [ 'shape' => 'GetBucketLifecycleConfigurationRequest', ], 'output' => [ 'shape' => 'GetBucketLifecycleConfigurationOutput', ], ], 'GetBucketLocation' => [ 'name' => 'GetBucketLocation', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?location', ], 'input' => [ 'shape' => 'GetBucketLocationRequest', ], 'output' => [ 'shape' => 'GetBucketLocationOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETlocation.html', ], 'GetBucketLogging' => [ 'name' => 'GetBucketLogging', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?logging', ], 'input' => [ 'shape' => 'GetBucketLoggingRequest', ], 'output' => [ 'shape' => 'GetBucketLoggingOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETlogging.html', ], 'GetBucketMetricsConfiguration' => [ 'name' => 'GetBucketMetricsConfiguration', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?metrics', ], 'input' => [ 'shape' => 'GetBucketMetricsConfigurationRequest', ], 'output' => [ 'shape' => 'GetBucketMetricsConfigurationOutput', ], ], 'GetBucketNotification' => [ 'name' => 'GetBucketNotification', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?notification', ], 'input' => [ 'shape' => 'GetBucketNotificationConfigurationRequest', ], 'output' => [ 'shape' => 'NotificationConfigurationDeprecated', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETnotification.html', 'deprecated' => true, ], 'GetBucketNotificationConfiguration' => [ 'name' => 'GetBucketNotificationConfiguration', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?notification', ], 'input' => [ 'shape' => 'GetBucketNotificationConfigurationRequest', ], 'output' => [ 'shape' => 'NotificationConfiguration', ], ], 'GetBucketPolicy' => [ 'name' => 'GetBucketPolicy', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?policy', ], 'input' => [ 'shape' => 'GetBucketPolicyRequest', ], 'output' => [ 'shape' => 'GetBucketPolicyOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETpolicy.html', ], 'GetBucketReplication' => [ 'name' => 'GetBucketReplication', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?replication', ], 'input' => [ 'shape' => 'GetBucketReplicationRequest', ], 'output' => [ 'shape' => 'GetBucketReplicationOutput', ], ], 'GetBucketRequestPayment' => [ 'name' => 'GetBucketRequestPayment', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?requestPayment', ], 'input' => [ 'shape' => 'GetBucketRequestPaymentRequest', ], 'output' => [ 'shape' => 'GetBucketRequestPaymentOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTrequestPaymentGET.html', ], 'GetBucketTagging' => [ 'name' => 'GetBucketTagging', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?tagging', ], 'input' => [ 'shape' => 'GetBucketTaggingRequest', ], 'output' => [ 'shape' => 'GetBucketTaggingOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETtagging.html', ], 'GetBucketVersioning' => [ 'name' => 'GetBucketVersioning', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?versioning', ], 'input' => [ 'shape' => 'GetBucketVersioningRequest', ], 'output' => [ 'shape' => 'GetBucketVersioningOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETversioningStatus.html', ], 'GetBucketWebsite' => [ 'name' => 'GetBucketWebsite', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?website', ], 'input' => [ 'shape' => 'GetBucketWebsiteRequest', ], 'output' => [ 'shape' => 'GetBucketWebsiteOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETwebsite.html', ], 'GetObject' => [ 'name' => 'GetObject', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'GetObjectRequest', ], 'output' => [ 'shape' => 'GetObjectOutput', ], 'errors' => [ [ 'shape' => 'NoSuchKey', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html', ], 'GetObjectAcl' => [ 'name' => 'GetObjectAcl', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}/{Key+}?acl', ], 'input' => [ 'shape' => 'GetObjectAclRequest', ], 'output' => [ 'shape' => 'GetObjectAclOutput', ], 'errors' => [ [ 'shape' => 'NoSuchKey', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGETacl.html', ], 'GetObjectTagging' => [ 'name' => 'GetObjectTagging', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}/{Key+}?tagging', ], 'input' => [ 'shape' => 'GetObjectTaggingRequest', ], 'output' => [ 'shape' => 'GetObjectTaggingOutput', ], ], 'GetObjectTorrent' => [ 'name' => 'GetObjectTorrent', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}/{Key+}?torrent', ], 'input' => [ 'shape' => 'GetObjectTorrentRequest', ], 'output' => [ 'shape' => 'GetObjectTorrentOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGETtorrent.html', ], 'HeadBucket' => [ 'name' => 'HeadBucket', 'http' => [ 'method' => 'HEAD', 'requestUri' => '/{Bucket}', ], 'input' => [ 'shape' => 'HeadBucketRequest', ], 'errors' => [ [ 'shape' => 'NoSuchBucket', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketHEAD.html', ], 'HeadObject' => [ 'name' => 'HeadObject', 'http' => [ 'method' => 'HEAD', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'HeadObjectRequest', ], 'output' => [ 'shape' => 'HeadObjectOutput', ], 'errors' => [ [ 'shape' => 'NoSuchKey', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectHEAD.html', ], 'ListBucketAnalyticsConfigurations' => [ 'name' => 'ListBucketAnalyticsConfigurations', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?analytics', ], 'input' => [ 'shape' => 'ListBucketAnalyticsConfigurationsRequest', ], 'output' => [ 'shape' => 'ListBucketAnalyticsConfigurationsOutput', ], ], 'ListBucketInventoryConfigurations' => [ 'name' => 'ListBucketInventoryConfigurations', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?inventory', ], 'input' => [ 'shape' => 'ListBucketInventoryConfigurationsRequest', ], 'output' => [ 'shape' => 'ListBucketInventoryConfigurationsOutput', ], ], 'ListBucketMetricsConfigurations' => [ 'name' => 'ListBucketMetricsConfigurations', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?metrics', ], 'input' => [ 'shape' => 'ListBucketMetricsConfigurationsRequest', ], 'output' => [ 'shape' => 'ListBucketMetricsConfigurationsOutput', ], ], 'ListBuckets' => [ 'name' => 'ListBuckets', 'http' => [ 'method' => 'GET', 'requestUri' => '/', ], 'output' => [ 'shape' => 'ListBucketsOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTServiceGET.html', 'alias' => 'GetService', ], 'ListMultipartUploads' => [ 'name' => 'ListMultipartUploads', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?uploads', ], 'input' => [ 'shape' => 'ListMultipartUploadsRequest', ], 'output' => [ 'shape' => 'ListMultipartUploadsOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadListMPUpload.html', ], 'ListObjectVersions' => [ 'name' => 'ListObjectVersions', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?versions', ], 'input' => [ 'shape' => 'ListObjectVersionsRequest', ], 'output' => [ 'shape' => 'ListObjectVersionsOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGETVersion.html', 'alias' => 'GetBucketObjectVersions', ], 'ListObjects' => [ 'name' => 'ListObjects', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}', ], 'input' => [ 'shape' => 'ListObjectsRequest', ], 'output' => [ 'shape' => 'ListObjectsOutput', ], 'errors' => [ [ 'shape' => 'NoSuchBucket', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGET.html', 'alias' => 'GetBucket', ], 'ListObjectsV2' => [ 'name' => 'ListObjectsV2', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}?list-type=2', ], 'input' => [ 'shape' => 'ListObjectsV2Request', ], 'output' => [ 'shape' => 'ListObjectsV2Output', ], 'errors' => [ [ 'shape' => 'NoSuchBucket', ], ], ], 'ListParts' => [ 'name' => 'ListParts', 'http' => [ 'method' => 'GET', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'ListPartsRequest', ], 'output' => [ 'shape' => 'ListPartsOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadListParts.html', ], 'PutBucketAccelerateConfiguration' => [ 'name' => 'PutBucketAccelerateConfiguration', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?accelerate', ], 'input' => [ 'shape' => 'PutBucketAccelerateConfigurationRequest', ], ], 'PutBucketAcl' => [ 'name' => 'PutBucketAcl', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?acl', ], 'input' => [ 'shape' => 'PutBucketAclRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTacl.html', ], 'PutBucketAnalyticsConfiguration' => [ 'name' => 'PutBucketAnalyticsConfiguration', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?analytics', ], 'input' => [ 'shape' => 'PutBucketAnalyticsConfigurationRequest', ], ], 'PutBucketCors' => [ 'name' => 'PutBucketCors', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?cors', ], 'input' => [ 'shape' => 'PutBucketCorsRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTcors.html', ], 'PutBucketInventoryConfiguration' => [ 'name' => 'PutBucketInventoryConfiguration', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?inventory', ], 'input' => [ 'shape' => 'PutBucketInventoryConfigurationRequest', ], ], 'PutBucketLifecycle' => [ 'name' => 'PutBucketLifecycle', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?lifecycle', ], 'input' => [ 'shape' => 'PutBucketLifecycleRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTlifecycle.html', 'deprecated' => true, ], 'PutBucketLifecycleConfiguration' => [ 'name' => 'PutBucketLifecycleConfiguration', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?lifecycle', ], 'input' => [ 'shape' => 'PutBucketLifecycleConfigurationRequest', ], ], 'PutBucketLogging' => [ 'name' => 'PutBucketLogging', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?logging', ], 'input' => [ 'shape' => 'PutBucketLoggingRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTlogging.html', ], 'PutBucketMetricsConfiguration' => [ 'name' => 'PutBucketMetricsConfiguration', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?metrics', ], 'input' => [ 'shape' => 'PutBucketMetricsConfigurationRequest', ], ], 'PutBucketNotification' => [ 'name' => 'PutBucketNotification', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?notification', ], 'input' => [ 'shape' => 'PutBucketNotificationRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTnotification.html', 'deprecated' => true, ], 'PutBucketNotificationConfiguration' => [ 'name' => 'PutBucketNotificationConfiguration', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?notification', ], 'input' => [ 'shape' => 'PutBucketNotificationConfigurationRequest', ], ], 'PutBucketPolicy' => [ 'name' => 'PutBucketPolicy', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?policy', ], 'input' => [ 'shape' => 'PutBucketPolicyRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTpolicy.html', ], 'PutBucketReplication' => [ 'name' => 'PutBucketReplication', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?replication', ], 'input' => [ 'shape' => 'PutBucketReplicationRequest', ], ], 'PutBucketRequestPayment' => [ 'name' => 'PutBucketRequestPayment', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?requestPayment', ], 'input' => [ 'shape' => 'PutBucketRequestPaymentRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTrequestPaymentPUT.html', ], 'PutBucketTagging' => [ 'name' => 'PutBucketTagging', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?tagging', ], 'input' => [ 'shape' => 'PutBucketTaggingRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTtagging.html', ], 'PutBucketVersioning' => [ 'name' => 'PutBucketVersioning', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?versioning', ], 'input' => [ 'shape' => 'PutBucketVersioningRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTVersioningStatus.html', ], 'PutBucketWebsite' => [ 'name' => 'PutBucketWebsite', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}?website', ], 'input' => [ 'shape' => 'PutBucketWebsiteRequest', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUTwebsite.html', ], 'PutObject' => [ 'name' => 'PutObject', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'PutObjectRequest', ], 'output' => [ 'shape' => 'PutObjectOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectPUT.html', ], 'PutObjectAcl' => [ 'name' => 'PutObjectAcl', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}/{Key+}?acl', ], 'input' => [ 'shape' => 'PutObjectAclRequest', ], 'output' => [ 'shape' => 'PutObjectAclOutput', ], 'errors' => [ [ 'shape' => 'NoSuchKey', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectPUTacl.html', ], 'PutObjectTagging' => [ 'name' => 'PutObjectTagging', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}/{Key+}?tagging', ], 'input' => [ 'shape' => 'PutObjectTaggingRequest', ], 'output' => [ 'shape' => 'PutObjectTaggingOutput', ], ], 'RestoreObject' => [ 'name' => 'RestoreObject', 'http' => [ 'method' => 'POST', 'requestUri' => '/{Bucket}/{Key+}?restore', ], 'input' => [ 'shape' => 'RestoreObjectRequest', ], 'output' => [ 'shape' => 'RestoreObjectOutput', ], 'errors' => [ [ 'shape' => 'ObjectAlreadyInActiveTierError', ], ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectRestore.html', 'alias' => 'PostObjectRestore', ], 'UploadPart' => [ 'name' => 'UploadPart', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'UploadPartRequest', ], 'output' => [ 'shape' => 'UploadPartOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadUploadPart.html', ], 'UploadPartCopy' => [ 'name' => 'UploadPartCopy', 'http' => [ 'method' => 'PUT', 'requestUri' => '/{Bucket}/{Key+}', ], 'input' => [ 'shape' => 'UploadPartCopyRequest', ], 'output' => [ 'shape' => 'UploadPartCopyOutput', ], 'documentationUrl' => 'http://docs.amazonwebservices.com/AmazonS3/latest/API/mpUploadUploadPartCopy.html', ], ], 'shapes' => [ 'AbortDate' => [ 'type' => 'timestamp', ], 'AbortIncompleteMultipartUpload' => [ 'type' => 'structure', 'members' => [ 'DaysAfterInitiation' => [ 'shape' => 'DaysAfterInitiation', ], ], ], 'AbortMultipartUploadOutput' => [ 'type' => 'structure', 'members' => [ 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'AbortMultipartUploadRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', 'UploadId', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'UploadId' => [ 'shape' => 'MultipartUploadId', 'location' => 'querystring', 'locationName' => 'uploadId', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'AbortRuleId' => [ 'type' => 'string', ], 'AccelerateConfiguration' => [ 'type' => 'structure', 'members' => [ 'Status' => [ 'shape' => 'BucketAccelerateStatus', ], ], ], 'AcceptRanges' => [ 'type' => 'string', ], 'AccessControlPolicy' => [ 'type' => 'structure', 'members' => [ 'Grants' => [ 'shape' => 'Grants', 'locationName' => 'AccessControlList', ], 'Owner' => [ 'shape' => 'Owner', ], ], ], 'AccountId' => [ 'type' => 'string', ], 'AllowedHeader' => [ 'type' => 'string', ], 'AllowedHeaders' => [ 'type' => 'list', 'member' => [ 'shape' => 'AllowedHeader', ], 'flattened' => true, ], 'AllowedMethod' => [ 'type' => 'string', ], 'AllowedMethods' => [ 'type' => 'list', 'member' => [ 'shape' => 'AllowedMethod', ], 'flattened' => true, ], 'AllowedOrigin' => [ 'type' => 'string', ], 'AllowedOrigins' => [ 'type' => 'list', 'member' => [ 'shape' => 'AllowedOrigin', ], 'flattened' => true, ], 'AnalyticsAndOperator' => [ 'type' => 'structure', 'members' => [ 'Prefix' => [ 'shape' => 'Prefix', ], 'Tags' => [ 'shape' => 'TagSet', 'flattened' => true, 'locationName' => 'Tag', ], ], ], 'AnalyticsConfiguration' => [ 'type' => 'structure', 'required' => [ 'Id', 'StorageClassAnalysis', ], 'members' => [ 'Id' => [ 'shape' => 'AnalyticsId', ], 'Filter' => [ 'shape' => 'AnalyticsFilter', ], 'StorageClassAnalysis' => [ 'shape' => 'StorageClassAnalysis', ], ], ], 'AnalyticsConfigurationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'AnalyticsConfiguration', ], 'flattened' => true, ], 'AnalyticsExportDestination' => [ 'type' => 'structure', 'required' => [ 'S3BucketDestination', ], 'members' => [ 'S3BucketDestination' => [ 'shape' => 'AnalyticsS3BucketDestination', ], ], ], 'AnalyticsFilter' => [ 'type' => 'structure', 'members' => [ 'Prefix' => [ 'shape' => 'Prefix', ], 'Tag' => [ 'shape' => 'Tag', ], 'And' => [ 'shape' => 'AnalyticsAndOperator', ], ], ], 'AnalyticsId' => [ 'type' => 'string', ], 'AnalyticsS3BucketDestination' => [ 'type' => 'structure', 'required' => [ 'Format', 'Bucket', ], 'members' => [ 'Format' => [ 'shape' => 'AnalyticsS3ExportFileFormat', ], 'BucketAccountId' => [ 'shape' => 'AccountId', ], 'Bucket' => [ 'shape' => 'BucketName', ], 'Prefix' => [ 'shape' => 'Prefix', ], ], ], 'AnalyticsS3ExportFileFormat' => [ 'type' => 'string', 'enum' => [ 'CSV', ], ], 'Body' => [ 'type' => 'blob', ], 'Bucket' => [ 'type' => 'structure', 'members' => [ 'Name' => [ 'shape' => 'BucketName', ], 'CreationDate' => [ 'shape' => 'CreationDate', ], ], ], 'BucketAccelerateStatus' => [ 'type' => 'string', 'enum' => [ 'Enabled', 'Suspended', ], ], 'BucketAlreadyExists' => [ 'type' => 'structure', 'members' => [], 'exception' => true, ], 'BucketAlreadyOwnedByYou' => [ 'type' => 'structure', 'members' => [], 'exception' => true, ], 'BucketCannedACL' => [ 'type' => 'string', 'enum' => [ 'private', 'public-read', 'public-read-write', 'authenticated-read', ], ], 'BucketLifecycleConfiguration' => [ 'type' => 'structure', 'required' => [ 'Rules', ], 'members' => [ 'Rules' => [ 'shape' => 'LifecycleRules', 'locationName' => 'Rule', ], ], ], 'BucketLocationConstraint' => [ 'type' => 'string', 'enum' => [ 'EU', 'eu-west-1', 'us-west-1', 'us-west-2', 'ap-south-1', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'sa-east-1', 'cn-north-1', 'eu-central-1', ], ], 'BucketLoggingStatus' => [ 'type' => 'structure', 'members' => [ 'LoggingEnabled' => [ 'shape' => 'LoggingEnabled', ], ], ], 'BucketLogsPermission' => [ 'type' => 'string', 'enum' => [ 'FULL_CONTROL', 'READ', 'WRITE', ], ], 'BucketName' => [ 'type' => 'string', ], 'BucketVersioningStatus' => [ 'type' => 'string', 'enum' => [ 'Enabled', 'Suspended', ], ], 'Buckets' => [ 'type' => 'list', 'member' => [ 'shape' => 'Bucket', 'locationName' => 'Bucket', ], ], 'CORSConfiguration' => [ 'type' => 'structure', 'required' => [ 'CORSRules', ], 'members' => [ 'CORSRules' => [ 'shape' => 'CORSRules', 'locationName' => 'CORSRule', ], ], ], 'CORSRule' => [ 'type' => 'structure', 'required' => [ 'AllowedMethods', 'AllowedOrigins', ], 'members' => [ 'AllowedHeaders' => [ 'shape' => 'AllowedHeaders', 'locationName' => 'AllowedHeader', ], 'AllowedMethods' => [ 'shape' => 'AllowedMethods', 'locationName' => 'AllowedMethod', ], 'AllowedOrigins' => [ 'shape' => 'AllowedOrigins', 'locationName' => 'AllowedOrigin', ], 'ExposeHeaders' => [ 'shape' => 'ExposeHeaders', 'locationName' => 'ExposeHeader', ], 'MaxAgeSeconds' => [ 'shape' => 'MaxAgeSeconds', ], ], ], 'CORSRules' => [ 'type' => 'list', 'member' => [ 'shape' => 'CORSRule', ], 'flattened' => true, ], 'CacheControl' => [ 'type' => 'string', ], 'CloudFunction' => [ 'type' => 'string', ], 'CloudFunctionConfiguration' => [ 'type' => 'structure', 'members' => [ 'Id' => [ 'shape' => 'NotificationId', ], 'Event' => [ 'shape' => 'Event', 'deprecated' => true, ], 'Events' => [ 'shape' => 'EventList', 'locationName' => 'Event', ], 'CloudFunction' => [ 'shape' => 'CloudFunction', ], 'InvocationRole' => [ 'shape' => 'CloudFunctionInvocationRole', ], ], ], 'CloudFunctionInvocationRole' => [ 'type' => 'string', ], 'Code' => [ 'type' => 'string', ], 'CommonPrefix' => [ 'type' => 'structure', 'members' => [ 'Prefix' => [ 'shape' => 'Prefix', ], ], ], 'CommonPrefixList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CommonPrefix', ], 'flattened' => true, ], 'CompleteMultipartUploadOutput' => [ 'type' => 'structure', 'members' => [ 'Location' => [ 'shape' => 'Location', ], 'Bucket' => [ 'shape' => 'BucketName', ], 'Key' => [ 'shape' => 'ObjectKey', ], 'Expiration' => [ 'shape' => 'Expiration', 'location' => 'header', 'locationName' => 'x-amz-expiration', ], 'ETag' => [ 'shape' => 'ETag', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'CompleteMultipartUploadRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', 'UploadId', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'MultipartUpload' => [ 'shape' => 'CompletedMultipartUpload', 'locationName' => 'CompleteMultipartUpload', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], 'UploadId' => [ 'shape' => 'MultipartUploadId', 'location' => 'querystring', 'locationName' => 'uploadId', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], 'payload' => 'MultipartUpload', ], 'CompletedMultipartUpload' => [ 'type' => 'structure', 'members' => [ 'Parts' => [ 'shape' => 'CompletedPartList', 'locationName' => 'Part', ], ], ], 'CompletedPart' => [ 'type' => 'structure', 'members' => [ 'ETag' => [ 'shape' => 'ETag', ], 'PartNumber' => [ 'shape' => 'PartNumber', ], ], ], 'CompletedPartList' => [ 'type' => 'list', 'member' => [ 'shape' => 'CompletedPart', ], 'flattened' => true, ], 'Condition' => [ 'type' => 'structure', 'members' => [ 'HttpErrorCodeReturnedEquals' => [ 'shape' => 'HttpErrorCodeReturnedEquals', ], 'KeyPrefixEquals' => [ 'shape' => 'KeyPrefixEquals', ], ], ], 'ContentDisposition' => [ 'type' => 'string', ], 'ContentEncoding' => [ 'type' => 'string', ], 'ContentLanguage' => [ 'type' => 'string', ], 'ContentLength' => [ 'type' => 'long', ], 'ContentMD5' => [ 'type' => 'string', ], 'ContentRange' => [ 'type' => 'string', ], 'ContentType' => [ 'type' => 'string', ], 'CopyObjectOutput' => [ 'type' => 'structure', 'members' => [ 'CopyObjectResult' => [ 'shape' => 'CopyObjectResult', ], 'Expiration' => [ 'shape' => 'Expiration', 'location' => 'header', 'locationName' => 'x-amz-expiration', ], 'CopySourceVersionId' => [ 'shape' => 'CopySourceVersionId', 'location' => 'header', 'locationName' => 'x-amz-copy-source-version-id', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], 'payload' => 'CopyObjectResult', ], 'CopyObjectRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'CopySource', 'Key', ], 'members' => [ 'ACL' => [ 'shape' => 'ObjectCannedACL', 'location' => 'header', 'locationName' => 'x-amz-acl', ], 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'CacheControl' => [ 'shape' => 'CacheControl', 'location' => 'header', 'locationName' => 'Cache-Control', ], 'ContentDisposition' => [ 'shape' => 'ContentDisposition', 'location' => 'header', 'locationName' => 'Content-Disposition', ], 'ContentEncoding' => [ 'shape' => 'ContentEncoding', 'location' => 'header', 'locationName' => 'Content-Encoding', ], 'ContentLanguage' => [ 'shape' => 'ContentLanguage', 'location' => 'header', 'locationName' => 'Content-Language', ], 'ContentType' => [ 'shape' => 'ContentType', 'location' => 'header', 'locationName' => 'Content-Type', ], 'CopySource' => [ 'shape' => 'CopySource', 'location' => 'header', 'locationName' => 'x-amz-copy-source', ], 'CopySourceIfMatch' => [ 'shape' => 'CopySourceIfMatch', 'location' => 'header', 'locationName' => 'x-amz-copy-source-if-match', ], 'CopySourceIfModifiedSince' => [ 'shape' => 'CopySourceIfModifiedSince', 'location' => 'header', 'locationName' => 'x-amz-copy-source-if-modified-since', ], 'CopySourceIfNoneMatch' => [ 'shape' => 'CopySourceIfNoneMatch', 'location' => 'header', 'locationName' => 'x-amz-copy-source-if-none-match', ], 'CopySourceIfUnmodifiedSince' => [ 'shape' => 'CopySourceIfUnmodifiedSince', 'location' => 'header', 'locationName' => 'x-amz-copy-source-if-unmodified-since', ], 'Expires' => [ 'shape' => 'Expires', 'location' => 'header', 'locationName' => 'Expires', ], 'GrantFullControl' => [ 'shape' => 'GrantFullControl', 'location' => 'header', 'locationName' => 'x-amz-grant-full-control', ], 'GrantRead' => [ 'shape' => 'GrantRead', 'location' => 'header', 'locationName' => 'x-amz-grant-read', ], 'GrantReadACP' => [ 'shape' => 'GrantReadACP', 'location' => 'header', 'locationName' => 'x-amz-grant-read-acp', ], 'GrantWriteACP' => [ 'shape' => 'GrantWriteACP', 'location' => 'header', 'locationName' => 'x-amz-grant-write-acp', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'Metadata' => [ 'shape' => 'Metadata', 'location' => 'headers', 'locationName' => 'x-amz-meta-', ], 'MetadataDirective' => [ 'shape' => 'MetadataDirective', 'location' => 'header', 'locationName' => 'x-amz-metadata-directive', ], 'TaggingDirective' => [ 'shape' => 'TaggingDirective', 'location' => 'header', 'locationName' => 'x-amz-tagging-directive', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'StorageClass' => [ 'shape' => 'StorageClass', 'location' => 'header', 'locationName' => 'x-amz-storage-class', ], 'WebsiteRedirectLocation' => [ 'shape' => 'WebsiteRedirectLocation', 'location' => 'header', 'locationName' => 'x-amz-website-redirect-location', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKey' => [ 'shape' => 'SSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'CopySourceSSECustomerAlgorithm' => [ 'shape' => 'CopySourceSSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-copy-source-server-side-encryption-customer-algorithm', ], 'CopySourceSSECustomerKey' => [ 'shape' => 'CopySourceSSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-copy-source-server-side-encryption-customer-key', ], 'CopySourceSSECustomerKeyMD5' => [ 'shape' => 'CopySourceSSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-copy-source-server-side-encryption-customer-key-MD5', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], 'Tagging' => [ 'shape' => 'TaggingHeader', 'location' => 'header', 'locationName' => 'x-amz-tagging', ], ], ], 'CopyObjectResult' => [ 'type' => 'structure', 'members' => [ 'ETag' => [ 'shape' => 'ETag', ], 'LastModified' => [ 'shape' => 'LastModified', ], ], ], 'CopyPartResult' => [ 'type' => 'structure', 'members' => [ 'ETag' => [ 'shape' => 'ETag', ], 'LastModified' => [ 'shape' => 'LastModified', ], ], ], 'CopySource' => [ 'type' => 'string', 'pattern' => '\\/.+\\/.+', ], 'CopySourceIfMatch' => [ 'type' => 'string', ], 'CopySourceIfModifiedSince' => [ 'type' => 'timestamp', ], 'CopySourceIfNoneMatch' => [ 'type' => 'string', ], 'CopySourceIfUnmodifiedSince' => [ 'type' => 'timestamp', ], 'CopySourceRange' => [ 'type' => 'string', ], 'CopySourceSSECustomerAlgorithm' => [ 'type' => 'string', ], 'CopySourceSSECustomerKey' => [ 'type' => 'string', 'sensitive' => true, ], 'CopySourceSSECustomerKeyMD5' => [ 'type' => 'string', ], 'CopySourceVersionId' => [ 'type' => 'string', ], 'CreateBucketConfiguration' => [ 'type' => 'structure', 'members' => [ 'LocationConstraint' => [ 'shape' => 'BucketLocationConstraint', ], ], ], 'CreateBucketOutput' => [ 'type' => 'structure', 'members' => [ 'Location' => [ 'shape' => 'Location', 'location' => 'header', 'locationName' => 'Location', ], ], ], 'CreateBucketRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'ACL' => [ 'shape' => 'BucketCannedACL', 'location' => 'header', 'locationName' => 'x-amz-acl', ], 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'CreateBucketConfiguration' => [ 'shape' => 'CreateBucketConfiguration', 'locationName' => 'CreateBucketConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], 'GrantFullControl' => [ 'shape' => 'GrantFullControl', 'location' => 'header', 'locationName' => 'x-amz-grant-full-control', ], 'GrantRead' => [ 'shape' => 'GrantRead', 'location' => 'header', 'locationName' => 'x-amz-grant-read', ], 'GrantReadACP' => [ 'shape' => 'GrantReadACP', 'location' => 'header', 'locationName' => 'x-amz-grant-read-acp', ], 'GrantWrite' => [ 'shape' => 'GrantWrite', 'location' => 'header', 'locationName' => 'x-amz-grant-write', ], 'GrantWriteACP' => [ 'shape' => 'GrantWriteACP', 'location' => 'header', 'locationName' => 'x-amz-grant-write-acp', ], ], 'payload' => 'CreateBucketConfiguration', ], 'CreateMultipartUploadOutput' => [ 'type' => 'structure', 'members' => [ 'AbortDate' => [ 'shape' => 'AbortDate', 'location' => 'header', 'locationName' => 'x-amz-abort-date', ], 'AbortRuleId' => [ 'shape' => 'AbortRuleId', 'location' => 'header', 'locationName' => 'x-amz-abort-rule-id', ], 'Bucket' => [ 'shape' => 'BucketName', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', ], 'UploadId' => [ 'shape' => 'MultipartUploadId', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'CreateMultipartUploadRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'ACL' => [ 'shape' => 'ObjectCannedACL', 'location' => 'header', 'locationName' => 'x-amz-acl', ], 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'CacheControl' => [ 'shape' => 'CacheControl', 'location' => 'header', 'locationName' => 'Cache-Control', ], 'ContentDisposition' => [ 'shape' => 'ContentDisposition', 'location' => 'header', 'locationName' => 'Content-Disposition', ], 'ContentEncoding' => [ 'shape' => 'ContentEncoding', 'location' => 'header', 'locationName' => 'Content-Encoding', ], 'ContentLanguage' => [ 'shape' => 'ContentLanguage', 'location' => 'header', 'locationName' => 'Content-Language', ], 'ContentType' => [ 'shape' => 'ContentType', 'location' => 'header', 'locationName' => 'Content-Type', ], 'Expires' => [ 'shape' => 'Expires', 'location' => 'header', 'locationName' => 'Expires', ], 'GrantFullControl' => [ 'shape' => 'GrantFullControl', 'location' => 'header', 'locationName' => 'x-amz-grant-full-control', ], 'GrantRead' => [ 'shape' => 'GrantRead', 'location' => 'header', 'locationName' => 'x-amz-grant-read', ], 'GrantReadACP' => [ 'shape' => 'GrantReadACP', 'location' => 'header', 'locationName' => 'x-amz-grant-read-acp', ], 'GrantWriteACP' => [ 'shape' => 'GrantWriteACP', 'location' => 'header', 'locationName' => 'x-amz-grant-write-acp', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'Metadata' => [ 'shape' => 'Metadata', 'location' => 'headers', 'locationName' => 'x-amz-meta-', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'StorageClass' => [ 'shape' => 'StorageClass', 'location' => 'header', 'locationName' => 'x-amz-storage-class', ], 'WebsiteRedirectLocation' => [ 'shape' => 'WebsiteRedirectLocation', 'location' => 'header', 'locationName' => 'x-amz-website-redirect-location', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKey' => [ 'shape' => 'SSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'CreationDate' => [ 'type' => 'timestamp', ], 'Date' => [ 'type' => 'timestamp', 'timestampFormat' => 'iso8601', ], 'Days' => [ 'type' => 'integer', ], 'DaysAfterInitiation' => [ 'type' => 'integer', ], 'Delete' => [ 'type' => 'structure', 'required' => [ 'Objects', ], 'members' => [ 'Objects' => [ 'shape' => 'ObjectIdentifierList', 'locationName' => 'Object', ], 'Quiet' => [ 'shape' => 'Quiet', ], ], ], 'DeleteBucketAnalyticsConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'AnalyticsId', 'location' => 'querystring', 'locationName' => 'id', ], ], ], 'DeleteBucketCorsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'DeleteBucketInventoryConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'InventoryId', 'location' => 'querystring', 'locationName' => 'id', ], ], ], 'DeleteBucketLifecycleRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'DeleteBucketMetricsConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'MetricsId', 'location' => 'querystring', 'locationName' => 'id', ], ], ], 'DeleteBucketPolicyRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'DeleteBucketReplicationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'DeleteBucketRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'DeleteBucketTaggingRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'DeleteBucketWebsiteRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'DeleteMarker' => [ 'type' => 'boolean', ], 'DeleteMarkerEntry' => [ 'type' => 'structure', 'members' => [ 'Owner' => [ 'shape' => 'Owner', ], 'Key' => [ 'shape' => 'ObjectKey', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', ], 'IsLatest' => [ 'shape' => 'IsLatest', ], 'LastModified' => [ 'shape' => 'LastModified', ], ], ], 'DeleteMarkerVersionId' => [ 'type' => 'string', ], 'DeleteMarkers' => [ 'type' => 'list', 'member' => [ 'shape' => 'DeleteMarkerEntry', ], 'flattened' => true, ], 'DeleteObjectOutput' => [ 'type' => 'structure', 'members' => [ 'DeleteMarker' => [ 'shape' => 'DeleteMarker', 'location' => 'header', 'locationName' => 'x-amz-delete-marker', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'DeleteObjectRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'MFA' => [ 'shape' => 'MFA', 'location' => 'header', 'locationName' => 'x-amz-mfa', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'DeleteObjectTaggingOutput' => [ 'type' => 'structure', 'members' => [ 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], ], ], 'DeleteObjectTaggingRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], ], ], 'DeleteObjectsOutput' => [ 'type' => 'structure', 'members' => [ 'Deleted' => [ 'shape' => 'DeletedObjects', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], 'Errors' => [ 'shape' => 'Errors', 'locationName' => 'Error', ], ], ], 'DeleteObjectsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Delete', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Delete' => [ 'shape' => 'Delete', 'locationName' => 'Delete', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], 'MFA' => [ 'shape' => 'MFA', 'location' => 'header', 'locationName' => 'x-amz-mfa', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], 'payload' => 'Delete', ], 'DeletedObject' => [ 'type' => 'structure', 'members' => [ 'Key' => [ 'shape' => 'ObjectKey', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', ], 'DeleteMarker' => [ 'shape' => 'DeleteMarker', ], 'DeleteMarkerVersionId' => [ 'shape' => 'DeleteMarkerVersionId', ], ], ], 'DeletedObjects' => [ 'type' => 'list', 'member' => [ 'shape' => 'DeletedObject', ], 'flattened' => true, ], 'Delimiter' => [ 'type' => 'string', ], 'Destination' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', ], 'StorageClass' => [ 'shape' => 'StorageClass', ], ], ], 'DisplayName' => [ 'type' => 'string', ], 'ETag' => [ 'type' => 'string', ], 'EmailAddress' => [ 'type' => 'string', ], 'EncodingType' => [ 'type' => 'string', 'enum' => [ 'url', ], ], 'Error' => [ 'type' => 'structure', 'members' => [ 'Key' => [ 'shape' => 'ObjectKey', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', ], 'Code' => [ 'shape' => 'Code', ], 'Message' => [ 'shape' => 'Message', ], ], ], 'ErrorDocument' => [ 'type' => 'structure', 'required' => [ 'Key', ], 'members' => [ 'Key' => [ 'shape' => 'ObjectKey', ], ], ], 'Errors' => [ 'type' => 'list', 'member' => [ 'shape' => 'Error', ], 'flattened' => true, ], 'Event' => [ 'type' => 'string', 'enum' => [ 's3:ReducedRedundancyLostObject', 's3:ObjectCreated:*', 's3:ObjectCreated:Put', 's3:ObjectCreated:Post', 's3:ObjectCreated:Copy', 's3:ObjectCreated:CompleteMultipartUpload', 's3:ObjectRemoved:*', 's3:ObjectRemoved:Delete', 's3:ObjectRemoved:DeleteMarkerCreated', ], ], 'EventList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Event', ], 'flattened' => true, ], 'Expiration' => [ 'type' => 'string', ], 'ExpirationStatus' => [ 'type' => 'string', 'enum' => [ 'Enabled', 'Disabled', ], ], 'ExpiredObjectDeleteMarker' => [ 'type' => 'boolean', ], 'Expires' => [ 'type' => 'timestamp', ], 'ExposeHeader' => [ 'type' => 'string', ], 'ExposeHeaders' => [ 'type' => 'list', 'member' => [ 'shape' => 'ExposeHeader', ], 'flattened' => true, ], 'FetchOwner' => [ 'type' => 'boolean', ], 'FilterRule' => [ 'type' => 'structure', 'members' => [ 'Name' => [ 'shape' => 'FilterRuleName', ], 'Value' => [ 'shape' => 'FilterRuleValue', ], ], ], 'FilterRuleList' => [ 'type' => 'list', 'member' => [ 'shape' => 'FilterRule', ], 'flattened' => true, ], 'FilterRuleName' => [ 'type' => 'string', 'enum' => [ 'prefix', 'suffix', ], ], 'FilterRuleValue' => [ 'type' => 'string', ], 'GetBucketAccelerateConfigurationOutput' => [ 'type' => 'structure', 'members' => [ 'Status' => [ 'shape' => 'BucketAccelerateStatus', ], ], ], 'GetBucketAccelerateConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketAclOutput' => [ 'type' => 'structure', 'members' => [ 'Owner' => [ 'shape' => 'Owner', ], 'Grants' => [ 'shape' => 'Grants', 'locationName' => 'AccessControlList', ], ], ], 'GetBucketAclRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketAnalyticsConfigurationOutput' => [ 'type' => 'structure', 'members' => [ 'AnalyticsConfiguration' => [ 'shape' => 'AnalyticsConfiguration', ], ], 'payload' => 'AnalyticsConfiguration', ], 'GetBucketAnalyticsConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'AnalyticsId', 'location' => 'querystring', 'locationName' => 'id', ], ], ], 'GetBucketCorsOutput' => [ 'type' => 'structure', 'members' => [ 'CORSRules' => [ 'shape' => 'CORSRules', 'locationName' => 'CORSRule', ], ], ], 'GetBucketCorsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketInventoryConfigurationOutput' => [ 'type' => 'structure', 'members' => [ 'InventoryConfiguration' => [ 'shape' => 'InventoryConfiguration', ], ], 'payload' => 'InventoryConfiguration', ], 'GetBucketInventoryConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'InventoryId', 'location' => 'querystring', 'locationName' => 'id', ], ], ], 'GetBucketLifecycleConfigurationOutput' => [ 'type' => 'structure', 'members' => [ 'Rules' => [ 'shape' => 'LifecycleRules', 'locationName' => 'Rule', ], ], ], 'GetBucketLifecycleConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketLifecycleOutput' => [ 'type' => 'structure', 'members' => [ 'Rules' => [ 'shape' => 'Rules', 'locationName' => 'Rule', ], ], ], 'GetBucketLifecycleRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketLocationOutput' => [ 'type' => 'structure', 'members' => [ 'LocationConstraint' => [ 'shape' => 'BucketLocationConstraint', ], ], ], 'GetBucketLocationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketLoggingOutput' => [ 'type' => 'structure', 'members' => [ 'LoggingEnabled' => [ 'shape' => 'LoggingEnabled', ], ], ], 'GetBucketLoggingRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketMetricsConfigurationOutput' => [ 'type' => 'structure', 'members' => [ 'MetricsConfiguration' => [ 'shape' => 'MetricsConfiguration', ], ], 'payload' => 'MetricsConfiguration', ], 'GetBucketMetricsConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'MetricsId', 'location' => 'querystring', 'locationName' => 'id', ], ], ], 'GetBucketNotificationConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketPolicyOutput' => [ 'type' => 'structure', 'members' => [ 'Policy' => [ 'shape' => 'Policy', ], ], 'payload' => 'Policy', ], 'GetBucketPolicyRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketReplicationOutput' => [ 'type' => 'structure', 'members' => [ 'ReplicationConfiguration' => [ 'shape' => 'ReplicationConfiguration', ], ], 'payload' => 'ReplicationConfiguration', ], 'GetBucketReplicationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketRequestPaymentOutput' => [ 'type' => 'structure', 'members' => [ 'Payer' => [ 'shape' => 'Payer', ], ], ], 'GetBucketRequestPaymentRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketTaggingOutput' => [ 'type' => 'structure', 'required' => [ 'TagSet', ], 'members' => [ 'TagSet' => [ 'shape' => 'TagSet', ], ], ], 'GetBucketTaggingRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketVersioningOutput' => [ 'type' => 'structure', 'members' => [ 'Status' => [ 'shape' => 'BucketVersioningStatus', ], 'MFADelete' => [ 'shape' => 'MFADeleteStatus', 'locationName' => 'MfaDelete', ], ], ], 'GetBucketVersioningRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetBucketWebsiteOutput' => [ 'type' => 'structure', 'members' => [ 'RedirectAllRequestsTo' => [ 'shape' => 'RedirectAllRequestsTo', ], 'IndexDocument' => [ 'shape' => 'IndexDocument', ], 'ErrorDocument' => [ 'shape' => 'ErrorDocument', ], 'RoutingRules' => [ 'shape' => 'RoutingRules', ], ], ], 'GetBucketWebsiteRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'GetObjectAclOutput' => [ 'type' => 'structure', 'members' => [ 'Owner' => [ 'shape' => 'Owner', ], 'Grants' => [ 'shape' => 'Grants', 'locationName' => 'AccessControlList', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'GetObjectAclRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'GetObjectOutput' => [ 'type' => 'structure', 'members' => [ 'Body' => [ 'shape' => 'Body', 'streaming' => true, ], 'DeleteMarker' => [ 'shape' => 'DeleteMarker', 'location' => 'header', 'locationName' => 'x-amz-delete-marker', ], 'AcceptRanges' => [ 'shape' => 'AcceptRanges', 'location' => 'header', 'locationName' => 'accept-ranges', ], 'Expiration' => [ 'shape' => 'Expiration', 'location' => 'header', 'locationName' => 'x-amz-expiration', ], 'Restore' => [ 'shape' => 'Restore', 'location' => 'header', 'locationName' => 'x-amz-restore', ], 'LastModified' => [ 'shape' => 'LastModified', 'location' => 'header', 'locationName' => 'Last-Modified', ], 'ContentLength' => [ 'shape' => 'ContentLength', 'location' => 'header', 'locationName' => 'Content-Length', ], 'ETag' => [ 'shape' => 'ETag', 'location' => 'header', 'locationName' => 'ETag', ], 'MissingMeta' => [ 'shape' => 'MissingMeta', 'location' => 'header', 'locationName' => 'x-amz-missing-meta', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], 'CacheControl' => [ 'shape' => 'CacheControl', 'location' => 'header', 'locationName' => 'Cache-Control', ], 'ContentDisposition' => [ 'shape' => 'ContentDisposition', 'location' => 'header', 'locationName' => 'Content-Disposition', ], 'ContentEncoding' => [ 'shape' => 'ContentEncoding', 'location' => 'header', 'locationName' => 'Content-Encoding', ], 'ContentLanguage' => [ 'shape' => 'ContentLanguage', 'location' => 'header', 'locationName' => 'Content-Language', ], 'ContentRange' => [ 'shape' => 'ContentRange', 'location' => 'header', 'locationName' => 'Content-Range', ], 'ContentType' => [ 'shape' => 'ContentType', 'location' => 'header', 'locationName' => 'Content-Type', ], 'Expires' => [ 'shape' => 'Expires', 'location' => 'header', 'locationName' => 'Expires', ], 'WebsiteRedirectLocation' => [ 'shape' => 'WebsiteRedirectLocation', 'location' => 'header', 'locationName' => 'x-amz-website-redirect-location', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'Metadata' => [ 'shape' => 'Metadata', 'location' => 'headers', 'locationName' => 'x-amz-meta-', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'StorageClass' => [ 'shape' => 'StorageClass', 'location' => 'header', 'locationName' => 'x-amz-storage-class', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], 'ReplicationStatus' => [ 'shape' => 'ReplicationStatus', 'location' => 'header', 'locationName' => 'x-amz-replication-status', ], 'PartsCount' => [ 'shape' => 'PartsCount', 'location' => 'header', 'locationName' => 'x-amz-mp-parts-count', ], 'TagCount' => [ 'shape' => 'TagCount', 'location' => 'header', 'locationName' => 'x-amz-tagging-count', ], ], 'payload' => 'Body', ], 'GetObjectRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'IfMatch' => [ 'shape' => 'IfMatch', 'location' => 'header', 'locationName' => 'If-Match', ], 'IfModifiedSince' => [ 'shape' => 'IfModifiedSince', 'location' => 'header', 'locationName' => 'If-Modified-Since', ], 'IfNoneMatch' => [ 'shape' => 'IfNoneMatch', 'location' => 'header', 'locationName' => 'If-None-Match', ], 'IfUnmodifiedSince' => [ 'shape' => 'IfUnmodifiedSince', 'location' => 'header', 'locationName' => 'If-Unmodified-Since', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'Range' => [ 'shape' => 'Range', 'location' => 'header', 'locationName' => 'Range', ], 'ResponseCacheControl' => [ 'shape' => 'ResponseCacheControl', 'location' => 'querystring', 'locationName' => 'response-cache-control', ], 'ResponseContentDisposition' => [ 'shape' => 'ResponseContentDisposition', 'location' => 'querystring', 'locationName' => 'response-content-disposition', ], 'ResponseContentEncoding' => [ 'shape' => 'ResponseContentEncoding', 'location' => 'querystring', 'locationName' => 'response-content-encoding', ], 'ResponseContentLanguage' => [ 'shape' => 'ResponseContentLanguage', 'location' => 'querystring', 'locationName' => 'response-content-language', ], 'ResponseContentType' => [ 'shape' => 'ResponseContentType', 'location' => 'querystring', 'locationName' => 'response-content-type', ], 'ResponseExpires' => [ 'shape' => 'ResponseExpires', 'location' => 'querystring', 'locationName' => 'response-expires', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKey' => [ 'shape' => 'SSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], 'PartNumber' => [ 'shape' => 'PartNumber', 'location' => 'querystring', 'locationName' => 'partNumber', ], ], ], 'GetObjectTaggingOutput' => [ 'type' => 'structure', 'required' => [ 'TagSet', ], 'members' => [ 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], 'TagSet' => [ 'shape' => 'TagSet', ], ], ], 'GetObjectTaggingRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], ], ], 'GetObjectTorrentOutput' => [ 'type' => 'structure', 'members' => [ 'Body' => [ 'shape' => 'Body', 'streaming' => true, ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], 'payload' => 'Body', ], 'GetObjectTorrentRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'GlacierJobParameters' => [ 'type' => 'structure', 'required' => [ 'Tier', ], 'members' => [ 'Tier' => [ 'shape' => 'Tier', ], ], ], 'Grant' => [ 'type' => 'structure', 'members' => [ 'Grantee' => [ 'shape' => 'Grantee', ], 'Permission' => [ 'shape' => 'Permission', ], ], ], 'GrantFullControl' => [ 'type' => 'string', ], 'GrantRead' => [ 'type' => 'string', ], 'GrantReadACP' => [ 'type' => 'string', ], 'GrantWrite' => [ 'type' => 'string', ], 'GrantWriteACP' => [ 'type' => 'string', ], 'Grantee' => [ 'type' => 'structure', 'required' => [ 'Type', ], 'members' => [ 'DisplayName' => [ 'shape' => 'DisplayName', ], 'EmailAddress' => [ 'shape' => 'EmailAddress', ], 'ID' => [ 'shape' => 'ID', ], 'Type' => [ 'shape' => 'Type', 'locationName' => 'xsi:type', 'xmlAttribute' => true, ], 'URI' => [ 'shape' => 'URI', ], ], 'xmlNamespace' => [ 'prefix' => 'xsi', 'uri' => 'http://www.w3.org/2001/XMLSchema-instance', ], ], 'Grants' => [ 'type' => 'list', 'member' => [ 'shape' => 'Grant', 'locationName' => 'Grant', ], ], 'HeadBucketRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], ], ], 'HeadObjectOutput' => [ 'type' => 'structure', 'members' => [ 'DeleteMarker' => [ 'shape' => 'DeleteMarker', 'location' => 'header', 'locationName' => 'x-amz-delete-marker', ], 'AcceptRanges' => [ 'shape' => 'AcceptRanges', 'location' => 'header', 'locationName' => 'accept-ranges', ], 'Expiration' => [ 'shape' => 'Expiration', 'location' => 'header', 'locationName' => 'x-amz-expiration', ], 'Restore' => [ 'shape' => 'Restore', 'location' => 'header', 'locationName' => 'x-amz-restore', ], 'LastModified' => [ 'shape' => 'LastModified', 'location' => 'header', 'locationName' => 'Last-Modified', ], 'ContentLength' => [ 'shape' => 'ContentLength', 'location' => 'header', 'locationName' => 'Content-Length', ], 'ETag' => [ 'shape' => 'ETag', 'location' => 'header', 'locationName' => 'ETag', ], 'MissingMeta' => [ 'shape' => 'MissingMeta', 'location' => 'header', 'locationName' => 'x-amz-missing-meta', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], 'CacheControl' => [ 'shape' => 'CacheControl', 'location' => 'header', 'locationName' => 'Cache-Control', ], 'ContentDisposition' => [ 'shape' => 'ContentDisposition', 'location' => 'header', 'locationName' => 'Content-Disposition', ], 'ContentEncoding' => [ 'shape' => 'ContentEncoding', 'location' => 'header', 'locationName' => 'Content-Encoding', ], 'ContentLanguage' => [ 'shape' => 'ContentLanguage', 'location' => 'header', 'locationName' => 'Content-Language', ], 'ContentType' => [ 'shape' => 'ContentType', 'location' => 'header', 'locationName' => 'Content-Type', ], 'Expires' => [ 'shape' => 'Expires', 'location' => 'header', 'locationName' => 'Expires', ], 'WebsiteRedirectLocation' => [ 'shape' => 'WebsiteRedirectLocation', 'location' => 'header', 'locationName' => 'x-amz-website-redirect-location', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'Metadata' => [ 'shape' => 'Metadata', 'location' => 'headers', 'locationName' => 'x-amz-meta-', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'StorageClass' => [ 'shape' => 'StorageClass', 'location' => 'header', 'locationName' => 'x-amz-storage-class', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], 'ReplicationStatus' => [ 'shape' => 'ReplicationStatus', 'location' => 'header', 'locationName' => 'x-amz-replication-status', ], 'PartsCount' => [ 'shape' => 'PartsCount', 'location' => 'header', 'locationName' => 'x-amz-mp-parts-count', ], ], ], 'HeadObjectRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'IfMatch' => [ 'shape' => 'IfMatch', 'location' => 'header', 'locationName' => 'If-Match', ], 'IfModifiedSince' => [ 'shape' => 'IfModifiedSince', 'location' => 'header', 'locationName' => 'If-Modified-Since', ], 'IfNoneMatch' => [ 'shape' => 'IfNoneMatch', 'location' => 'header', 'locationName' => 'If-None-Match', ], 'IfUnmodifiedSince' => [ 'shape' => 'IfUnmodifiedSince', 'location' => 'header', 'locationName' => 'If-Unmodified-Since', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'Range' => [ 'shape' => 'Range', 'location' => 'header', 'locationName' => 'Range', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKey' => [ 'shape' => 'SSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], 'PartNumber' => [ 'shape' => 'PartNumber', 'location' => 'querystring', 'locationName' => 'partNumber', ], ], ], 'HostName' => [ 'type' => 'string', ], 'HttpErrorCodeReturnedEquals' => [ 'type' => 'string', ], 'HttpRedirectCode' => [ 'type' => 'string', ], 'ID' => [ 'type' => 'string', ], 'IfMatch' => [ 'type' => 'string', ], 'IfModifiedSince' => [ 'type' => 'timestamp', ], 'IfNoneMatch' => [ 'type' => 'string', ], 'IfUnmodifiedSince' => [ 'type' => 'timestamp', ], 'IndexDocument' => [ 'type' => 'structure', 'required' => [ 'Suffix', ], 'members' => [ 'Suffix' => [ 'shape' => 'Suffix', ], ], ], 'Initiated' => [ 'type' => 'timestamp', ], 'Initiator' => [ 'type' => 'structure', 'members' => [ 'ID' => [ 'shape' => 'ID', ], 'DisplayName' => [ 'shape' => 'DisplayName', ], ], ], 'InventoryConfiguration' => [ 'type' => 'structure', 'required' => [ 'Destination', 'IsEnabled', 'Id', 'IncludedObjectVersions', 'Schedule', ], 'members' => [ 'Destination' => [ 'shape' => 'InventoryDestination', ], 'IsEnabled' => [ 'shape' => 'IsEnabled', ], 'Filter' => [ 'shape' => 'InventoryFilter', ], 'Id' => [ 'shape' => 'InventoryId', ], 'IncludedObjectVersions' => [ 'shape' => 'InventoryIncludedObjectVersions', ], 'OptionalFields' => [ 'shape' => 'InventoryOptionalFields', ], 'Schedule' => [ 'shape' => 'InventorySchedule', ], ], ], 'InventoryConfigurationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'InventoryConfiguration', ], 'flattened' => true, ], 'InventoryDestination' => [ 'type' => 'structure', 'required' => [ 'S3BucketDestination', ], 'members' => [ 'S3BucketDestination' => [ 'shape' => 'InventoryS3BucketDestination', ], ], ], 'InventoryFilter' => [ 'type' => 'structure', 'required' => [ 'Prefix', ], 'members' => [ 'Prefix' => [ 'shape' => 'Prefix', ], ], ], 'InventoryFormat' => [ 'type' => 'string', 'enum' => [ 'CSV', ], ], 'InventoryFrequency' => [ 'type' => 'string', 'enum' => [ 'Daily', 'Weekly', ], ], 'InventoryId' => [ 'type' => 'string', ], 'InventoryIncludedObjectVersions' => [ 'type' => 'string', 'enum' => [ 'All', 'Current', ], ], 'InventoryOptionalField' => [ 'type' => 'string', 'enum' => [ 'Size', 'LastModifiedDate', 'StorageClass', 'ETag', 'IsMultipartUploaded', 'ReplicationStatus', ], ], 'InventoryOptionalFields' => [ 'type' => 'list', 'member' => [ 'shape' => 'InventoryOptionalField', 'locationName' => 'Field', ], ], 'InventoryS3BucketDestination' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Format', ], 'members' => [ 'AccountId' => [ 'shape' => 'AccountId', ], 'Bucket' => [ 'shape' => 'BucketName', ], 'Format' => [ 'shape' => 'InventoryFormat', ], 'Prefix' => [ 'shape' => 'Prefix', ], ], ], 'InventorySchedule' => [ 'type' => 'structure', 'required' => [ 'Frequency', ], 'members' => [ 'Frequency' => [ 'shape' => 'InventoryFrequency', ], ], ], 'IsEnabled' => [ 'type' => 'boolean', ], 'IsLatest' => [ 'type' => 'boolean', ], 'IsTruncated' => [ 'type' => 'boolean', ], 'KeyCount' => [ 'type' => 'integer', ], 'KeyMarker' => [ 'type' => 'string', ], 'KeyPrefixEquals' => [ 'type' => 'string', ], 'LambdaFunctionArn' => [ 'type' => 'string', ], 'LambdaFunctionConfiguration' => [ 'type' => 'structure', 'required' => [ 'LambdaFunctionArn', 'Events', ], 'members' => [ 'Id' => [ 'shape' => 'NotificationId', ], 'LambdaFunctionArn' => [ 'shape' => 'LambdaFunctionArn', 'locationName' => 'CloudFunction', ], 'Events' => [ 'shape' => 'EventList', 'locationName' => 'Event', ], 'Filter' => [ 'shape' => 'NotificationConfigurationFilter', ], ], ], 'LambdaFunctionConfigurationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'LambdaFunctionConfiguration', ], 'flattened' => true, ], 'LastModified' => [ 'type' => 'timestamp', ], 'LifecycleConfiguration' => [ 'type' => 'structure', 'required' => [ 'Rules', ], 'members' => [ 'Rules' => [ 'shape' => 'Rules', 'locationName' => 'Rule', ], ], ], 'LifecycleExpiration' => [ 'type' => 'structure', 'members' => [ 'Date' => [ 'shape' => 'Date', ], 'Days' => [ 'shape' => 'Days', ], 'ExpiredObjectDeleteMarker' => [ 'shape' => 'ExpiredObjectDeleteMarker', ], ], ], 'LifecycleRule' => [ 'type' => 'structure', 'required' => [ 'Status', ], 'members' => [ 'Expiration' => [ 'shape' => 'LifecycleExpiration', ], 'ID' => [ 'shape' => 'ID', ], 'Prefix' => [ 'shape' => 'Prefix', 'deprecated' => true, ], 'Filter' => [ 'shape' => 'LifecycleRuleFilter', ], 'Status' => [ 'shape' => 'ExpirationStatus', ], 'Transitions' => [ 'shape' => 'TransitionList', 'locationName' => 'Transition', ], 'NoncurrentVersionTransitions' => [ 'shape' => 'NoncurrentVersionTransitionList', 'locationName' => 'NoncurrentVersionTransition', ], 'NoncurrentVersionExpiration' => [ 'shape' => 'NoncurrentVersionExpiration', ], 'AbortIncompleteMultipartUpload' => [ 'shape' => 'AbortIncompleteMultipartUpload', ], ], ], 'LifecycleRuleAndOperator' => [ 'type' => 'structure', 'members' => [ 'Prefix' => [ 'shape' => 'Prefix', ], 'Tags' => [ 'shape' => 'TagSet', 'flattened' => true, 'locationName' => 'Tag', ], ], ], 'LifecycleRuleFilter' => [ 'type' => 'structure', 'members' => [ 'Prefix' => [ 'shape' => 'Prefix', ], 'Tag' => [ 'shape' => 'Tag', ], 'And' => [ 'shape' => 'LifecycleRuleAndOperator', ], ], ], 'LifecycleRules' => [ 'type' => 'list', 'member' => [ 'shape' => 'LifecycleRule', ], 'flattened' => true, ], 'ListBucketAnalyticsConfigurationsOutput' => [ 'type' => 'structure', 'members' => [ 'IsTruncated' => [ 'shape' => 'IsTruncated', ], 'ContinuationToken' => [ 'shape' => 'Token', ], 'NextContinuationToken' => [ 'shape' => 'NextToken', ], 'AnalyticsConfigurationList' => [ 'shape' => 'AnalyticsConfigurationList', 'locationName' => 'AnalyticsConfiguration', ], ], ], 'ListBucketAnalyticsConfigurationsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContinuationToken' => [ 'shape' => 'Token', 'location' => 'querystring', 'locationName' => 'continuation-token', ], ], ], 'ListBucketInventoryConfigurationsOutput' => [ 'type' => 'structure', 'members' => [ 'ContinuationToken' => [ 'shape' => 'Token', ], 'InventoryConfigurationList' => [ 'shape' => 'InventoryConfigurationList', 'locationName' => 'InventoryConfiguration', ], 'IsTruncated' => [ 'shape' => 'IsTruncated', ], 'NextContinuationToken' => [ 'shape' => 'NextToken', ], ], ], 'ListBucketInventoryConfigurationsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContinuationToken' => [ 'shape' => 'Token', 'location' => 'querystring', 'locationName' => 'continuation-token', ], ], ], 'ListBucketMetricsConfigurationsOutput' => [ 'type' => 'structure', 'members' => [ 'IsTruncated' => [ 'shape' => 'IsTruncated', ], 'ContinuationToken' => [ 'shape' => 'Token', ], 'NextContinuationToken' => [ 'shape' => 'NextToken', ], 'MetricsConfigurationList' => [ 'shape' => 'MetricsConfigurationList', 'locationName' => 'MetricsConfiguration', ], ], ], 'ListBucketMetricsConfigurationsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContinuationToken' => [ 'shape' => 'Token', 'location' => 'querystring', 'locationName' => 'continuation-token', ], ], ], 'ListBucketsOutput' => [ 'type' => 'structure', 'members' => [ 'Buckets' => [ 'shape' => 'Buckets', ], 'Owner' => [ 'shape' => 'Owner', ], ], ], 'ListMultipartUploadsOutput' => [ 'type' => 'structure', 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', ], 'KeyMarker' => [ 'shape' => 'KeyMarker', ], 'UploadIdMarker' => [ 'shape' => 'UploadIdMarker', ], 'NextKeyMarker' => [ 'shape' => 'NextKeyMarker', ], 'Prefix' => [ 'shape' => 'Prefix', ], 'Delimiter' => [ 'shape' => 'Delimiter', ], 'NextUploadIdMarker' => [ 'shape' => 'NextUploadIdMarker', ], 'MaxUploads' => [ 'shape' => 'MaxUploads', ], 'IsTruncated' => [ 'shape' => 'IsTruncated', ], 'Uploads' => [ 'shape' => 'MultipartUploadList', 'locationName' => 'Upload', ], 'CommonPrefixes' => [ 'shape' => 'CommonPrefixList', ], 'EncodingType' => [ 'shape' => 'EncodingType', ], ], ], 'ListMultipartUploadsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Delimiter' => [ 'shape' => 'Delimiter', 'location' => 'querystring', 'locationName' => 'delimiter', ], 'EncodingType' => [ 'shape' => 'EncodingType', 'location' => 'querystring', 'locationName' => 'encoding-type', ], 'KeyMarker' => [ 'shape' => 'KeyMarker', 'location' => 'querystring', 'locationName' => 'key-marker', ], 'MaxUploads' => [ 'shape' => 'MaxUploads', 'location' => 'querystring', 'locationName' => 'max-uploads', ], 'Prefix' => [ 'shape' => 'Prefix', 'location' => 'querystring', 'locationName' => 'prefix', ], 'UploadIdMarker' => [ 'shape' => 'UploadIdMarker', 'location' => 'querystring', 'locationName' => 'upload-id-marker', ], ], ], 'ListObjectVersionsOutput' => [ 'type' => 'structure', 'members' => [ 'IsTruncated' => [ 'shape' => 'IsTruncated', ], 'KeyMarker' => [ 'shape' => 'KeyMarker', ], 'VersionIdMarker' => [ 'shape' => 'VersionIdMarker', ], 'NextKeyMarker' => [ 'shape' => 'NextKeyMarker', ], 'NextVersionIdMarker' => [ 'shape' => 'NextVersionIdMarker', ], 'Versions' => [ 'shape' => 'ObjectVersionList', 'locationName' => 'Version', ], 'DeleteMarkers' => [ 'shape' => 'DeleteMarkers', 'locationName' => 'DeleteMarker', ], 'Name' => [ 'shape' => 'BucketName', ], 'Prefix' => [ 'shape' => 'Prefix', ], 'Delimiter' => [ 'shape' => 'Delimiter', ], 'MaxKeys' => [ 'shape' => 'MaxKeys', ], 'CommonPrefixes' => [ 'shape' => 'CommonPrefixList', ], 'EncodingType' => [ 'shape' => 'EncodingType', ], ], ], 'ListObjectVersionsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Delimiter' => [ 'shape' => 'Delimiter', 'location' => 'querystring', 'locationName' => 'delimiter', ], 'EncodingType' => [ 'shape' => 'EncodingType', 'location' => 'querystring', 'locationName' => 'encoding-type', ], 'KeyMarker' => [ 'shape' => 'KeyMarker', 'location' => 'querystring', 'locationName' => 'key-marker', ], 'MaxKeys' => [ 'shape' => 'MaxKeys', 'location' => 'querystring', 'locationName' => 'max-keys', ], 'Prefix' => [ 'shape' => 'Prefix', 'location' => 'querystring', 'locationName' => 'prefix', ], 'VersionIdMarker' => [ 'shape' => 'VersionIdMarker', 'location' => 'querystring', 'locationName' => 'version-id-marker', ], ], ], 'ListObjectsOutput' => [ 'type' => 'structure', 'members' => [ 'IsTruncated' => [ 'shape' => 'IsTruncated', ], 'Marker' => [ 'shape' => 'Marker', ], 'NextMarker' => [ 'shape' => 'NextMarker', ], 'Contents' => [ 'shape' => 'ObjectList', ], 'Name' => [ 'shape' => 'BucketName', ], 'Prefix' => [ 'shape' => 'Prefix', ], 'Delimiter' => [ 'shape' => 'Delimiter', ], 'MaxKeys' => [ 'shape' => 'MaxKeys', ], 'CommonPrefixes' => [ 'shape' => 'CommonPrefixList', ], 'EncodingType' => [ 'shape' => 'EncodingType', ], ], ], 'ListObjectsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Delimiter' => [ 'shape' => 'Delimiter', 'location' => 'querystring', 'locationName' => 'delimiter', ], 'EncodingType' => [ 'shape' => 'EncodingType', 'location' => 'querystring', 'locationName' => 'encoding-type', ], 'Marker' => [ 'shape' => 'Marker', 'location' => 'querystring', 'locationName' => 'marker', ], 'MaxKeys' => [ 'shape' => 'MaxKeys', 'location' => 'querystring', 'locationName' => 'max-keys', ], 'Prefix' => [ 'shape' => 'Prefix', 'location' => 'querystring', 'locationName' => 'prefix', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'ListObjectsV2Output' => [ 'type' => 'structure', 'members' => [ 'IsTruncated' => [ 'shape' => 'IsTruncated', ], 'Contents' => [ 'shape' => 'ObjectList', ], 'Name' => [ 'shape' => 'BucketName', ], 'Prefix' => [ 'shape' => 'Prefix', ], 'Delimiter' => [ 'shape' => 'Delimiter', ], 'MaxKeys' => [ 'shape' => 'MaxKeys', ], 'CommonPrefixes' => [ 'shape' => 'CommonPrefixList', ], 'EncodingType' => [ 'shape' => 'EncodingType', ], 'KeyCount' => [ 'shape' => 'KeyCount', ], 'ContinuationToken' => [ 'shape' => 'Token', ], 'NextContinuationToken' => [ 'shape' => 'NextToken', ], 'StartAfter' => [ 'shape' => 'StartAfter', ], ], ], 'ListObjectsV2Request' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Delimiter' => [ 'shape' => 'Delimiter', 'location' => 'querystring', 'locationName' => 'delimiter', ], 'EncodingType' => [ 'shape' => 'EncodingType', 'location' => 'querystring', 'locationName' => 'encoding-type', ], 'MaxKeys' => [ 'shape' => 'MaxKeys', 'location' => 'querystring', 'locationName' => 'max-keys', ], 'Prefix' => [ 'shape' => 'Prefix', 'location' => 'querystring', 'locationName' => 'prefix', ], 'ContinuationToken' => [ 'shape' => 'Token', 'location' => 'querystring', 'locationName' => 'continuation-token', ], 'FetchOwner' => [ 'shape' => 'FetchOwner', 'location' => 'querystring', 'locationName' => 'fetch-owner', ], 'StartAfter' => [ 'shape' => 'StartAfter', 'location' => 'querystring', 'locationName' => 'start-after', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'ListPartsOutput' => [ 'type' => 'structure', 'members' => [ 'AbortDate' => [ 'shape' => 'AbortDate', 'location' => 'header', 'locationName' => 'x-amz-abort-date', ], 'AbortRuleId' => [ 'shape' => 'AbortRuleId', 'location' => 'header', 'locationName' => 'x-amz-abort-rule-id', ], 'Bucket' => [ 'shape' => 'BucketName', ], 'Key' => [ 'shape' => 'ObjectKey', ], 'UploadId' => [ 'shape' => 'MultipartUploadId', ], 'PartNumberMarker' => [ 'shape' => 'PartNumberMarker', ], 'NextPartNumberMarker' => [ 'shape' => 'NextPartNumberMarker', ], 'MaxParts' => [ 'shape' => 'MaxParts', ], 'IsTruncated' => [ 'shape' => 'IsTruncated', ], 'Parts' => [ 'shape' => 'Parts', 'locationName' => 'Part', ], 'Initiator' => [ 'shape' => 'Initiator', ], 'Owner' => [ 'shape' => 'Owner', ], 'StorageClass' => [ 'shape' => 'StorageClass', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'ListPartsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', 'UploadId', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'MaxParts' => [ 'shape' => 'MaxParts', 'location' => 'querystring', 'locationName' => 'max-parts', ], 'PartNumberMarker' => [ 'shape' => 'PartNumberMarker', 'location' => 'querystring', 'locationName' => 'part-number-marker', ], 'UploadId' => [ 'shape' => 'MultipartUploadId', 'location' => 'querystring', 'locationName' => 'uploadId', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'Location' => [ 'type' => 'string', ], 'LoggingEnabled' => [ 'type' => 'structure', 'members' => [ 'TargetBucket' => [ 'shape' => 'TargetBucket', ], 'TargetGrants' => [ 'shape' => 'TargetGrants', ], 'TargetPrefix' => [ 'shape' => 'TargetPrefix', ], ], ], 'MFA' => [ 'type' => 'string', ], 'MFADelete' => [ 'type' => 'string', 'enum' => [ 'Enabled', 'Disabled', ], ], 'MFADeleteStatus' => [ 'type' => 'string', 'enum' => [ 'Enabled', 'Disabled', ], ], 'Marker' => [ 'type' => 'string', ], 'MaxAgeSeconds' => [ 'type' => 'integer', ], 'MaxKeys' => [ 'type' => 'integer', ], 'MaxParts' => [ 'type' => 'integer', ], 'MaxUploads' => [ 'type' => 'integer', ], 'Message' => [ 'type' => 'string', ], 'Metadata' => [ 'type' => 'map', 'key' => [ 'shape' => 'MetadataKey', ], 'value' => [ 'shape' => 'MetadataValue', ], ], 'MetadataDirective' => [ 'type' => 'string', 'enum' => [ 'COPY', 'REPLACE', ], ], 'MetadataKey' => [ 'type' => 'string', ], 'MetadataValue' => [ 'type' => 'string', ], 'MetricsAndOperator' => [ 'type' => 'structure', 'members' => [ 'Prefix' => [ 'shape' => 'Prefix', ], 'Tags' => [ 'shape' => 'TagSet', 'flattened' => true, 'locationName' => 'Tag', ], ], ], 'MetricsConfiguration' => [ 'type' => 'structure', 'required' => [ 'Id', ], 'members' => [ 'Id' => [ 'shape' => 'MetricsId', ], 'Filter' => [ 'shape' => 'MetricsFilter', ], ], ], 'MetricsConfigurationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'MetricsConfiguration', ], 'flattened' => true, ], 'MetricsFilter' => [ 'type' => 'structure', 'members' => [ 'Prefix' => [ 'shape' => 'Prefix', ], 'Tag' => [ 'shape' => 'Tag', ], 'And' => [ 'shape' => 'MetricsAndOperator', ], ], ], 'MetricsId' => [ 'type' => 'string', ], 'MissingMeta' => [ 'type' => 'integer', ], 'MultipartUpload' => [ 'type' => 'structure', 'members' => [ 'UploadId' => [ 'shape' => 'MultipartUploadId', ], 'Key' => [ 'shape' => 'ObjectKey', ], 'Initiated' => [ 'shape' => 'Initiated', ], 'StorageClass' => [ 'shape' => 'StorageClass', ], 'Owner' => [ 'shape' => 'Owner', ], 'Initiator' => [ 'shape' => 'Initiator', ], ], ], 'MultipartUploadId' => [ 'type' => 'string', ], 'MultipartUploadList' => [ 'type' => 'list', 'member' => [ 'shape' => 'MultipartUpload', ], 'flattened' => true, ], 'NextKeyMarker' => [ 'type' => 'string', ], 'NextMarker' => [ 'type' => 'string', ], 'NextPartNumberMarker' => [ 'type' => 'integer', ], 'NextToken' => [ 'type' => 'string', ], 'NextUploadIdMarker' => [ 'type' => 'string', ], 'NextVersionIdMarker' => [ 'type' => 'string', ], 'NoSuchBucket' => [ 'type' => 'structure', 'members' => [], 'exception' => true, ], 'NoSuchKey' => [ 'type' => 'structure', 'members' => [], 'exception' => true, ], 'NoSuchUpload' => [ 'type' => 'structure', 'members' => [], 'exception' => true, ], 'NoncurrentVersionExpiration' => [ 'type' => 'structure', 'members' => [ 'NoncurrentDays' => [ 'shape' => 'Days', ], ], ], 'NoncurrentVersionTransition' => [ 'type' => 'structure', 'members' => [ 'NoncurrentDays' => [ 'shape' => 'Days', ], 'StorageClass' => [ 'shape' => 'TransitionStorageClass', ], ], ], 'NoncurrentVersionTransitionList' => [ 'type' => 'list', 'member' => [ 'shape' => 'NoncurrentVersionTransition', ], 'flattened' => true, ], 'NotificationConfiguration' => [ 'type' => 'structure', 'members' => [ 'TopicConfigurations' => [ 'shape' => 'TopicConfigurationList', 'locationName' => 'TopicConfiguration', ], 'QueueConfigurations' => [ 'shape' => 'QueueConfigurationList', 'locationName' => 'QueueConfiguration', ], 'LambdaFunctionConfigurations' => [ 'shape' => 'LambdaFunctionConfigurationList', 'locationName' => 'CloudFunctionConfiguration', ], ], ], 'NotificationConfigurationDeprecated' => [ 'type' => 'structure', 'members' => [ 'TopicConfiguration' => [ 'shape' => 'TopicConfigurationDeprecated', ], 'QueueConfiguration' => [ 'shape' => 'QueueConfigurationDeprecated', ], 'CloudFunctionConfiguration' => [ 'shape' => 'CloudFunctionConfiguration', ], ], ], 'NotificationConfigurationFilter' => [ 'type' => 'structure', 'members' => [ 'Key' => [ 'shape' => 'S3KeyFilter', 'locationName' => 'S3Key', ], ], ], 'NotificationId' => [ 'type' => 'string', ], 'Object' => [ 'type' => 'structure', 'members' => [ 'Key' => [ 'shape' => 'ObjectKey', ], 'LastModified' => [ 'shape' => 'LastModified', ], 'ETag' => [ 'shape' => 'ETag', ], 'Size' => [ 'shape' => 'Size', ], 'StorageClass' => [ 'shape' => 'ObjectStorageClass', ], 'Owner' => [ 'shape' => 'Owner', ], ], ], 'ObjectAlreadyInActiveTierError' => [ 'type' => 'structure', 'members' => [], 'exception' => true, ], 'ObjectCannedACL' => [ 'type' => 'string', 'enum' => [ 'private', 'public-read', 'public-read-write', 'authenticated-read', 'aws-exec-read', 'bucket-owner-read', 'bucket-owner-full-control', ], ], 'ObjectIdentifier' => [ 'type' => 'structure', 'required' => [ 'Key', ], 'members' => [ 'Key' => [ 'shape' => 'ObjectKey', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', ], ], ], 'ObjectIdentifierList' => [ 'type' => 'list', 'member' => [ 'shape' => 'ObjectIdentifier', ], 'flattened' => true, ], 'ObjectKey' => [ 'type' => 'string', 'min' => 1, ], 'ObjectList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Object', ], 'flattened' => true, ], 'ObjectNotInActiveTierError' => [ 'type' => 'structure', 'members' => [], 'exception' => true, ], 'ObjectStorageClass' => [ 'type' => 'string', 'enum' => [ 'STANDARD', 'REDUCED_REDUNDANCY', 'GLACIER', ], ], 'ObjectVersion' => [ 'type' => 'structure', 'members' => [ 'ETag' => [ 'shape' => 'ETag', ], 'Size' => [ 'shape' => 'Size', ], 'StorageClass' => [ 'shape' => 'ObjectVersionStorageClass', ], 'Key' => [ 'shape' => 'ObjectKey', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', ], 'IsLatest' => [ 'shape' => 'IsLatest', ], 'LastModified' => [ 'shape' => 'LastModified', ], 'Owner' => [ 'shape' => 'Owner', ], ], ], 'ObjectVersionId' => [ 'type' => 'string', ], 'ObjectVersionList' => [ 'type' => 'list', 'member' => [ 'shape' => 'ObjectVersion', ], 'flattened' => true, ], 'ObjectVersionStorageClass' => [ 'type' => 'string', 'enum' => [ 'STANDARD', ], ], 'Owner' => [ 'type' => 'structure', 'members' => [ 'DisplayName' => [ 'shape' => 'DisplayName', ], 'ID' => [ 'shape' => 'ID', ], ], ], 'Part' => [ 'type' => 'structure', 'members' => [ 'PartNumber' => [ 'shape' => 'PartNumber', ], 'LastModified' => [ 'shape' => 'LastModified', ], 'ETag' => [ 'shape' => 'ETag', ], 'Size' => [ 'shape' => 'Size', ], ], ], 'PartNumber' => [ 'type' => 'integer', ], 'PartNumberMarker' => [ 'type' => 'integer', ], 'Parts' => [ 'type' => 'list', 'member' => [ 'shape' => 'Part', ], 'flattened' => true, ], 'PartsCount' => [ 'type' => 'integer', ], 'Payer' => [ 'type' => 'string', 'enum' => [ 'Requester', 'BucketOwner', ], ], 'Permission' => [ 'type' => 'string', 'enum' => [ 'FULL_CONTROL', 'WRITE', 'WRITE_ACP', 'READ', 'READ_ACP', ], ], 'Policy' => [ 'type' => 'string', ], 'Prefix' => [ 'type' => 'string', ], 'Protocol' => [ 'type' => 'string', 'enum' => [ 'http', 'https', ], ], 'PutBucketAccelerateConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'AccelerateConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'AccelerateConfiguration' => [ 'shape' => 'AccelerateConfiguration', 'locationName' => 'AccelerateConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'AccelerateConfiguration', ], 'PutBucketAclRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'ACL' => [ 'shape' => 'BucketCannedACL', 'location' => 'header', 'locationName' => 'x-amz-acl', ], 'AccessControlPolicy' => [ 'shape' => 'AccessControlPolicy', 'locationName' => 'AccessControlPolicy', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'GrantFullControl' => [ 'shape' => 'GrantFullControl', 'location' => 'header', 'locationName' => 'x-amz-grant-full-control', ], 'GrantRead' => [ 'shape' => 'GrantRead', 'location' => 'header', 'locationName' => 'x-amz-grant-read', ], 'GrantReadACP' => [ 'shape' => 'GrantReadACP', 'location' => 'header', 'locationName' => 'x-amz-grant-read-acp', ], 'GrantWrite' => [ 'shape' => 'GrantWrite', 'location' => 'header', 'locationName' => 'x-amz-grant-write', ], 'GrantWriteACP' => [ 'shape' => 'GrantWriteACP', 'location' => 'header', 'locationName' => 'x-amz-grant-write-acp', ], ], 'payload' => 'AccessControlPolicy', ], 'PutBucketAnalyticsConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', 'AnalyticsConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'AnalyticsId', 'location' => 'querystring', 'locationName' => 'id', ], 'AnalyticsConfiguration' => [ 'shape' => 'AnalyticsConfiguration', 'locationName' => 'AnalyticsConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'AnalyticsConfiguration', ], 'PutBucketCorsRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'CORSConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'CORSConfiguration' => [ 'shape' => 'CORSConfiguration', 'locationName' => 'CORSConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], ], 'payload' => 'CORSConfiguration', ], 'PutBucketInventoryConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', 'InventoryConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'InventoryId', 'location' => 'querystring', 'locationName' => 'id', ], 'InventoryConfiguration' => [ 'shape' => 'InventoryConfiguration', 'locationName' => 'InventoryConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'InventoryConfiguration', ], 'PutBucketLifecycleConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'LifecycleConfiguration' => [ 'shape' => 'BucketLifecycleConfiguration', 'locationName' => 'LifecycleConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'LifecycleConfiguration', ], 'PutBucketLifecycleRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'LifecycleConfiguration' => [ 'shape' => 'LifecycleConfiguration', 'locationName' => 'LifecycleConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'LifecycleConfiguration', ], 'PutBucketLoggingRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'BucketLoggingStatus', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'BucketLoggingStatus' => [ 'shape' => 'BucketLoggingStatus', 'locationName' => 'BucketLoggingStatus', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], ], 'payload' => 'BucketLoggingStatus', ], 'PutBucketMetricsConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Id', 'MetricsConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Id' => [ 'shape' => 'MetricsId', 'location' => 'querystring', 'locationName' => 'id', ], 'MetricsConfiguration' => [ 'shape' => 'MetricsConfiguration', 'locationName' => 'MetricsConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'MetricsConfiguration', ], 'PutBucketNotificationConfigurationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'NotificationConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'NotificationConfiguration' => [ 'shape' => 'NotificationConfiguration', 'locationName' => 'NotificationConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'NotificationConfiguration', ], 'PutBucketNotificationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'NotificationConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'NotificationConfiguration' => [ 'shape' => 'NotificationConfigurationDeprecated', 'locationName' => 'NotificationConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'NotificationConfiguration', ], 'PutBucketPolicyRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Policy', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'Policy' => [ 'shape' => 'Policy', ], ], 'payload' => 'Policy', ], 'PutBucketReplicationRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'ReplicationConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'ReplicationConfiguration' => [ 'shape' => 'ReplicationConfiguration', 'locationName' => 'ReplicationConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'ReplicationConfiguration', ], 'PutBucketRequestPaymentRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'RequestPaymentConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'RequestPaymentConfiguration' => [ 'shape' => 'RequestPaymentConfiguration', 'locationName' => 'RequestPaymentConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'RequestPaymentConfiguration', ], 'PutBucketTaggingRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Tagging', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'Tagging' => [ 'shape' => 'Tagging', 'locationName' => 'Tagging', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'Tagging', ], 'PutBucketVersioningRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'VersioningConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'MFA' => [ 'shape' => 'MFA', 'location' => 'header', 'locationName' => 'x-amz-mfa', ], 'VersioningConfiguration' => [ 'shape' => 'VersioningConfiguration', 'locationName' => 'VersioningConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'VersioningConfiguration', ], 'PutBucketWebsiteRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'WebsiteConfiguration', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'WebsiteConfiguration' => [ 'shape' => 'WebsiteConfiguration', 'locationName' => 'WebsiteConfiguration', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'WebsiteConfiguration', ], 'PutObjectAclOutput' => [ 'type' => 'structure', 'members' => [ 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'PutObjectAclRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'ACL' => [ 'shape' => 'ObjectCannedACL', 'location' => 'header', 'locationName' => 'x-amz-acl', ], 'AccessControlPolicy' => [ 'shape' => 'AccessControlPolicy', 'locationName' => 'AccessControlPolicy', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'GrantFullControl' => [ 'shape' => 'GrantFullControl', 'location' => 'header', 'locationName' => 'x-amz-grant-full-control', ], 'GrantRead' => [ 'shape' => 'GrantRead', 'location' => 'header', 'locationName' => 'x-amz-grant-read', ], 'GrantReadACP' => [ 'shape' => 'GrantReadACP', 'location' => 'header', 'locationName' => 'x-amz-grant-read-acp', ], 'GrantWrite' => [ 'shape' => 'GrantWrite', 'location' => 'header', 'locationName' => 'x-amz-grant-write', ], 'GrantWriteACP' => [ 'shape' => 'GrantWriteACP', 'location' => 'header', 'locationName' => 'x-amz-grant-write-acp', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], ], 'payload' => 'AccessControlPolicy', ], 'PutObjectOutput' => [ 'type' => 'structure', 'members' => [ 'Expiration' => [ 'shape' => 'Expiration', 'location' => 'header', 'locationName' => 'x-amz-expiration', ], 'ETag' => [ 'shape' => 'ETag', 'location' => 'header', 'locationName' => 'ETag', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'PutObjectRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'ACL' => [ 'shape' => 'ObjectCannedACL', 'location' => 'header', 'locationName' => 'x-amz-acl', ], 'Body' => [ 'shape' => 'Body', 'streaming' => true, ], 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'CacheControl' => [ 'shape' => 'CacheControl', 'location' => 'header', 'locationName' => 'Cache-Control', ], 'ContentDisposition' => [ 'shape' => 'ContentDisposition', 'location' => 'header', 'locationName' => 'Content-Disposition', ], 'ContentEncoding' => [ 'shape' => 'ContentEncoding', 'location' => 'header', 'locationName' => 'Content-Encoding', ], 'ContentLanguage' => [ 'shape' => 'ContentLanguage', 'location' => 'header', 'locationName' => 'Content-Language', ], 'ContentLength' => [ 'shape' => 'ContentLength', 'location' => 'header', 'locationName' => 'Content-Length', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'ContentType' => [ 'shape' => 'ContentType', 'location' => 'header', 'locationName' => 'Content-Type', ], 'Expires' => [ 'shape' => 'Expires', 'location' => 'header', 'locationName' => 'Expires', ], 'GrantFullControl' => [ 'shape' => 'GrantFullControl', 'location' => 'header', 'locationName' => 'x-amz-grant-full-control', ], 'GrantRead' => [ 'shape' => 'GrantRead', 'location' => 'header', 'locationName' => 'x-amz-grant-read', ], 'GrantReadACP' => [ 'shape' => 'GrantReadACP', 'location' => 'header', 'locationName' => 'x-amz-grant-read-acp', ], 'GrantWriteACP' => [ 'shape' => 'GrantWriteACP', 'location' => 'header', 'locationName' => 'x-amz-grant-write-acp', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'Metadata' => [ 'shape' => 'Metadata', 'location' => 'headers', 'locationName' => 'x-amz-meta-', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'StorageClass' => [ 'shape' => 'StorageClass', 'location' => 'header', 'locationName' => 'x-amz-storage-class', ], 'WebsiteRedirectLocation' => [ 'shape' => 'WebsiteRedirectLocation', 'location' => 'header', 'locationName' => 'x-amz-website-redirect-location', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKey' => [ 'shape' => 'SSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], 'Tagging' => [ 'shape' => 'TaggingHeader', 'location' => 'header', 'locationName' => 'x-amz-tagging', ], ], 'payload' => 'Body', ], 'PutObjectTaggingOutput' => [ 'type' => 'structure', 'members' => [ 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'header', 'locationName' => 'x-amz-version-id', ], ], ], 'PutObjectTaggingRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', 'Tagging', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'Tagging' => [ 'shape' => 'Tagging', 'locationName' => 'Tagging', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], ], 'payload' => 'Tagging', ], 'QueueArn' => [ 'type' => 'string', ], 'QueueConfiguration' => [ 'type' => 'structure', 'required' => [ 'QueueArn', 'Events', ], 'members' => [ 'Id' => [ 'shape' => 'NotificationId', ], 'QueueArn' => [ 'shape' => 'QueueArn', 'locationName' => 'Queue', ], 'Events' => [ 'shape' => 'EventList', 'locationName' => 'Event', ], 'Filter' => [ 'shape' => 'NotificationConfigurationFilter', ], ], ], 'QueueConfigurationDeprecated' => [ 'type' => 'structure', 'members' => [ 'Id' => [ 'shape' => 'NotificationId', ], 'Event' => [ 'shape' => 'Event', 'deprecated' => true, ], 'Events' => [ 'shape' => 'EventList', 'locationName' => 'Event', ], 'Queue' => [ 'shape' => 'QueueArn', ], ], ], 'QueueConfigurationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'QueueConfiguration', ], 'flattened' => true, ], 'Quiet' => [ 'type' => 'boolean', ], 'Range' => [ 'type' => 'string', ], 'Redirect' => [ 'type' => 'structure', 'members' => [ 'HostName' => [ 'shape' => 'HostName', ], 'HttpRedirectCode' => [ 'shape' => 'HttpRedirectCode', ], 'Protocol' => [ 'shape' => 'Protocol', ], 'ReplaceKeyPrefixWith' => [ 'shape' => 'ReplaceKeyPrefixWith', ], 'ReplaceKeyWith' => [ 'shape' => 'ReplaceKeyWith', ], ], ], 'RedirectAllRequestsTo' => [ 'type' => 'structure', 'required' => [ 'HostName', ], 'members' => [ 'HostName' => [ 'shape' => 'HostName', ], 'Protocol' => [ 'shape' => 'Protocol', ], ], ], 'ReplaceKeyPrefixWith' => [ 'type' => 'string', ], 'ReplaceKeyWith' => [ 'type' => 'string', ], 'ReplicationConfiguration' => [ 'type' => 'structure', 'required' => [ 'Role', 'Rules', ], 'members' => [ 'Role' => [ 'shape' => 'Role', ], 'Rules' => [ 'shape' => 'ReplicationRules', 'locationName' => 'Rule', ], ], ], 'ReplicationRule' => [ 'type' => 'structure', 'required' => [ 'Prefix', 'Status', 'Destination', ], 'members' => [ 'ID' => [ 'shape' => 'ID', ], 'Prefix' => [ 'shape' => 'Prefix', ], 'Status' => [ 'shape' => 'ReplicationRuleStatus', ], 'Destination' => [ 'shape' => 'Destination', ], ], ], 'ReplicationRuleStatus' => [ 'type' => 'string', 'enum' => [ 'Enabled', 'Disabled', ], ], 'ReplicationRules' => [ 'type' => 'list', 'member' => [ 'shape' => 'ReplicationRule', ], 'flattened' => true, ], 'ReplicationStatus' => [ 'type' => 'string', 'enum' => [ 'COMPLETE', 'PENDING', 'FAILED', 'REPLICA', ], ], 'RequestCharged' => [ 'type' => 'string', 'enum' => [ 'requester', ], ], 'RequestPayer' => [ 'type' => 'string', 'enum' => [ 'requester', ], ], 'RequestPaymentConfiguration' => [ 'type' => 'structure', 'required' => [ 'Payer', ], 'members' => [ 'Payer' => [ 'shape' => 'Payer', ], ], ], 'ResponseCacheControl' => [ 'type' => 'string', ], 'ResponseContentDisposition' => [ 'type' => 'string', ], 'ResponseContentEncoding' => [ 'type' => 'string', ], 'ResponseContentLanguage' => [ 'type' => 'string', ], 'ResponseContentType' => [ 'type' => 'string', ], 'ResponseExpires' => [ 'type' => 'timestamp', ], 'Restore' => [ 'type' => 'string', ], 'RestoreObjectOutput' => [ 'type' => 'structure', 'members' => [ 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'RestoreObjectRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'VersionId' => [ 'shape' => 'ObjectVersionId', 'location' => 'querystring', 'locationName' => 'versionId', ], 'RestoreRequest' => [ 'shape' => 'RestoreRequest', 'locationName' => 'RestoreRequest', 'xmlNamespace' => [ 'uri' => 'http://s3.amazonaws.com/doc/2006-03-01/', ], ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], 'payload' => 'RestoreRequest', ], 'RestoreRequest' => [ 'type' => 'structure', 'required' => [ 'Days', ], 'members' => [ 'Days' => [ 'shape' => 'Days', ], 'GlacierJobParameters' => [ 'shape' => 'GlacierJobParameters', ], ], ], 'Role' => [ 'type' => 'string', ], 'RoutingRule' => [ 'type' => 'structure', 'required' => [ 'Redirect', ], 'members' => [ 'Condition' => [ 'shape' => 'Condition', ], 'Redirect' => [ 'shape' => 'Redirect', ], ], ], 'RoutingRules' => [ 'type' => 'list', 'member' => [ 'shape' => 'RoutingRule', 'locationName' => 'RoutingRule', ], ], 'Rule' => [ 'type' => 'structure', 'required' => [ 'Prefix', 'Status', ], 'members' => [ 'Expiration' => [ 'shape' => 'LifecycleExpiration', ], 'ID' => [ 'shape' => 'ID', ], 'Prefix' => [ 'shape' => 'Prefix', ], 'Status' => [ 'shape' => 'ExpirationStatus', ], 'Transition' => [ 'shape' => 'Transition', ], 'NoncurrentVersionTransition' => [ 'shape' => 'NoncurrentVersionTransition', ], 'NoncurrentVersionExpiration' => [ 'shape' => 'NoncurrentVersionExpiration', ], 'AbortIncompleteMultipartUpload' => [ 'shape' => 'AbortIncompleteMultipartUpload', ], ], ], 'Rules' => [ 'type' => 'list', 'member' => [ 'shape' => 'Rule', ], 'flattened' => true, ], 'S3KeyFilter' => [ 'type' => 'structure', 'members' => [ 'FilterRules' => [ 'shape' => 'FilterRuleList', 'locationName' => 'FilterRule', ], ], ], 'SSECustomerAlgorithm' => [ 'type' => 'string', ], 'SSECustomerKey' => [ 'type' => 'string', 'sensitive' => true, ], 'SSECustomerKeyMD5' => [ 'type' => 'string', ], 'SSEKMSKeyId' => [ 'type' => 'string', 'sensitive' => true, ], 'ServerSideEncryption' => [ 'type' => 'string', 'enum' => [ 'AES256', 'aws:kms', ], ], 'Size' => [ 'type' => 'integer', ], 'StartAfter' => [ 'type' => 'string', ], 'StorageClass' => [ 'type' => 'string', 'enum' => [ 'STANDARD', 'REDUCED_REDUNDANCY', 'STANDARD_IA', ], ], 'StorageClassAnalysis' => [ 'type' => 'structure', 'members' => [ 'DataExport' => [ 'shape' => 'StorageClassAnalysisDataExport', ], ], ], 'StorageClassAnalysisDataExport' => [ 'type' => 'structure', 'required' => [ 'OutputSchemaVersion', 'Destination', ], 'members' => [ 'OutputSchemaVersion' => [ 'shape' => 'StorageClassAnalysisSchemaVersion', ], 'Destination' => [ 'shape' => 'AnalyticsExportDestination', ], ], ], 'StorageClassAnalysisSchemaVersion' => [ 'type' => 'string', 'enum' => [ 'V_1', ], ], 'Suffix' => [ 'type' => 'string', ], 'Tag' => [ 'type' => 'structure', 'required' => [ 'Key', 'Value', ], 'members' => [ 'Key' => [ 'shape' => 'ObjectKey', ], 'Value' => [ 'shape' => 'Value', ], ], ], 'TagCount' => [ 'type' => 'integer', ], 'TagSet' => [ 'type' => 'list', 'member' => [ 'shape' => 'Tag', 'locationName' => 'Tag', ], ], 'Tagging' => [ 'type' => 'structure', 'required' => [ 'TagSet', ], 'members' => [ 'TagSet' => [ 'shape' => 'TagSet', ], ], ], 'TaggingDirective' => [ 'type' => 'string', 'enum' => [ 'COPY', 'REPLACE', ], ], 'TaggingHeader' => [ 'type' => 'string', ], 'TargetBucket' => [ 'type' => 'string', ], 'TargetGrant' => [ 'type' => 'structure', 'members' => [ 'Grantee' => [ 'shape' => 'Grantee', ], 'Permission' => [ 'shape' => 'BucketLogsPermission', ], ], ], 'TargetGrants' => [ 'type' => 'list', 'member' => [ 'shape' => 'TargetGrant', 'locationName' => 'Grant', ], ], 'TargetPrefix' => [ 'type' => 'string', ], 'Tier' => [ 'type' => 'string', 'enum' => [ 'Standard', 'Bulk', 'Expedited', ], ], 'Token' => [ 'type' => 'string', ], 'TopicArn' => [ 'type' => 'string', ], 'TopicConfiguration' => [ 'type' => 'structure', 'required' => [ 'TopicArn', 'Events', ], 'members' => [ 'Id' => [ 'shape' => 'NotificationId', ], 'TopicArn' => [ 'shape' => 'TopicArn', 'locationName' => 'Topic', ], 'Events' => [ 'shape' => 'EventList', 'locationName' => 'Event', ], 'Filter' => [ 'shape' => 'NotificationConfigurationFilter', ], ], ], 'TopicConfigurationDeprecated' => [ 'type' => 'structure', 'members' => [ 'Id' => [ 'shape' => 'NotificationId', ], 'Events' => [ 'shape' => 'EventList', 'locationName' => 'Event', ], 'Event' => [ 'shape' => 'Event', 'deprecated' => true, ], 'Topic' => [ 'shape' => 'TopicArn', ], ], ], 'TopicConfigurationList' => [ 'type' => 'list', 'member' => [ 'shape' => 'TopicConfiguration', ], 'flattened' => true, ], 'Transition' => [ 'type' => 'structure', 'members' => [ 'Date' => [ 'shape' => 'Date', ], 'Days' => [ 'shape' => 'Days', ], 'StorageClass' => [ 'shape' => 'TransitionStorageClass', ], ], ], 'TransitionList' => [ 'type' => 'list', 'member' => [ 'shape' => 'Transition', ], 'flattened' => true, ], 'TransitionStorageClass' => [ 'type' => 'string', 'enum' => [ 'GLACIER', 'STANDARD_IA', ], ], 'Type' => [ 'type' => 'string', 'enum' => [ 'CanonicalUser', 'AmazonCustomerByEmail', 'Group', ], ], 'URI' => [ 'type' => 'string', ], 'UploadIdMarker' => [ 'type' => 'string', ], 'UploadPartCopyOutput' => [ 'type' => 'structure', 'members' => [ 'CopySourceVersionId' => [ 'shape' => 'CopySourceVersionId', 'location' => 'header', 'locationName' => 'x-amz-copy-source-version-id', ], 'CopyPartResult' => [ 'shape' => 'CopyPartResult', ], 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], 'payload' => 'CopyPartResult', ], 'UploadPartCopyRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'CopySource', 'Key', 'PartNumber', 'UploadId', ], 'members' => [ 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'CopySource' => [ 'shape' => 'CopySource', 'location' => 'header', 'locationName' => 'x-amz-copy-source', ], 'CopySourceIfMatch' => [ 'shape' => 'CopySourceIfMatch', 'location' => 'header', 'locationName' => 'x-amz-copy-source-if-match', ], 'CopySourceIfModifiedSince' => [ 'shape' => 'CopySourceIfModifiedSince', 'location' => 'header', 'locationName' => 'x-amz-copy-source-if-modified-since', ], 'CopySourceIfNoneMatch' => [ 'shape' => 'CopySourceIfNoneMatch', 'location' => 'header', 'locationName' => 'x-amz-copy-source-if-none-match', ], 'CopySourceIfUnmodifiedSince' => [ 'shape' => 'CopySourceIfUnmodifiedSince', 'location' => 'header', 'locationName' => 'x-amz-copy-source-if-unmodified-since', ], 'CopySourceRange' => [ 'shape' => 'CopySourceRange', 'location' => 'header', 'locationName' => 'x-amz-copy-source-range', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'PartNumber' => [ 'shape' => 'PartNumber', 'location' => 'querystring', 'locationName' => 'partNumber', ], 'UploadId' => [ 'shape' => 'MultipartUploadId', 'location' => 'querystring', 'locationName' => 'uploadId', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKey' => [ 'shape' => 'SSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'CopySourceSSECustomerAlgorithm' => [ 'shape' => 'CopySourceSSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-copy-source-server-side-encryption-customer-algorithm', ], 'CopySourceSSECustomerKey' => [ 'shape' => 'CopySourceSSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-copy-source-server-side-encryption-customer-key', ], 'CopySourceSSECustomerKeyMD5' => [ 'shape' => 'CopySourceSSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-copy-source-server-side-encryption-customer-key-MD5', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], ], 'UploadPartOutput' => [ 'type' => 'structure', 'members' => [ 'ServerSideEncryption' => [ 'shape' => 'ServerSideEncryption', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption', ], 'ETag' => [ 'shape' => 'ETag', 'location' => 'header', 'locationName' => 'ETag', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'SSEKMSKeyId' => [ 'shape' => 'SSEKMSKeyId', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-aws-kms-key-id', ], 'RequestCharged' => [ 'shape' => 'RequestCharged', 'location' => 'header', 'locationName' => 'x-amz-request-charged', ], ], ], 'UploadPartRequest' => [ 'type' => 'structure', 'required' => [ 'Bucket', 'Key', 'PartNumber', 'UploadId', ], 'members' => [ 'Body' => [ 'shape' => 'Body', 'streaming' => true, ], 'Bucket' => [ 'shape' => 'BucketName', 'location' => 'uri', 'locationName' => 'Bucket', ], 'ContentLength' => [ 'shape' => 'ContentLength', 'location' => 'header', 'locationName' => 'Content-Length', ], 'ContentMD5' => [ 'shape' => 'ContentMD5', 'location' => 'header', 'locationName' => 'Content-MD5', ], 'Key' => [ 'shape' => 'ObjectKey', 'location' => 'uri', 'locationName' => 'Key', ], 'PartNumber' => [ 'shape' => 'PartNumber', 'location' => 'querystring', 'locationName' => 'partNumber', ], 'UploadId' => [ 'shape' => 'MultipartUploadId', 'location' => 'querystring', 'locationName' => 'uploadId', ], 'SSECustomerAlgorithm' => [ 'shape' => 'SSECustomerAlgorithm', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-algorithm', ], 'SSECustomerKey' => [ 'shape' => 'SSECustomerKey', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key', ], 'SSECustomerKeyMD5' => [ 'shape' => 'SSECustomerKeyMD5', 'location' => 'header', 'locationName' => 'x-amz-server-side-encryption-customer-key-MD5', ], 'RequestPayer' => [ 'shape' => 'RequestPayer', 'location' => 'header', 'locationName' => 'x-amz-request-payer', ], ], 'payload' => 'Body', ], 'Value' => [ 'type' => 'string', ], 'VersionIdMarker' => [ 'type' => 'string', ], 'VersioningConfiguration' => [ 'type' => 'structure', 'members' => [ 'MFADelete' => [ 'shape' => 'MFADelete', 'locationName' => 'MfaDelete', ], 'Status' => [ 'shape' => 'BucketVersioningStatus', ], ], ], 'WebsiteConfiguration' => [ 'type' => 'structure', 'members' => [ 'ErrorDocument' => [ 'shape' => 'ErrorDocument', ], 'IndexDocument' => [ 'shape' => 'IndexDocument', ], 'RedirectAllRequestsTo' => [ 'shape' => 'RedirectAllRequestsTo', ], 'RoutingRules' => [ 'shape' => 'RoutingRules', ], ], ], 'WebsiteRedirectLocation' => [ 'type' => 'string', ], ],];
<?php
// This file was auto-generated from sdk-root/src/data/s3/2006-03-01/paginators-1.json
return [ 'pagination' => [ 'ListBuckets' => [ 'result_key' => 'Buckets', ], 'ListMultipartUploads' => [ 'limit_key' => 'MaxUploads', 'more_results' => 'IsTruncated', 'output_token' => [ 'NextKeyMarker', 'NextUploadIdMarker', ], 'input_token' => [ 'KeyMarker', 'UploadIdMarker', ], 'result_key' => [ 'Uploads', 'CommonPrefixes', ], ], 'ListObjectVersions' => [ 'more_results' => 'IsTruncated', 'limit_key' => 'MaxKeys', 'output_token' => [ 'NextKeyMarker', 'NextVersionIdMarker', ], 'input_token' => [ 'KeyMarker', 'VersionIdMarker', ], 'result_key' => [ 'Versions', 'DeleteMarkers', 'CommonPrefixes', ], ], 'ListObjects' => [ 'more_results' => 'IsTruncated', 'limit_key' => 'MaxKeys', 'output_token' => 'NextMarker || Contents[-1].Key', 'input_token' => 'Marker', 'result_key' => [ 'Contents', 'CommonPrefixes', ], ], 'ListObjectsV2' => [ 'limit_key' => 'MaxKeys', 'output_token' => 'NextContinuationToken', 'input_token' => 'ContinuationToken', 'result_key' => [ 'Contents', 'CommonPrefixes', ], ], 'ListParts' => [ 'more_results' => 'IsTruncated', 'limit_key' => 'MaxParts', 'output_token' => 'NextPartNumberMarker', 'input_token' => 'PartNumberMarker', 'result_key' => 'Parts', ], ],];
<?php
// This file was auto-generated from sdk-root/src/data/s3/2006-03-01/waiters-1.json
return [ 'waiters' => [ '__default__' => [ 'interval' => 5, 'max_attempts' => 20, ], 'BucketExists' => [ 'operation' => 'HeadBucket', 'ignore_errors' => [ 'NoSuchBucket', ], 'success_type' => 'output', ], 'BucketNotExists' => [ 'operation' => 'HeadBucket', 'success_type' => 'error', 'success_value' => 'NoSuchBucket', ], 'ObjectExists' => [ 'operation' => 'HeadObject', 'ignore_errors' => [ 'NoSuchKey', ], 'success_type' => 'output', ], 'ObjectNotExists' => [ 'operation' => 'HeadObject', 'success_type' => 'error', 'success_value' => 'NoSuchKey', ], ],];
<?php
// This file was auto-generated from sdk-root/src/data/s3/2006-03-01/waiters-2.json
return [ 'version' => 2, 'waiters' => [ 'BucketExists' => [ 'delay' => 5, 'operation' => 'HeadBucket', 'maxAttempts' => 20, 'acceptors' => [ [ 'expected' => 200, 'matcher' => 'status', 'state' => 'success', ], [ 'expected' => 301, 'matcher' => 'status', 'state' => 'success', ], [ 'expected' => 403, 'matcher' => 'status', 'state' => 'success', ], [ 'expected' => 404, 'matcher' => 'status', 'state' => 'retry', ], ], ], 'BucketNotExists' => [ 'delay' => 5, 'operation' => 'HeadBucket', 'maxAttempts' => 20, 'acceptors' => [ [ 'expected' => 404, 'matcher' => 'status', 'state' => 'success', ], ], ], 'ObjectExists' => [ 'delay' => 5, 'operation' => 'HeadObject', 'maxAttempts' => 20, 'acceptors' => [ [ 'expected' => 200, 'matcher' => 'status', 'state' => 'success', ], [ 'expected' => 404, 'matcher' => 'status', 'state' => 'retry', ], ], ], 'ObjectNotExists' => [ 'delay' => 5, 'operation' => 'HeadObject', 'maxAttempts' => 20, 'acceptors' => [ [ 'expected' => 404, 'matcher' => 'status', 'state' => 'success', ], ], ], ],];
<?php
namespace Aws;

use Doctrine\Common\Cache\Cache;

class DoctrineCacheAdapter implements CacheInterface, Cache
{
    /** @var Cache */
    private $cache;

    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }

    public function get($key)
    {
        return $this->cache->fetch($key);
    }

    public function fetch($key)
    {
        return $this->get($key);
    }

    public function set($key, $value, $ttl = 0)
    {
        return $this->cache->save($key, $value, $ttl);
    }

    public function save($key, $value, $ttl = 0)
    {
        return $this->set($key, $value, $ttl);
    }

    public function remove($key)
    {
        return $this->cache->delete($key);
    }

    public function delete($key)
    {
        return $this->remove($key);
    }

    public function contains($key)
    {
        return $this->cache->contains($key);
    }

    public function getStats()
    {
        return $this->cache->getStats();
    }
}
<?php
namespace Aws\Endpoint;

use Aws\Exception\UnresolvedEndpointException;

/**
 * Endpoint providers.
 *
 * An endpoint provider is a function that accepts a hash of endpoint options,
 * including but not limited to "service" and "region" key value pairs. The
 * endpoint provider function returns a hash of endpoint data, which MUST
 * include an "endpoint" key value pair that represents the resolved endpoint
 * or NULL if an endpoint cannot be determined.
 *
 * You can wrap your calls to an endpoint provider with the
 * {@see EndpointProvider::resolve} function to ensure that an endpoint hash is
 * created. If an endpoint hash is not created, then the resolve() function
 * will throw an {@see Aws\Exception\UnresolvedEndpointException}.
 *
 *     use Aws\Endpoint\EndpointProvider;
 *     $provider = EndpointProvider::defaultProvider();
 *     // Returns an array or NULL.
 *     $endpoint = $provider(['service' => 'ec2', 'region' => 'us-west-2']);
 *     // Returns an endpoint array or throws.
 *     $endpoint = EndpointProvider::resolve($provider, [
 *         'service' => 'ec2',
 *         'region'  => 'us-west-2'
 *     ]);
 *
 * You can compose multiple providers into a single provider using
 * {@see Aws\or_chain}. This function accepts providers as arguments and
 * returns a new function that will invoke each provider until a non-null value
 * is returned.
 *
 *     $a = function (array $args) {
 *         if ($args['region'] === 'my-test-region') {
 *             return ['endpoint' => 'http://localhost:123/api'];
 *         }
 *     };
 *     $b = EndpointProvider::defaultProvider();
 *     $c = \Aws\or_chain($a, $b);
 *     $config = ['service' => 'ec2', 'region' => 'my-test-region'];
 *     $res = $c($config);  // $a handles this.
 *     $config['region'] = 'us-west-2';
 *     $res = $c($config); // $b handles this.
 */
class EndpointProvider
{
    /**
     * Resolves and endpoint provider and ensures a non-null return value.
     *
     * @param callable $provider Provider function to invoke.
     * @param array    $args     Endpoint arguments to pass to the provider.
     *
     * @return array
     * @throws UnresolvedEndpointException
     */
    public static function resolve(callable $provider, array $args = [])
    {
        $result = $provider($args);
        if (is_array($result)) {
            return $result;
        }

        throw new UnresolvedEndpointException(
            'Unable to resolve an endpoint using the provider arguments: '
            . json_encode($args) . '. Note: you can provide an "endpoint" '
            . 'option to a client constructor to bypass invoking an endpoint '
            . 'provider.');
    }

    /**
     * Creates and returns the default SDK endpoint provider.
     *
     * @deprecated Use an instance of \Aws\Endpoint\Partition instead.
     *
     * @return callable
     */
    public static function defaultProvider()
    {
        return PartitionEndpointProvider::defaultProvider();
    }

    /**
     * Creates and returns an endpoint provider that uses patterns from an
     * array.
     *
     * @param array $patterns Endpoint patterns
     *
     * @return callable
     */
    public static function patterns(array $patterns)
    {
        return new PatternEndpointProvider($patterns);
    }
}
<?php
namespace Aws\Endpoint;

use InvalidArgumentException as Iae;

/**
 * Default implementation of an AWS partition.
 */
final class Partition implements PartitionInterface
{
    private $data;

    /**
     * The partition constructor accepts the following options:
     *
     * - `partition`: (string, required) The partition name as specified in an
     *   ARN (e.g., `aws`)
     * - `partitionName`: (string) The human readable name of the partition
     *   (e.g., "AWS Standard")
     * - `dnsSuffix`: (string, required) The DNS suffix of the partition. This
     *   value is used to determine how endpoints in the partition are resolved.
     * - `regionRegex`: (string) A PCRE regular expression that specifies the
     *   pattern that region names in the endpoint adhere to.
     * - `regions`: (array, required) A map of the regions in the partition.
     *   Each key is the region as present in a hostname (e.g., `us-east-1`),
     *   and each value is a structure containing region information.
     * - `defaults`: (array) A map of default key value pairs to apply to each
     *   endpoint of the partition. Any value in an `endpoint` definition will
     *   supersede any values specified in `defaults`.
     * - `services`: (array, required) A map of service endpoint prefix name
     *   (the value found in a hostname) to information about the service.
     *
     * @param array $definition
     *
     * @throws Iae if any required options are missing
     */
    public function __construct(array $definition)
    {
        foreach (['partition', 'regions', 'services', 'dnsSuffix'] as $key) {
            if (!isset($definition[$key])) {
                throw new Iae("Partition missing required $key field");
            }
        }

        $this->data = $definition;
    }

    public function getName()
    {
        return $this->data['partition'];
    }

    public function isRegionMatch($region, $service)
    {
        if (isset($this->data['regions'][$region])
            || isset($this->data['services'][$service]['endpoints'][$region])
        ) {
            return true;
        }

        if (isset($this->data['regionRegex'])) {
            return (bool) preg_match(
                "@{$this->data['regionRegex']}@",
                $region
            );
        }

        return false;
    }

    public function getAvailableEndpoints(
        $service,
        $allowNonRegionalEndpoints = false
    ) {
        if ($this->isServicePartitionGlobal($service)) {
            return [$this->getPartitionEndpoint($service)];
        }

        if (isset($this->data['services'][$service]['endpoints'])) {
            $serviceRegions = array_keys(
                $this->data['services'][$service]['endpoints']
            );

            return $allowNonRegionalEndpoints
                ? $serviceRegions
                : array_intersect($serviceRegions, array_keys(
                    $this->data['regions']
                ));
        }

        return [];
    }

    public function __invoke(array $args = [])
    {
        $service = isset($args['service']) ? $args['service'] : '';
        $region = isset($args['region']) ? $args['region'] : '';
        $scheme = isset($args['scheme']) ? $args['scheme'] : 'https';
        $data = $this->getEndpointData($service, $region);

        return [
            'endpoint' => "{$scheme}://" . $this->formatEndpoint(
                    isset($data['hostname']) ? $data['hostname'] : '',
                    $service,
                    $region
                ),
            'signatureVersion' => $this->getSignatureVersion($data),
            'signingRegion' => isset($data['credentialScope']['region'])
                ? $data['credentialScope']['region']
                : $region,
            'signingName' => isset($data['credentialScope']['service'])
                ? $data['credentialScope']['service']
                : $service,
        ];
    }

    private function getEndpointData($service, $region)
    {

        $resolved = $this->resolveRegion($service, $region);
        $data = isset($this->data['services'][$service]['endpoints'][$resolved])
            ? $this->data['services'][$service]['endpoints'][$resolved]
            : [];
        $data += isset($this->data['services'][$service]['defaults'])
            ? $this->data['services'][$service]['defaults']
            : [];
        $data += isset($this->data['defaults'])
            ? $this->data['defaults']
            : [];

        return $data;
    }

    private function getSignatureVersion(array $data)
    {
        static $supportedBySdk = [
            's3v4',
            'v4',
            'anonymous',
        ];

        $possibilities = array_intersect(
            $supportedBySdk,
            isset($data['signatureVersions'])
                ? $data['signatureVersions']
                : ['v4']
        );

        return array_shift($possibilities);
    }

    private function resolveRegion($service, $region)
    {
        if ($this->isServicePartitionGlobal($service)) {
            return $this->getPartitionEndpoint($service);
        }

        return $region;
    }

    private function isServicePartitionGlobal($service)
    {
        return isset($this->data['services'][$service]['isRegionalized'])
            && false === $this->data['services'][$service]['isRegionalized'];
    }

    private function getPartitionEndpoint($service)
    {
        return $this->data['services'][$service]['partitionEndpoint'];
    }

    private function formatEndpoint($template, $service, $region)
    {
        return strtr($template, [
            '{service}' => $service,
            '{region}' => $region,
            '{dnsSuffix}' => $this->data['dnsSuffix'],
        ]);
    }
}
<?php
namespace Aws\Endpoint;

class PartitionEndpointProvider
{
    /** @var Partition[] */
    private $partitions;
    /** @var string */
    private $defaultPartition;

    public function __construct(array $partitions, $defaultPartition = 'aws')
    {
        $this->partitions = array_map(function (array $definition) {
            return new Partition($definition);
        }, array_values($partitions));
        $this->defaultPartition = $defaultPartition;
    }

    public function __invoke(array $args = [])
    {
        $partition = $this->getPartition(
            isset($args['region']) ? $args['region'] : '',
            isset($args['service']) ? $args['service'] : ''
        );

        return $partition($args);
    }

    /**
     * Returns the partition containing the provided region or the default
     * partition if no match is found.
     *
     * @param string $region
     * @param string $service
     *
     * @return Partition
     */
    public function getPartition($region, $service)
    {
        foreach ($this->partitions as $partition) {
            if ($partition->isRegionMatch($region, $service)) {
                return $partition;
            }
        }

        return $this->getPartitionByName($this->defaultPartition);
    }

    /**
     * Returns the partition with the provided name or null if no partition with
     * the provided name can be found.
     *
     * @param string $name
     * 
     * @return Partition|null
     */
    public function getPartitionByName($name)
    {
        foreach ($this->partitions as $partition) {
            if ($name === $partition->getName()) {
                return $partition;
            }
        }
    }

    /**
     * Creates and returns the default SDK partition provider.
     *
     * @return PartitionEndpointProvider
     */
    public static function defaultProvider()
    {
        $data = \Aws\load_compiled_json(__DIR__ . '/../data/endpoints.json');

        return new self($data['partitions']);
    }
}
<?php
namespace Aws\Endpoint;

/**
 * Represents a section of the AWS cloud.
 */
interface PartitionInterface
{
    /**
     * Returns the partition's short name, e.g., 'aws,' 'aws-cn,' or
     * 'aws-us-gov.'
     *
     * @return string
     */
    public function getName();

    /**
     * Determine if this partition contains the provided region. Include the
     * name of the service to inspect non-regional endpoints
     *
     * @param string $region
     * @param string $service
     *
     * @return bool
     */
    public function isRegionMatch($region, $service);

    /**
     * Return the endpoints supported by a given service.
     *
     * @param string    $service                    Identifier of the service
     *                                              whose endpoints should be
     *                                              listed (e.g., 's3' or 'ses')
     * @param bool      $allowNonRegionalEndpoints  Set to `true` to include
     *                                              endpoints that are not AWS
     *                                              regions (e.g., 'local' for
     *                                              DynamoDB or
     *                                              'fips-us-gov-west-1' for S3)
     * 
     * @return string[]
     */
    public function getAvailableEndpoints(
        $service,
        $allowNonRegionalEndpoints = false
    );

    /**
     * A partition must be invokable as an endpoint provider.
     *
     * @see EndpointProvider
     * 
     * @param array $args
     * @return array
     */
    public function __invoke(array $args = []);
}
<?php
namespace Aws\Endpoint;

/**
 * Provides endpoints based on an endpoint pattern configuration array.
 */
class PatternEndpointProvider
{
    /** @var array */
    private $patterns;

    /**
     * @param array $patterns Hash of endpoint patterns mapping to endpoint
     *                        configurations.
     */
    public function __construct(array $patterns)
    {
        $this->patterns = $patterns;
    }

    public function __invoke(array $args = [])
    {
        $service = isset($args['service']) ? $args['service'] : '';
        $region = isset($args['region']) ? $args['region'] : '';
        $keys = ["{$region}/{$service}", "{$region}/*", "*/{$service}", "*/*"];

        foreach ($keys as $key) {
            if (isset($this->patterns[$key])) {
                return $this->expand(
                    $this->patterns[$key],
                    isset($args['scheme']) ? $args['scheme'] : 'https',
                    $service,
                    $region
                );
            }
        }

        return null;
    }

    private function expand(array $config, $scheme, $service, $region)
    {
        $config['endpoint'] = $scheme . '://'
            . strtr($config['endpoint'], [
                '{service}' => $service,
                '{region}'  => $region
            ]);

        return $config;
    }
}
<?php
namespace Aws\Exception;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\RequestInterface;
use Aws\CommandInterface;
use Aws\ResultInterface;

/**
 * Represents an AWS exception that is thrown when a command fails.
 */
class AwsException extends \RuntimeException
{
    /** @var ResponseInterface */
    private $response;
    private $request;
    private $result;
    private $command;
    private $requestId;
    private $errorType;
    private $errorCode;
    private $connectionError;
    private $transferInfo;

    /**
     * @param string           $message Exception message
     * @param CommandInterface $command
     * @param array            $context Exception context
     * @param \Exception       $previous  Previous exception (if any)
     */
    public function __construct(
        $message,
        CommandInterface $command,
        array $context = [],
        \Exception $previous = null
    ) {
        $this->command = $command;
        $this->response = isset($context['response']) ? $context['response'] : null;
        $this->request = isset($context['request']) ? $context['request'] : null;
        $this->requestId = isset($context['request_id'])
            ? $context['request_id']
            : null;
        $this->errorType = isset($context['type']) ? $context['type'] : null;
        $this->errorCode = isset($context['code']) ? $context['code'] : null;
        $this->connectionError = !empty($context['connection_error']);
        $this->result = isset($context['result']) ? $context['result'] : null;
        $this->transferInfo = isset($context['transfer_stats'])
            ? $context['transfer_stats']
            : [];
        parent::__construct($message, 0, $previous);
    }

    public function __toString()
    {
        if (!$this->getPrevious()) {
            return parent::__toString();
        }

        // PHP strangely shows the innermost exception first before the outer
        // exception message. It also has a default character limit for
        // exception message strings such that the "next" exception (this one)
        // might not even get shown, causing developers to attempt to catch
        // the inner exception instead of the actual exception because they
        // can't see the outer exception's __toString output.
        return sprintf(
            "exception '%s' with message '%s'\n\n%s",
            get_class($this),
            $this->getMessage(),
            parent::__toString()
        );
    }

    /**
     * Get the command that was executed.
     *
     * @return CommandInterface
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * Get the sent HTTP request if any.
     *
     * @return RequestInterface|null
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * Get the received HTTP response if any.
     *
     * @return ResponseInterface|null
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Get the result of the exception if available
     *
     * @return ResultInterface|null
     */
    public function getResult()
    {
        return $this->result;
    }

    /**
     * Returns true if this is a connection error.
     *
     * @return bool
     */
    public function isConnectionError()
    {
        return $this->connectionError;
    }

    /**
     * If available, gets the HTTP status code of the corresponding response
     *
     * @return int|null
     */
    public function getStatusCode()
    {
        return $this->response ? $this->response->getStatusCode() : null;
    }

    /**
     * Get the request ID of the error. This value is only present if a
     * response was received and is not present in the event of a networking
     * error.
     *
     * @return string|null Returns null if no response was received
     */
    public function getAwsRequestId()
    {
        return $this->requestId;
    }

    /**
     * Get the AWS error type.
     *
     * @return string|null Returns null if no response was received
     */
    public function getAwsErrorType()
    {
        return $this->errorType;
    }

    /**
     * Get the AWS error code.
     *
     * @return string|null Returns null if no response was received
     */
    public function getAwsErrorCode()
    {
        return $this->errorCode;
    }

    /**
     * Get all transfer information as an associative array if no $name
     * argument is supplied, or gets a specific transfer statistic if
     * a $name attribute is supplied (e.g., 'retries_attempted').
     *
     * @param string $name Name of the transfer stat to retrieve
     *
     * @return mixed|null|array
     */
    public function getTransferInfo($name = null)
    {
        if (!$name) {
            return $this->transferInfo;
        }

        return isset($this->transferInfo[$name])
            ? $this->transferInfo[$name]
            : null;
    }

    /**
     * Replace the transfer information associated with an exception.
     *
     * @param array $info
     */
    public function setTransferInfo(array $info)
    {
        $this->transferInfo = $info;
    }
}
<?php
namespace Aws\Exception;

class CouldNotCreateChecksumException extends \RuntimeException
{
    public function __construct($algorithm, \Exception $previous = null)
    {
        $prefix = $algorithm === 'md5' ? "An" : "A";
        parent::__construct("{$prefix} {$algorithm} checksum could not be "
            . "calculated for the provided upload body, because it was not "
            . "seekable. To prevent this error you can either 1) include the "
            . "ContentMD5 or ContentSHA256 parameters with your request, 2) "
            . "use a seekable stream for the body, or 3) wrap the non-seekable "
            . "stream in a GuzzleHttp\\Psr7\\CachingStream object. You "
            . "should be careful though and remember that the CachingStream "
            . "utilizes PHP temp streams. This means that the stream will be "
            . "temporarily stored on the local disk.", 0, $previous);
    }
}
<?php
namespace Aws\Exception;

class CredentialsException extends \RuntimeException {}
<?php
namespace Aws\Exception;

use Aws\Multipart\UploadState;

class MultipartUploadException extends \RuntimeException
{
    /** @var UploadState State of the erroneous transfer */
    private $state;

    /**
     * @param UploadState      $state Upload state at time of the exception.
     * @param \Exception|array $prev  Exception being thrown.
     */
    public function __construct(UploadState $state, $prev = null) {
        $msg = 'An exception occurred while performing a multipart upload';

        if (is_array($prev)) {
            $msg = strtr($msg, ['performing' => 'uploading parts to']);
            $msg .= ". The following parts had errors:\n";
            /** @var $error AwsException */
            foreach ($prev as $part => $error) {
                $msg .= "- Part {$part}: " . $error->getMessage(). "\n";
            }
        } elseif ($prev instanceof AwsException) {
            switch ($prev->getCommand()->getName()) {
                case 'CreateMultipartUpload':
                case 'InitiateMultipartUpload':
                    $action = 'initiating';
                    break;
                case 'CompleteMultipartUpload':
                    $action = 'completing';
                    break;
            }
            if (isset($action)) {
                $msg = strtr($msg, ['performing' => $action]);
            }
            $msg .= ": {$prev->getMessage()}";
        }

        if (!$prev instanceof \Exception) {
            $prev = null;
        }

        parent::__construct($msg, 0, $prev);
        $this->state = $state;
    }

    /**
     * Get the state of the transfer
     *
     * @return UploadState
     */
    public function getState()
    {
        return $this->state;
    }
}
<?php
namespace Aws\Exception;

class UnresolvedApiException extends \RuntimeException {}
<?php
namespace Aws\Exception;

class UnresolvedEndpointException extends \RuntimeException {}
<?php
namespace Aws\Exception;

class UnresolvedSignatureException extends \RuntimeException {}
<?php
namespace Aws;

use Psr\Http\Message\RequestInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\FulfilledPromise;

//-----------------------------------------------------------------------------
// Functional functions
//-----------------------------------------------------------------------------

/**
 * Returns a function that always returns the same value;
 *
 * @param mixed $value Value to return.
 *
 * @return callable
 */
function constantly($value)
{
    return function () use ($value) { return $value; };
}

/**
 * Filters values that do not satisfy the predicate function $pred.
 *
 * @param mixed    $iterable Iterable sequence of data.
 * @param callable $pred Function that accepts a value and returns true/false
 *
 * @return \Generator
 */
function filter($iterable, callable $pred)
{
    foreach ($iterable as $value) {
        if ($pred($value)) {
            yield ($value);
        }
    }
}

/**
 * Applies a map function $f to each value in a collection.
 *
 * @param mixed    $iterable Iterable sequence of data.
 * @param callable $f        Map function to apply.
 *
 * @return \Generator
 */
function map($iterable, callable $f)
{
    foreach ($iterable as $value) {
        yield ($f($value));
    }
}

/**
 * Creates a generator that iterates over a sequence, then iterates over each
 * value in the sequence and yields the application of the map function to each
 * value.
 *
 * @param mixed    $iterable Iterable sequence of data.
 * @param callable $f        Map function to apply.
 *
 * @return \Generator
 */
function flatmap($iterable, callable $f)
{
    foreach (map($iterable, $f) as $outer) {
        foreach ($outer as $inner) {
            yield ($inner);
        }
    }
}

/**
 * Partitions the input sequence into partitions of the specified size.
 *
 * @param mixed    $iterable Iterable sequence of data.
 * @param int $size Size to make each partition (except possibly the last chunk)
 *
 * @return \Generator
 */
function partition($iterable, $size)
{
    $buffer = [];
    foreach ($iterable as $value) {
        $buffer[] = $value;
        if (count($buffer) === $size) {
            yield ($buffer);
            $buffer = [];
        }
    }

    if ($buffer) {
        yield ($buffer);
    }
}

/**
 * Returns a function that invokes the provided variadic functions one
 * after the other until one of the functions returns a non-null value.
 * The return function will call each passed function with any arguments it
 * is provided.
 *
 *     $a = function ($x, $y) { return null; };
 *     $b = function ($x, $y) { return $x + $y; };
 *     $fn = \Aws\or_chain($a, $b);
 *     echo $fn(1, 2); // 3
 *
 * @return callable
 */
function or_chain()
{
    $fns = func_get_args();
    return function () use ($fns) {
        $args = func_get_args();
        foreach ($fns as $fn) {
            $result = $args ? call_user_func_array($fn, $args) : $fn();
            if ($result) {
                return $result;
            }
        }
        return null;
    };
}

//-----------------------------------------------------------------------------
// JSON compiler and loading functions
//-----------------------------------------------------------------------------

/**
 * Loads a compiled JSON file from a PHP file.
 *
 * If the JSON file has not been cached to disk as a PHP file, it will be loaded
 * from the JSON source file and returned.
 *
 * @param string $path Path to the JSON file on disk
 *
 * @return mixed Returns the JSON decoded data. Note that JSON objects are
 *     decoded as associative arrays.
 */
function load_compiled_json($path)
{
    if ($compiled = @include("$path.php")) {
        return $compiled;
    }

    if (!file_exists($path)) {
        throw new \InvalidArgumentException(
            sprintf("File not found: %s", $path)
        );
    }

    return json_decode(file_get_contents($path), true);
}

/**
 * No-op
 */
function clear_compiled_json()
{
    // pass
}

//-----------------------------------------------------------------------------
// Directory iterator functions.
//-----------------------------------------------------------------------------

/**
 * Iterates over the files in a directory and works with custom wrappers.
 *
 * @param string   $path Path to open (e.g., "s3://foo/bar").
 * @param resource $context Stream wrapper context.
 *
 * @return \Generator Yields relative filename strings.
 */
function dir_iterator($path, $context = null)
{
    $dh = $context ? opendir($path, $context) : opendir($path);
    if (!$dh) {
        throw new \InvalidArgumentException('File not found: ' . $path);
    }
    while (($file = readdir($dh)) !== false) {
        yield ($file);
    }
    closedir($dh);
}

/**
 * Returns a recursive directory iterator that yields absolute filenames.
 *
 * This iterator is not broken like PHP's built-in DirectoryIterator (which
 * will read the first file from a stream wrapper, then rewind, then read
 * it again).
 *
 * @param string   $path    Path to traverse (e.g., s3://bucket/key, /tmp)
 * @param resource $context Stream context options.
 *
 * @return \Generator Yields absolute filenames.
 */
function recursive_dir_iterator($path, $context = null)
{
    $invalid = ['.' => true, '..' => true];
    $pathLen = strlen($path) + 1;
    $iterator = dir_iterator($path, $context);
    $queue = [];
    do {
        while ($iterator->valid()) {
            $file = $iterator->current();
            $iterator->next();
            if (isset($invalid[basename($file)])) {
                continue;
            }
            $fullPath = "{$path}/{$file}";
            yield ($fullPath);
            if (is_dir($fullPath)) {
                $queue[] = $iterator;
                $iterator = map(
                    dir_iterator($fullPath, $context),
                    function ($file) use ($fullPath, $pathLen) {
                        return substr("{$fullPath}/{$file}", $pathLen);
                    }
                );
                continue;
            }
        }
        $iterator = array_pop($queue);
    } while ($iterator);
}

//-----------------------------------------------------------------------------
// Misc. functions.
//-----------------------------------------------------------------------------

/**
 * Debug function used to describe the provided value type and class.
 *
 * @param mixed $input
 *
 * @return string Returns a string containing the type of the variable and
 *                if a class is provided, the class name.
 */
function describe_type($input)
{
    switch (gettype($input)) {
        case 'object':
            return 'object(' . get_class($input) . ')';
        case 'array':
            return 'array(' . count($input) . ')';
        default:
            ob_start();
            var_dump($input);
            // normalize float vs double
            return str_replace('double(', 'float(', rtrim(ob_get_clean()));
    }
}

/**
 * Creates a default HTTP handler based on the available clients.
 *
 * @return callable
 */
function default_http_handler()
{
    $version = (string) ClientInterface::VERSION;
    if ($version[0] === '5') {
        return new \Aws\Handler\GuzzleV5\GuzzleHandler();
    } elseif ($version[0] === '6') {
        return new \Aws\Handler\GuzzleV6\GuzzleHandler();
    } else {
        throw new \RuntimeException('Unknown Guzzle version: ' . $version);
    }
}

/**
 * Serialize a request for a command but do not send it.
 *
 * Returns a promise that is fulfilled with the serialized request.
 *
 * @param CommandInterface $command Command to serialize.
 *
 * @return RequestInterface
 * @throws \RuntimeException
 */
function serialize(CommandInterface $command)
{
    $request = null;
    $handlerList = $command->getHandlerList();

    // Return a mock result.
    $handlerList->setHandler(
        function (CommandInterface $_, RequestInterface $r) use (&$request) {
            $request = $r;
            return new FulfilledPromise(new Result([]));
        }
    );

    call_user_func($handlerList->resolve(), $command)->wait();
    if (!$request instanceof RequestInterface) {
        throw new \RuntimeException(
            'Calling handler did not serialize request'
        );
    }

    return $request;
}

/**
 * Retrieves data for a service from the SDK's service manifest file.
 *
 * Manifest data is stored statically, so it does not need to be loaded more
 * than once per process. The JSON data is also cached in opcache.
 *
 * @param string $service Case-insensitive namespace or endpoint prefix of the
 *                        service for which you are retrieving manifest data.
 *
 * @return array
 * @throws \InvalidArgumentException if the service is not supported.
 */
function manifest($service = null)
{
    // Load the manifest and create aliases for lowercased namespaces
    static $manifest = [];
    static $aliases = [];
    if (empty($manifest)) {
        $manifest = load_compiled_json(__DIR__ . '/data/manifest.json');
        foreach ($manifest as $endpoint => $info) {
            $alias = strtolower($info['namespace']);
            if ($alias !== $endpoint) {
                $aliases[$alias] = $endpoint;
            }
        }
    }

    // If no service specified, then return the whole manifest.
    if ($service === null) {
        return $manifest;
    }

    // Look up the service's info in the manifest data.
    $service = strtolower($service);
    if (isset($manifest[$service])) {
        return $manifest[$service] + ['endpoint' => $service];
    } elseif (isset($aliases[$service])) {
        return manifest($aliases[$service]);
    } else {
        throw new \InvalidArgumentException(
            "The service \"{$service}\" is not provided by the AWS SDK for PHP."
        );
    }
}
<?php
namespace Aws\Handler\GuzzleV5;

use Aws\Sdk;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Event\EndEvent;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Message\ResponseInterface as GuzzleResponse;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7\Response as Psr7Response;
use Psr\Http\Message\RequestInterface as Psr7Request;
use Psr\Http\Message\StreamInterface as Psr7StreamInterface;

/**
 * A request handler that sends PSR-7-compatible requests with Guzzle 5.
 *
 * The handler accepts a PSR-7 Request object and an array of transfer options
 * and returns a Guzzle 6 Promise. The promise is either resolved with a
 * PSR-7 Response object or rejected with an array of error data.
 *
 * @codeCoverageIgnore
 */
class GuzzleHandler
{
    private static $validOptions = [
        'proxy'           => true,
        'verify'          => true,
        'timeout'         => true,
        'debug'           => true,
        'connect_timeout' => true,
        'stream'          => true,
        'delay'           => true,
        'sink'            => true,
    ];

    /** @var ClientInterface */
    private $client;

    /**
     * @param ClientInterface $client
     */
    public function __construct(ClientInterface $client = null)
    {
        $this->client = $client ?: new Client();
    }

    /**
     * @param Psr7Request $request
     * @param array       $options
     *
     * @return Promise\Promise
     */
    public function __invoke(Psr7Request $request, array $options = [])
    {
        // Create and send a Guzzle 5 request
        $guzzlePromise = $this->client->send(
            $this->createGuzzleRequest($request, $options)
        );

        $promise = new Promise\Promise(
            function () use ($guzzlePromise) {
                try {
                    $guzzlePromise->wait();
                } catch (\Exception $e) {
                    // The promise is already delivered when the exception is
                    // thrown, so don't rethrow it.
                }
            },
            [$guzzlePromise, 'cancel']
        );

        $guzzlePromise->then([$promise, 'resolve'], [$promise, 'reject']);

        return $promise->then(
            function (GuzzleResponse $response) {
                // Adapt the Guzzle 5 Future to a Guzzle 6 ResponsePromise.
                return $this->createPsr7Response($response);
            },
            function (Exception $exception) {
                // Reject with information about the error.
                return new Promise\RejectedPromise($this->prepareErrorData($exception));
            }
        );
    }

    private function createGuzzleRequest(Psr7Request $psrRequest, array $options)
    {
        $ringConfig = [];
        $statsCallback = isset($options['http_stats_receiver'])
            ? $options['http_stats_receiver']
            : null;
        unset($options['http_stats_receiver']);

        // Remove unsupported options.
        foreach (array_keys($options) as $key) {
            if (!isset(self::$validOptions[$key])) {
                unset($options[$key]);
            }
        }

        // Handle delay option.
        if (isset($options['delay'])) {
            $ringConfig['delay'] = $options['delay'];
            unset($options['delay']);
        }

        // Prepare sink option.
        if (isset($options['sink'])) {
            $ringConfig['save_to'] = ($options['sink'] instanceof Psr7StreamInterface)
                ? new GuzzleStream($options['sink'])
                : $options['sink'];
            unset($options['sink']);
        }

        // Ensure that all requests are async and lazy like Guzzle 6.
        $options['future'] = 'lazy';

        // Create the Guzzle 5 request from the provided PSR7 request.
        $request = $this->client->createRequest(
            $psrRequest->getMethod(),
            $psrRequest->getUri(),
            $options
        );

        if (is_callable($statsCallback)) {
            $request->getEmitter()->on(
                'end',
                function (EndEvent $event) use ($statsCallback) {
                    $statsCallback($event->getTransferInfo());
                }
            );
        }

        // For the request body, adapt the PSR stream to a Guzzle stream.
        $body = $psrRequest->getBody();
        if ($body->getSize() === 0) {
            $request->setBody(null);
        } else {
            $request->setBody(new GuzzleStream($body));
        }

        $request->setHeaders($psrRequest->getHeaders());

        $request->setHeader(
            'User-Agent',
            $request->getHeader('User-Agent')
                . ' ' . Client::getDefaultUserAgent()
        );

        // Make sure the delay is configured, if provided.
        if ($ringConfig) {
            foreach ($ringConfig as $k => $v) {
                $request->getConfig()->set($k, $v);
            }
        }

        return $request;
    }

    private function createPsr7Response(GuzzleResponse $response)
    {
        if ($body = $response->getBody()) {
            $body = new PsrStream($body);
        }

        return new Psr7Response(
            $response->getStatusCode(),
            $response->getHeaders(),
            $body,
            $response->getReasonPhrase()
        );
    }

    private function prepareErrorData(Exception $e)
    {
        $error = [
            'exception'        => $e,
            'connection_error' => false,
            'response'         => null,
        ];

        if ($e instanceof ConnectException) {
            $error['connection_error'] = true;
        }

        if ($e instanceof RequestException && $e->getResponse()) {
            $error['response'] = $this->createPsr7Response($e->getResponse());
        }

        return $error;
    }
}
<?php
namespace Aws\Handler\GuzzleV5;

use GuzzleHttp\Stream\StreamDecoratorTrait;
use GuzzleHttp\Stream\StreamInterface as GuzzleStreamInterface;
use Psr\Http\Message\StreamInterface as Psr7StreamInterface;

/**
 * Adapts a PSR-7 Stream to a Guzzle 5 Stream.
 *
 * @codeCoverageIgnore
 */
class GuzzleStream implements GuzzleStreamInterface
{
    use StreamDecoratorTrait;

    /** @var Psr7StreamInterface */
    private $stream;

    public function __construct(Psr7StreamInterface $stream)
    {
        $this->stream = $stream;
    }
}
<?php
namespace Aws\Handler\GuzzleV5;

use GuzzleHttp\Stream\StreamDecoratorTrait;
use GuzzleHttp\Stream\StreamInterface as GuzzleStreamInterface;
use Psr\Http\Message\StreamInterface as Psr7StreamInterface;

/**
 * Adapts a Guzzle 5 Stream to a PSR-7 Stream.
 *
 * @codeCoverageIgnore
 */
class PsrStream implements Psr7StreamInterface
{
    use StreamDecoratorTrait;

    /** @var GuzzleStreamInterface */
    private $stream;

    public function __construct(GuzzleStreamInterface $stream)
    {
        $this->stream = $stream;
    }

    public function rewind()
    {
        $this->stream->seek(0);
    }

    public function getContents()
    {
        return $this->stream->getContents();
    }
}
<?php
namespace Aws\Handler\GuzzleV6;

use Aws\Sdk;
use Exception;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface as Psr7Request;

/**
 * A request handler that sends PSR-7-compatible requests with Guzzle 6.
 */
class GuzzleHandler
{
    /** @var ClientInterface */
    private $client;

    /**
     * @param ClientInterface $client
     */
    public function __construct(ClientInterface $client = null)
    {
        $this->client = $client ?: new Client();
    }

    /**
     * @param Psr7Request $request
     * @param array       $options
     *
     * @return Promise\Promise
     */
    public function __invoke(Psr7Request $request, array $options = [])
    {
        $request = $request->withHeader(
            'User-Agent',
            $request->getHeaderLine('User-Agent')
                . ' ' . \GuzzleHttp\default_user_agent()
        );

        return $this->client->sendAsync($request, $this->parseOptions($options))
            ->otherwise(
                static function (\Exception $e) {
                    $error = [
                        'exception'        => $e,
                        'connection_error' => $e instanceof ConnectException,
                        'response'         => null,
                    ];

                    if ($e instanceof RequestException && $e->getResponse()) {
                        $error['response'] = $e->getResponse();
                    }

                    return new Promise\RejectedPromise($error);
                }
            );
    }

    private function parseOptions(array $options)
    {
        if (isset($options['http_stats_receiver'])) {
            $fn = $options['http_stats_receiver'];
            unset($options['http_stats_receiver']);

            $prev = isset($options['on_stats'])
                ? $options['on_stats']
                : null;

            $options['on_stats'] = static function (
                TransferStats $stats
            ) use ($fn, $prev) {
                if (is_callable($prev)) {
                    $prev($stats);
                }
                $transferStats = ['total_time' => $stats->getTransferTime()];
                $transferStats += $stats->getHandlerStats();
                $fn($transferStats);
            };
        }

        return $options;
    }
}
<?php
namespace Aws;

/**
 * Builds a single handler function from zero or more middleware functions and
 * a handler. The handler function is then used to send command objects and
 * return a promise that is resolved with an AWS result object.
 *
 * The "front" of the list is invoked before the "end" of the list. You can add
 * middleware to the front of the list using one of the "prepend" method, and
 * the end of the list using one of the "append" method. The last function
 * invoked in a handler list is the handler (a function that does not accept a
 * next handler but rather is responsible for returning a promise that is
 * fulfilled with an Aws\ResultInterface object).
 *
 * Handlers are ordered using a "step" that describes the step at which the
 * SDK is when sending a command. The available steps are:
 *
 * - init: The command is being initialized, allowing you to do things like add
 *   default options.
 * - validate: The command is being validated before it is serialized
 * - build: The command is being serialized into an HTTP request. A middleware
 *   in this step MUST serialize an HTTP request and populate the "@request"
 *   parameter of a command with the request such that it is available to
 *   subsequent middleware.
 * - sign: The request is being signed and prepared to be sent over the wire.
 *
 * Middleware can be registered with a name to allow you to easily add a
 * middleware before or after another middleware by name. This also allows you
 * to remove a middleware by name (in addition to removing by instance).
 */
class HandlerList implements \Countable
{
    const INIT = 'init';
    const VALIDATE = 'validate';
    const BUILD = 'build';
    const SIGN = 'sign';

    /** @var callable */
    private $handler;

    /** @var array */
    private $named = [];

    /** @var array */
    private $sorted;

    /** @var callable|null */
    private $interposeFn;

    /** @var array Steps (in reverse order) */
    private $steps = [
        self::SIGN     => [],
        self::BUILD    => [],
        self::VALIDATE => [],
        self::INIT     => [],
    ];

    /**
     * @param callable $handler HTTP handler.
     */
    public function __construct(callable $handler = null)
    {
        $this->handler = $handler;
    }

    /**
     * Dumps a string representation of the list.
     *
     * @return string
     */
    public function __toString()
    {
        $str = '';
        $i = 0;

        foreach (array_reverse($this->steps) as $k => $step) {
            foreach (array_reverse($step) as $j => $tuple) {
                $str .= "{$i}) Step: {$k}, ";
                if ($tuple[1]) {
                    $str .= "Name: {$tuple[1]}, ";
                }
                $str .= "Function: " . $this->debugCallable($tuple[0]) . "\n";
                $i++;
            }
        }

        if ($this->handler) {
            $str .= "{$i}) Handler: " . $this->debugCallable($this->handler) . "\n";
        }

        return $str;
    }

    /**
     * Set the HTTP handler that actually returns a response.
     *
     * @param callable $handler Function that accepts a request and array of
     *                          options and returns a Promise.
     */
    public function setHandler(callable $handler)
    {
        $this->handler = $handler;
    }

    /**
     * Returns true if the builder has a handler.
     *
     * @return bool
     */
    public function hasHandler()
    {
        return (bool) $this->handler;
    }

    /**
     * Append a middleware to the init step.
     *
     * @param callable $middleware Middleware function to add.
     * @param string   $name       Name of the middleware.
     */
    public function appendInit(callable $middleware, $name = null)
    {
        $this->add(self::INIT, $name, $middleware);
    }

    /**
     * Prepend a middleware to the init step.
     *
     * @param callable $middleware Middleware function to add.
     * @param string   $name       Name of the middleware.
     */
    public function prependInit(callable $middleware, $name = null)
    {
        $this->add(self::INIT, $name, $middleware, true);
    }

    /**
     * Append a middleware to the validate step.
     *
     * @param callable $middleware Middleware function to add.
     * @param string   $name       Name of the middleware.
     */
    public function appendValidate(callable $middleware, $name = null)
    {
        $this->add(self::VALIDATE, $name, $middleware);
    }

    /**
     * Prepend a middleware to the validate step.
     *
     * @param callable $middleware Middleware function to add.
     * @param string   $name       Name of the middleware.
     */
    public function prependValidate(callable $middleware, $name = null)
    {
        $this->add(self::VALIDATE, $name, $middleware, true);
    }

    /**
     * Append a middleware to the build step.
     *
     * @param callable $middleware Middleware function to add.
     * @param string   $name       Name of the middleware.
     */
    public function appendBuild(callable $middleware, $name = null)
    {
        $this->add(self::BUILD, $name, $middleware);
    }

    /**
     * Prepend a middleware to the build step.
     *
     * @param callable $middleware Middleware function to add.
     * @param string   $name       Name of the middleware.
     */
    public function prependBuild(callable $middleware, $name = null)
    {
        $this->add(self::BUILD, $name, $middleware, true);
    }

    /**
     * Append a middleware to the sign step.
     *
     * @param callable $middleware Middleware function to add.
     * @param string   $name       Name of the middleware.
     */
    public function appendSign(callable $middleware, $name = null)
    {
        $this->add(self::SIGN, $name, $middleware);
    }

    /**
     * Prepend a middleware to the sign step.
     *
     * @param callable $middleware Middleware function to add.
     * @param string   $name       Name of the middleware.
     */
    public function prependSign(callable $middleware, $name = null)
    {
        $this->add(self::SIGN, $name, $middleware, true);
    }

    /**
     * Add a middleware before the given middleware by name.
     *
     * @param string|callable $findName   Add before this
     * @param string          $withName   Optional name to give the middleware
     * @param callable        $middleware Middleware to add.
     */
    public function before($findName, $withName, callable $middleware)
    {
        $this->splice($findName, $withName, $middleware, true);
    }

    /**
     * Add a middleware after the given middleware by name.
     *
     * @param string|callable $findName   Add after this
     * @param string          $withName   Optional name to give the middleware
     * @param callable        $middleware Middleware to add.
     */
    public function after($findName, $withName, callable $middleware)
    {
        $this->splice($findName, $withName, $middleware, false);
    }

    /**
     * Remove a middleware by name or by instance from the list.
     *
     * @param string|callable $nameOrInstance Middleware to remove.
     */
    public function remove($nameOrInstance)
    {
        if (is_callable($nameOrInstance)) {
            $this->removeByInstance($nameOrInstance);
        } elseif (is_string($nameOrInstance)) {
            $this->removeByName($nameOrInstance);
        }
    }

    /**
     * Interpose a function between each middleware (e.g., allowing for a trace
     * through the middleware layers).
     *
     * The interpose function is a function that accepts a "step" argument as a
     * string and a "name" argument string. This function must then return a
     * function that accepts the next handler in the list. This function must
     * then return a function that accepts a CommandInterface and optional
     * RequestInterface and returns a promise that is fulfilled with an
     * Aws\ResultInterface or rejected with an Aws\Exception\AwsException
     * object.
     *
     * @param callable|null $fn Pass null to remove any previously set function
     */
    public function interpose(callable $fn = null)
    {
        $this->sorted = null;
        $this->interposeFn = $fn;
    }

    /**
     * Compose the middleware and handler into a single callable function.
     *
     * @return callable
     */
    public function resolve()
    {
        if (!($prev = $this->handler)) {
            throw new \LogicException('No handler has been specified');
        }

        if ($this->sorted === null) {
            $this->sortMiddleware();
        }

        foreach ($this->sorted as $fn) {
            $prev = $fn($prev);
        }

        return $prev;
    }

    public function count()
    {
        return count($this->steps[self::INIT])
            + count($this->steps[self::VALIDATE])
            + count($this->steps[self::BUILD])
            + count($this->steps[self::SIGN]);
    }

    /**
     * Splices a function into the middleware list at a specific position.
     *
     * @param          $findName
     * @param          $withName
     * @param callable $middleware
     * @param          $before
     */
    private function splice($findName, $withName, callable $middleware, $before)
    {
        if (!isset($this->named[$findName])) {
            throw new \InvalidArgumentException("$findName not found");
        }

        $idx = $this->sorted = null;
        $step = $this->named[$findName];

        if ($withName) {
            $this->named[$withName] = $step;
        }

        foreach ($this->steps[$step] as $i => $tuple) {
            if ($tuple[1] === $findName) {
                $idx = $i;
                break;
            }
        }

        $replacement = $before
            ? [$this->steps[$step][$idx], [$middleware, $withName]]
            : [[$middleware, $withName], $this->steps[$step][$idx]];
        array_splice($this->steps[$step], $idx, 1, $replacement);
    }

    /**
     * Provides a debug string for a given callable.
     *
     * @param array|callable $fn Function to write as a string.
     *
     * @return string
     */
    private function debugCallable($fn)
    {
        if (is_string($fn)) {
            return "callable({$fn})";
        } elseif (is_array($fn)) {
            $ele = is_string($fn[0]) ? $fn[0] : get_class($fn[0]);
            return "callable(['{$ele}', '{$fn[1]}'])";
        } else {
            return 'callable(' . spl_object_hash($fn) . ')';
        }
    }

    /**
     * Sort the middleware, and interpose if needed in the sorted list.
     */
    private function sortMiddleware()
    {
        $this->sorted = [];

        if (!$this->interposeFn) {
            foreach ($this->steps as $step) {
                foreach ($step as $fn) {
                    $this->sorted[] = $fn[0];
                }
            }
            return;
        }

        $ifn = $this->interposeFn;
        // Interpose the interposeFn into the handler stack.
        foreach ($this->steps as $stepName => $step) {
            foreach ($step as $fn) {
                $this->sorted[] = $ifn($stepName, $fn[1]);
                $this->sorted[] = $fn[0];
            }
        }
    }

    private function removeByName($name)
    {
        if (!isset($this->named[$name])) {
            return;
        }

        $this->sorted = null;
        $step = $this->named[$name];
        $this->steps[$step] = array_values(
            array_filter(
                $this->steps[$step],
                function ($tuple) use ($name) {
                    return $tuple[1] !== $name;
                }
            )
        );
    }

    private function removeByInstance(callable $fn)
    {
        foreach ($this->steps as $k => $step) {
            foreach ($step as $j => $tuple) {
                if ($tuple[0] === $fn) {
                    $this->sorted = null;
                    unset($this->named[$this->steps[$k][$j][1]]);
                    unset($this->steps[$k][$j]);
                }
            }
        }
    }

    /**
     * Add a middleware to a step.
     *
     * @param string   $step       Middleware step.
     * @param string   $name       Middleware name.
     * @param callable $middleware Middleware function to add.
     * @param bool     $prepend    Prepend instead of append.
     */
    private function add($step, $name, callable $middleware, $prepend = false)
    {
        $this->sorted = null;

        if ($prepend) {
            $this->steps[$step][] = [$middleware, $name];
        } else {
            array_unshift($this->steps[$step], [$middleware, $name]);
        }

        if ($name) {
            $this->named[$name] = $step;
        }
    }
}
<?php
namespace Aws;

/**
 * Trait implementing ToArrayInterface, \ArrayAccess, \Countable, and
 * \IteratorAggregate
 */
trait HasDataTrait
{
    /** @var array */
    private $data = [];

    public function getIterator()
    {
        return new \ArrayIterator($this->data);
    }

    /**
     * This method returns a reference to the variable to allow for indirect
     * array modification (e.g., $foo['bar']['baz'] = 'qux').
     *
     * @param $offset
     *
     * @return mixed|null
     */
    public function & offsetGet($offset)
    {
        if (isset($this->data[$offset])) {
            return $this->data[$offset];
        }

        $value = null;
        return $value;
    }

    public function offsetSet($offset, $value)
    {
        $this->data[$offset] = $value;
    }

    public function offsetExists($offset)
    {
        return isset($this->data[$offset]);
    }

    public function offsetUnset($offset)
    {
        unset($this->data[$offset]);
    }

    public function toArray()
    {
        return $this->data;
    }

    public function count()
    {
        return count($this->data);
    }
}
<?php
namespace Aws;

use GuzzleHttp\Psr7\StreamDecoratorTrait;
use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that calculates a rolling hash of the stream as it is read.
 */
class HashingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var HashInterface */
    private $hash;

    /** @var callable|null */
    private $callback;

    /**
     * @param StreamInterface $stream     Stream that is being read.
     * @param HashInterface   $hash       Hash used to calculate checksum.
     * @param callable        $onComplete Optional function invoked when the
     *                                    hash calculation is completed.
     */
    public function __construct(
        StreamInterface $stream,
        HashInterface $hash,
        callable $onComplete = null
    ) {
        $this->stream = $stream;
        $this->hash = $hash;
        $this->callback = $onComplete;
    }

    public function read($length)
    {
        $data = $this->stream->read($length);
        $this->hash->update($data);
        if ($this->eof()) {
            $result = $this->hash->complete();
            if ($this->callback) {
                call_user_func($this->callback, $result);
            }
        }

        return $data;
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        if ($offset === 0) {
            $this->hash->reset();
            return $this->stream->seek($offset);
        } else {
            // Seeking arbitrarily is not supported.
            return false;
        }
    }
}
<?php
namespace Aws;

/**
 * Interface that allows implementing various incremental hashes.
 */
interface HashInterface
{
    /**
     * Adds data to the hash.
     *
     * @param string $data Data to add to the hash
     */
    public function update($data);

    /**
     * Finalizes the incremental hash and returns the resulting digest.
     *
     * @return string
     */
    public function complete();

    /**
     * Removes all data from the hash, effectively starting a new hash.
     */
    public function reset();
}
<?php
namespace Aws;

use Psr\Http\Message\RequestInterface;
use Aws\Exception\AwsException;

/**
 * Represents a history container that is required when using the history
 * middleware.
 */
class History implements \Countable, \IteratorAggregate
{
    private $maxEntries;
    private $entries;

    /**
     * @param int $maxEntries Maximum number of entries to store.
     */
    public function __construct($maxEntries = 10)
    {
        $this->maxEntries = $maxEntries;
    }

    public function count()
    {
        return count($this->entries);
    }

    public function getIterator()
    {
        return new \ArrayIterator(array_values($this->entries));
    }

    /**
     * Get the last finished command seen by the history container.
     *
     * @return CommandInterface
     * @throws \LogicException if no commands have been seen.
     */
    public function getLastCommand()
    {
        if (!$this->entries) {
            throw new \LogicException('No commands received');
        }

        return end($this->entries)['command'];
    }

    /**
     * Get the last finished request seen by the history container.
     *
     * @return RequestInterface
     * @throws \LogicException if no requests have been seen.
     */
    public function getLastRequest()
    {
        if (!$this->entries) {
            throw new \LogicException('No requests received');
        }

        return end($this->entries)['request'];
    }

    /**
     * Get the last received result or exception.
     *
     * @return ResultInterface|AwsException
     * @throws \LogicException if no return values have been received.
     */
    public function getLastReturn()
    {
        if (!$this->entries) {
            throw new \LogicException('No entries');
        }

        $last = end($this->entries);

        if (isset($last['result'])) {
            return $last['result'];
        } elseif (isset($last['exception'])) {
            return $last['exception'];
        } else {
            throw new \LogicException('No return value for last entry.');
        }
    }

    /**
     * Initiate an entry being added to the history.
     *
     * @param CommandInterface $cmd Command be executed.
     * @param RequestInterface $req Request being sent.
     *
     * @return string Returns the ticket used to finish the entry.
     */
    public function start(CommandInterface $cmd, RequestInterface $req)
    {
        $ticket = uniqid();
        $this->entries[$ticket] = [
            'command'   => $cmd,
            'request'   => $req,
            'result'    => null,
            'exception' => null,
        ];

        return $ticket;
    }

    /**
     * Finish adding an entry to the history container.
     *
     * @param string $ticket Ticket returned from the start call.
     * @param mixed  $result The result (an exception or AwsResult).
     */
    public function finish($ticket, $result)
    {
        if (!isset($this->entries[$ticket])) {
            throw new \InvalidArgumentException('Invalid history ticket');
        } elseif (isset($this->entries[$ticket]['result'])
            || isset($this->entries[$ticket]['exception'])
        ) {
            throw new \LogicException('History entry is already finished');
        }

        if ($result instanceof \Exception) {
            $this->entries[$ticket]['exception'] = $result;
        } else {
            $this->entries[$ticket]['result'] = $result;
        }

        if (count($this->entries) >= $this->maxEntries) {
            $this->entries = array_slice($this->entries, -$this->maxEntries, null, true);
        }
    }

    /**
     * Flush the history
     */
    public function clear()
    {
        $this->entries = [];
    }

    /**
     * Converts the history to an array.
     *
     * @return array
     */
    public function toArray()
    {
        return array_values($this->entries);
    }
}
<?php
namespace Aws;

use Aws\Api\Service;
use Psr\Http\Message\RequestInterface;

/**
 * @internal Middleware that auto fills parameters with `idempotencyToken` trait
 */
class IdempotencyTokenMiddleware
{
    /** @var Service */
    private $service;
    /** @var string */
    private $bytesGenerator;
    /** @var callable */
    private $nextHandler;

    /**
     * Creates a middleware that populates operation parameter
     * with trait 'idempotencyToken' enabled with a random UUIDv4
     *
     * One of following functions needs to be available
     * in order to generate random bytes used for UUID
     * (SDK will attempt to utilize function in following order):
     *  - random_bytes (requires PHP 7.0 or above) 
     *  - openssl_random_pseudo_bytes (requires 'openssl' module enabled)
     *  - mcrypt_create_iv (requires 'mcrypt' module enabled)
     *
     * @param \Aws\Api\Service $service
     * @return callable
     */
    public static function wrap(Service $service)
    {
        return function (callable $handler) use ($service) {
            return new self($handler, $service);
        };
    }

    public function __construct(callable $nextHandler, Service $service) {
        $this->bytesGenerator = $this->checkCompatibility();
        $this->service = $service;
        $this->nextHandler = $nextHandler;
    }

    public function __invoke(
        CommandInterface $command,
        RequestInterface $request = null
    ) {
        $handler = $this->nextHandler;
        if ($this->bytesGenerator) {
            $operation = $this->service->getOperation($command->getName());
            $members = $operation->getInput()->getMembers();
            foreach ($members as $member => $value) {
                if (!empty($value->toArray()['idempotencyToken'])) {
                    $bytes = call_user_func($this->bytesGenerator, 16);
                    // populating UUIDv4 only when the parameter is not set
                    $command[$member] = $command[$member] ?: $this->getUUIDV4($bytes);
                    // only one member could have the trait enabled as identifier
                    break;
                }
            }
        }
        return $handler($command, $request);
    }

    /**
     * This function generates a random UUID v4 string,
     * which is used as auto filled token value.
     *
     * @param string $bytes 16 bytes of pseudo-random bytes
     * @return string
     * More information about UUID v4, see:
     * https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29
     * https://tools.ietf.org/html/rfc4122#page-14
     */
    private static function getUUIDV4($bytes)
    {
        // set version to 0100
        $bytes[6] = chr(ord($bytes[6]) & 0x0f | 0x40);
        // set bits 6-7 to 10
        $bytes[8] = chr(ord($bytes[8]) & 0x3f | 0x80);
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4));
    }

    /**
     * This function decides the PHP function used in
     * generating random bytes.
     *
     * @return callable|null
     */
    private function checkCompatibility()
    {
        if (function_exists('random_bytes')) {
            return 'random_bytes';
        } elseif (function_exists('openssl_random_pseudo_bytes')) {
            return 'openssl_random_pseudo_bytes';
        } elseif (function_exists('mcrypt_create_iv')) {
            return 'mcrypt_create_iv';
        }
    }
}
<?php
namespace Aws;

/**
 * Loads JSON files and compiles them into PHP arrays.
 *
 * @internal Please use json_decode instead.
 * @deprecated
 */
class JsonCompiler
{
    const CACHE_ENV = 'AWS_PHP_CACHE_DIR';

    /**
     * Loads a JSON file from cache or from the JSON file directly.
     *
     * @param string $path Path to the JSON file to load.
     *
     * @return mixed
     */
    public function load($path)
    {
        return load_compiled_json($path);
    }
}
<?php
namespace Aws;

/**
 * Simple in-memory LRU cache that limits the number of cached entries.
 *
 * The LRU cache is implemented using PHP's ordered associative array. When
 * accessing an element, the element is removed from the hash and re-added to
 * ensure that recently used items are always at the end of the list while
 * least recently used are at the beginning. When a value is added to the
 * cache, if the number of cached items exceeds the allowed number, the first
 * N number of items are removed from the array.
 */
class LruArrayCache implements CacheInterface
{
    /** @var int */
    private $maxItems;

    /** @var array */
    private $items;

    /**
     * @param int $maxItems Maximum number of allowed cache items.
     */
    public function __construct($maxItems = 1000)
    {
        $this->maxItems = $maxItems;
    }

    public function get($key)
    {
        if (!isset($this->items[$key])) {
            return null;
        }

        $entry = $this->items[$key];

        // Ensure the item is not expired.
        if (!$entry[1] || time() < $entry[1]) {
            // LRU: remove the item and push it to the end of the array.
            unset($this->items[$key]);
            $this->items[$key] = $entry;
            return $entry[0];
        }

        unset($this->items[$key]);
        return null;
    }

    public function set($key, $value, $ttl = 0)
    {
        // Only call time() if the TTL is not 0/false/null
        $ttl = $ttl ? time() + $ttl : 0;
        $this->items[$key] = [$value, $ttl];

        // Determine if there are more items in the cache than allowed.
        $diff = count($this->items) - $this->maxItems;

        // Clear out least recently used items.
        if ($diff > 0) {
            // Reset to the beginning of the array and begin unsetting.
            reset($this->items);
            for ($i = 0; $i < $diff; $i++) {
                unset($this->items[key($this->items)]);
                next($this->items);
            }
        }
    }

    public function remove($key)
    {
        unset($this->items[$key]);
    }
}
<?php
namespace Aws;

use Aws\Api\Service;
use Aws\Api\Validator;
use Aws\Credentials\CredentialsInterface;
use Aws\Exception\AwsException;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\LazyOpenStream;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

final class Middleware
{
    /**
     * Middleware used to allow a command parameter (e.g., "SourceFile") to
     * be used to specify the source of data for an upload operation.
     *
     * @param Service $api
     * @param string  $bodyParameter
     * @param string  $sourceParameter
     *
     * @return callable
     */
    public static function sourceFile(
        Service $api,
        $bodyParameter = 'Body',
        $sourceParameter = 'SourceFile'
    ) {
        return function (callable $handler) use (
            $api,
            $bodyParameter,
            $sourceParameter
        ) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null)
            use (
                $handler,
                $api,
                $bodyParameter,
                $sourceParameter
            ) {
                $operation = $api->getOperation($command->getName());
                $source = $command[$sourceParameter];

                if ($source !== null
                    && $operation->getInput()->hasMember($bodyParameter)
                ) {
                    $command[$bodyParameter] = new LazyOpenStream($source, 'r');
                    unset($command[$sourceParameter]);
                }

                return $handler($command, $request);
            };
        };
    }

    /**
     * Adds a middleware that uses client-side validation.
     *
     * @param Service $api API being accessed.
     *
     * @return callable
     */
    public static function validation(Service $api, Validator $validator = null)
    {
        $validator = $validator ?: new Validator();
        return function (callable $handler) use ($api, $validator) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($api, $validator, $handler) {
                $operation = $api->getOperation($command->getName());
                $validator->validate(
                    $command->getName(),
                    $operation->getInput(),
                    $command->toArray()
                );
                return $handler($command, $request);
            };
        };
    }

    /**
     * Builds an HTTP request for a command.
     *
     * @param callable $serializer Function used to serialize a request for a
     *                             command.
     * @return callable
     */
    public static function requestBuilder(callable $serializer)
    {
        return function (callable $handler) use ($serializer) {
            return function (CommandInterface $command) use ($serializer, $handler) {
                return $handler($command, $serializer($command));
            };
        };
    }

    /**
     * Creates a middleware that signs requests for a command.
     *
     * @param callable $credProvider      Credentials provider function that
     *                                    returns a promise that is resolved
     *                                    with a CredentialsInterface object.
     * @param callable $signatureFunction Function that accepts a Command
     *                                    object and returns a
     *                                    SignatureInterface.
     *
     * @return callable
     */
    public static function signer(callable $credProvider, callable $signatureFunction)
    {
        return function (callable $handler) use ($signatureFunction, $credProvider) {
            return function (
                CommandInterface $command,
                RequestInterface $request
            ) use ($handler, $signatureFunction, $credProvider) {
                $signer = $signatureFunction($command);
                return $credProvider()->then(
                    function (CredentialsInterface $creds)
                    use ($handler, $command, $signer, $request) {
                        return $handler(
                            $command,
                            $signer->signRequest($request, $creds)
                        );
                    }
                );
            };
        };
    }

    /**
     * Creates a middleware that invokes a callback at a given step.
     *
     * The tap callback accepts a CommandInterface and RequestInterface as
     * arguments but is not expected to return a new value or proxy to
     * downstream middleware. It's simply a way to "tap" into the handler chain
     * to debug or get an intermediate value.
     *
     * @param callable $fn Tap function
     *
     * @return callable
     */
    public static function tap(callable $fn)
    {
        return function (callable $handler) use ($fn) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler, $fn) {
                $fn($command, $request);
                return $handler($command, $request);
            };
        };
    }

    /**
     * Middleware wrapper function that retries requests based on the boolean
     * result of invoking the provided "decider" function.
     *
     * If no delay function is provided, a simple implementation of exponential
     * backoff will be utilized.
     *
     * @param callable $decider Function that accepts the number of retries,
     *                          a request, [result], and [exception] and
     *                          returns true if the command is to be retried.
     * @param callable $delay   Function that accepts the number of retries and
     *                          returns the number of milliseconds to delay.
     * @param bool $stats       Whether to collect statistics on retries and the
     *                          associated delay.
     *
     * @return callable
     */
    public static function retry(
        callable $decider = null,
        callable $delay = null,
        $stats = false
    ) {
        $decider = $decider ?: RetryMiddleware::createDefaultDecider();
        $delay = $delay ?: [RetryMiddleware::class, 'exponentialDelay'];

        return function (callable $handler) use ($decider, $delay, $stats) {
            return new RetryMiddleware($decider, $delay, $handler, $stats);
        };
    }
    /**
     * Middleware wrapper function that adds an invocation id header to
     * requests, which is only applied after the build step.
     *
     * This is a uniquely generated UUID to identify initial and subsequent
     * retries as part of a complete request lifecycle.
     *
     * @return callable
     */
    public static function invocationId()
    {
        return function (callable $handler) {
            return function (
                CommandInterface $command,
                RequestInterface $request
            ) use ($handler){
                return $handler($command, $request->withHeader(
                    'aws-sdk-invocation-id',
                    md5(uniqid(gethostname(), true))
                ));
            };
        };
    }
    /**
     * Middleware wrapper function that adds a Content-Type header to requests.
     * This is only done when the Content-Type has not already been set, and the
     * request body's URI is available. It then checks the file extension of the
     * URI to determine the mime-type.
     *
     * @param array $operations Operations that Content-Type should be added to.
     *
     * @return callable
     */
    public static function contentType(array $operations)
    {
        return function (callable $handler) use ($operations) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler, $operations) {
                if (!$request->hasHeader('Content-Type')
                    && in_array($command->getName(), $operations, true)
                    && ($uri = $request->getBody()->getMetadata('uri'))
                ) {
                    $request = $request->withHeader(
                        'Content-Type',
                        Psr7\mimetype_from_filename($uri) ?: 'application/octet-stream'
                    );
                }

                return $handler($command, $request);
            };
        };
    }

    /**
     * Tracks command and request history using a history container.
     *
     * This is useful for testing.
     *
     * @param History $history History container to store entries.
     *
     * @return callable
     */
    public static function history(History $history)
    {
        return function (callable $handler) use ($history) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler, $history) {
                $ticket = $history->start($command, $request);
                return $handler($command, $request)
                    ->then(
                        function ($result) use ($history, $ticket) {
                            $history->finish($ticket, $result);
                            return $result;
                        },
                        function ($reason) use ($history, $ticket) {
                            $history->finish($ticket, $reason);
                            return Promise\rejection_for($reason);
                        }
                    );
            };
        };
    }

    /**
     * Creates a middleware that applies a map function to requests as they
     * pass through the middleware.
     *
     * @param callable $f Map function that accepts a RequestInterface and
     *                    returns a RequestInterface.
     *
     * @return callable
     */
    public static function mapRequest(callable $f)
    {
        return function (callable $handler) use ($f) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler, $f) {
                return $handler($command, $f($request));
            };
        };
    }

    /**
     * Creates a middleware that applies a map function to commands as they
     * pass through the middleware.
     *
     * @param callable $f Map function that accepts a command and returns a
     *                    command.
     *
     * @return callable
     */
    public static function mapCommand(callable $f)
    {
        return function (callable $handler) use ($f) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler, $f) {
                return $handler($f($command), $request);
            };
        };
    }

    /**
     * Creates a middleware that applies a map function to results.
     *
     * @param callable $f Map function that accepts an Aws\ResultInterface and
     *                    returns an Aws\ResultInterface.
     *
     * @return callable
     */
    public static function mapResult(callable $f)
    {
        return function (callable $handler) use ($f) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler, $f) {
                return $handler($command, $request)->then($f);
            };
        };
    }

    public static function timer()
    {
        return function (callable $handler) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler) {
                $start = microtime(true);
                return $handler($command, $request)
                    ->then(
                        function (ResultInterface $res) use ($start) {
                            if (!isset($res['@metadata'])) {
                                $res['@metadata'] = [];
                            }
                            if (!isset($res['@metadata']['transferStats'])) {
                                $res['@metadata']['transferStats'] = [];
                            }

                            $res['@metadata']['transferStats']['total_time']
                                = microtime(true) - $start;

                            return $res;
                        },
                        function ($err) use ($start) {
                            if ($err instanceof AwsException) {
                                $err->setTransferInfo([
                                    'total_time' => microtime(true) - $start,
                                ] + $err->getTransferInfo());
                            }
                            return Promise\rejection_for($err);
                        }
                    );
            };
        };
    }
}
<?php
namespace Aws;

use Aws\Exception\AwsException;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use Psr\Http\Message\RequestInterface;

/**
 * Returns promises that are rejected or fulfilled using a queue of
 * Aws\ResultInterface and Aws\Exception\AwsException objects.
 */
class MockHandler implements \Countable
{
    private $queue;
    private $lastCommand;
    private $lastRequest;
    private $onFulfilled;
    private $onRejected;

    /**
     * The passed in value must be an array of {@see Aws\ResultInterface} or
     * {@see AwsException} objects that acts as a queue of results or
     * exceptions to return each time the handler is invoked.
     *
     * @param array    $resultOrQueue
     * @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable $onRejected  Callback to invoke when the return value is rejected.
     */
    public function __construct(
        array $resultOrQueue = [],
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        $this->onFulfilled = $onFulfilled;
        $this->onRejected = $onRejected;

        if ($resultOrQueue) {
            call_user_func_array([$this, 'append'], $resultOrQueue);
        }
    }

    /**
     * Adds one or more variadic ResultInterface or AwsException objects to the
     * queue.
     */
    public function append()
    {
        foreach (func_get_args() as $value) {
            if ($value instanceof ResultInterface
                || $value instanceof AwsException
                || is_callable($value)
            ) {
                $this->queue[] = $value;
            } else {
                throw new \InvalidArgumentException('Expected an Aws\ResultInterface or Aws\Exception\AwsException.');
            }
        }
    }

    public function __invoke(
        CommandInterface $command,
        RequestInterface $request
    ) {
        if (!$this->queue) {
            $last = $this->lastCommand
                ? ' The last command sent was ' . $this->lastCommand->getName() . '.'
                : '';
            throw new \RuntimeException('Mock queue is empty. Trying to send a '
                . $command->getName() . ' command failed.' . $last);
        }

        $this->lastCommand = $command;
        $this->lastRequest = $request;

        $result = array_shift($this->queue);

        if (is_callable($result)) {
            $result = $result($command, $request);
        }

        if ($result instanceof \Exception) {
            $result = new RejectedPromise($result);
        } else {
            // Add an effective URI and statusCode if not present.
            $meta = $result['@metadata'];
            if (!isset($meta['effectiveUri'])) {
                $meta['effectiveUri'] = (string) $request->getUri();
            }
            if (!isset($meta['statusCode'])) {
                $meta['statusCode'] = 200;
            }
            $result['@metadata'] = $meta;
            $result = Promise\promise_for($result);
        }

        $result->then($this->onFulfilled, $this->onRejected);

        return $result;
    }

    /**
     * Get the last received request.
     *
     * @return RequestInterface
     */
    public function getLastRequest()
    {
        return $this->lastRequest;
    }

    /**
     * Get the last received command.
     *
     * @return CommandInterface
     */
    public function getLastCommand()
    {
        return $this->lastCommand;
    }

    /**
     * Returns the number of remaining items in the queue.
     *
     * @return int
     */
    public function count()
    {
        return count($this->queue);
    }
}
<?php
namespace Aws\Multipart;

use Aws\AwsClientInterface as Client;
use GuzzleHttp\Psr7;
use InvalidArgumentException as IAE;
use Psr\Http\Message\StreamInterface as Stream;

abstract class AbstractUploader extends AbstractUploadManager
{
    /** @var Stream Source of the data to be uploaded. */
    protected $source;

    /**
     * @param Client $client
     * @param mixed  $source
     * @param array  $config
     */
    public function __construct(Client $client, $source, array $config = [])
    {
        $this->source = $this->determineSource($source);
        parent::__construct($client, $config);
    }

    /**
     * Create a stream for a part that starts at the current position and
     * has a length of the upload part size (or less with the final part).
     *
     * @param Stream $stream
     *
     * @return Psr7\LimitStream
     */
    protected function limitPartStream(Stream $stream)
    {
        // Limit what is read from the stream to the part size.
        return new Psr7\LimitStream(
            $stream,
            $this->state->getPartSize(),
            $this->source->tell()
        );
    }

    protected function getUploadCommands(callable $resultHandler)
    {
        // Determine if the source can be seeked.
        $seekable = $this->source->isSeekable()
            && $this->source->getMetadata('wrapper_type') === 'plainfile';

        for ($partNumber = 1; $this->isEof($seekable); $partNumber++) {
            // If we haven't already uploaded this part, yield a new part.
            if (!$this->state->hasPartBeenUploaded($partNumber)) {
                $partStartPos = $this->source->tell();
                if (!($data = $this->createPart($seekable, $partNumber))) {
                    break;
                }
                $command = $this->client->getCommand(
                    $this->info['command']['upload'],
                    $data + $this->state->getId()
                );
                $command->getHandlerList()->appendSign($resultHandler, 'mup');
                yield $command;
                if ($this->source->tell() > $partStartPos) {
                    continue;
                }
            }

            // Advance the source's offset if not already advanced.
            if ($seekable) {
                $this->source->seek(min(
                    $this->source->tell() + $this->state->getPartSize(),
                    $this->source->getSize()
                ));
            } else {
                $this->source->read($this->state->getPartSize());
            }
        }
    }

    /**
     * Generates the parameters for an upload part by analyzing a range of the
     * source starting from the current offset up to the part size.
     *
     * @param bool $seekable
     * @param int  $number
     *
     * @return array|null
     */
    abstract protected function createPart($seekable, $number);

    /**
     * Checks if the source is at EOF.
     *
     * @param bool $seekable
     *
     * @return bool
     */
    private function isEof($seekable)
    {
        return $seekable
            ? $this->source->tell() < $this->source->getSize()
            : !$this->source->eof();
    }

    /**
     * Turns the provided source into a stream and stores it.
     *
     * If a string is provided, it is assumed to be a filename, otherwise, it
     * passes the value directly to `Psr7\stream_for()`.
     *
     * @param mixed $source
     *
     * @return Stream
     */
    private function determineSource($source)
    {
        // Use the contents of a file as the data source.
        if (is_string($source)) {
            $source = Psr7\try_fopen($source, 'r');
        }

        // Create a source stream.
        $stream = Psr7\stream_for($source);
        if (!$stream->isReadable()) {
            throw new IAE('Source stream must be readable.');
        }

        return $stream;
    }
}
<?php
namespace Aws\Multipart;

use Aws\AwsClientInterface as Client;
use Aws\CommandInterface;
use Aws\CommandPool;
use Aws\Exception\AwsException;
use Aws\Exception\MultipartUploadException;
use Aws\Result;
use Aws\ResultInterface;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use InvalidArgumentException as IAE;
use Psr\Http\Message\RequestInterface;

/**
 * Encapsulates the execution of a multipart upload to S3 or Glacier.
 *
 * @internal
 */
abstract class AbstractUploadManager implements Promise\PromisorInterface
{
    const DEFAULT_CONCURRENCY = 5;

    /** @var array Default values for base multipart configuration */
    private static $defaultConfig = [
        'part_size'       => null,
        'state'           => null,
        'concurrency'     => self::DEFAULT_CONCURRENCY,
        'before_initiate' => null,
        'before_upload'   => null,
        'before_complete' => null,
        'exception_class' => 'Aws\Exception\MultipartUploadException',
    ];

    /** @var Client Client used for the upload. */
    protected $client;

    /** @var array Configuration used to perform the upload. */
    protected $config;

    /** @var array Service-specific information about the upload workflow. */
    protected $info;

    /** @var PromiseInterface Promise that represents the multipart upload. */
    protected $promise;

    /** @var UploadState State used to manage the upload. */
    protected $state;

    /**
     * @param Client $client
     * @param array  $config
     */
    public function __construct(Client $client, array $config = [])
    {
        $this->client = $client;
        $this->info = $this->loadUploadWorkflowInfo();
        $this->config = $config + self::$defaultConfig;
        $this->state = $this->determineState();
    }

    /**
     * Returns the current state of the upload
     *
     * @return UploadState
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * Upload the source using multipart upload operations.
     *
     * @return Result The result of the CompleteMultipartUpload operation.
     * @throws \LogicException if the upload is already complete or aborted.
     * @throws MultipartUploadException if an upload operation fails.
     */
    public function upload()
    {
        return $this->promise()->wait();
    }

    /**
     * Upload the source asynchronously using multipart upload operations.
     *
     * @return PromiseInterface
     */
    public function promise()
    {
        if ($this->promise) {
            return $this->promise;
        }

        return $this->promise = Promise\coroutine(function () {
            // Initiate the upload.
            if ($this->state->isCompleted()) {
                throw new \LogicException('This multipart upload has already '
                    . 'been completed or aborted.'
                );
            } elseif (!$this->state->isInitiated()) {
                $result = (yield $this->execCommand('initiate', $this->getInitiateParams()));
                $this->state->setUploadId(
                    $this->info['id']['upload_id'],
                    $result[$this->info['id']['upload_id']]
                );
                $this->state->setStatus(UploadState::INITIATED);
            }

            // Create a command pool from a generator that yields UploadPart
            // commands for each upload part.
            $resultHandler = $this->getResultHandler($errors);
            $commands = new CommandPool(
                $this->client,
                $this->getUploadCommands($resultHandler),
                [
                    'concurrency' => $this->config['concurrency'],
                    'before'      => $this->config['before_upload'],
                ]
            );

            // Execute the pool of commands concurrently, and process errors.
            yield $commands->promise();
            if ($errors) {
                throw new $this->config['exception_class']($this->state, $errors);
            }

            // Complete the multipart upload.
            yield $this->execCommand('complete', $this->getCompleteParams());
            $this->state->setStatus(UploadState::COMPLETED);
        })->otherwise(function (\Exception $e) {
            // Throw errors from the operations as a specific Multipart error.
            if ($e instanceof AwsException) {
                $e = new $this->config['exception_class']($this->state, $e);
            }
            throw $e;
        });
    }

    protected function getConfig()
    {
        return $this->config;
    }

    /**
     * Provides service-specific information about the multipart upload
     * workflow.
     *
     * This array of data should include the keys: 'command', 'id', and 'part_num'.
     *
     * @return array
     */
    abstract protected function loadUploadWorkflowInfo();

    /**
     * Determines the part size to use for upload parts.
     *
     * Examines the provided partSize value and the source to determine the
     * best possible part size.
     *
     * @throws \InvalidArgumentException if the part size is invalid.
     *
     * @return int
     */
    abstract protected function determinePartSize();

    /**
     * Uses information from the Command and Result to determine which part was
     * uploaded and mark it as uploaded in the upload's state.
     *
     * @param CommandInterface $command
     * @param ResultInterface  $result
     */
    abstract protected function handleResult(
        CommandInterface $command,
        ResultInterface $result
    );

    /**
     * Gets the service-specific parameters used to initiate the upload.
     *
     * @return array
     */
    abstract protected function getInitiateParams();

    /**
     * Gets the service-specific parameters used to complete the upload.
     *
     * @return array
     */
    abstract protected function getCompleteParams();

    /**
     * Based on the config and service-specific workflow info, creates a
     * `Promise` for an `UploadState` object.
     *
     * @return PromiseInterface A `Promise` that resolves to an `UploadState`.
     */
    private function determineState()
    {
        // If the state was provided via config, then just use it.
        if ($this->config['state'] instanceof UploadState) {
            return $this->config['state'];
        }

        // Otherwise, construct a new state from the provided identifiers.
        $required = $this->info['id'];
        $id = [$required['upload_id'] => null];
        unset($required['upload_id']);
        foreach ($required as $key => $param) {
            if (!$this->config[$key]) {
                throw new IAE('You must provide a value for "' . $key . '" in '
                    . 'your config for the MultipartUploader for '
                    . $this->client->getApi()->getServiceFullName() . '.');
            }
            $id[$param] = $this->config[$key];
        }
        $state = new UploadState($id);
        $state->setPartSize($this->determinePartSize());

        return $state;
    }

    /**
     * Executes a MUP command with all of the parameters for the operation.
     *
     * @param string $operation Name of the operation.
     * @param array  $params    Service-specific params for the operation.
     *
     * @return PromiseInterface
     */
    private function execCommand($operation, array $params)
    {
        // Create the command.
        $command = $this->client->getCommand(
            $this->info['command'][$operation],
            $params + $this->state->getId()
        );

        // Execute the before callback.
        if (is_callable($this->config["before_{$operation}"])) {
            $this->config["before_{$operation}"]($command);
        }

        // Execute the command asynchronously and return the promise.
        return $this->client->executeAsync($command);
    }

    /**
     * Returns a middleware for processing responses of part upload operations.
     *
     * - Adds an onFulfilled callback that calls the service-specific
     *   handleResult method on the Result of the operation.
     * - Adds an onRejected callback that adds the error to an array of errors.
     * - Has a passedByRef $errors arg that the exceptions get added to. The
     *   caller should use that &$errors array to do error handling.
     *
     * @param array $errors Errors from upload operations are added to this.
     *
     * @return callable
     */
    private function getResultHandler(&$errors = [])
    {
        return function (callable $handler) use (&$errors) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler, &$errors) {
                return $handler($command, $request)->then(
                    function (ResultInterface $result) use ($command) {
                        $this->handleResult($command, $result);
                        return $result;
                    },
                    function (AwsException $e) use (&$errors) {
                        $errors[$e->getCommand()[$this->info['part_num']]] = $e;
                        return new Result();
                    }
                );
            };
        };
    }

    /**
     * Creates a generator that yields part data for the upload's source.
     *
     * Yields associative arrays of parameters that are ultimately merged in
     * with others to form the complete parameters of a  command. This can
     * include the Body parameter, which is a limited stream (i.e., a Stream
     * object, decorated with a LimitStream).
     *
     * @param callable $resultHandler
     *
     * @return \Generator
     */
    abstract protected function getUploadCommands(callable $resultHandler);
}
<?php
namespace Aws\Multipart;

/**
 * Representation of the multipart upload.
 *
 * This object keeps track of the state of the upload, including the status and
 * which parts have been uploaded.
 */
class UploadState
{
    const CREATED = 0;
    const INITIATED = 1;
    const COMPLETED = 2;

    /** @var array Params used to identity the upload. */
    private $id;

    /** @var int Part size being used by the upload. */
    private $partSize;

    /** @var array Parts that have been uploaded. */
    private $uploadedParts = [];

    /** @var int Identifies the status the upload. */
    private $status = self::CREATED;

    /**
     * @param array $id Params used to identity the upload.
     */
    public function __construct(array $id)
    {
        $this->id = $id;
    }

    /**
     * Get the upload's ID, which is a tuple of parameters that can uniquely
     * identify the upload.
     *
     * @return array
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set's the "upload_id", or 3rd part of the upload's ID. This typically
     * only needs to be done after initiating an upload.
     *
     * @param string $key   The param key of the upload_id.
     * @param string $value The param value of the upload_id.
     */
    public function setUploadId($key, $value)
    {
        $this->id[$key] = $value;
    }

    /**
     * Get the part size.
     *
     * @return int
     */
    public function getPartSize()
    {
        return $this->partSize;
    }

    /**
     * Set the part size.
     *
     * @param $partSize int Size of upload parts.
     */
    public function setPartSize($partSize)
    {
        $this->partSize = $partSize;
    }

    /**
     * Marks a part as being uploaded.
     *
     * @param int   $partNumber The part number.
     * @param array $partData   Data from the upload operation that needs to be
     *                          recalled during the complete operation.
     */
    public function markPartAsUploaded($partNumber, array $partData = [])
    {
        $this->uploadedParts[$partNumber] = $partData;
    }

    /**
     * Returns whether a part has been uploaded.
     *
     * @param int $partNumber The part number.
     *
     * @return bool
     */
    public function hasPartBeenUploaded($partNumber)
    {
        return isset($this->uploadedParts[$partNumber]);
    }

    /**
     * Returns a sorted list of all the uploaded parts.
     *
     * @return array
     */
    public function getUploadedParts()
    {
        ksort($this->uploadedParts);

        return $this->uploadedParts;
    }

    /**
     * Set the status of the upload.
     *
     * @param int $status Status is an integer code defined by the constants
     *                    CREATED, INITIATED, and COMPLETED on this class.
     */
    public function setStatus($status)
    {
        $this->status = $status;
    }

    /**
     * Determines whether the upload state is in the INITIATED status.
     *
     * @return bool
     */
    public function isInitiated()
    {
        return $this->status === self::INITIATED;
    }

    /**
     * Determines whether the upload state is in the COMPLETED status.
     *
     * @return bool
     */
    public function isCompleted()
    {
        return $this->status === self::COMPLETED;
    }
}
<?php
namespace Aws;

use Aws\Endpoint\PartitionEndpointProvider;
use Aws\Endpoint\PartitionInterface;
use GuzzleHttp\Promise\FulfilledPromise;

class MultiRegionClient implements AwsClientInterface
{
    use AwsClientTrait;

    /** @var AwsClientInterface[] A pool of clients keyed by region. */
    private $clientPool = [];
    /** @var callable */
    private $factory;
    /** @var PartitionInterface */
    private $partition;
    /** @var array */
    private $args;
    /** @var array */
    private $config;

    public static function getArguments()
    {
        $args = array_intersect_key(
            ClientResolver::getDefaultArguments(),
            ['service' => true, 'region' => true]
        );
        $args['region']['required'] = false;

        return $args + [
            'client_factory' => [
                'type' => 'config',
                'valid' => ['callable'],
                'doc' => 'A callable that takes an array of client'
                    . ' configuration arguments and returns a regionalized'
                    . ' client.',
                'required' => true,
                'internal' => true,
                'default' => function (array $args) {
                    $namespace = manifest($args['service'])['namespace'];
                    $klass = "Aws\\{$namespace}\\{$namespace}Client";
                    $region = isset($args['region']) ? $args['region'] : null;

                    return function (array $args) use ($klass, $region) {
                        if ($region && empty($args['region'])) {
                            $args['region'] = $region;
                        }

                        return new $klass($args);
                    };
                },
            ],
            'partition' => [
                'type'    => 'config',
                'valid'   => ['string', PartitionInterface::class],
                'doc'     => 'AWS partition to connect to. Valid partitions'
                    . ' include "aws," "aws-cn," and "aws-us-gov." Used to'
                    . ' restrict the scope of the mapRegions method.',
                'default' => function (array $args) {
                    $region = isset($args['region']) ? $args['region'] : '';
                    return PartitionEndpointProvider::defaultProvider()
                        ->getPartition($region, $args['service']);
                },
                'fn'      => function ($value, array &$args) {
                    if (is_string($value)) {
                        $value = PartitionEndpointProvider::defaultProvider()
                            ->getPartitionByName($value);
                    }

                    if (!$value instanceof PartitionInterface) {
                        throw new \InvalidArgumentException('No valid partition'
                            . ' was provided. Provide a concrete partition or'
                            . ' the name of a partition (e.g., "aws," "aws-cn,"'
                            . ' or "aws-us-gov").'
                        );
                    }

                    $args['partition'] = $value;
                    $args['endpoint_provider'] = $value;
                }
            ],
        ];
    }

    /**
     * The multi-region client constructor accepts the following options:
     *
     * - client_factory: (callable) An optional callable that takes an array of
     *   client configuration arguments and returns a regionalized client.
     * - partition: (Aws\Endpoint\Partition|string) AWS partition to connect to.
     *   Valid partitions include "aws," "aws-cn," and "aws-us-gov." Used to
     *   restrict the scope of the mapRegions method.
     * - region: (string) Region to connect to when no override is provided.
     *   Used to create the default client factory and determine the appropriate
     *   AWS partition when present.
     *
     * @param array $args Client configuration arguments.
     */
    public function __construct(array $args = [])
    {
        if (!isset($args['service'])) {
            $args['service'] = $this->parseClass();
        }

        $argDefinitions = static::getArguments();
        $resolver = new ClientResolver($argDefinitions);
        $args = $resolver->resolve($args, new HandlerList);
        $this->config = $args['config'];
        $this->factory = $args['client_factory'];
        $this->partition = $args['partition'];
        $this->args = array_diff_key($args, $args['config']);
    }

    /**
     * Get the region to which the client is configured to send requests by
     * default.
     *
     * @return string
     */
    public function getRegion()
    {
        return $this->getClientFromPool()->getRegion();
    }

    /**
     * Create a command for an operation name.
     *
     * Special keys may be set on the command to control how it behaves,
     * including:
     *
     * - @http: Associative array of transfer specific options to apply to the
     *   request that is serialized for this command. Available keys include
     *   "proxy", "verify", "timeout", "connect_timeout", "debug", "delay", and
     *   "headers".
     * - @region: The region to which the command should be sent.
     *
     * @param string $name Name of the operation to use in the command
     * @param array  $args Arguments to pass to the command
     *
     * @return CommandInterface
     * @throws \InvalidArgumentException if no command can be found by name
     */
    public function getCommand($name, array $args = [])
    {
        list($region, $args) = $this->getRegionFromArgs($args);

        return $this->getClientFromPool($region)->getCommand($name, $args);
    }

    public function getConfig($option = null)
    {
        if (null === $option) {
            return $this->config;
        }

        if (isset($this->config[$option])) {
            return $this->config[$option];
        }

        return $this->getClientFromPool()->getConfig($option);
    }

    public function getCredentials()
    {
        return $this->getClientFromPool()->getCredentials();
    }

    public function getHandlerList()
    {
        return $this->getClientFromPool()->getHandlerList();
    }

    public function getApi()
    {
        return $this->getClientFromPool()->getApi();
    }

    public function getEndpoint()
    {
        return $this->getClientFromPool()->getEndpoint();
    }

    /**
     * @param string $region    Omit this argument or pass in an empty string to
     *                          allow the configured client factory to apply the
     *                          region.
     *
     * @return AwsClientInterface
     */
    protected function getClientFromPool($region = '')
    {
        if (empty($this->clientPool[$region])) {
            $factory = $this->factory;
            $this->clientPool[$region] = $factory(
                array_replace($this->args, array_filter(['region' => $region]))
            );
        }

        return $this->clientPool[$region];
    }

    /**
     * Parse the class name and return the "service" name of the client.
     *
     * @return string
     */
    private function parseClass()
    {
        $klass = get_class($this);

        if ($klass === __CLASS__) {
            return '';
        }

        return strtolower(substr($klass, strrpos($klass, '\\') + 1, -17));
    }

    private function getRegionFromArgs(array $args)
    {
        $region = isset($args['@region'])
            ? $args['@region']
            : $this->getRegion();
        unset($args['@region']);

        return [$region, $args];
    }
}
<?php
namespace Aws;

/**
 * Incremental hashing using PHP's hash functions.
 */
class PhpHash implements HashInterface
{
    /** @var resource */
    private $context;

    /** @var string */
    private $algo;

    /** @var array */
    private $options;

    /** @var string */
    private $hash;

    /**
     * @param string $algo Hashing algorithm. One of PHP's hash_algos()
     *     return values (e.g. md5, sha1, etc...).
     * @param array  $options Associative array of hashing options:
     *     - key: Secret key used with the hashing algorithm.
     *     - base64: Set to true to base64 encode the value when complete.
     */
    public function __construct($algo, array $options = [])
    {
        $this->algo = $algo;
        $this->options = $options;
    }

    public function update($data)
    {
        if ($this->hash !== null) {
            $this->reset();
        }

        hash_update($this->getContext(), $data);
    }

    public function complete()
    {
        if ($this->hash) {
            return $this->hash;
        }

        $this->hash = hash_final($this->getContext(), true);

        if (isset($this->options['base64']) && $this->options['base64']) {
            $this->hash = base64_encode($this->hash);
        }

        return $this->hash;
    }

    public function reset()
    {
        $this->context = $this->hash = null;
    }

    /**
     * Get a hash context or create one if needed
     *
     * @return resource
     */
    private function getContext()
    {
        if (!$this->context) {
            $key = isset($this->options['key']) ? $this->options['key'] : null;
            $this->context = hash_init(
                $this->algo,
                $key ? HASH_HMAC : 0,
                $key
            );
        }

        return $this->context;
    }
}
<?php
namespace Aws;

use Aws\AwsClientInterface;
use Aws\Signature\SignatureV4;
use Aws\Endpoint\EndpointProvider;
use Aws\CommandInterface;
use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\RequestInterface;

/**
 * @internal Adds computed values to service operations that need presigned url.
 */
class PresignUrlMiddleware
{
    private $client;
    private $endpointProvider;
    private $nextHandler;
    /** @var array names of operations that require presign url */
    private $commandPool;
    /** @var string */
    private $serviceName;
    /** @var string */
    private $presignParam;

    public function __construct(
        array $options,
        callable $endpointProvider,
        AwsClientInterface $client,
        callable $nextHandler
    ) {
        $this->endpointProvider = $endpointProvider;
        $this->client = $client;
        $this->nextHandler = $nextHandler;
        $this->commandPool = $options['operations'];
        $this->serviceName = $options['service'];
        $this->presignParam = $options['presign_param'];
    }

    public static function wrap(
        AwsClientInterface $client,
        callable $endpointProvider,
        array $options = []
    ) {
        return function (callable $handler) use ($endpointProvider, $client, $options) {
            $f = new PreSignUrlMiddleware($options, $endpointProvider, $client, $handler);
            return $f;
        };
    }

    public function __invoke(CommandInterface $cmd, RequestInterface $request = null)
    {
        if (in_array($cmd->getName(), $this->commandPool)
            && (!isset($cmd->{'__skip' . $cmd->getName()}))
        ) {
            $cmd[$this->presignParam] = $this->createPresignedUrl($this->client, $cmd);
            $cmd['DestinationRegion'] = $this->client->getRegion();
        }

        $f = $this->nextHandler;
        return $f($cmd, $request);
    }

    private function createPresignedUrl(
        AwsClientInterface $client,
        CommandInterface $cmd
    ) {
        $cmdName = $cmd->getName();
        $newCmd = $client->getCommand($cmdName, $cmd->toArray());
        // Avoid infinite recursion by flagging the new command.
        $newCmd->{'__skip' . $cmdName} = true;

        // Serialize a request for the operation.
        $request = \Aws\serialize($newCmd);
        // Create the new endpoint for the target endpoint.
        $endpoint = EndpointProvider::resolve($this->endpointProvider, [
            'region'  => $cmd['SourceRegion'],
            'service' => $this->serviceName,
        ])['endpoint'];

        // Set the request to hit the target endpoint.
        $uri = $request->getUri()->withHost((new Uri($endpoint))->getHost());
        $request = $request->withUri($uri);
        // Create a presigned URL for our generated request.
        $signer = new SignatureV4($this->serviceName, $cmd['SourceRegion']);

        return (string) $signer->presign(
            SignatureV4::convertPostToGet($request),
            $client->getCredentials()->wait(),
            '+1 hour'
        )->getUri();
    }
}
<?php
namespace Aws;

use Psr\Cache\CacheItemPoolInterface;

class PsrCacheAdapter implements CacheInterface
{
    /** @var CacheItemPoolInterface */
    private $pool;

    public function __construct(CacheItemPoolInterface $pool)
    {
        $this->pool = $pool;
    }

    public function get($key)
    {
        $item = $this->pool->getItem($key);

        return $item->isHit() ? $item->get() : null;
    }

    public function set($key, $value, $ttl = 0)
    {
        $item = $this->pool->getItem($key);
        $item->set($value);
        if ($ttl > 0) {
            $item->expiresAfter($ttl);
        }

        $this->pool->save($item);
    }

    public function remove($key)
    {
        $this->pool->deleteItem($key);
    }
}
<?php
namespace Aws;

use JmesPath\Env as JmesPath;

/**
 * AWS result.
 */
class Result implements ResultInterface
{
    use HasDataTrait;

    public function __construct(array $data = [])
    {
        $this->data = $data;
    }

    public function hasKey($name)
    {
        return isset($this->data[$name]);
    }

    public function get($key)
    {
        return $this[$key];
    }

    public function search($expression)
    {
        return JmesPath::search($expression, $this->toArray());
    }

    public function __toString()
    {
        $jsonData = json_encode($this->toArray(), JSON_PRETTY_PRINT);
        return <<<EOT
Model Data
----------
Data can be retrieved from the model object using the get() method of the
model (e.g., `\$result->get(\$key)`) or "accessing the result like an
associative array (e.g. `\$result['key']`). You can also execute JMESPath
expressions on the result data using the search() method.

{$jsonData}

EOT;
    }

    /**
     * @deprecated
     */
    public function getPath($path)
    {
        return $this->search(str_replace('/', '.', $path));
    }
}
<?php
namespace Aws;

/**
 * Represents an AWS result object that is returned from executing an operation.
 */
interface ResultInterface extends \ArrayAccess, \IteratorAggregate, \Countable
{
    /**
     * Provides debug information about the result object
     *
     * @return string
     */
    public function __toString();

    /**
     * Convert the result to an array.
     *
     * @return array
     */
    public function toArray();

    /**
     * Check if the model contains a key by name
     *
     * @param string $name Name of the key to retrieve
     *
     * @return bool
     */
    public function hasKey($name);

    /**
     * Get a specific key value from the result model.
     *
     * @param string $key Key to retrieve.
     *
     * @return mixed|null Value of the key or NULL if not found.
     */
    public function get($key);

    /**
     * Returns the result of executing a JMESPath expression on the contents
     * of the Result model.
     *
     *     $result = $client->execute($command);
     *     $jpResult = $result->search('foo.*.bar[?baz > `10`]');
     *
     * @param string $expression JMESPath expression to execute
     *
     * @return mixed Returns the result of the JMESPath expression.
     * @link http://jmespath.readthedocs.org/en/latest/ JMESPath documentation
     */
    public function search($expression);
};
<?php
namespace Aws;

use GuzzleHttp\Promise;

/**
 * Iterator that yields each page of results of a pageable operation.
 */
class ResultPaginator implements \Iterator
{
    /** @var AwsClientInterface Client performing operations. */
    private $client;

    /** @var string Name of the operation being paginated. */
    private $operation;

    /** @var array Args for the operation. */
    private $args;

    /** @var array Configuration for the paginator. */
    private $config;

    /** @var Result Most recent result from the client. */
    private $result;

    /** @var string|array Next token to use for pagination. */
    private $nextToken;

    /** @var int Number of operations/requests performed. */
    private $requestCount = 0;

    /**
     * @param AwsClientInterface $client
     * @param string             $operation
     * @param array              $args
     * @param array              $config
     */
    public function __construct(
        AwsClientInterface $client,
        $operation,
        array $args,
        array $config
    ) {
        $this->client = $client;
        $this->operation = $operation;
        $this->args = $args;
        $this->config = $config;
    }

    /**
     * Runs a paginator asynchronously and uses a callback to handle results.
     *
     * The callback should have the signature: function (Aws\Result $result).
     * A non-null return value from the callback will be yielded by the
     * promise. This means that you can return promises from the callback that
     * will need to be resolved before continuing iteration over the remaining
     * items, essentially merging in other promises to the iteration. The last
     * non-null value returned by the callback will be the result that fulfills
     * the promise to any downstream promises.
     *
     * @param callable $handleResult Callback for handling each page of results.
     *                               The callback accepts the result that was
     *                               yielded as a single argument. If the
     *                               callback returns a promise, the promise
     *                               will be merged into the coroutine.
     *
     * @return Promise\Promise
     */
    public function each(callable $handleResult)
    {
        return Promise\coroutine(function () use ($handleResult) {
            $nextToken = null;
            do {
                $command = $this->createNextCommand($this->args, $nextToken);
                $result = (yield $this->client->executeAsync($command));
                $nextToken = $this->determineNextToken($result);
                $retVal = $handleResult($result);
                if ($retVal !== null) {
                    yield Promise\promise_for($retVal);
                }
            } while ($nextToken);
        });
    }

    /**
     * Returns an iterator that iterates over the values of applying a JMESPath
     * search to each result yielded by the iterator as a flat sequence.
     *
     * @param string $expression JMESPath expression to apply to each result.
     *
     * @return \Iterator
     */
    public function search($expression)
    {
        // Apply JMESPath expression on each result, but as a flat sequence.
        return flatmap($this, function (Result $result) use ($expression) {
            return (array) $result->search($expression);
        });
    }

    /**
     * @return Result
     */
    public function current()
    {
        return $this->valid() ? $this->result : false;
    }

    public function key()
    {
        return $this->valid() ? $this->requestCount - 1 : null;
    }

    public function next()
    {
        $this->result = null;
    }

    public function valid()
    {
        if ($this->result) {
            return true;
        }

        if ($this->nextToken || !$this->requestCount) {
            $this->result = $this->client->execute(
                $this->createNextCommand($this->args, $this->nextToken)
            );
            $this->nextToken = $this->determineNextToken($this->result);
            $this->requestCount++;
            return true;
        }

        return false;
    }

    public function rewind()
    {
        $this->requestCount = 0;
        $this->nextToken = null;
        $this->result = null;
    }

    private function createNextCommand(array $args, array $nextToken = null)
    {
        return $this->client->getCommand($this->operation, $args + ($nextToken ?: []));
    }

    private function determineNextToken(Result $result)
    {
        if (!$this->config['output_token']) {
            return null;
        }

        if ($this->config['more_results']
            && !$result->search($this->config['more_results'])
        ) {
            return null;
        }

        $nextToken = is_scalar($this->config['output_token'])
            ? [$this->config['input_token'] => $this->config['output_token']]
            : array_combine($this->config['input_token'], $this->config['output_token']);

        return array_filter(array_map(function ($outputToken) use ($result) {
            return $result->search($outputToken);
        }, $nextToken));
    }
}
<?php
namespace Aws;

use Aws\Exception\AwsException;
use Exception;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;

/**
 * @internal Middleware that retries failures.
 */
class RetryMiddleware
{
    private static $retryStatusCodes = [
        500 => true,
        502 => true,
        503 => true,
        504 => true
    ];

    private static $retryCodes = [
        // Throttling error
        'RequestLimitExceeded'                   => true,
        'Throttling'                             => true,
        'ThrottlingException'                    => true,
        'ProvisionedThroughputExceededException' => true,
        'RequestThrottled'                       => true,
        'BandwidthLimitExceeded'                 => true,
    ];

    private $decider;
    private $delay;
    private $nextHandler;
    private $collectStats;

    public function __construct(
        callable $decider,
        callable $delay,
        callable $nextHandler,
        $collectStats = false
    ) {
        $this->decider = $decider;
        $this->delay = $delay;
        $this->nextHandler = $nextHandler;
        $this->collectStats = (bool) $collectStats;
    }

    /**
     * Creates a default AWS retry decider function.
     *
     * @param int $maxRetries
     *
     * @return callable
     */
    public static function createDefaultDecider($maxRetries = 3)
    {
        return function (
            $retries,
            CommandInterface $command,
            RequestInterface $request,
            ResultInterface $result = null,
            $error = null
        ) use ($maxRetries) {
            // Allow command-level options to override this value
            $maxRetries = null !== $command['@retries'] ?
                $command['@retries']
                : $maxRetries;

            if ($retries >= $maxRetries) {
                return false;
            } elseif (!$error) {
                return isset(self::$retryStatusCodes[$result['@metadata']['statusCode']]);
            } elseif (!($error instanceof AwsException)) {
                return false;
            } elseif ($error->isConnectionError()) {
                return true;
            } elseif (isset(self::$retryCodes[$error->getAwsErrorCode()])) {
                return true;
            } elseif (isset(self::$retryStatusCodes[$error->getStatusCode()])) {
                return true;
            } else {
                return false;
            }
        };
    }

    /**
     * Delay function that calculates an exponential delay.
     *
     * Exponential backoff with jitter, 100ms base, 20 sec ceiling
     *
     * @param $retries
     *
     * @return int
     */
    public static function exponentialDelay($retries)
    {
        return mt_rand(0, (int) min(20000, (int) pow(2, $retries - 1) * 100));
    }

    /**
     * @param CommandInterface $command
     * @param RequestInterface $request
     *
     * @return PromiseInterface
     */
    public function __invoke(
        CommandInterface $command,
        RequestInterface $request = null
    ) {
        $retries = 0;
        $requestStats = [];
        $handler = $this->nextHandler;
        $decider = $this->decider;
        $delay = $this->delay;

        $request = $this->addRetryHeader($request, 0, 0);

        $g = function ($value) use (
            $handler,
            $decider,
            $delay,
            $command,
            $request,
            &$retries,
            &$requestStats,
            &$g
        ) {
            $this->updateHttpStats($value, $requestStats);

            if ($value instanceof \Exception || $value instanceof \Throwable) {
                if (!$decider($retries, $command, $request, null, $value)) {
                    return \GuzzleHttp\Promise\rejection_for(
                        $this->bindStatsToReturn($value, $requestStats)
                    );
                }
            } elseif ($value instanceof ResultInterface
                && !$decider($retries, $command, $request, $value, null)
            ) {
                return $this->bindStatsToReturn($value, $requestStats);
            }

            // Delay fn is called with 0, 1, ... so increment after the call.
            $delayBy = $delay($retries++);
            $command['@http']['delay'] = $delayBy;
            if ($this->collectStats) {
                $this->updateStats($retries, $delayBy, $requestStats);
            }

            // Update retry header with retry count and delayBy
            $request = $this->addRetryHeader($request, $retries, $delayBy);

            return $handler($command, $request)->then($g, $g);
        };

        return $handler($command, $request)->then($g, $g);
    }

    private function addRetryHeader($request, $retries, $delayBy)
    {
        return $request->withHeader('aws-sdk-retry', "{$retries}/{$delayBy}");
    }

    private function updateStats($retries, $delay, array &$stats)
    {
        if (!isset($stats['total_retry_delay'])) {
            $stats['total_retry_delay'] = 0;
        }

        $stats['total_retry_delay'] += $delay;
        $stats['retries_attempted'] = $retries;
    }

    private function updateHttpStats($value, array &$stats)
    {
        if (empty($stats['http'])) {
            $stats['http'] = [];
        }

        if ($value instanceof AwsException) {
            $resultStats = isset($value->getTransferInfo('http')[0])
                ? $value->getTransferInfo('http')[0]
                : [];
            $stats['http'] []= $resultStats;
        } elseif ($value instanceof ResultInterface) {
            $resultStats = isset($value['@metadata']['transferStats']['http'][0])
                ? $value['@metadata']['transferStats']['http'][0]
                : [];
            $stats['http'] []= $resultStats;
        }
    }

    private function bindStatsToReturn($return, array $stats)
    {
        if ($return instanceof ResultInterface) {
            if (!isset($return['@metadata'])) {
                $return['@metadata'] = [];
            }

            $return['@metadata']['transferStats'] = $stats;
        } elseif ($return instanceof AwsException) {
            $return->setTransferInfo($stats);
        }

        return $return;
    }
}
<?php
namespace Aws\S3;

use Aws\Api\Parser\AbstractParser;
use Aws\CommandInterface;
use Aws\Exception\AwsException;
use Psr\Http\Message\ResponseInterface;

/**
 * Converts errors returned with a status code of 200 to a retryable error type.
 *
 * @internal
 */
class AmbiguousSuccessParser extends AbstractParser
{
    private static $ambiguousSuccesses = [
        'UploadPartCopy' => true,
        'CopyObject' => true,
        'CompleteMultipartUpload' => true,
    ];

    /** @var callable */
    private $parser;
    /** @var callable */
    private $errorParser;
    /** @var string */
    private $exceptionClass;

    public function __construct(
        callable $parser,
        callable $errorParser,
        $exceptionClass = AwsException::class
    ) {
        $this->parser = $parser;
        $this->errorParser = $errorParser;
        $this->exceptionClass = $exceptionClass;
    }

    public function __invoke(
        CommandInterface $command,
        ResponseInterface $response
    ) {
        if (200 === $response->getStatusCode()
            && isset(self::$ambiguousSuccesses[$command->getName()])
        ) {
            $errorParser = $this->errorParser;
            $parsed = $errorParser($response);
            if (isset($parsed['code']) && isset($parsed['message'])) {
                throw new $this->exceptionClass(
                    $parsed['message'],
                    $command,
                    ['connection_error' => true]
                );
            }
        }

        $fn = $this->parser;
        return $fn($command, $response);
    }
}
<?php
namespace Aws\S3;

use Aws\CommandInterface;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Apply required or optional MD5s to requests before sending.
 *
 * IMPORTANT: This middleware must be added after the "build" step.
 *
 * @internal
 */
class ApplyChecksumMiddleware
{
    private static $md5 = [
        'DeleteObjects',
        'PutBucketCors',
        'PutBucketLifecycle',
        'PutBucketLifecycleConfiguration',
        'PutBucketPolicy',
        'PutBucketTagging',
        'PutBucketReplication',
    ];

    private static $sha256 = [
        'PutObject',
        'UploadPart',
    ];

    private $nextHandler;

    /**
     * Create a middleware wrapper function.
     *
     * @return callable
     */
    public static function wrap()
    {
        return function (callable $handler) {
            return new self($handler);
        };
    }

    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    public function __invoke(
        CommandInterface $command,
        RequestInterface $request
    ) {
        $next = $this->nextHandler;
        $name = $command->getName();
        $body = $request->getBody();

        if (in_array($name, self::$md5) && !$request->hasHeader('Content-MD5')) {
            // Set the content MD5 header for operations that require it.
            $request = $request->withHeader(
                'Content-MD5',
                base64_encode(Psr7\hash($body, 'md5', true))
            );
        } elseif (in_array($name, self::$sha256) && $command['ContentSHA256']) {
            // Set the content hash header if provided in the parameters.
            $request = $request->withHeader(
                'X-Amz-Content-Sha256',
                $command['ContentSHA256']
            );
        }

        return $next($command, $request);
    }
}
<?php
namespace Aws\S3;

use Aws\AwsClientInterface;
use Aws\S3\Exception\DeleteMultipleObjectsException;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\PromisorInterface;
use GuzzleHttp\Promise\PromiseInterface;

/**
 * Efficiently deletes many objects from a single Amazon S3 bucket using an
 * iterator that yields keys. Deletes are made using the DeleteObjects API
 * operation.
 *
 *     $s3 = new Aws\S3\Client([
 *         'region' => 'us-west-2',
 *         'version' => 'latest'
 *     ]);
 *
 *     $listObjectsParams = ['Bucket' => 'foo', 'Prefix' => 'starts/with/'];
 *     $delete = Aws\S3\BatchDelete::fromListObjects($s3, $listObjectsParams);
 *     // Asynchronously delete
 *     $promise = $delete->promise();
 *     // Force synchronous completion
 *     $delete->delete();
 *
 * When using one of the batch delete creational static methods, you can supply
 * an associative array of options:
 *
 * - before: Function invoked before executing a command. The function is
 *   passed the command that is about to be executed. This can be useful
 *   for logging, adding custom request headers, etc.
 * - batch_size: The size of each delete batch. Defaults to 1000.
 *
 * @link http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
 */
class BatchDelete implements PromisorInterface
{
    private $bucket;
    /** @var AwsClientInterface */
    private $client;
    /** @var callable */
    private $before;
    /** @var PromiseInterface */
    private $cachedPromise;
    /** @var callable */
    private $promiseCreator;
    private $batchSize = 1000;
    private $queue = [];

    /**
     * Creates a BatchDelete object from all of the paginated results of a
     * ListObjects operation. Each result that is returned by the ListObjects
     * operation will be deleted.
     *
     * @param AwsClientInterface $client            AWS Client to use.
     * @param array              $listObjectsParams ListObjects API parameters
     * @param array              $options           BatchDelete options.
     *
     * @return BatchDelete
     */
    public static function fromListObjects(
        AwsClientInterface $client,
        array $listObjectsParams,
        array $options = []
    ) {
        $iter = $client->getPaginator('ListObjects', $listObjectsParams);
        $bucket = $listObjectsParams['Bucket'];
        $fn = function (BatchDelete $that) use ($iter) {
            return $iter->each(function ($result) use ($that) {
                $promises = [];
                if (is_array($result['Contents'])) {
                    foreach ($result['Contents'] as $object) {
                        if ($promise = $that->enqueue($object)) {
                            $promises[] = $promise;
                        }
                    }
                }
                return $promises ? Promise\all($promises) : null;
            });
        };

        return new self($client, $bucket, $fn, $options);
    }

    /**
     * Creates a BatchDelete object from an iterator that yields results.
     *
     * @param AwsClientInterface $client  AWS Client to use to execute commands
     * @param string             $bucket  Bucket where the objects are stored
     * @param \Iterator          $iter    Iterator that yields assoc arrays
     * @param array              $options BatchDelete options
     *
     * @return BatchDelete
     */
    public static function fromIterator(
        AwsClientInterface $client,
        $bucket,
        \Iterator $iter,
        array $options = []
    ) {
        $fn = function (BatchDelete $that) use ($iter) {
            return \GuzzleHttp\Promise\coroutine(function () use ($that, $iter) {
                foreach ($iter as $obj) {
                    if ($promise = $that->enqueue($obj)) {
                        yield $promise;
                    }
                }
            });
        };

        return new self($client, $bucket, $fn, $options);
    }

    public function promise()
    {
        if (!$this->cachedPromise) {
            $this->cachedPromise = $this->createPromise();
        }

        return $this->cachedPromise;
    }

    /**
     * Synchronously deletes all of the objects.
     *
     * @throws DeleteMultipleObjectsException on error.
     */
    public function delete()
    {
        $this->promise()->wait();
    }

    /**
     * @param AwsClientInterface $client    Client used to transfer the requests
     * @param string             $bucket    Bucket to delete from.
     * @param callable           $promiseFn Creates a promise.
     * @param array              $options   Hash of options used with the batch
     *
     * @throws \InvalidArgumentException if the provided batch_size is <= 0
     */
    private function __construct(
        AwsClientInterface $client,
        $bucket,
        callable $promiseFn,
        array $options = []
    ) {
        $this->client = $client;
        $this->bucket = $bucket;
        $this->promiseCreator = $promiseFn;

        if (isset($options['before'])) {
            if (!is_callable($options['before'])) {
                throw new \InvalidArgumentException('before must be callable');
            }
            $this->before = $options['before'];
        }

        if (isset($options['batch_size'])) {
            if ($options['batch_size'] <= 0) {
                throw new \InvalidArgumentException('batch_size is not > 0');
            }
            $this->batchSize = min($options['batch_size'], 1000);
        }
    }

    private function enqueue(array $obj)
    {
        $this->queue[] = $obj;
        return count($this->queue) >= $this->batchSize
            ? $this->flushQueue()
            : null;
    }

    private function flushQueue()
    {
        static $validKeys = ['Key' => true, 'VersionId' => true];

        if (count($this->queue) === 0) {
            return null;
        }

        $batch = [];
        while ($obj = array_shift($this->queue)) {
            $batch[] = array_intersect_key($obj, $validKeys);
        }

        $command = $this->client->getCommand('DeleteObjects', [
            'Bucket' => $this->bucket,
            'Delete' => ['Objects' => $batch]
        ]);

        if ($this->before) {
            call_user_func($this->before, $command);
        }

        return $this->client->executeAsync($command)
            ->then(function ($result) {
                if (!empty($result['Errors'])) {
                    throw new DeleteMultipleObjectsException(
                        $result['Deleted'] ?: [],
                        $result['Errors']
                    );
                }
                return $result;
            });
    }

    /**
     * Returns a promise that will clean up any references when it completes.
     *
     * @return PromiseInterface
     */
    private function createPromise()
    {
        // Create the promise
        $promise = call_user_func($this->promiseCreator, $this);
        $this->promiseCreator = null;

        // Cleans up the promise state and references.
        $cleanup = function () {
            $this->before = $this->client = $this->queue = null;
        };

        // When done, ensure cleanup and that any remaining are processed.
        return $promise->then(
            function () use ($cleanup)  {
                return Promise\promise_for($this->flushQueue())
                    ->then($cleanup);
            },
            function ($reason) use ($cleanup)  {
                $cleanup();
                return Promise\rejection_for($reason);
            }
        );
    }
}
<?php
namespace Aws\S3;

use Aws\CommandInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Used to update the host used for S3 requests in the case of using a
 * "bucket endpoint" or CNAME bucket.
 *
 * IMPORTANT: this middleware must be added after the "build" step.
 *
 * @internal
 */
class BucketEndpointMiddleware
{
    private static $exclusions = ['GetBucketLocation' => true];
    private $nextHandler;

    /**
     * Create a middleware wrapper function.
     *
     * @return callable
     */
    public static function wrap()
    {
        return function (callable $handler) {
            return new self($handler);
        };
    }

    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    public function __invoke(CommandInterface $command, RequestInterface $request)
    {
        $nextHandler = $this->nextHandler;
        $bucket = $command['Bucket'];

        if ($bucket && !isset(self::$exclusions[$command->getName()])) {
            $request = $this->modifyRequest($request, $command);
        }

        return $nextHandler($command, $request);
    }

    private function removeBucketFromPath($path, $bucket)
    {
        $len = strlen($bucket) + 1;
        if (substr($path, 0, $len) === "/{$bucket}") {
            $path = substr($path, $len);
        }

        return $path ?: '/';
    }

    private function modifyRequest(
        RequestInterface $request,
        CommandInterface $command
    ) {
        $uri = $request->getUri();
        $path = $uri->getPath();
        $bucket = $command['Bucket'];
        $path = $this->removeBucketFromPath($path, $bucket);

        // Modify the Key to make sure the key is encoded, but slashes are not.
        if ($command['Key']) {
            $path = S3Client::encodeKey(rawurldecode($path));
        }

        return $request->withUri($uri->withPath($path));
    }
}
<?php
namespace Aws\S3\Exception;

/**
 * Exception thrown when errors occur while deleting objects using a
 * {@see S3\BatchDelete} object.
 */
class DeleteMultipleObjectsException extends \Exception
{
    private $deleted = [];
    private $errors = [];

    /**
     * @param array       $deleted Array of successfully deleted keys
     * @param array       $errors  Array of errors that were encountered
     */
    public function __construct(array $deleted, array $errors)
    {
        $this->deleted = array_values($deleted);
        $this->errors = array_values($errors);
        parent::__construct('Unable to delete certain keys when executing a'
            . ' DeleteMultipleObjects request: '
            . self::createMessageFromErrors($errors));
    }

    /**
     * Create a single error message from multiple errors.
     *
     * @param array $errors Errors encountered
     *
     * @return string
     */
    public static function createMessageFromErrors(array $errors)
    {
        return "\n- " . implode("\n- ", array_map(function ($key) {
            return json_encode($key);
        }, $errors));
    }

    /**
     * Get the errored objects
     *
     * @return array Returns an array of associative arrays, each containing
     *               a 'Code', 'Message', and 'Key' key.
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * Get the successfully deleted objects
     *
     * @return array Returns an array of associative arrays, each containing
     *               a 'Key' and optionally 'DeleteMarker' and
     *              'DeleterMarkerVersionId'
     */
    public function getDeleted()
    {
        return $this->deleted;
    }
}
<?php
namespace Aws\S3\Exception;

class PermanentRedirectException extends S3Exception {}
<?php
namespace Aws\S3\Exception;

use Aws\Exception\AwsException;

/**
 * Represents an error interacting with the Amazon Simple Storage Service.
 */
class S3Exception extends AwsException {}
<?php
namespace Aws\S3;

use Aws\Api\Parser\AbstractParser;
use Aws\CommandInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @internal Decorates a parser for the S3 service to correctly handle the
 *           GetBucketLocation operation.
 */
class GetBucketLocationParser extends AbstractParser
{
    /** @var callable */
    private $parser;

    /**
     * @param callable $parser Parser to wrap.
     */
    public function __construct(callable $parser)
    {
        $this->parser = $parser;
    }

    public function __invoke(
        CommandInterface $command,
        ResponseInterface $response
    ) {
        $fn = $this->parser;
        $result = $fn($command, $response);

        if ($command->getName() === 'GetBucketLocation') {
            $location = 'us-east-1';
            if (preg_match('/>(.+?)<\/LocationConstraint>/', $response->getBody(), $matches)) {
                $location = $matches[1] === 'EU' ? 'eu-west-1' : $matches[1];
            }
            $result['LocationConstraint'] = $location;
        }

        return $result;
    }
}
<?php
namespace Aws\S3;

use Aws\Multipart\AbstractUploadManager;
use Aws\ResultInterface;
use GuzzleHttp\Psr7;

class MultipartCopy extends AbstractUploadManager
{
    use MultipartUploadingTrait;

    /** @var string */
    private $source;
    /** @var ResultInterface */
    private $sourceMetadata;

    /**
     * Creates a multipart upload for copying an S3 object.
     *
     * The valid configuration options are as follows:
     *
     * - acl: (string) ACL to set on the object being upload. Objects are
     *   private by default.
     * - before_complete: (callable) Callback to invoke before the
     *   `CompleteMultipartUpload` operation. The callback should have a
     *   function signature like `function (Aws\Command $command) {...}`.
     * - before_initiate: (callable) Callback to invoke before the
     *   `CreateMultipartUpload` operation. The callback should have a function
     *   signature like `function (Aws\Command $command) {...}`.
     * - before_upload: (callable) Callback to invoke before `UploadPartCopy`
     *   operations. The callback should have a function signature like
     *   `function (Aws\Command $command) {...}`.
     * - bucket: (string, required) Name of the bucket to which the object is
     *   being uploaded.
     * - concurrency: (int, default=int(5)) Maximum number of concurrent
     *   `UploadPart` operations allowed during the multipart upload.
     * - key: (string, required) Key to use for the object being uploaded.
     * - part_size: (int, default=int(5242880)) Part size, in bytes, to use when
     *   doing a multipart upload. This must between 5 MB and 5 GB, inclusive.
     * - state: (Aws\Multipart\UploadState) An object that represents the state
     *   of the multipart upload and that is used to resume a previous upload.
     *   When this option is provided, the `bucket`, `key`, and `part_size`
     *   options are ignored.
     * - source_metadata: (Aws\ResultInterface) An object that represents the
     *   result of executing a HeadObject command on the copy source.
     *
     * @param S3ClientInterface $client Client used for the upload.
     * @param string            $source Location of the data to be copied
     *                                  (in the form /<bucket>/<key>).
     * @param array             $config Configuration used to perform the upload.
     */
    public function __construct(
        S3ClientInterface $client,
        $source,
        array $config = []
    ) {
        $this->source = '/' . ltrim($source, '/');
        parent::__construct($client, array_change_key_case($config) + [
            'source_metadata' => null
        ]);
    }

    /**
     * An alias of the self::upload method.
     *
     * @see self::upload
     */
    public function copy()
    {
        return $this->upload();
    }

    protected function loadUploadWorkflowInfo()
    {
        return [
            'command' => [
                'initiate' => 'CreateMultipartUpload',
                'upload'   => 'UploadPartCopy',
                'complete' => 'CompleteMultipartUpload',
            ],
            'id' => [
                'bucket'    => 'Bucket',
                'key'       => 'Key',
                'upload_id' => 'UploadId',
            ],
            'part_num' => 'PartNumber',
        ];
    }

    protected function getUploadCommands(callable $resultHandler)
    {
        $parts = ceil($this->getSourceSize() / $this->determinePartSize());

        for ($partNumber = 1; $partNumber <= $parts; $partNumber++) {
            // If we haven't already uploaded this part, yield a new part.
            if (!$this->state->hasPartBeenUploaded($partNumber)) {
                $command = $this->client->getCommand(
                    $this->info['command']['upload'],
                    $this->createPart($partNumber, $parts)
                        + $this->getState()->getId()
                );
                $command->getHandlerList()->appendSign($resultHandler, 'mup');
                yield $command;
            }
        }
    }

    private function createPart($partNumber, $partsCount)
    {
        $defaultPartSize = $this->determinePartSize();
        $startByte = $defaultPartSize * ($partNumber - 1);
        $partSize = $partNumber < $partsCount
            ? $defaultPartSize
            : $this->getSourceSize() - ($defaultPartSize * ($partsCount - 1));
        $endByte = $startByte + $partSize - 1;

        return [
            'ContentLength' => $partSize,
            'CopySource' => $this->source,
            'CopySourceRange' => "bytes=$startByte-$endByte",
            'PartNumber' => $partNumber,
        ];
    }

    protected function extractETag(ResultInterface $result)
    {
        return $result->search('CopyPartResult.ETag');
    }

    protected function getSourceMimeType()
    {
        return $this->getSourceMetadata()['ContentType'];
    }

    protected function getSourceSize()
    {
        return $this->getSourceMetadata()['ContentLength'];
    }

    private function getSourceMetadata()
    {
        if (empty($this->sourceMetadata)) {
            $this->sourceMetadata = $this->fetchSourceMetadata();
        }

        return $this->sourceMetadata;
    }

    private function fetchSourceMetadata()
    {
        if ($this->config['source_metadata'] instanceof ResultInterface) {
            return $this->config['source_metadata'];
        }

        list($bucket, $key) = explode('/', ltrim($this->source, '/'), 2);
        $headParams = [
            'Bucket' => $bucket,
            'Key' => $key,
        ];
        if (strpos($key, '?')) {
            list($key, $query) = explode('?', $key, 2);
            $headParams['Key'] = $key;
            $query = Psr7\parse_query($query, false);
            if (isset($query['versionId'])) {
                $headParams['VersionId'] = $query['versionId'];
            }
        }
        return $this->client->headObject($headParams);
    }
}
<?php
namespace Aws\S3;

use Aws\HashingStream;
use Aws\Multipart\AbstractUploader;
use Aws\PhpHash;
use Aws\ResultInterface;
use GuzzleHttp\Psr7;
use Psr\Http\Message\StreamInterface as Stream;

/**
 * Encapsulates the execution of a multipart upload to S3 or Glacier.
 */
class MultipartUploader extends AbstractUploader
{
    use MultipartUploadingTrait;

    const PART_MIN_SIZE = 5242880;
    const PART_MAX_SIZE = 5368709120;
    const PART_MAX_NUM = 10000;

    /**
     * Creates a multipart upload for an S3 object.
     *
     * The valid configuration options are as follows:
     *
     * - acl: (string) ACL to set on the object being upload. Objects are
     *   private by default.
     * - before_complete: (callable) Callback to invoke before the
     *   `CompleteMultipartUpload` operation. The callback should have a
     *   function signature like `function (Aws\Command $command) {...}`.
     * - before_initiate: (callable) Callback to invoke before the
     *   `CreateMultipartUpload` operation. The callback should have a function
     *   signature like `function (Aws\Command $command) {...}`.
     * - before_upload: (callable) Callback to invoke before any `UploadPart`
     *   operations. The callback should have a function signature like
     *   `function (Aws\Command $command) {...}`.
     * - bucket: (string, required) Name of the bucket to which the object is
     *   being uploaded.
     * - concurrency: (int, default=int(5)) Maximum number of concurrent
     *   `UploadPart` operations allowed during the multipart upload.
     * - key: (string, required) Key to use for the object being uploaded.
     * - part_size: (int, default=int(5242880)) Part size, in bytes, to use when
     *   doing a multipart upload. This must between 5 MB and 5 GB, inclusive.
     * - state: (Aws\Multipart\UploadState) An object that represents the state
     *   of the multipart upload and that is used to resume a previous upload.
     *   When this option is provided, the `bucket`, `key`, and `part_size`
     *   options are ignored.
     *
     * @param S3ClientInterface $client Client used for the upload.
     * @param mixed             $source Source of the data to upload.
     * @param array             $config Configuration used to perform the upload.
     */
    public function __construct(
        S3ClientInterface $client,
        $source,
        array $config = []
    ) {
        parent::__construct($client, $source, array_change_key_case($config) + [
            'bucket' => null,
            'key'    => null,
        ]);
    }

    protected function loadUploadWorkflowInfo()
    {
        return [
            'command' => [
                'initiate' => 'CreateMultipartUpload',
                'upload'   => 'UploadPart',
                'complete' => 'CompleteMultipartUpload',
            ],
            'id' => [
                'bucket'    => 'Bucket',
                'key'       => 'Key',
                'upload_id' => 'UploadId',
            ],
            'part_num' => 'PartNumber',
        ];
    }

    protected function createPart($seekable, $number)
    {
        // Initialize the array of part data that will be returned.
        $data = ['PartNumber' => $number];

        // Read from the source to create the body stream.
        if ($seekable) {
            // Case 1: Source is seekable, use lazy stream to defer work.
            $body = $this->limitPartStream(
                new Psr7\LazyOpenStream($this->source->getMetadata('uri'), 'r')
            );
        } else {
            // Case 2: Stream is not seekable; must store in temp stream.
            $source = $this->limitPartStream($this->source);
            $source = $this->decorateWithHashes($source, $data);
            $body = Psr7\stream_for();
            Psr7\copy_to_stream($source, $body);
            $data['ContentLength'] = $body->getSize();
        }

        // Do not create a part if the body size is zero.
        if ($body->getSize() === 0) {
            return false;
        }

        $body->seek(0);
        $data['Body'] = $body;

        return $data;
    }

    protected function extractETag(ResultInterface $result)
    {
        return $result['ETag'];
    }

    protected function getSourceMimeType()
    {
        if ($uri = $this->source->getMetadata('uri')) {
            return Psr7\mimetype_from_filename($uri)
                ?: 'application/octet-stream';
        }
    }

    protected function getSourceSize()
    {
        return $this->source->getSize();
    }

    /**
     * Decorates a stream with a sha256 linear hashing stream.
     *
     * @param Stream $stream Stream to decorate.
     * @param array  $data   Part data to augment with the hash result.
     *
     * @return Stream
     */
    private function decorateWithHashes(Stream $stream, array &$data)
    {
        // Decorate source with a hashing stream
        $hash = new PhpHash('sha256');
        return new HashingStream($stream, $hash, function ($result) use (&$data) {
            $data['ContentSHA256'] = bin2hex($result);
        });
    }
}
<?php
namespace Aws\S3;

use Aws\CommandInterface;
use Aws\Multipart\UploadState;
use Aws\ResultInterface;

trait MultipartUploadingTrait
{
    /**
     * Creates an UploadState object for a multipart upload by querying the
     * service for the specified upload's information.
     *
     * @param S3ClientInterface $client   S3Client used for the upload.
     * @param string            $bucket   Bucket for the multipart upload.
     * @param string            $key      Object key for the multipart upload.
     * @param string            $uploadId Upload ID for the multipart upload.
     *
     * @return UploadState
     */
    public static function getStateFromService(
        S3ClientInterface $client,
        $bucket,
        $key,
        $uploadId
    ) {
        $state = new UploadState([
            'Bucket'   => $bucket,
            'Key'      => $key,
            'UploadId' => $uploadId,
        ]);

        foreach ($client->getPaginator('ListParts', $state->getId()) as $result) {
            // Get the part size from the first part in the first result.
            if (!$state->getPartSize()) {
                $state->setPartSize($result->search('Parts[0].Size'));
            }
            // Mark all the parts returned by ListParts as uploaded.
            foreach ($result['Parts'] as $part) {
                $state->markPartAsUploaded($part['PartNumber'], [
                    'PartNumber' => $part['PartNumber'],
                    'ETag'       => $part['ETag']
                ]);
            }
        }

        $state->setStatus(UploadState::INITIATED);

        return $state;
    }

    protected function handleResult(CommandInterface $command, ResultInterface $result)
    {
        $this->getState()->markPartAsUploaded($command['PartNumber'], [
            'PartNumber' => $command['PartNumber'],
            'ETag'       => $this->extractETag($result),
        ]);
    }

    abstract protected function extractETag(ResultInterface $result);

    protected function getCompleteParams()
    {
        return ['MultipartUpload' => [
            'Parts' => $this->getState()->getUploadedParts()
        ]];
    }

    protected function determinePartSize()
    {
        // Make sure the part size is set.
        $partSize = $this->getConfig()['part_size'] ?: MultipartUploader::PART_MIN_SIZE;

        // Adjust the part size to be larger for known, x-large uploads.
        if ($sourceSize = $this->getSourceSize()) {
            $partSize = (int) max(
                $partSize,
                ceil($sourceSize / MultipartUploader::PART_MAX_NUM)
            );
        }

        // Ensure that the part size follows the rules: 5 MB <= size <= 5 GB.
        if ($partSize < MultipartUploader::PART_MIN_SIZE || $partSize > MultipartUploader::PART_MAX_SIZE) {
            throw new \InvalidArgumentException('The part size must be no less '
                . 'than 5 MB and no greater than 5 GB.');
        }

        return $partSize;
    }

    protected function getInitiateParams()
    {
        $params = [];

        if (isset($this->getConfig()['acl'])) {
            $params['ACL'] = $this->getConfig()['acl'];
        }

        // Set the content type
        if ($type = $this->getSourceMimeType()) {
            $params['ContentType'] = $type;
        }

        return $params;
    }

    /**
     * @return UploadState
     */
    abstract protected function getState();

    /**
     * @return array
     */
    abstract protected function getConfig();

    /**
     * @return int
     */
    abstract protected function getSourceSize();

    /**
     * @return string|null
     */
    abstract protected function getSourceMimeType();
}
<?php
namespace Aws\S3;

use Aws\Exception\MultipartUploadException;
use Aws\Result;
use Aws\S3\Exception\S3Exception;
use GuzzleHttp\Promise\PromisorInterface;
use InvalidArgumentException;

/**
 * Copies objects from one S3 location to another, utilizing a multipart copy
 * when appropriate.
 */
class ObjectCopier implements PromisorInterface
{
    const DEFAULT_MULTIPART_THRESHOLD = MultipartUploader::PART_MAX_SIZE;

    private $client;
    private $source;
    private $destination;
    private $acl;
    private $options;

    private static $defaults = [
        'before_upload' => null,
        'concurrency'   => 5,
        'mup_threshold' => self::DEFAULT_MULTIPART_THRESHOLD,
        'params'        => [],
        'part_size'     => null,
        'version_id'    => null,
    ];

    /**
     * @param S3ClientInterface $client         The S3 Client used to execute
     *                                          the copy command(s).
     * @param array             $source         The object to copy, specified as
     *                                          an array with a 'Bucket' and
     *                                          'Key' keys. Provide a
     *                                          'VersionID' key to copy a
     *                                          specified version of an object.
     * @param array             $destination    The bucket and key to which to
     *                                          copy the $source, specified as
     *                                          an array with a 'Bucket' and
     *                                          'Key' keys.
     * @param string            $acl            ACL to apply to the copy
     *                                          (default: private).
     * @param array             $options        Options used to configure the
     *                                          copy process.
     *
     * @throws InvalidArgumentException
     */
    public function __construct(
        S3ClientInterface $client,
        array $source,
        array $destination,
        $acl = 'private',
        array $options = []
    ) {
        $this->validateLocation($source);
        $this->validateLocation($destination);

        $this->client = $client;
        $this->source = $source;
        $this->destination = $destination;
        $this->acl = $acl;
        $this->options = $options + self::$defaults;
    }

    /**
     * Perform the configured copy asynchronously. Returns a promise that is
     * fulfilled with the result of the CompleteMultipartUpload or CopyObject
     * operation or rejected with an exception.
     *
     * @return \GuzzleHttp\Promise\Promise
     */
    public function promise()
    {
        return \GuzzleHttp\Promise\coroutine(function () {
            $objectStats = (yield $this->client->executeAsync(
                $this->client->getCommand('HeadObject', $this->source)
            ));

            if ($objectStats['ContentLength'] > $this->options['mup_threshold']) {
                $mup = new MultipartCopy(
                    $this->client,
                    $this->getSourcePath(),
                    ['source_metadata' => $objectStats, 'acl' => $this->acl]
                        + $this->destination
                        + $this->options
                );

                yield $mup->promise();
            } else {
                $defaults = [
                    'ACL' => $this->acl,
                    'MetadataDirective' => 'COPY',
                    'CopySource' => $this->getSourcePath(),
                ];

                $params = array_diff_key($this->options, self::$defaults)
                    + $this->destination + $defaults + $this->options['params'];

                yield $this->client->executeAsync(
                    $this->client->getCommand('CopyObject', $params)
                );
            }
        });
    }

    /**
     * Perform the configured copy synchronously. Returns the result of the
     * CompleteMultipartUpload or CopyObject operation.
     *
     * @return Result
     *
     * @throws S3Exception
     * @throws MultipartUploadException
     */
    public function copy()
    {
        return $this->promise()->wait();
    }

    private function validateLocation(array $location)
    {
        if (empty($location['Bucket']) || empty($location['Key'])) {
            throw new \InvalidArgumentException('Locations provided to an'
                . ' Aws\S3\ObjectCopier must have a non-empty Bucket and Key');
        }
    }

    private function getSourcePath()
    {
        $sourcePath = "/{$this->source['Bucket']}/"
            . rawurlencode($this->source['Key']);
        if (isset($this->source['VersionId'])) {
            $sourcePath .= "?versionId={$this->source['VersionId']}";
        }

        return $sourcePath;
    }
}
<?php
namespace Aws\S3;

use GuzzleHttp\Promise\PromisorInterface;
use GuzzleHttp\Psr7;
use Psr\Http\Message\StreamInterface;

/**
 * Uploads an object to S3, using a PutObject command or a multipart upload as
 * appropriate.
 */
class ObjectUploader implements PromisorInterface
{
    const DEFAULT_MULTIPART_THRESHOLD = 16777216;

    private $client;
    private $bucket;
    private $key;
    private $body;
    private $acl;
    private $options;
    private static $defaults = [
        'before_upload' => null,
        'concurrency'   => 3,
        'mup_threshold' => self::DEFAULT_MULTIPART_THRESHOLD,
        'params'        => [],
        'part_size'     => null,
    ];

    /**
     * @param S3ClientInterface $client         The S3 Client used to execute
     *                                          the upload command(s).
     * @param string            $bucket         Bucket to upload the object.
     * @param string            $key            Key of the object.
     * @param mixed             $body           Object data to upload. Can be a
     *                                          StreamInterface, PHP stream
     *                                          resource, or a string of data to
     *                                          upload.
     * @param string            $acl            ACL to apply to the copy
     *                                          (default: private).
     * @param array             $options        Options used to configure the
     *                                          copy process.
     */
    public function __construct(
        S3ClientInterface $client,
        $bucket,
        $key,
        $body,
        $acl = 'private',
        array $options = []
    ) {
        $this->client = $client;
        $this->bucket = $bucket;
        $this->key = $key;
        $this->body = Psr7\stream_for($body);
        $this->acl = $acl;
        $this->options = array_intersect_key(
            $options + self::$defaults,
            self::$defaults
        );
    }

    public function promise()
    {
        /** @var int $mup_threshold */
        $mup_threshold = $this->options['mup_threshold'];
        if ($this->requiresMultipart($this->body, $mup_threshold)) {
            // Perform a multipart upload.
            $this->options['before_initiate'] = function ($command) {
                foreach ($this->options['params'] as $k => $v) {
                    $command[$k] = $v;
                }
            };
            return (new MultipartUploader($this->client, $this->body, [
                    'bucket' => $this->bucket,
                    'key'    => $this->key,
                    'acl'    => $this->acl
                ] + $this->options))->promise();
        } else {
            // Perform a regular PutObject operation.
            $command = $this->client->getCommand('PutObject', [
                    'Bucket' => $this->bucket,
                    'Key'    => $this->key,
                    'Body'   => $this->body,
                    'ACL'    => $this->acl,
                ] + $this->options['params']);
            if (is_callable($this->options['before_upload'])) {
                $this->options['before_upload']($command);
            }
            return $this->client->executeAsync($command);
        }
    }

    public function upload()
    {
        return $this->promise()->wait();
    }

    /**
     * Determines if the body should be uploaded using PutObject or the
     * Multipart Upload System. It also modifies the passed-in $body as needed
     * to support the upload.
     *
     * @param StreamInterface $body      Stream representing the body.
     * @param integer             $threshold Minimum bytes before using Multipart.
     *
     * @return bool
     */
    private function requiresMultipart(StreamInterface &$body, $threshold)
    {
        // If body size known, compare to threshold to determine if Multipart.
        if ($body->getSize() !== null) {
            return $body->getSize() >= $threshold;
        }

        /**
         * Handle the situation where the body size is unknown.
         * Read up to 5MB into a buffer to determine how to upload the body.
         * @var StreamInterface $buffer
         */
        $buffer = Psr7\stream_for();
        Psr7\copy_to_stream($body, $buffer, MultipartUploader::PART_MIN_SIZE);

        // If body < 5MB, use PutObject with the buffer.
        if ($buffer->getSize() < MultipartUploader::PART_MIN_SIZE) {
            $buffer->seek(0);
            $body = $buffer;
            return false;
        }

        // If body >= 5 MB, then use multipart. [YES]
        if ($body->isSeekable()) {
            // If the body is seekable, just rewind the body.
            $body->seek(0);
        } else {
            // If the body is non-seekable, stitch the rewind the buffer and
            // the partially read body together into one stream. This avoids
            // unnecessary disc usage and does not require seeking on the
            // original stream.
            $buffer->seek(0);
            $body = new Psr7\AppendStream([$buffer, $body]);
        }

        return true;
    }
}
<?php
namespace Aws\S3;

use Aws\CommandInterface;
use Aws\ResultInterface;
use Aws\S3\Exception\PermanentRedirectException;
use Psr\Http\Message\RequestInterface;

/**
 * Throws a PermanentRedirectException exception when a 301 redirect is
 * encountered.
 *
 * @internal
 */
class PermanentRedirectMiddleware
{
    /** @var callable  */
    private $nextHandler;

    /**
     * Create a middleware wrapper function.
     *
     * @return callable
     */
    public static function wrap()
    {
        return function (callable $handler) {
            return new self($handler);
        };
    }

    /**
     * @param callable $nextHandler Next handler to invoke.
     */
    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    public function __invoke(CommandInterface $command, RequestInterface $request = null)
    {
        $next = $this->nextHandler;
        return $next($command, $request)->then(
            function (ResultInterface $result) use ($command) {
                $status = isset($result['@metadata']['statusCode'])
                    ? $result['@metadata']['statusCode']
                    : null;
                if ($status == 301) {
                    throw new PermanentRedirectException(
                        'Encountered a permanent redirect while requesting '
                        . $result->search('"@metadata".effectiveUri') . '. '
                        . 'Are you sure you are using the correct region for '
                        . 'this bucket?',
                        $command,
                        ['result' => $result]
                    );
                }
                return $result;
            }
        );
    }
}
<?php
namespace Aws\S3;

use Aws\Credentials\CredentialsInterface;
use GuzzleHttp\Psr7\Uri;

/**
 * @deprecated
 */
class PostObject
{
    private $client;
    private $bucket;
    private $formAttributes;
    private $formInputs;
    private $jsonPolicy;

    /**
     * Constructs the PostObject.
     *
     * @param S3ClientInterface $client     Client used with the POST object
     * @param string            $bucket     Bucket to use
     * @param array             $formInputs Associative array of form input
     *                                      fields.
     * @param string|array      $jsonPolicy JSON encoded POST policy document.
     *                                      The policy will be base64 encoded
     *                                      and applied to the form on your
     *                                      behalf.
     */
    public function __construct(
        S3ClientInterface $client,
        $bucket,
        array $formInputs,
        $jsonPolicy
    ) {
        $this->client = $client;
        $this->bucket = $bucket;

        if (is_array($jsonPolicy)) {
            $jsonPolicy = json_encode($jsonPolicy);
        }

        $this->jsonPolicy = $jsonPolicy;
        $this->formAttributes = [
            'action'  => $this->generateUri(),
            'method'  => 'POST',
            'enctype' => 'multipart/form-data'
        ];

        $this->formInputs = $formInputs + ['key' => '${filename}'];
        $credentials = $client->getCredentials()->wait();
        $this->formInputs += $this->getPolicyAndSignature($credentials);
    }

    /**
     * Gets the S3 client.
     *
     * @return S3ClientInterface
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * Gets the bucket name.
     *
     * @return string
     */
    public function getBucket()
    {
        return $this->bucket;
    }

    /**
     * Gets the form attributes as an array.
     *
     * @return array
     */
    public function getFormAttributes()
    {
        return $this->formAttributes;
    }

    /**
     * Set a form attribute.
     *
     * @param string $attribute Form attribute to set.
     * @param string $value     Value to set.
     */
    public function setFormAttribute($attribute, $value)
    {
        $this->formAttributes[$attribute] = $value;
    }

    /**
     * Gets the form inputs as an array.
     *
     * @return array
     */
    public function getFormInputs()
    {
        return $this->formInputs;
    }

    /**
     * Set a form input.
     *
     * @param string $field Field name to set
     * @param string $value Value to set.
     */
    public function setFormInput($field, $value)
    {
        $this->formInputs[$field] = $value;
    }

    /**
     * Gets the raw JSON policy.
     *
     * @return string
     */
    public function getJsonPolicy()
    {
        return $this->jsonPolicy;
    }

    private function generateUri()
    {
        $uri = new Uri($this->client->getEndpoint());

        if ($uri->getScheme() === 'https'
            && strpos($this->bucket, '.') !== false
        ) {
            // Use path-style URLs
            $uri = $uri->withPath($this->bucket);
        } else {
            // Use virtual-style URLs
            $uri = $uri->withHost($this->bucket . '.' . $uri->getHost());
        }

        return (string) $uri;
    }

    protected function getPolicyAndSignature(CredentialsInterface $creds)
    {
        $jsonPolicy64 = base64_encode($this->jsonPolicy);

        return [
            'AWSAccessKeyId' => $creds->getAccessKeyId(),
            'policy'    => $jsonPolicy64,
            'signature' => base64_encode(hash_hmac(
                'sha1',
                $jsonPolicy64,
                $creds->getSecretKey(),
                true
            ))
        ];
    }
}
<?php
namespace Aws\S3;

use Aws\Credentials\CredentialsInterface;
use GuzzleHttp\Psr7\Uri;
use Aws\Signature\SignatureTrait;
use Aws\Signature\SignatureV4 as SignatureV4;
use Aws\Api\TimestampShape as TimestampShape;

/**
 * Encapsulates the logic for getting the data for an S3 object POST upload form
 *
 * @link http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPOST.html
 * @link http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
 */
class PostObjectV4
{
    use SignatureTrait;

    private $client;
    private $bucket;
    private $formAttributes;
    private $formInputs;
    private $jsonPolicy;

    /**
     * Constructs the PostObject.
     *
     * The options array accepts the following keys:
     * @link http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
     *
     * @param S3ClientInterface $client     Client used with the POST object
     * @param string            $bucket     Bucket to use
     * @param array             $formInputs Associative array of form input
     *                                      fields.
     * @param array             $options    Policy condition options
     * @param mixed             $expiration Upload expiration time value. By
     *                                      default: 1 hour valid period.
     */
    public function __construct(
        S3ClientInterface $client,
        $bucket,
        array $formInputs,
        array $options = [],
        $expiration = '+1 hours'
    ) {
        $this->client = $client;
        $this->bucket = $bucket;

        // setup form attributes
        $this->formAttributes = [
            'action'  => $this->generateUri(),
            'method'  => 'POST',
            'enctype' => 'multipart/form-data'
        ];

        $credentials   = $this->client->getCredentials()->wait();
        $securityToken = $credentials->getSecurityToken();

        if (null !== $securityToken) {
            array_push($options, ['x-amz-security-token' => $securityToken]);
            $formInputs['X-Amz-Security-Token'] = $securityToken;
        }

        // setup basic policy
        $policy = [
            'expiration' => TimestampShape::format($expiration, 'iso8601'),
            'conditions' => $options,
        ];

        // setup basic formInputs
        $this->formInputs = $formInputs + ['key' => '${filename}'];

        // finalize policy and signature

        $this->formInputs += $this->getPolicyAndSignature(
            $credentials,
            $policy
        );
    }

    /**
     * Gets the S3 client.
     *
     * @return S3ClientInterface
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * Gets the bucket name.
     *
     * @return string
     */
    public function getBucket()
    {
        return $this->bucket;
    }

    /**
     * Gets the form attributes as an array.
     *
     * @return array
     */
    public function getFormAttributes()
    {
        return $this->formAttributes;
    }

    /**
     * Set a form attribute.
     *
     * @param string $attribute Form attribute to set.
     * @param string $value     Value to set.
     */
    public function setFormAttribute($attribute, $value)
    {
        $this->formAttributes[$attribute] = $value;
    }

    /**
     * Gets the form inputs as an array.
     *
     * @return array
     */
    public function getFormInputs()
    {
        return $this->formInputs;
    }

    /**
     * Set a form input.
     *
     * @param string $field Field name to set
     * @param string $value Value to set.
     */
    public function setFormInput($field, $value)
    {
        $this->formInputs[$field] = $value;
    }

    private function generateUri()
    {
        $uri = new Uri($this->client->getEndpoint());

        if ($uri->getScheme() === 'https'
            && strpos($this->bucket, '.') !== false
        ) {
            // Use path-style URLs
            $uri = $uri->withPath($this->bucket);
        } else {
            // Use virtual-style URLs if haven't been set up already
            if (strpos($uri->getHost(), $this->bucket . '.') !== 0) {
                $uri = $uri->withHost($this->bucket . '.' . $uri->getHost());
            }
        }

        return (string) $uri;
    }

    protected function getPolicyAndSignature(
        CredentialsInterface $credentials,
        array $policy
    ){
        $ldt = gmdate(SignatureV4::ISO8601_BASIC);
        $sdt = substr($ldt, 0, 8);
        $policy['conditions'][] = ['X-Amz-Date' => $ldt];

        $region = $this->client->getRegion();
        $scope = $this->createScope($sdt, $region, 's3');
        $creds = "{$credentials->getAccessKeyId()}/$scope";
        $policy['conditions'][] = ['X-Amz-Credential' => $creds];

        $policy['conditions'][] = ['X-Amz-Algorithm' => "AWS4-HMAC-SHA256"];

        $jsonPolicy64 = base64_encode(json_encode($policy));
        $key = $this->getSigningKey(
            $sdt,
            $region,
            's3',
            $credentials->getSecretKey()
        );

        return [
            'X-Amz-Credential' => $creds,
            'X-Amz-Algorithm' => "AWS4-HMAC-SHA256",
            'X-Amz-Date' => $ldt,
            'Policy'           => $jsonPolicy64,
            'X-Amz-Signature'  => bin2hex(
                hash_hmac('sha256', $jsonPolicy64, $key, true)
            ),
        ];
    }
}
<?php
namespace Aws\S3;

use Aws\CommandInterface;
use Aws\ResultInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Injects ObjectURL into the result of the PutObject operation.
 *
 * @internal
 */
class PutObjectUrlMiddleware
{
    /** @var callable  */
    private $nextHandler;

    /**
     * Create a middleware wrapper function.
     *
     * @return callable
     */
    public static function wrap()
    {
        return function (callable $handler) {
            return new self($handler);
        };
    }

    /**
     * @param callable $nextHandler Next handler to invoke.
     */
    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    public function __invoke(CommandInterface $command, RequestInterface $request = null)
    {
        $next = $this->nextHandler;
        return $next($command, $request)->then(
            function (ResultInterface $result) use ($command) {
                $name = $command->getName();
                switch ($name) {
                    case 'PutObject':
                    case 'CopyObject':
                        $result['ObjectURL'] = $result['@metadata']['effectiveUri'];
                        break;
                    case 'CompleteMultipartUpload':
                        $result['ObjectURL'] = $result['Location'];
                        break;
                }
                return $result;
            }
        );
    }
}
<?php
namespace Aws\S3;

use Aws\Api\Parser\AbstractParser;
use Aws\Api\Parser\Exception\ParserException;
use Aws\CommandInterface;
use Aws\Exception\AwsException;
use Psr\Http\Message\ResponseInterface;

/**
 * Converts malformed responses to a retryable error type.
 *
 * @internal
 */
class RetryableMalformedResponseParser extends AbstractParser
{
    /** @var callable */
    private $parser;
    /** @var string */
    private $exceptionClass;

    public function __construct(
        callable $parser,
        $exceptionClass = AwsException::class
    ) {
        $this->parser = $parser;
        $this->exceptionClass = $exceptionClass;
    }

    public function __invoke(
        CommandInterface $command,
        ResponseInterface $response
    ) {
        $fn = $this->parser;

        try {
            return $fn($command, $response);
        } catch (ParserException $e) {
            throw new $this->exceptionClass(
                "Error parsing response for {$command->getName()}:"
                    . " AWS parsing error: {$e->getMessage()}",
                $command,
                ['connection_error' => true, 'exception' => $e],
                $e
            );
        }
    }
}
<?php
namespace Aws\S3;

use Aws\Api\ApiProvider;
use Aws\Api\DocModel;
use Aws\Api\Service;
use Aws\AwsClient;
use Aws\ClientResolver;
use Aws\Command;
use Aws\Exception\AwsException;
use Aws\HandlerList;
use Aws\Middleware;
use Aws\RetryMiddleware;
use Aws\ResultInterface;
use Aws\CommandInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Client used to interact with **Amazon Simple Storage Service (Amazon S3)**.
 *
 * @method \Aws\Result abortMultipartUpload(array $args = [])
 * @method \GuzzleHttp\Promise\Promise abortMultipartUploadAsync(array $args = [])
 * @method \Aws\Result completeMultipartUpload(array $args = [])
 * @method \GuzzleHttp\Promise\Promise completeMultipartUploadAsync(array $args = [])
 * @method \Aws\Result copyObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise copyObjectAsync(array $args = [])
 * @method \Aws\Result createBucket(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createBucketAsync(array $args = [])
 * @method \Aws\Result createMultipartUpload(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createMultipartUploadAsync(array $args = [])
 * @method \Aws\Result deleteBucket(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketAsync(array $args = [])
 * @method \Aws\Result deleteBucketAnalyticsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketAnalyticsConfigurationAsync(array $args = [])
 * @method \Aws\Result deleteBucketCors(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketCorsAsync(array $args = [])
 * @method \Aws\Result deleteBucketInventoryConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketInventoryConfigurationAsync(array $args = [])
 * @method \Aws\Result deleteBucketLifecycle(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketLifecycleAsync(array $args = [])
 * @method \Aws\Result deleteBucketMetricsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketMetricsConfigurationAsync(array $args = [])
 * @method \Aws\Result deleteBucketPolicy(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketPolicyAsync(array $args = [])
 * @method \Aws\Result deleteBucketReplication(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketReplicationAsync(array $args = [])
 * @method \Aws\Result deleteBucketTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketTaggingAsync(array $args = [])
 * @method \Aws\Result deleteBucketWebsite(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketWebsiteAsync(array $args = [])
 * @method \Aws\Result deleteObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteObjectAsync(array $args = [])
 * @method \Aws\Result deleteObjectTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteObjectTaggingAsync(array $args = [])
 * @method \Aws\Result deleteObjects(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteObjectsAsync(array $args = [])
 * @method \Aws\Result getBucketAccelerateConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketAccelerateConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketAcl(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketAclAsync(array $args = [])
 * @method \Aws\Result getBucketAnalyticsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketAnalyticsConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketCors(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketCorsAsync(array $args = [])
 * @method \Aws\Result getBucketInventoryConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketInventoryConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketLifecycle(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketLifecycleAsync(array $args = [])
 * @method \Aws\Result getBucketLifecycleConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketLifecycleConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketLocation(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketLocationAsync(array $args = [])
 * @method \Aws\Result getBucketLogging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketLoggingAsync(array $args = [])
 * @method \Aws\Result getBucketMetricsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketMetricsConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketNotification(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketNotificationAsync(array $args = [])
 * @method \Aws\Result getBucketNotificationConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketNotificationConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketPolicy(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketPolicyAsync(array $args = [])
 * @method \Aws\Result getBucketReplication(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketReplicationAsync(array $args = [])
 * @method \Aws\Result getBucketRequestPayment(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketRequestPaymentAsync(array $args = [])
 * @method \Aws\Result getBucketTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketTaggingAsync(array $args = [])
 * @method \Aws\Result getBucketVersioning(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketVersioningAsync(array $args = [])
 * @method \Aws\Result getBucketWebsite(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketWebsiteAsync(array $args = [])
 * @method \Aws\Result getObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getObjectAsync(array $args = [])
 * @method \Aws\Result getObjectAcl(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getObjectAclAsync(array $args = [])
 * @method \Aws\Result getObjectTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getObjectTaggingAsync(array $args = [])
 * @method \Aws\Result getObjectTorrent(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getObjectTorrentAsync(array $args = [])
 * @method \Aws\Result headBucket(array $args = [])
 * @method \GuzzleHttp\Promise\Promise headBucketAsync(array $args = [])
 * @method \Aws\Result headObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise headObjectAsync(array $args = [])
 * @method \Aws\Result listBucketAnalyticsConfigurations(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listBucketAnalyticsConfigurationsAsync(array $args = [])
 * @method \Aws\Result listBucketInventoryConfigurations(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listBucketInventoryConfigurationsAsync(array $args = [])
 * @method \Aws\Result listBucketMetricsConfigurations(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listBucketMetricsConfigurationsAsync(array $args = [])
 * @method \Aws\Result listBuckets(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listBucketsAsync(array $args = [])
 * @method \Aws\Result listMultipartUploads(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listMultipartUploadsAsync(array $args = [])
 * @method \Aws\Result listObjectVersions(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listObjectVersionsAsync(array $args = [])
 * @method \Aws\Result listObjects(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listObjectsAsync(array $args = [])
 * @method \Aws\Result listObjectsV2(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listObjectsV2Async(array $args = [])
 * @method \Aws\Result listParts(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listPartsAsync(array $args = [])
 * @method \Aws\Result putBucketAccelerateConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketAccelerateConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketAcl(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketAclAsync(array $args = [])
 * @method \Aws\Result putBucketAnalyticsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketAnalyticsConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketCors(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketCorsAsync(array $args = [])
 * @method \Aws\Result putBucketInventoryConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketInventoryConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketLifecycle(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketLifecycleAsync(array $args = [])
 * @method \Aws\Result putBucketLifecycleConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketLifecycleConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketLogging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketLoggingAsync(array $args = [])
 * @method \Aws\Result putBucketMetricsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketMetricsConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketNotification(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketNotificationAsync(array $args = [])
 * @method \Aws\Result putBucketNotificationConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketNotificationConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketPolicy(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketPolicyAsync(array $args = [])
 * @method \Aws\Result putBucketReplication(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketReplicationAsync(array $args = [])
 * @method \Aws\Result putBucketRequestPayment(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketRequestPaymentAsync(array $args = [])
 * @method \Aws\Result putBucketTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketTaggingAsync(array $args = [])
 * @method \Aws\Result putBucketVersioning(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketVersioningAsync(array $args = [])
 * @method \Aws\Result putBucketWebsite(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketWebsiteAsync(array $args = [])
 * @method \Aws\Result putObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putObjectAsync(array $args = [])
 * @method \Aws\Result putObjectAcl(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putObjectAclAsync(array $args = [])
 * @method \Aws\Result putObjectTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putObjectTaggingAsync(array $args = [])
 * @method \Aws\Result restoreObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise restoreObjectAsync(array $args = [])
 * @method \Aws\Result uploadPart(array $args = [])
 * @method \GuzzleHttp\Promise\Promise uploadPartAsync(array $args = [])
 * @method \Aws\Result uploadPartCopy(array $args = [])
 * @method \GuzzleHttp\Promise\Promise uploadPartCopyAsync(array $args = [])
 */
class S3Client extends AwsClient implements S3ClientInterface
{
    use S3ClientTrait;

    public static function getArguments()
    {
        $args = parent::getArguments();
        $args['retries']['fn'] = [__CLASS__, '_applyRetryConfig'];
        $args['api_provider']['fn'] = [__CLASS__, '_applyApiProvider'];

        return $args + [
            'bucket_endpoint' => [
                'type'    => 'config',
                'valid'   => ['bool'],
                'doc'     => 'Set to true to send requests to a hardcoded '
                    . 'bucket endpoint rather than create an endpoint as a '
                    . 'result of injecting the bucket into the URL. This '
                    . 'option is useful for interacting with CNAME endpoints.',
            ],
            'use_accelerate_endpoint' => [
                'type' => 'config',
                'valid' => ['bool'],
                'doc' => 'Set to true to send requests to an S3 Accelerate'
                    . ' endpoint by default. Can be enabled or disabled on'
                    . ' individual operations by setting'
                    . ' \'@use_accelerate_endpoint\' to true or false. Note:'
                    . ' you must enable S3 Accelerate on a bucket before it can'
                    . ' be accessed via an Accelerate endpoint.',
                'default' => false,
            ],
            'use_dual_stack_endpoint' => [
                'type' => 'config',
                'valid' => ['bool'],
                'doc' => 'Set to true to send requests to an S3 Dual Stack'
                    . ' endpoint by default, which enables IPv6 Protocol.'
                    . ' Can be enabled or disabled on individual operations by setting'
                    . ' \'@use_dual_stack_endpoint\' to true or false.',
                'default' => false,
            ],
        ];
    }

    /**
     * {@inheritdoc}
     *
     * In addition to the options available to
     * {@see Aws\AwsClient::__construct}, S3Client accepts the following
     * options:
     *
     * - bucket_endpoint: (bool) Set to true to send requests to a
     *   hardcoded bucket endpoint rather than create an endpoint as a result
     *   of injecting the bucket into the URL. This option is useful for
     *   interacting with CNAME endpoints.
     * - calculate_md5: (bool) Set to false to disable calculating an MD5
     *   for all Amazon S3 signed uploads.
     * - use_accelerate_endpoint: (bool) Set to true to send requests to an S3
     *   Accelerate endpoint by default. Can be enabled or disabled on
     *   individual operations by setting '@use_accelerate_endpoint' to true or
     *   false. Note: you must enable S3 Accelerate on a bucket before it can be
     *   accessed via an Accelerate endpoint.
     * - use_dual_stack_endpoint: (bool) Set to true to send requests to an S3
     *   Dual Stack endpoint by default, which enables IPv6 Protocol.
     *   Can be enabled or disabled on individual operations by setting
     *   '@use_dual_stack_endpoint\' to true or false. Note:
     *   you cannot use it together with an accelerate endpoint.
     *
     * @param array $args
     */
    public function __construct(array $args)
    {
        parent::__construct($args);
        $stack = $this->getHandlerList();
        $stack->appendInit(SSECMiddleware::wrap($this->getEndpoint()->getScheme()), 's3.ssec');
        $stack->appendBuild(ApplyChecksumMiddleware::wrap(), 's3.checksum');
        $stack->appendBuild(
            Middleware::contentType(['PutObject', 'UploadPart']),
            's3.content_type'
        );

        $stack->appendBuild(
            S3EndpointMiddleware::wrap(
                $this->getRegion(),
                [
                    'dual_stack' => $this->getConfig('use_dual_stack_endpoint'),
                    'accelerate' => $this->getConfig('use_accelerate_endpoint')
                ]
            ),
            's3.endpoint_middleware'
        );

        // Use the bucket style middleware when using a "bucket_endpoint" (for cnames)
        if ($this->getConfig('bucket_endpoint')) {
            $stack->appendBuild(BucketEndpointMiddleware::wrap(), 's3.bucket_endpoint');
        }

        $stack->appendSign(PutObjectUrlMiddleware::wrap(), 's3.put_object_url');
        $stack->appendSign(PermanentRedirectMiddleware::wrap(), 's3.permanent_redirect');
        $stack->appendInit(Middleware::sourceFile($this->getApi()), 's3.source_file');
        $stack->appendInit($this->getSaveAsParameter(), 's3.save_as');
        $stack->appendInit($this->getLocationConstraintMiddleware(), 's3.location');
        $stack->appendInit($this->getEncodingTypeMiddleware(), 's3.auto_encode');
        $stack->appendInit($this->getHeadObjectMiddleware(), 's3.head_object');
    }

    /**
     * Determine if a string is a valid name for a DNS compatible Amazon S3
     * bucket.
     *
     * DNS compatible bucket names can be used as a subdomain in a URL (e.g.,
     * "<bucket>.s3.amazonaws.com").
     *
     * @param string $bucket Bucket name to check.
     *
     * @return bool
     */
    public static function isBucketDnsCompatible($bucket)
    {
        $bucketLen = strlen($bucket);

        return ($bucketLen >= 3 && $bucketLen <= 63) &&
            // Cannot look like an IP address
            !filter_var($bucket, FILTER_VALIDATE_IP) &&
            preg_match('/^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$/', $bucket);
    }

    public function createPresignedRequest(CommandInterface $command, $expires)
    {
        $command = clone $command;
        $command->getHandlerList()->remove('signer');

        /** @var \Aws\Signature\SignatureInterface $signer */
        $signer = call_user_func(
            $this->getSignatureProvider(),
            $this->getConfig('signature_version'),
            $this->getConfig('signing_name'),
            $this->getConfig('signing_region')
        );

        return $signer->presign(
            \Aws\serialize($command),
            $this->getCredentials()->wait(),
            $expires
        );
    }

    public function getObjectUrl($bucket, $key)
    {
        $command = $this->getCommand('GetObject', [
            'Bucket' => $bucket,
            'Key'    => $key
        ]);

        return (string) \Aws\serialize($command)->getUri();
    }

    /**
     * Raw URL encode a key and allow for '/' characters
     *
     * @param string $key Key to encode
     *
     * @return string Returns the encoded key
     */
    public static function encodeKey($key)
    {
        return str_replace('%2F', '/', rawurlencode($key));
    }

    /**
     * Provides a middleware that removes the need to specify LocationConstraint on CreateBucket.
     *
     * @return \Closure
     */
    private function getLocationConstraintMiddleware()
    {
        $region = $this->getRegion();
        return static function (callable $handler) use ($region) {
            return function (Command $command, $request = null) use ($handler, $region) {
                if ($command->getName() === 'CreateBucket') {
                    $locationConstraint = isset($command['CreateBucketConfiguration']['LocationConstraint'])
                        ? $command['CreateBucketConfiguration']['LocationConstraint']
                        : null;

                    if ($locationConstraint === 'us-east-1') {
                        unset($command['CreateBucketConfiguration']);
                    } elseif ('us-east-1' !== $region && empty($locationConstraint)) {
                        $command['CreateBucketConfiguration'] = ['LocationConstraint' => $region];
                    }
                }

                return $handler($command, $request);
            };
        };
    }

    /**
     * Provides a middleware that supports the `SaveAs` parameter.
     *
     * @return \Closure
     */
    private function getSaveAsParameter()
    {
        return static function (callable $handler) {
            return function (Command $command, $request = null) use ($handler) {
                if ($command->getName() === 'GetObject' && isset($command['SaveAs'])) {
                    $command['@http']['sink'] = $command['SaveAs'];
                    unset($command['SaveAs']);
                }

                return $handler($command, $request);
            };
        };
    }

    /**
     * Provides a middleware that disables content decoding on HeadObject
     * commands.
     *
     * @return \Closure
     */
    private function getHeadObjectMiddleware()
    {
        return static function (callable $handler) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($handler) {
                if ($command->getName() === 'HeadObject'
                    && !isset($command['@http']['decode_content'])
                ) {
                    $command['@http']['decode_content'] = false;
                }

                return $handler($command, $request);
            };
        };
    }

    /**
     * Provides a middleware that autopopulates the EncodingType parameter on
     * ListObjects commands.
     *
     * @return \Closure
     */
    private function getEncodingTypeMiddleware()
    {
        return static function (callable $handler) {
            return function (Command $command, $request = null) use ($handler) {
                $autoSet = false;
                if ($command->getName() === 'ListObjects'
                    && empty($command['EncodingType'])
                ) {
                    $command['EncodingType'] = 'url';
                    $autoSet = true;
                }

                return $handler($command, $request)
                    ->then(function (ResultInterface $result) use ($autoSet) {
                        if ($result['EncodingType'] === 'url' && $autoSet) {
                            static $topLevel = [
                                'Delimiter',
                                'Marker',
                                'NextMarker',
                                'Prefix',
                            ];
                            static $nested = [
                                ['Contents', 'Key'],
                                ['CommonPrefixes', 'Prefix'],
                            ];

                            foreach ($topLevel as $key) {
                                if (isset($result[$key])) {
                                    $result[$key] = urldecode($result[$key]);
                                }
                            }
                            foreach ($nested as $steps) {
                                if (isset($result[$steps[0]])) {
                                    foreach ($result[$steps[0]] as &$part) {
                                        if (isset($part[$steps[1]])) {
                                            $part[$steps[1]]
                                                = urldecode($part[$steps[1]]);
                                        }
                                    }
                                }
                            }

                        }

                        return $result;
                    });
            };
        };
    }

    /** @internal */
    public static function _applyRetryConfig($value, $_, HandlerList $list)
    {
        if (!$value) {
            return;
        }

        $decider = RetryMiddleware::createDefaultDecider($value);
        $decider = function ($retries, $command, $request, $result, $error) use ($decider, $value) {
            $maxRetries = null !== $command['@retries']
                ? $command['@retries']
                : $value;

            if ($decider($retries, $command, $request, $result, $error)) {
                return true;
            } elseif ($error instanceof AwsException
                && $retries < $maxRetries
            ) {
                if (
                    $error->getResponse()
                    && $error->getResponse()->getStatusCode() >= 400
                ) {
                    return strpos(
                        $error->getResponse()->getBody(),
                        'Your socket connection to the server'
                    ) !== false;
                } elseif ($error->getPrevious() instanceof RequestException) {
                    // All commands except CompleteMultipartUpload are
                    // idempotent and may be retried without worry if a
                    // networking error has occurred.
                    return $command->getName() !== 'CompleteMultipartUpload';
                }
            }
            return false;
        };

        $delay = [RetryMiddleware::class, 'exponentialDelay'];
        $list->appendSign(Middleware::retry($decider, $delay), 'retry');
    }

    /** @internal */
    public static function _applyApiProvider($value, array &$args, HandlerList $list)
    {
        ClientResolver::_apply_api_provider($value, $args, $list);
        $args['parser'] = new GetBucketLocationParser(
            new AmbiguousSuccessParser(
                new RetryableMalformedResponseParser(
                    $args['parser'],
                    $args['exception_class']
                ),
                $args['error_parser'],
                $args['exception_class']
            )
        );
    }

    /**
     * @internal
     * @codeCoverageIgnore
     */
    public static function applyDocFilters(array $api, array $docs)
    {
        $b64 = '<div class="alert alert-info">This value will be base64 encoded on your behalf.</div>';
        $opt = '<div class="alert alert-info">This value will be computed for you it is not supplied.</div>';

        // Add the SourceFile parameter.
        $docs['shapes']['SourceFile']['base'] = 'The path to a file on disk to use instead of the Body parameter.';
        $api['shapes']['SourceFile'] = ['type' => 'string'];
        $api['shapes']['PutObjectRequest']['members']['SourceFile'] = ['shape' => 'SourceFile'];
        $api['shapes']['UploadPartRequest']['members']['SourceFile'] = ['shape' => 'SourceFile'];

        // Add the ContentSHA256 parameter.
        $docs['shapes']['ContentSHA256']['base'] = 'A SHA256 hash of the body content of the request.';
        $api['shapes']['ContentSHA256'] = ['type' => 'string'];
        $api['shapes']['PutObjectRequest']['members']['ContentSHA256'] = ['shape' => 'ContentSHA256'];
        $api['shapes']['UploadPartRequest']['members']['ContentSHA256'] = ['shape' => 'ContentSHA256'];
        unset($api['shapes']['PutObjectRequest']['members']['ContentMD5']);
        unset($api['shapes']['UploadPartRequest']['members']['ContentMD5']);
        $docs['shapes']['ContentSHA256']['append'] = $opt;

        // Add the SaveAs parameter.
        $docs['shapes']['SaveAs']['base'] = 'The path to a file on disk to save the object data.';
        $api['shapes']['SaveAs'] = ['type' => 'string'];
        $api['shapes']['GetObjectRequest']['members']['SaveAs'] = ['shape' => 'SaveAs'];

        // Several SSECustomerKey documentation updates.
        $docs['shapes']['SSECustomerKey']['append'] = $b64;
        $docs['shapes']['CopySourceSSECustomerKey']['append'] = $b64;
        $docs['shapes']['SSECustomerKeyMd5']['append'] = $opt;

        // Add the ObjectURL to various output shapes and documentation.
        $docs['shapes']['ObjectURL']['base'] = 'The URI of the created object.';
        $api['shapes']['ObjectURL'] = ['type' => 'string'];
        $api['shapes']['PutObjectOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
        $api['shapes']['CopyObjectOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];
        $api['shapes']['CompleteMultipartUploadOutput']['members']['ObjectURL'] = ['shape' => 'ObjectURL'];

        // Fix references to Location Constraint.
        unset($api['shapes']['CreateBucketRequest']['payload']);
        $api['shapes']['BucketLocationConstraint']['enum'] = [
            "ap-northeast-1",
            "ap-southeast-2",
            "ap-southeast-1",
            "cn-north-1",
            "eu-central-1",
            "eu-west-1",
            "us-east-1",
            "us-west-1",
            "us-west-2",
            "sa-east-1",
        ];

        // Add a note that the ContentMD5 is optional.
        $docs['shapes']['ContentMD5']['append'] = '<div class="alert alert-info">The value will be computed on '
            . 'your behalf.</div>';

        return [
            new Service($api, ApiProvider::defaultProvider()),
            new DocModel($docs)
        ];
    }
}
<?php
namespace Aws\S3;

use Aws\AwsClientInterface;
use Aws\CommandInterface;
use Aws\ResultInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\RequestInterface;

interface S3ClientInterface extends AwsClientInterface
{
    /**
     * Create a pre-signed URL for the given S3 command object.
     *
     * @param CommandInterface $command     Command to create a pre-signed
     *                                      URL for.
     * @param int|string|\DateTime $expires The time at which the URL should
     *                                      expire. This can be a Unix
     *                                      timestamp, a PHP DateTime object,
     *                                      or a string that can be evaluated
     *                                      by strtotime().
     *
     * @return RequestInterface
     */
    public function createPresignedRequest(CommandInterface $command, $expires);

    /**
     * Returns the URL to an object identified by its bucket and key.
     *
     * The URL returned by this method is not signed nor does it ensure the the
     * bucket and key given to the method exist. If you need a signed URL, then
     * use the {@see \Aws\S3\S3Client::createPresignedRequest} method and get
     * the URI of the signed request.
     *
     * @param string $bucket  The name of the bucket where the object is located
     * @param string $key     The key of the object
     *
     * @return string The URL to the object
     */
    public function getObjectUrl($bucket, $key);

    /**
     * Determines whether or not a bucket exists by name.
     *
     * @param string $bucket  The name of the bucket
     *
     * @return bool
     */
    public function doesBucketExist($bucket);

    /**
     * Determines whether or not an object exists by name.
     *
     * @param string $bucket  The name of the bucket
     * @param string $key     The key of the object
     * @param array  $options Additional options available in the HeadObject
     *                        operation (e.g., VersionId).
     *
     * @return bool
     */
    public function doesObjectExist($bucket, $key, array $options = []);

    /**
     * Register the Amazon S3 stream wrapper with this client instance.
     */
    public function registerStreamWrapper();

    /**
     * Deletes objects from Amazon S3 that match the result of a ListObjects
     * operation. For example, this allows you to do things like delete all
     * objects that match a specific key prefix.
     *
     * @param string $bucket  Bucket that contains the object keys
     * @param string $prefix  Optionally delete only objects under this key prefix
     * @param string $regex   Delete only objects that match this regex
     * @param array  $options Aws\S3\BatchDelete options array.
     *
     * @see Aws\S3\S3Client::listObjects
     * @throws \RuntimeException if no prefix and no regex is given
     */
    public function deleteMatchingObjects(
        $bucket,
        $prefix = '',
        $regex = '',
        array $options = []
    );

    /**
     * Deletes objects from Amazon S3 that match the result of a ListObjects
     * operation. For example, this allows you to do things like delete all
     * objects that match a specific key prefix.
     *
     * @param string $bucket  Bucket that contains the object keys
     * @param string $prefix  Optionally delete only objects under this key prefix
     * @param string $regex   Delete only objects that match this regex
     * @param array  $options Aws\S3\BatchDelete options array.
     *
     * @see Aws\S3\S3Client::listObjects
     *
     * @return PromiseInterface     A promise that is settled when matching
     *                              objects are deleted.
     */
    public function deleteMatchingObjectsAsync(
        $bucket,
        $prefix = '',
        $regex = '',
        array $options = []
    );

    /**
     * Upload a file, stream, or string to a bucket.
     *
     * If the upload size exceeds the specified threshold, the upload will be
     * performed using concurrent multipart uploads.
     *
     * The options array accepts the following options:
     *
     * - before_upload: (callable) Callback to invoke before any upload
     *   operations during the upload process. The callback should have a
     *   function signature like `function (Aws\Command $command) {...}`.
     * - concurrency: (int, default=int(3)) Maximum number of concurrent
     *   `UploadPart` operations allowed during a multipart upload.
     * - mup_threshold: (int, default=int(16777216)) The size, in bytes, allowed
     *   before the upload must be sent via a multipart upload. Default: 16 MB.
     * - params: (array, default=array([])) Custom parameters to use with the
     *   upload. For single uploads, they must correspond to those used for the
     *   `PutObject` operation. For multipart uploads, they correspond to the
     *   parameters of the `CreateMultipartUpload` operation.
     * - part_size: (int) Part size to use when doing a multipart upload.
     *
     * @param string $bucket  Bucket to upload the object.
     * @param string $key     Key of the object.
     * @param mixed  $body    Object data to upload. Can be a
     *                        StreamInterface, PHP stream resource, or a
     *                        string of data to upload.
     * @param string $acl     ACL to apply to the object (default: private).
     * @param array  $options Options used to configure the upload process.
     *
     * @see Aws\S3\MultipartUploader for more info about multipart uploads.
     * @return ResultInterface Returns the result of the upload.
     */
    public function upload(
        $bucket,
        $key,
        $body,
        $acl = 'private',
        array $options = []
    );

    /**
     * Upload a file, stream, or string to a bucket asynchronously.
     *
     * @param string $bucket  Bucket to upload the object.
     * @param string $key     Key of the object.
     * @param mixed  $body    Object data to upload. Can be a
     *                        StreamInterface, PHP stream resource, or a
     *                        string of data to upload.
     * @param string $acl     ACL to apply to the object (default: private).
     * @param array  $options Options used to configure the upload process.
     *
     * @see self::upload
     * @return PromiseInterface     Returns a promise that will be fulfilled
     *                              with the result of the upload.
     */
    public function uploadAsync(
        $bucket,
        $key,
        $body,
        $acl = 'private',
        array $options = []
    );

    /**
     * Copy an object of any size to a different location.
     *
     * If the upload size exceeds the maximum allowable size for direct S3
     * copying, a multipart copy will be used.
     *
     * The options array accepts the following options:
     *
     * - before_upload: (callable) Callback to invoke before any upload
     *   operations during the upload process. The callback should have a
     *   function signature like `function (Aws\Command $command) {...}`.
     * - concurrency: (int, default=int(5)) Maximum number of concurrent
     *   `UploadPart` operations allowed during a multipart upload.
     * - params: (array, default=array([])) Custom parameters to use with the
     *   upload. For single uploads, they must correspond to those used for the
     *   `CopyObject` operation. For multipart uploads, they correspond to the
     *   parameters of the `CreateMultipartUpload` operation.
     * - part_size: (int) Part size to use when doing a multipart upload.
     *
     * @param string $fromBucket    Bucket where the copy source resides.
     * @param string $fromKey       Key of the copy source.
     * @param string $destBucket    Bucket to which to copy the object.
     * @param string $destKey       Key to which to copy the object.
     * @param string $acl           ACL to apply to the copy (default: private).
     * @param array  $options       Options used to configure the upload process.
     *
     * @see Aws\S3\MultipartCopy for more info about multipart uploads.
     * @return ResultInterface Returns the result of the copy.
     */
    public function copy(
        $fromBucket,
        $fromKey,
        $destBucket,
        $destKey,
        $acl = 'private',
        array $options = []
    );

    /**
     * Copy an object of any size to a different location asynchronously.
     *
     * @param string $fromBucket    Bucket where the copy source resides.
     * @param string $fromKey       Key of the copy source.
     * @param string $destBucket    Bucket to which to copy the object.
     * @param string $destKey       Key to which to copy the object.
     * @param string $acl           ACL to apply to the copy (default: private).
     * @param array  $options       Options used to configure the upload process.
     *
     * @see self::copy for more info about the parameters above.
     * @return PromiseInterface     Returns a promise that will be fulfilled
     *                              with the result of the copy.
     */
    public function copyAsync(
        $fromBucket,
        $fromKey,
        $destBucket,
        $destKey,
        $acl = 'private',
        array $options = []
    );

    /**
     * Recursively uploads all files in a given directory to a given bucket.
     *
     * @param string $directory Full path to a directory to upload
     * @param string $bucket    Name of the bucket
     * @param string $keyPrefix Virtual directory key prefix to add to each upload
     * @param array  $options   Options available in Aws\S3\Transfer::__construct
     *
     * @see Aws\S3\Transfer for more options and customization
     */
    public function uploadDirectory(
        $directory,
        $bucket,
        $keyPrefix = null,
        array $options = []
    );

    /**
     * Recursively uploads all files in a given directory to a given bucket.
     *
     * @param string $directory Full path to a directory to upload
     * @param string $bucket    Name of the bucket
     * @param string $keyPrefix Virtual directory key prefix to add to each upload
     * @param array  $options   Options available in Aws\S3\Transfer::__construct
     *
     * @see Aws\S3\Transfer for more options and customization
     *
     * @return PromiseInterface A promise that is settled when the upload is
     *                          complete.
     */
    public function uploadDirectoryAsync(
        $directory,
        $bucket,
        $keyPrefix = null,
        array $options = []
    );

    /**
     * Downloads a bucket to the local filesystem
     *
     * @param string $directory Directory to download to
     * @param string $bucket    Bucket to download from
     * @param string $keyPrefix Only download objects that use this key prefix
     * @param array  $options   Options available in Aws\S3\Transfer::__construct
     */
    public function downloadBucket(
        $directory,
        $bucket,
        $keyPrefix = '',
        array $options = []
    );

    /**
     * Downloads a bucket to the local filesystem
     *
     * @param string $directory Directory to download to
     * @param string $bucket    Bucket to download from
     * @param string $keyPrefix Only download objects that use this key prefix
     * @param array  $options   Options available in Aws\S3\Transfer::__construct
     *
     * @return PromiseInterface A promise that is settled when the download is
     *                          complete.
     */
    public function downloadBucketAsync(
        $directory,
        $bucket,
        $keyPrefix = '',
        array $options = []
    );

    /**
     * Returns the region in which a given bucket is located.
     *
     * @param string $bucketName
     *
     * @return string
     */
    public function determineBucketRegion($bucketName);

    /**
     * Returns a promise fulfilled with the region in which a given bucket is
     * located.
     *
     * @param string $bucketName
     *
     * @return PromiseInterface
     */
    public function determineBucketRegionAsync($bucketName);
}
<?php
namespace Aws\S3;

use Aws\CommandInterface;
use Aws\Exception\AwsException;
use Aws\HandlerList;
use Aws\ResultInterface;
use Aws\S3\Exception\S3Exception;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;

/**
 * A trait providing S3-specific functionality. This is meant to be used in
 * classes implementing \Aws\S3\S3ClientInterface
 */
trait S3ClientTrait
{
    /**
     * @see S3ClientInterface::upload()
     */
    public function upload(
        $bucket,
        $key,
        $body,
        $acl = 'private',
        array $options = []
    ) {
        return $this
            ->uploadAsync($bucket, $key, $body, $acl, $options)
            ->wait();
    }

    /**
     * @see S3ClientInterface::uploadAsync()
     */
    public function uploadAsync(
        $bucket,
        $key,
        $body,
        $acl = 'private',
        array $options = []
    ) {
        return (new ObjectUploader($this, $bucket, $key, $body, $acl, $options))
            ->promise();
    }

    /**
     * @see S3ClientInterface::copy()
     */
    public function copy(
        $fromB,
        $fromK,
        $destB,
        $destK,
        $acl = 'private',
        array $opts = []
    ) {
        return $this->copyAsync($fromB, $fromK, $destB, $destK, $acl, $opts)
            ->wait();
    }

    /**
     * @see S3ClientInterface::copyAsync()
     */
    public function copyAsync(
        $fromB,
        $fromK,
        $destB,
        $destK,
        $acl = 'private',
        array $opts = []
    ) {
        $source = [
            'Bucket' => $fromB,
            'Key' => $fromK,
        ];
        if (isset($opts['version_id'])) {
            $source['VersionId'] = $opts['version_id'];
        }
        $destination = [
            'Bucket' => $destB,
            'Key' => $destK
        ];

        return (new ObjectCopier($this, $source, $destination, $acl, $opts))
            ->promise();
    }

    /**
     * @see S3ClientInterface::registerStreamWrapper()
     */
    public function registerStreamWrapper()
    {
        StreamWrapper::register($this);
    }

    /**
     * @see S3ClientInterface::deleteMatchingObjects()
     */
    public function deleteMatchingObjects(
        $bucket,
        $prefix = '',
        $regex = '',
        array $options = []
    ) {
        $this->deleteMatchingObjectsAsync($bucket, $prefix, $regex, $options)
            ->wait();
    }

    /**
     * @see S3ClientInterface::deleteMatchingObjectsAsync()
     */
    public function deleteMatchingObjectsAsync(
        $bucket,
        $prefix = '',
        $regex = '',
        array $options = []
    ) {
        if (!$prefix && !$regex) {
            return new RejectedPromise(
                new \RuntimeException('A prefix or regex is required.')
            );
        }

        $params = ['Bucket' => $bucket, 'Prefix' => $prefix];
        $iter = $this->getIterator('ListObjects', $params);

        if ($regex) {
            $iter = \Aws\filter($iter, function ($c) use ($regex) {
                return preg_match($regex, $c['Key']);
            });
        }

        return BatchDelete::fromIterator($this, $bucket, $iter, $options)
            ->promise();
    }

    /**
     * @see S3ClientInterface::uploadDirectory()
     */
    public function uploadDirectory(
        $directory,
        $bucket,
        $keyPrefix = null,
        array $options = []
    ) {
        $this->uploadDirectoryAsync($directory, $bucket, $keyPrefix, $options)
            ->wait();
    }

    /**
     * @see S3ClientInterface::uploadDirectoryAsync()
     */
    public function uploadDirectoryAsync(
        $directory,
        $bucket,
        $keyPrefix = null,
        array $options = []
    ) {
        $d = "s3://$bucket" . ($keyPrefix ? '/' . ltrim($keyPrefix, '/') : '');
        return (new Transfer($this, $directory, $d, $options))->promise();
    }

    /**
     * @see S3ClientInterface::downloadBucket()
     */
    public function downloadBucket(
        $directory,
        $bucket,
        $keyPrefix = '',
        array $options = []
    ) {
        $this->downloadBucketAsync($directory, $bucket, $keyPrefix, $options)
            ->wait();
    }

    /**
     * @see S3ClientInterface::downloadBucketAsync()
     */
    public function downloadBucketAsync(
        $directory,
        $bucket,
        $keyPrefix = '',
        array $options = []
    ) {
        $s = "s3://$bucket" . ($keyPrefix ? '/' . ltrim($keyPrefix, '/') : '');
        return (new Transfer($this, $s, $directory, $options))->promise();
    }

    /**
     * @see S3ClientInterface::determineBucketRegion()
     */
    public function determineBucketRegion($bucketName)
    {
        return $this->determineBucketRegionAsync($bucketName)->wait();
    }

    /**
     * @see S3ClientInterface::determineBucketRegionAsync()
     *
     * @param string $bucketName
     *
     * @return PromiseInterface
     */
    public function determineBucketRegionAsync($bucketName)
    {
        $command = $this->getCommand('HeadBucket', ['Bucket' => $bucketName]);
        $handlerList = clone $this->getHandlerList();
        $handlerList->remove('s3.permanent_redirect');
        $handlerList->remove('signer');
        $handler = $handlerList->resolve();

        return $handler($command)
            ->then(static function (ResultInterface $result) {
                return $result['@metadata']['headers']['x-amz-bucket-region'];
            }, static function (AwsException $exception) {
                $response = $exception->getResponse();
                if ($response === null) {
                    throw $exception;
                }
                return $response->getHeaderLine('x-amz-bucket-region');
            });
    }

    /**
     * @see S3ClientInterface::doesBucketExist()
     */
    public function doesBucketExist($bucket)
    {
        return $this->checkExistenceWithCommand(
            $this->getCommand('HeadBucket', ['Bucket' => $bucket])
        );
    }

    /**
     * @see S3ClientInterface::doesObjectExist()
     */
    public function doesObjectExist($bucket, $key, array $options = [])
    {
        return $this->checkExistenceWithCommand(
            $this->getCommand('HeadObject', [
                    'Bucket' => $bucket,
                    'Key'    => $key
                ] + $options)
        );
    }

    /**
     * Determines whether or not a resource exists using a command
     *
     * @param CommandInterface $command Command used to poll for the resource
     *
     * @return bool
     * @throws S3Exception|\Exception if there is an unhandled exception
     */
    private function checkExistenceWithCommand(CommandInterface $command)
    {
        try {
            $this->execute($command);
            return true;
        } catch (S3Exception $e) {
            if ($e->getAwsErrorCode() == 'AccessDenied') {
                return true;
            }
            if ($e->getStatusCode() >= 500) {
                throw $e;
            }
            return false;
        }
    }

    /**
     * @see S3ClientInterface::execute()
     */
    abstract public function execute(CommandInterface $command);

    /**
     * @see S3ClientInterface::getCommand()
     */
    abstract public function getCommand($name, array $args = []);

    /**
     * @see S3ClientInterface::getHandlerList()
     *
     * @return HandlerList
     */
    abstract public function getHandlerList();

    /**
     * @see S3ClientInterface::getIterator()
     *
     * @return \Iterator
     */
    abstract public function getIterator($name, array $args = []);
}
<?php
namespace Aws\S3;

use Aws\CommandInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Used to update the URL used for S3 requests to support:
 * S3 Accelerate, S3 DualStack or Both
 *
 * IMPORTANT: this middleware must be added after the "build" step.
 *
 * @internal
 */
class S3EndpointMiddleware
{
    private static $exclusions = [
        'CreateBucket' => true,
        'DeleteBucket' => true,
        'ListBuckets' => true,
    ];

    const NO_PATTERN = 0;
    const DUALSTACK = 1;
    const ACCELERATE = 2;
    const ACCELERATE_DUALSTACK = 3;

    /** @var bool */
    private $accelerateByDefault;
    /** @var bool */
    private $dualStackByDefault;
    /** @var string */
    private $region;
    /** @var callable */
    private $nextHandler;

    /**
     * Create a middleware wrapper function
     *
     * @param string $region
     * @param array  $options
     *
     * @return callable
     */
    public static function wrap($region, array $options)
    {
        return function (callable $handler) use ($region, $options) {
            return new self($handler, $region, $options);
        };
    }

    public function __construct(
        callable $nextHandler,
        $region,
        array $options
    ) {
        $this->dualStackByDefault = isset($options['dual_stack'])
            ? (bool) $options['dual_stack'] : false;
        $this->accelerateByDefault = isset($options['accelerate'])
            ? (bool) $options['accelerate'] : false;
        $this->region = (string) $region;
        $this->nextHandler = $nextHandler;
    }

    public function __invoke(CommandInterface $command, RequestInterface $request)
    {
        $endpointPattern = $this->endpointPatternDecider($command);
        switch ($endpointPattern) {
            case self::NO_PATTERN:
                break;
            case self::DUALSTACK:
                $request = $this->applyDualStackEndpoint($request);
                break;
            case self::ACCELERATE:
                $request = $this->applyAccelerateEndpoint(
                    $command,
                    $request,
                    's3-accelerate'
                );
                break;
            case self::ACCELERATE_DUALSTACK:
                $request = $this->applyAccelerateEndpoint(
                    $command,
                    $request,
                    's3-accelerate.dualstack'
                );
                break;
        }

        $nextHandler = $this->nextHandler;
        return $nextHandler($command, $request);
    }

    private function endpointPatternDecider(CommandInterface $command)
    {
        $accelerate = isset($command['@use_accelerate_endpoint'])
            ? $command['@use_accelerate_endpoint'] : $this->accelerateByDefault;
        $dualStack = isset($command['@use_dual_stack_endpoint'])
            ? $command['@use_dual_stack_endpoint'] : $this->dualStackByDefault;

        if ($accelerate && $dualStack) {
            // When try to enable both for operations excluded from s3-accelerate,
            // only dualstack endpoints will be enabled.
            return $this->canAccelerate($command)
                ? self::ACCELERATE_DUALSTACK
                : self::DUALSTACK;
        } elseif ($accelerate && $this->canAccelerate($command)) {
            return self::ACCELERATE;
        } elseif ($dualStack) {
            return self::DUALSTACK;
        }
        return self::NO_PATTERN;
    }

    private function canAccelerate(CommandInterface $command)
    {
        return empty(self::$exclusions[$command->getName()])
            && S3Client::isBucketDnsCompatible($command['Bucket']);
    }

    private function applyDualStackEndpoint(RequestInterface $request)
    {
        $request = $request->withUri(
            $request->getUri()
                ->withHost($this->getDualStackHost())
        );
        return $request;
    }

    private function getDualStackHost()
    {
        return "s3.dualstack.{$this->region}.amazonaws.com";
    }

    private function applyAccelerateEndpoint(
        CommandInterface $command,
        RequestInterface $request,
        $pattern
    ) {
        $request = $request->withUri(
            $request->getUri()
                ->withHost($this->getAccelerateHost($command, $pattern))
                ->withPath($this->getBucketlessPath(
                    $request->getUri()->getPath(),
                    $command
                ))
        );
        return $request;
    }

    private function getAccelerateHost(CommandInterface $command, $pattern)
    {
        return "{$command['Bucket']}.{$pattern}.amazonaws.com";
    }

    private function getBucketlessPath($path, CommandInterface $command)
    {
        $pattern = '/^\\/' . preg_quote($command['Bucket'], '/') . '/';
        return preg_replace($pattern, '', $path) ?: '/';
    }
}
<?php
namespace Aws\S3;

use Aws\CacheInterface;
use Aws\CommandInterface;
use Aws\LruArrayCache;
use Aws\MultiRegionClient as BaseClient;
use Aws\S3\Exception\PermanentRedirectException;
use GuzzleHttp\Promise;

/**
 * **Amazon Simple Storage Service** multi-region client.
 *
 * @method \Aws\Result abortMultipartUpload(array $args = [])
 * @method \GuzzleHttp\Promise\Promise abortMultipartUploadAsync(array $args = [])
 * @method \Aws\Result completeMultipartUpload(array $args = [])
 * @method \GuzzleHttp\Promise\Promise completeMultipartUploadAsync(array $args = [])
 * @method \Aws\Result copyObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise copyObjectAsync(array $args = [])
 * @method \Aws\Result createBucket(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createBucketAsync(array $args = [])
 * @method \Aws\Result createMultipartUpload(array $args = [])
 * @method \GuzzleHttp\Promise\Promise createMultipartUploadAsync(array $args = [])
 * @method \Aws\Result deleteBucket(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketAsync(array $args = [])
 * @method \Aws\Result deleteBucketAnalyticsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketAnalyticsConfigurationAsync(array $args = [])
 * @method \Aws\Result deleteBucketCors(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketCorsAsync(array $args = [])
 * @method \Aws\Result deleteBucketInventoryConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketInventoryConfigurationAsync(array $args = [])
 * @method \Aws\Result deleteBucketLifecycle(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketLifecycleAsync(array $args = [])
 * @method \Aws\Result deleteBucketMetricsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketMetricsConfigurationAsync(array $args = [])
 * @method \Aws\Result deleteBucketPolicy(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketPolicyAsync(array $args = [])
 * @method \Aws\Result deleteBucketReplication(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketReplicationAsync(array $args = [])
 * @method \Aws\Result deleteBucketTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketTaggingAsync(array $args = [])
 * @method \Aws\Result deleteBucketWebsite(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteBucketWebsiteAsync(array $args = [])
 * @method \Aws\Result deleteObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteObjectAsync(array $args = [])
 * @method \Aws\Result deleteObjectTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteObjectTaggingAsync(array $args = [])
 * @method \Aws\Result deleteObjects(array $args = [])
 * @method \GuzzleHttp\Promise\Promise deleteObjectsAsync(array $args = [])
 * @method \Aws\Result getBucketAccelerateConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketAccelerateConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketAcl(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketAclAsync(array $args = [])
 * @method \Aws\Result getBucketAnalyticsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketAnalyticsConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketCors(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketCorsAsync(array $args = [])
 * @method \Aws\Result getBucketInventoryConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketInventoryConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketLifecycle(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketLifecycleAsync(array $args = [])
 * @method \Aws\Result getBucketLifecycleConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketLifecycleConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketLocation(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketLocationAsync(array $args = [])
 * @method \Aws\Result getBucketLogging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketLoggingAsync(array $args = [])
 * @method \Aws\Result getBucketMetricsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketMetricsConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketNotification(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketNotificationAsync(array $args = [])
 * @method \Aws\Result getBucketNotificationConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketNotificationConfigurationAsync(array $args = [])
 * @method \Aws\Result getBucketPolicy(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketPolicyAsync(array $args = [])
 * @method \Aws\Result getBucketReplication(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketReplicationAsync(array $args = [])
 * @method \Aws\Result getBucketRequestPayment(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketRequestPaymentAsync(array $args = [])
 * @method \Aws\Result getBucketTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketTaggingAsync(array $args = [])
 * @method \Aws\Result getBucketVersioning(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketVersioningAsync(array $args = [])
 * @method \Aws\Result getBucketWebsite(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getBucketWebsiteAsync(array $args = [])
 * @method \Aws\Result getObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getObjectAsync(array $args = [])
 * @method \Aws\Result getObjectAcl(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getObjectAclAsync(array $args = [])
 * @method \Aws\Result getObjectTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getObjectTaggingAsync(array $args = [])
 * @method \Aws\Result getObjectTorrent(array $args = [])
 * @method \GuzzleHttp\Promise\Promise getObjectTorrentAsync(array $args = [])
 * @method \Aws\Result headBucket(array $args = [])
 * @method \GuzzleHttp\Promise\Promise headBucketAsync(array $args = [])
 * @method \Aws\Result headObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise headObjectAsync(array $args = [])
 * @method \Aws\Result listBucketAnalyticsConfigurations(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listBucketAnalyticsConfigurationsAsync(array $args = [])
 * @method \Aws\Result listBucketInventoryConfigurations(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listBucketInventoryConfigurationsAsync(array $args = [])
 * @method \Aws\Result listBucketMetricsConfigurations(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listBucketMetricsConfigurationsAsync(array $args = [])
 * @method \Aws\Result listBuckets(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listBucketsAsync(array $args = [])
 * @method \Aws\Result listMultipartUploads(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listMultipartUploadsAsync(array $args = [])
 * @method \Aws\Result listObjectVersions(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listObjectVersionsAsync(array $args = [])
 * @method \Aws\Result listObjects(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listObjectsAsync(array $args = [])
 * @method \Aws\Result listObjectsV2(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listObjectsV2Async(array $args = [])
 * @method \Aws\Result listParts(array $args = [])
 * @method \GuzzleHttp\Promise\Promise listPartsAsync(array $args = [])
 * @method \Aws\Result putBucketAccelerateConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketAccelerateConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketAcl(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketAclAsync(array $args = [])
 * @method \Aws\Result putBucketAnalyticsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketAnalyticsConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketCors(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketCorsAsync(array $args = [])
 * @method \Aws\Result putBucketInventoryConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketInventoryConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketLifecycle(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketLifecycleAsync(array $args = [])
 * @method \Aws\Result putBucketLifecycleConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketLifecycleConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketLogging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketLoggingAsync(array $args = [])
 * @method \Aws\Result putBucketMetricsConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketMetricsConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketNotification(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketNotificationAsync(array $args = [])
 * @method \Aws\Result putBucketNotificationConfiguration(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketNotificationConfigurationAsync(array $args = [])
 * @method \Aws\Result putBucketPolicy(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketPolicyAsync(array $args = [])
 * @method \Aws\Result putBucketReplication(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketReplicationAsync(array $args = [])
 * @method \Aws\Result putBucketRequestPayment(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketRequestPaymentAsync(array $args = [])
 * @method \Aws\Result putBucketTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketTaggingAsync(array $args = [])
 * @method \Aws\Result putBucketVersioning(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketVersioningAsync(array $args = [])
 * @method \Aws\Result putBucketWebsite(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putBucketWebsiteAsync(array $args = [])
 * @method \Aws\Result putObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putObjectAsync(array $args = [])
 * @method \Aws\Result putObjectAcl(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putObjectAclAsync(array $args = [])
 * @method \Aws\Result putObjectTagging(array $args = [])
 * @method \GuzzleHttp\Promise\Promise putObjectTaggingAsync(array $args = [])
 * @method \Aws\Result restoreObject(array $args = [])
 * @method \GuzzleHttp\Promise\Promise restoreObjectAsync(array $args = [])
 * @method \Aws\Result uploadPart(array $args = [])
 * @method \GuzzleHttp\Promise\Promise uploadPartAsync(array $args = [])
 * @method \Aws\Result uploadPartCopy(array $args = [])
 * @method \GuzzleHttp\Promise\Promise uploadPartCopyAsync(array $args = [])
 */
class S3MultiRegionClient extends BaseClient implements S3ClientInterface
{
    use S3ClientTrait {
        determineBucketRegionAsync as private lookupBucketRegion;
    }

    /** @var CacheInterface */
    private $cache;

    public static function getArguments()
    {
        $args = parent::getArguments();
        $args['region']['default'] = 'us-east-1';

        return $args + [
            'bucket_region_cache' => [
                'type' => 'config',
                'valid' => [CacheInterface::class],
                'doc' => 'Cache of regions in which given buckets are located.',
                'default' => function () { return new LruArrayCache; },
            ],
        ];
    }

    public function __construct(array $args)
    {
        parent::__construct($args);
        $this->cache = $this->getConfig('bucket_region_cache');
    }

    public function executeAsync(CommandInterface $c)
    {
        return Promise\coroutine(function () use ($c) {
            if ($region = $this->cache->get($this->getCacheKey($c['Bucket']))) {
                $c = $this->getRegionalizedCommand($c, $region);
            }

            try {
                yield parent::executeAsync($c);
            } catch (PermanentRedirectException $e) {
                if (empty($c['Bucket'])) {
                    throw $e;
                }
                $region = (yield $this->lookupBucketRegion($c['Bucket']));
                $this->cache->set($this->getCacheKey($c['Bucket']), $region);
                $c = $this->getRegionalizedCommand($c, $region);
                yield parent::executeAsync($c);
            }
        });
    }

    public function createPresignedRequest(CommandInterface $command, $expires)
    {
        if (empty($command['Bucket'])) {
            throw new \InvalidArgumentException('The S3\\MultiRegionClient'
                . ' cannot create presigned requests for commands without a'
                . ' specified bucket.');
        }

        /** @var S3ClientInterface $client */
        $client = $this->getClientFromPool(
            $this->determineBucketRegion($command['Bucket'])
        );
        return $client->createPresignedRequest(
            $client->getCommand($command->getName(), $command->toArray()),
            $expires
        );
    }

    public function getObjectUrl($bucket, $key)
    {
        /** @var S3Client $regionalClient */
        $regionalClient = $this->getClientFromPool(
            $this->determineBucketRegion($bucket)
        );

        return $regionalClient->getObjectUrl($bucket, $key);
    }

    public function determineBucketRegionAsync($bucketName)
    {
        if ($cached = $this->cache->get($this->getCacheKey($bucketName))) {
            return Promise\promise_for($cached);
        }

        return $this->lookupBucketRegion($bucketName)
            ->then(function ($region) use ($bucketName) {
                $this->cache->set($this->getCacheKey($bucketName), $region);

                return $region;
            });
    }

    private function getRegionalizedCommand(CommandInterface $command, $region)
    {
        return $this->getClientFromPool($region)
            ->getCommand($command->getName(), $command->toArray());
    }

    private function getCacheKey($bucketName)
    {
        return "aws:s3:{$bucketName}:location";
    }
}
<?php
namespace Aws\S3;

use GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;

/**
 * Extracts a region, bucket, key, and and if a URI is in path-style
 */
class S3UriParser
{
    private $pattern = '/^(.+\\.)?s3[.-]([A-Za-z0-9-]+)\\./';

    private static $defaultResult = [
        'path_style' => true,
        'bucket'     => null,
        'key'        => null,
        'region'     => null
    ];

    /**
     * Parses a URL into an associative array of Amazon S3 data including:
     *
     * - bucket: The Amazon S3 bucket (null if none)
     * - key: The Amazon S3 key (null if none)
     * - path_style: Set to true if using path style, or false if not
     * - region: Set to a string if a non-class endpoint is used or null.
     *
     * @param string|UriInterface $uri
     *
     * @return array
     * @throws \InvalidArgumentException
     */
    public function parse($uri)
    {
        $url = Psr7\uri_for($uri);
        if (!$url->getHost()) {
            throw new \InvalidArgumentException('No hostname found in URI: '
                . $uri);
        }

        if (!preg_match($this->pattern, $url->getHost(), $matches)) {
            return $this->parseCustomEndpoint($url);
        }

        // Parse the URI based on the matched format (path / virtual)
        $result = empty($matches[1])
            ? $this->parsePathStyle($url)
            : $this->parseVirtualHosted($url, $matches);

        // Add the region if one was found and not the classic endpoint
        $result['region'] = $matches[2] == 'amazonaws' ? null : $matches[2];

        return $result;
    }

    private function parseCustomEndpoint(UriInterface $url)
    {
        $result = $result = self::$defaultResult;
        $path = ltrim($url->getPath(), '/ ');
        $segments = explode('/', $path, 2);

        if (isset($segments[0])) {
            $result['bucket'] = $segments[0];
            if (isset($segments[1])) {
                $result['key'] = $segments[1];
            }
        }

        return $result;
    }

    private function parsePathStyle(UriInterface $url)
    {
        $result = self::$defaultResult;

        if ($url->getPath() != '/') {
            $path = ltrim($url->getPath(), '/');
            if ($path) {
                $pathPos = strpos($path, '/');
                if ($pathPos === false) {
                    // https://s3.amazonaws.com/bucket
                    $result['bucket'] = $path;
                } elseif ($pathPos == strlen($path) - 1) {
                    // https://s3.amazonaws.com/bucket/
                    $result['bucket'] = substr($path, 0, -1);
                } else {
                    // https://s3.amazonaws.com/bucket/key
                    $result['bucket'] = substr($path, 0, $pathPos);
                    $result['key'] = substr($path, $pathPos + 1) ?: null;
                }
            }
        }

        return $result;
    }

    private function parseVirtualHosted(UriInterface $url, array $matches)
    {
        $result = self::$defaultResult;
        $result['path_style'] = false;
        // Remove trailing "." from the prefix to get the bucket
        $result['bucket'] = substr($matches[1], 0, -1);
        $path = $url->getPath();
        // Check if a key was present, and if so, removing the leading "/"
        $result['key'] = !$path || $path == '/' ? null : substr($path, 1);

        return $result;
    }
}
<?php
namespace Aws\S3;

use Aws\CommandInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Simplifies the SSE-C process by encoding and hashing the key.
 * @internal
 */
class SSECMiddleware
{
    private $endpointScheme;
    private $nextHandler;

    /**
     * Provide the URI scheme of the client sending requests.
     *
     * @param string $endpointScheme URI scheme (http/https).
     *
     * @return callable
     */
    public static function wrap($endpointScheme)
    {
        return function (callable $handler) use ($endpointScheme) {
            return new self($endpointScheme, $handler);
        };
    }

    public function __construct($endpointScheme, callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
        $this->endpointScheme = $endpointScheme;
    }

    public function __invoke(
        CommandInterface $command,
        RequestInterface $request = null
    ) {
        // Allows only HTTPS connections when using SSE-C
        if (($command['SSECustomerKey'] || $command['CopySourceSSECustomerKey'])
            && $this->endpointScheme !== 'https'
        ) {
            throw new \RuntimeException('You must configure your S3 client to '
                . 'use HTTPS in order to use the SSE-C features.');
        }

        // Prepare the normal SSE-CPK headers
        if ($command['SSECustomerKey']) {
            $this->prepareSseParams($command);
        }

        // If it's a copy operation, prepare the SSE-CPK headers for the source.
        if ($command['CopySourceSSECustomerKey']) {
            $this->prepareSseParams($command, 'CopySource');
        }

        $f = $this->nextHandler;
        return $f($command, $request);
    }

    private function prepareSseParams(CommandInterface $command, $prefix = '')
    {
        // Base64 encode the provided key
        $key = $command[$prefix . 'SSECustomerKey'];
        $command[$prefix . 'SSECustomerKey'] = base64_encode($key);

        // Base64 the provided MD5 or, generate an MD5 if not provided
        if ($md5 = $command[$prefix . 'SSECustomerKeyMD5']) {
            $command[$prefix . 'SSECustomerKeyMD5'] = base64_encode($md5);
        } else {
            $command[$prefix . 'SSECustomerKeyMD5'] = base64_encode(md5($key, true));
        }
    }
}
<?php
namespace Aws\S3;

use Aws\CacheInterface;
use Aws\LruArrayCache;
use Aws\Result;
use Aws\S3\Exception\S3Exception;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\CachingStream;
use Psr\Http\Message\StreamInterface;

/**
 * Amazon S3 stream wrapper to use "s3://<bucket>/<key>" files with PHP
 * streams, supporting "r", "w", "a", "x".
 *
 * # Opening "r" (read only) streams:
 *
 * Read only streams are truly streaming by default and will not allow you to
 * seek. This is because data read from the stream is not kept in memory or on
 * the local filesystem. You can force a "r" stream to be seekable by setting
 * the "seekable" stream context option true. This will allow true streaming of
 * data from Amazon S3, but will maintain a buffer of previously read bytes in
 * a 'php://temp' stream to allow seeking to previously read bytes from the
 * stream.
 *
 * You may pass any GetObject parameters as 's3' stream context options. These
 * options will affect how the data is downloaded from Amazon S3.
 *
 * # Opening "w" and "x" (write only) streams:
 *
 * Because Amazon S3 requires a Content-Length header, write only streams will
 * maintain a 'php://temp' stream to buffer data written to the stream until
 * the stream is flushed (usually by closing the stream with fclose).
 *
 * You may pass any PutObject parameters as 's3' stream context options. These
 * options will affect how the data is uploaded to Amazon S3.
 *
 * When opening an "x" stream, the file must exist on Amazon S3 for the stream
 * to open successfully.
 *
 * # Opening "a" (write only append) streams:
 *
 * Similar to "w" streams, opening append streams requires that the data be
 * buffered in a "php://temp" stream. Append streams will attempt to download
 * the contents of an object in Amazon S3, seek to the end of the object, then
 * allow you to append to the contents of the object. The data will then be
 * uploaded using a PutObject operation when the stream is flushed (usually
 * with fclose).
 *
 * You may pass any GetObject and/or PutObject parameters as 's3' stream
 * context options. These options will affect how the data is downloaded and
 * uploaded from Amazon S3.
 *
 * Stream context options:
 *
 * - "seekable": Set to true to create a seekable "r" (read only) stream by
 *   using a php://temp stream buffer
 * - For "unlink" only: Any option that can be passed to the DeleteObject
 *   operation
 */
class StreamWrapper
{
    /** @var resource|null Stream context (this is set by PHP) */
    public $context;

    /** @var StreamInterface Underlying stream resource */
    private $body;

    /** @var int Size of the body that is opened */
    private $size;

    /** @var array Hash of opened stream parameters */
    private $params = [];

    /** @var string Mode in which the stream was opened */
    private $mode;

    /** @var \Iterator Iterator used with opendir() related calls */
    private $objectIterator;

    /** @var string The bucket that was opened when opendir() was called */
    private $openedBucket;

    /** @var string The prefix of the bucket that was opened with opendir() */
    private $openedBucketPrefix;

    /** @var string Opened bucket path */
    private $openedPath;

    /** @var CacheInterface Cache for object and dir lookups */
    private $cache;

    /** @var string The opened protocol (e.g., "s3") */
    private $protocol = 's3';

    /**
     * Register the 's3://' stream wrapper
     *
     * @param S3ClientInterface $client   Client to use with the stream wrapper
     * @param string            $protocol Protocol to register as.
     * @param CacheInterface    $cache    Default cache for the protocol.
     */
    public static function register(
        S3ClientInterface $client,
        $protocol = 's3',
        CacheInterface $cache = null
    ) {
        if (in_array($protocol, stream_get_wrappers())) {
            stream_wrapper_unregister($protocol);
        }

        // Set the client passed in as the default stream context client
        stream_wrapper_register($protocol, get_called_class(), STREAM_IS_URL);
        $default = stream_context_get_options(stream_context_get_default());
        $default[$protocol]['client'] = $client;

        if ($cache) {
            $default[$protocol]['cache'] = $cache;
        } elseif (!isset($default[$protocol]['cache'])) {
            // Set a default cache adapter.
            $default[$protocol]['cache'] = new LruArrayCache();
        }

        stream_context_set_default($default);
    }

    public function stream_close()
    {
        $this->body = $this->cache = null;
    }

    public function stream_open($path, $mode, $options, &$opened_path)
    {
        $this->initProtocol($path);
        $this->params = $this->getBucketKey($path);
        $this->mode = rtrim($mode, 'bt');

        if ($errors = $this->validate($path, $this->mode)) {
            return $this->triggerError($errors);
        }

        return $this->boolCall(function() use ($path) {
            switch ($this->mode) {
                case 'r': return $this->openReadStream($path);
                case 'a': return $this->openAppendStream($path);
                default: return $this->openWriteStream($path);
            }
        });
    }

    public function stream_eof()
    {
        return $this->body->eof();
    }

    public function stream_flush()
    {
        if ($this->mode == 'r') {
            return false;
        }

        if ($this->body->isSeekable()) {
            $this->body->seek(0);
        }
        $params = $this->getOptions(true);
        $params['Body'] = $this->body;

        // Attempt to guess the ContentType of the upload based on the
        // file extension of the key
        if (!isset($params['ContentType']) &&
            ($type = Psr7\mimetype_from_filename($params['Key']))
        ) {
            $params['ContentType'] = $type;
        }

        $this->clearCacheKey("s3://{$params['Bucket']}/{$params['Key']}");
        return $this->boolCall(function () use ($params) {
            return (bool) $this->getClient()->putObject($params);
        });
    }

    public function stream_read($count)
    {
        return $this->body->read($count);
    }

    public function stream_seek($offset, $whence = SEEK_SET)
    {
        return !$this->body->isSeekable()
            ? false
            : $this->boolCall(function () use ($offset, $whence) {
                $this->body->seek($offset, $whence);
                return true;
            });
    }

    public function stream_tell()
    {
        return $this->boolCall(function() { return $this->body->tell(); });
    }

    public function stream_write($data)
    {
        return $this->body->write($data);
    }

    public function unlink($path)
    {
        $this->initProtocol($path);

        return $this->boolCall(function () use ($path) {
            $this->clearCacheKey($path);
            $this->getClient()->deleteObject($this->withPath($path));
            return true;
        });
    }

    public function stream_stat()
    {
        $stat = $this->getStatTemplate();
        $stat[7] = $stat['size'] = $this->getSize();
        $stat[2] = $stat['mode'] = $this->mode;

        return $stat;
    }

    /**
     * Provides information for is_dir, is_file, filesize, etc. Works on
     * buckets, keys, and prefixes.
     * @link http://www.php.net/manual/en/streamwrapper.url-stat.php
     */
    public function url_stat($path, $flags)
    {
        $this->initProtocol($path);

        // Some paths come through as S3:// for some reason.
        $split = explode('://', $path);
        $path = strtolower($split[0]) . '://' . $split[1];

        // Check if this path is in the url_stat cache
        if ($value = $this->getCacheStorage()->get($path)) {
            return $value;
        }

        $stat = $this->createStat($path, $flags);

        if (is_array($stat)) {
            $this->getCacheStorage()->set($path, $stat);
        }

        return $stat;
    }

    /**
     * Parse the protocol out of the given path.
     *
     * @param $path
     */
    private function initProtocol($path)
    {
        $parts = explode('://', $path, 2);
        $this->protocol = $parts[0] ?: 's3';
    }

    private function createStat($path, $flags)
    {
        $this->initProtocol($path);
        $parts = $this->withPath($path);

        if (!$parts['Key']) {
            return $this->statDirectory($parts, $path, $flags);
        }

        return $this->boolCall(function () use ($parts, $path) {
            try {
                $result = $this->getClient()->headObject($parts);
                if (substr($parts['Key'], -1, 1) == '/' &&
                    $result['ContentLength'] == 0
                ) {
                    // Return as if it is a bucket to account for console
                    // bucket objects (e.g., zero-byte object "foo/")
                    return $this->formatUrlStat($path);
                } else {
                    // Attempt to stat and cache regular object
                    return $this->formatUrlStat($result->toArray());
                }
            } catch (S3Exception $e) {
                // Maybe this isn't an actual key, but a prefix. Do a prefix
                // listing of objects to determine.
                $result = $this->getClient()->listObjects([
                    'Bucket'  => $parts['Bucket'],
                    'Prefix'  => rtrim($parts['Key'], '/') . '/',
                    'MaxKeys' => 1
                ]);
                if (!$result['Contents'] && !$result['CommonPrefixes']) {
                    throw new \Exception("File or directory not found: $path");
                }
                return $this->formatUrlStat($path);
            }
        }, $flags);
    }

    private function statDirectory($parts, $path, $flags)
    {
        // Stat "directories": buckets, or "s3://"
        if (!$parts['Bucket'] ||
            $this->getClient()->doesBucketExist($parts['Bucket'])
        ) {
            return $this->formatUrlStat($path);
        }

        return $this->triggerError("File or directory not found: $path", $flags);
    }

    /**
     * Support for mkdir().
     *
     * @param string $path    Directory which should be created.
     * @param int    $mode    Permissions. 700-range permissions map to
     *                        ACL_PUBLIC. 600-range permissions map to
     *                        ACL_AUTH_READ. All other permissions map to
     *                        ACL_PRIVATE. Expects octal form.
     * @param int    $options A bitwise mask of values, such as
     *                        STREAM_MKDIR_RECURSIVE.
     *
     * @return bool
     * @link http://www.php.net/manual/en/streamwrapper.mkdir.php
     */
    public function mkdir($path, $mode, $options)
    {
        $this->initProtocol($path);
        $params = $this->withPath($path);
        $this->clearCacheKey($path);
        if (!$params['Bucket']) {
            return false;
        }

        if (!isset($params['ACL'])) {
            $params['ACL'] = $this->determineAcl($mode);
        }

        return empty($params['Key'])
            ? $this->createBucket($path, $params)
            : $this->createSubfolder($path, $params);
    }

    public function rmdir($path, $options)
    {
        $this->initProtocol($path);
        $this->clearCacheKey($path);
        $params = $this->withPath($path);
        $client = $this->getClient();

        if (!$params['Bucket']) {
            return $this->triggerError('You must specify a bucket');
        }

        return $this->boolCall(function () use ($params, $path, $client) {
            if (!$params['Key']) {
                $client->deleteBucket(['Bucket' => $params['Bucket']]);
                return true;
            }
            return $this->deleteSubfolder($path, $params);
        });
    }

    /**
     * Support for opendir().
     *
     * The opendir() method of the Amazon S3 stream wrapper supports a stream
     * context option of "listFilter". listFilter must be a callable that
     * accepts an associative array of object data and returns true if the
     * object should be yielded when iterating the keys in a bucket.
     *
     * @param string $path    The path to the directory
     *                        (e.g. "s3://dir[</prefix>]")
     * @param string $options Unused option variable
     *
     * @return bool true on success
     * @see http://www.php.net/manual/en/function.opendir.php
     */
    public function dir_opendir($path, $options)
    {
        $this->initProtocol($path);
        $this->openedPath = $path;
        $params = $this->withPath($path);
        $delimiter = $this->getOption('delimiter');
        /** @var callable $filterFn */
        $filterFn = $this->getOption('listFilter');
        $op = ['Bucket' => $params['Bucket']];
        $this->openedBucket = $params['Bucket'];

        if ($delimiter === null) {
            $delimiter = '/';
        }

        if ($delimiter) {
            $op['Delimiter'] = $delimiter;
        }

        if ($params['Key']) {
            $params['Key'] = rtrim($params['Key'], $delimiter) . $delimiter;
            $op['Prefix'] = $params['Key'];
        }

        $this->openedBucketPrefix = $params['Key'];

        // Filter our "/" keys added by the console as directories, and ensure
        // that if a filter function is provided that it passes the filter.
        $this->objectIterator = \Aws\flatmap(
            $this->getClient()->getPaginator('ListObjects', $op),
            function (Result $result) use ($filterFn) {
                $contentsAndPrefixes = $result->search('[Contents[], CommonPrefixes[]][]');
                // Filter out dir place holder keys and use the filter fn.
                return array_filter(
                    $contentsAndPrefixes,
                    function ($key) use ($filterFn) {
                        return (!$filterFn || call_user_func($filterFn, $key))
                            && (!isset($key['Key']) || substr($key['Key'], -1, 1) !== '/');
                    }
                );
            }
        );

        return true;
    }

    /**
     * Close the directory listing handles
     *
     * @return bool true on success
     */
    public function dir_closedir()
    {
        $this->objectIterator = null;
        gc_collect_cycles();

        return true;
    }

    /**
     * This method is called in response to rewinddir()
     *
     * @return boolean true on success
     */
    public function dir_rewinddir()
    {
        $this->boolCall(function() {
            $this->objectIterator = null;
            $this->dir_opendir($this->openedPath, null);
            return true;
        });
    }

    /**
     * This method is called in response to readdir()
     *
     * @return string Should return a string representing the next filename, or
     *                false if there is no next file.
     * @link http://www.php.net/manual/en/function.readdir.php
     */
    public function dir_readdir()
    {
        // Skip empty result keys
        if (!$this->objectIterator->valid()) {
            return false;
        }

        // First we need to create a cache key. This key is the full path to
        // then object in s3: protocol://bucket/key.
        // Next we need to create a result value. The result value is the
        // current value of the iterator without the opened bucket prefix to
        // emulate how readdir() works on directories.
        // The cache key and result value will depend on if this is a prefix
        // or a key.
        $cur = $this->objectIterator->current();
        if (isset($cur['Prefix'])) {
            // Include "directories". Be sure to strip a trailing "/"
            // on prefixes.
            $result = rtrim($cur['Prefix'], '/');
            $key = $this->formatKey($result);
            $stat = $this->formatUrlStat($key);
        } else {
            $result = $cur['Key'];
            $key = $this->formatKey($cur['Key']);
            $stat = $this->formatUrlStat($cur);
        }

        // Cache the object data for quick url_stat lookups used with
        // RecursiveDirectoryIterator.
        $this->getCacheStorage()->set($key, $stat);
        $this->objectIterator->next();

        // Remove the prefix from the result to emulate other stream wrappers.
        return $this->openedBucketPrefix
            ? substr($result, strlen($this->openedBucketPrefix))
            : $result;
    }

    private function formatKey($key)
    {
        $protocol = explode('://', $this->openedPath)[0];
        return "{$protocol}://{$this->openedBucket}/{$key}";
    }

    /**
     * Called in response to rename() to rename a file or directory. Currently
     * only supports renaming objects.
     *
     * @param string $path_from the path to the file to rename
     * @param string $path_to   the new path to the file
     *
     * @return bool true if file was successfully renamed
     * @link http://www.php.net/manual/en/function.rename.php
     */
    public function rename($path_from, $path_to)
    {
        // PHP will not allow rename across wrapper types, so we can safely
        // assume $path_from and $path_to have the same protocol
        $this->initProtocol($path_from);
        $partsFrom = $this->withPath($path_from);
        $partsTo = $this->withPath($path_to);
        $this->clearCacheKey($path_from);
        $this->clearCacheKey($path_to);

        if (!$partsFrom['Key'] || !$partsTo['Key']) {
            return $this->triggerError('The Amazon S3 stream wrapper only '
                . 'supports copying objects');
        }

        return $this->boolCall(function () use ($partsFrom, $partsTo) {
            $options = $this->getOptions(true);
            // Copy the object and allow overriding default parameters if
            // desired, but by default copy metadata
            $this->getClient()->copy(
                $partsFrom['Bucket'],
                $partsFrom['Key'],
                $partsTo['Bucket'],
                $partsTo['Key'],
                isset($options['acl']) ? $options['acl'] : 'private',
                $options
            );
            // Delete the original object
            $this->getClient()->deleteObject([
                'Bucket' => $partsFrom['Bucket'],
                'Key'    => $partsFrom['Key']
            ] + $options);
            return true;
        });
    }

    public function stream_cast($cast_as)
    {
        return false;
    }

    /**
     * Validates the provided stream arguments for fopen and returns an array
     * of errors.
     */
    private function validate($path, $mode)
    {
        $errors = [];

        if (!$this->getOption('Key')) {
            $errors[] = 'Cannot open a bucket. You must specify a path in the '
                . 'form of s3://bucket/key';
        }

        if (!in_array($mode, ['r', 'w', 'a', 'x'])) {
            $errors[] = "Mode not supported: {$mode}. "
                . "Use one 'r', 'w', 'a', or 'x'.";
        }

        // When using mode "x" validate if the file exists before attempting
        // to read
        if ($mode == 'x' &&
            $this->getClient()->doesObjectExist(
                $this->getOption('Bucket'),
                $this->getOption('Key'),
                $this->getOptions(true)
            )
        ) {
            $errors[] = "{$path} already exists on Amazon S3";
        }

        return $errors;
    }

    /**
     * Get the stream context options available to the current stream
     *
     * @param bool $removeContextData Set to true to remove contextual kvp's
     *                                like 'client' from the result.
     *
     * @return array
     */
    private function getOptions($removeContextData = false)
    {
        // Context is not set when doing things like stat
        if ($this->context === null) {
            $options = [];
        } else {
            $options = stream_context_get_options($this->context);
            $options = isset($options[$this->protocol])
                ? $options[$this->protocol]
                : [];
        }

        $default = stream_context_get_options(stream_context_get_default());
        $default = isset($default[$this->protocol])
            ? $default[$this->protocol]
            : [];
        $result = $this->params + $options + $default;

        if ($removeContextData) {
            unset($result['client'], $result['seekable'], $result['cache']);
        }

        return $result;
    }

    /**
     * Get a specific stream context option
     *
     * @param string $name Name of the option to retrieve
     *
     * @return mixed|null
     */
    private function getOption($name)
    {
        $options = $this->getOptions();

        return isset($options[$name]) ? $options[$name] : null;
    }

    /**
     * Gets the client from the stream context
     *
     * @return S3ClientInterface
     * @throws \RuntimeException if no client has been configured
     */
    private function getClient()
    {
        if (!$client = $this->getOption('client')) {
            throw new \RuntimeException('No client in stream context');
        }

        return $client;
    }

    private function getBucketKey($path)
    {
        // Remove the protocol
        $parts = explode('://', $path);
        // Get the bucket, key
        $parts = explode('/', $parts[1], 2);

        return [
            'Bucket' => $parts[0],
            'Key'    => isset($parts[1]) ? $parts[1] : null
        ];
    }

    /**
     * Get the bucket and key from the passed path (e.g. s3://bucket/key)
     *
     * @param string $path Path passed to the stream wrapper
     *
     * @return array Hash of 'Bucket', 'Key', and custom params from the context
     */
    private function withPath($path)
    {
        $params = $this->getOptions(true);

        return $this->getBucketKey($path) + $params;
    }

    private function openReadStream()
    {
        $client = $this->getClient();
        $command = $client->getCommand('GetObject', $this->getOptions(true));
        $command['@http']['stream'] = true;
        $result = $client->execute($command);
        $this->size = $result['ContentLength'];
        $this->body = $result['Body'];

        // Wrap the body in a caching entity body if seeking is allowed
        if ($this->getOption('seekable') && !$this->body->isSeekable()) {
            $this->body = new CachingStream($this->body);
        }

        return true;
    }

    private function openWriteStream()
    {
        $this->body = new Stream(fopen('php://temp', 'r+'));
        return true;
    }

    private function openAppendStream()
    {
        try {
            // Get the body of the object and seek to the end of the stream
            $client = $this->getClient();
            $this->body = $client->getObject($this->getOptions(true))['Body'];
            $this->body->seek(0, SEEK_END);
            return true;
        } catch (S3Exception $e) {
            // The object does not exist, so use a simple write stream
            return $this->openWriteStream();
        }
    }

    /**
     * Trigger one or more errors
     *
     * @param string|array $errors Errors to trigger
     * @param mixed        $flags  If set to STREAM_URL_STAT_QUIET, then no
     *                             error or exception occurs
     *
     * @return bool Returns false
     * @throws \RuntimeException if throw_errors is true
     */
    private function triggerError($errors, $flags = null)
    {
        // This is triggered with things like file_exists()
        if ($flags & STREAM_URL_STAT_QUIET) {
            return $flags & STREAM_URL_STAT_LINK
                // This is triggered for things like is_link()
                ? $this->formatUrlStat(false)
                : false;
        }

        // This is triggered when doing things like lstat() or stat()
        trigger_error(implode("\n", (array) $errors), E_USER_WARNING);

        return false;
    }

    /**
     * Prepare a url_stat result array
     *
     * @param string|array $result Data to add
     *
     * @return array Returns the modified url_stat result
     */
    private function formatUrlStat($result = null)
    {
        $stat = $this->getStatTemplate();
        switch (gettype($result)) {
            case 'NULL':
            case 'string':
                // Directory with 0777 access - see "man 2 stat".
                $stat['mode'] = $stat[2] = 0040777;
                break;
            case 'array':
                // Regular file with 0777 access - see "man 2 stat".
                $stat['mode'] = $stat[2] = 0100777;
                // Pluck the content-length if available.
                if (isset($result['ContentLength'])) {
                    $stat['size'] = $stat[7] = $result['ContentLength'];
                } elseif (isset($result['Size'])) {
                    $stat['size'] = $stat[7] = $result['Size'];
                }
                if (isset($result['LastModified'])) {
                    // ListObjects or HeadObject result
                    $stat['mtime'] = $stat[9] = $stat['ctime'] = $stat[10]
                        = strtotime($result['LastModified']);
                }
        }

        return $stat;
    }

    /**
     * Creates a bucket for the given parameters.
     *
     * @param string $path   Stream wrapper path
     * @param array  $params A result of StreamWrapper::withPath()
     *
     * @return bool Returns true on success or false on failure
     */
    private function createBucket($path, array $params)
    {
        if ($this->getClient()->doesBucketExist($params['Bucket'])) {
            return $this->triggerError("Bucket already exists: {$path}");
        }

        return $this->boolCall(function () use ($params, $path) {
            $this->getClient()->createBucket($params);
            $this->clearCacheKey($path);
            return true;
        });
    }

    /**
     * Creates a pseudo-folder by creating an empty "/" suffixed key
     *
     * @param string $path   Stream wrapper path
     * @param array  $params A result of StreamWrapper::withPath()
     *
     * @return bool
     */
    private function createSubfolder($path, array $params)
    {
        // Ensure the path ends in "/" and the body is empty.
        $params['Key'] = rtrim($params['Key'], '/') . '/';
        $params['Body'] = '';

        // Fail if this pseudo directory key already exists
        if ($this->getClient()->doesObjectExist(
            $params['Bucket'],
            $params['Key'])
        ) {
            return $this->triggerError("Subfolder already exists: {$path}");
        }

        return $this->boolCall(function () use ($params, $path) {
            $this->getClient()->putObject($params);
            $this->clearCacheKey($path);
            return true;
        });
    }

    /**
     * Deletes a nested subfolder if it is empty.
     *
     * @param string $path   Path that is being deleted (e.g., 's3://a/b/c')
     * @param array  $params A result of StreamWrapper::withPath()
     *
     * @return bool
     */
    private function deleteSubfolder($path, $params)
    {
        // Use a key that adds a trailing slash if needed.
        $prefix = rtrim($params['Key'], '/') . '/';
        $result = $this->getClient()->listObjects([
            'Bucket'  => $params['Bucket'],
            'Prefix'  => $prefix,
            'MaxKeys' => 1
        ]);

        // Check if the bucket contains keys other than the placeholder
        if ($contents = $result['Contents']) {
            return (count($contents) > 1 || $contents[0]['Key'] != $prefix)
                ? $this->triggerError('Subfolder is not empty')
                : $this->unlink(rtrim($path, '/') . '/');
        }

        return $result['CommonPrefixes']
            ? $this->triggerError('Subfolder contains nested folders')
            : true;
    }

    /**
     * Determine the most appropriate ACL based on a file mode.
     *
     * @param int $mode File mode
     *
     * @return string
     */
    private function determineAcl($mode)
    {
        switch (substr(decoct($mode), 0, 1)) {
            case '7': return 'public-read';
            case '6': return 'authenticated-read';
            default: return 'private';
        }
    }

    /**
     * Gets a URL stat template with default values
     *
     * @return array
     */
    private function getStatTemplate()
    {
        return [
            0  => 0,  'dev'     => 0,
            1  => 0,  'ino'     => 0,
            2  => 0,  'mode'    => 0,
            3  => 0,  'nlink'   => 0,
            4  => 0,  'uid'     => 0,
            5  => 0,  'gid'     => 0,
            6  => -1, 'rdev'    => -1,
            7  => 0,  'size'    => 0,
            8  => 0,  'atime'   => 0,
            9  => 0,  'mtime'   => 0,
            10 => 0,  'ctime'   => 0,
            11 => -1, 'blksize' => -1,
            12 => -1, 'blocks'  => -1,
        ];
    }

    /**
     * Invokes a callable and triggers an error if an exception occurs while
     * calling the function.
     *
     * @param callable $fn
     * @param int      $flags
     *
     * @return bool
     */
    private function boolCall(callable $fn, $flags = null)
    {
        try {
            return $fn();
        } catch (\Exception $e) {
            return $this->triggerError($e->getMessage(), $flags);
        }
    }

    /**
     * @return LruArrayCache
     */
    private function getCacheStorage()
    {
        if (!$this->cache) {
            $this->cache = $this->getOption('cache') ?: new LruArrayCache();
        }

        return $this->cache;
    }

    /**
     * Clears a specific stat cache value from the stat cache and LRU cache.
     *
     * @param string $key S3 path (s3://bucket/key).
     */
    private function clearCacheKey($key)
    {
        clearstatcache(true, $key);
        $this->getCacheStorage()->remove($key);
    }

    /**
     * Returns the size of the opened object body.
     *
     * @return int|null
     */
    private function getSize()
    {
        $size = $this->body->getSize();

        return $size !== null ? $size : $this->size;
    }
}
<?php
namespace Aws\S3;

use Aws;
use Aws\CommandInterface;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7;
use GuzzleHttp\Promise\PromisorInterface;
use Iterator;

/**
 * Transfers files from the local filesystem to S3 or from S3 to the local
 * filesystem.
 *
 * This class does not support copying from the local filesystem to somewhere
 * else on the local filesystem or from one S3 bucket to another.
 */
class Transfer implements PromisorInterface
{
    private $client;
    private $promise;
    private $source;
    private $sourceMetadata;
    private $destination;
    private $concurrency;
    private $mupThreshold;
    private $before;
    private $s3Args = [];

    /**
     * When providing the $source argument, you may provide a string referencing
     * the path to a directory on disk to upload, an s3 scheme URI that contains
     * the bucket and key (e.g., "s3://bucket/key"), or an \Iterator object
     * that yields strings containing filenames that are the path to a file on
     * disk or an s3 scheme URI. The "/key" portion of an s3 URI is optional.
     *
     * When providing an iterator for the $source argument, you must also
     * provide a 'base_dir' key value pair in the $options argument.
     *
     * The $dest argument can be the path to a directory on disk or an s3
     * scheme URI (e.g., "s3://bucket/key").
     *
     * The options array can contain the following key value pairs:
     *
     * - base_dir: (string) Base directory of the source, if $source is an
     *   iterator. If the $source option is not an array, then this option is
     *   ignored.
     * - before: (callable) A callback to invoke before each transfer. The
     *   callback accepts the following positional arguments: string $source,
     *   string $dest, Aws\CommandInterface $command. The provided command will
     *   be either a GetObject, PutObject, InitiateMultipartUpload, or
     *   UploadPart command.
     * - mup_threshold: (int) Size in bytes in which a multipart upload should
     *   be used instead of PutObject. Defaults to 20971520 (20 MB).
     * - concurrency: (int, default=5) Number of files to upload concurrently.
     *   The ideal concurrency value will vary based on the number of files
     *   being uploaded and the average size of each file. Generally speaking,
     *   smaller files benefit from a higher concurrency while larger files
     *   will not.
     * - debug: (bool) Set to true to print out debug information for
     *   transfers. Set to an fopen() resource to write to a specific stream
     *   rather than writing to STDOUT.
     *
     * @param S3ClientInterface $client  Client used for transfers.
     * @param string|Iterator   $source  Where the files are transferred from.
     * @param string            $dest    Where the files are transferred to.
     * @param array             $options Hash of options.
     */
    public function __construct(
        S3ClientInterface $client,
        $source,
        $dest,
        array $options = []
    ) {
        $this->client = $client;

        // Prepare the destination.
        $this->destination = $this->prepareTarget($dest);
        if ($this->destination['scheme'] === 's3') {
            $this->s3Args = $this->getS3Args($this->destination['path']);
        }

        // Prepare the source.
        if (is_string($source)) {
            $this->sourceMetadata = $this->prepareTarget($source);
            $this->source = $source;
        } elseif ($source instanceof Iterator) {
            if (empty($options['base_dir'])) {
                throw new \InvalidArgumentException('You must provide the source'
                    . ' argument as a string or provide the "base_dir" option.');
            }

            $this->sourceMetadata = $this->prepareTarget($options['base_dir']);
            $this->source = $source;
        } else {
            throw new \InvalidArgumentException('source must be the path to a '
                . 'directory or an iterator that yields file names.');
        }

        // Validate schemes.
        if ($this->sourceMetadata['scheme'] === $this->destination['scheme']) {
            throw new \InvalidArgumentException("You cannot copy from"
                . " {$this->sourceMetadata['scheme']} to"
                . " {$this->destination['scheme']}."
            );
        }

        // Handle multipart-related options.
        $this->concurrency = isset($options['concurrency'])
            ? $options['concurrency']
            : MultipartUploader::DEFAULT_CONCURRENCY;
        $this->mupThreshold = isset($options['mup_threshold'])
            ? $options['mup_threshold']
            : 16777216;
        if ($this->mupThreshold < MultipartUploader::PART_MIN_SIZE) {
            throw new \InvalidArgumentException('mup_threshold must be >= 5MB');
        }

        // Handle "before" callback option.
        if (isset($options['before'])) {
            $this->before = $options['before'];
            if (!is_callable($this->before)) {
                throw new \InvalidArgumentException('before must be a callable.');
            }
        }

        // Handle "debug" option.
        if (isset($options['debug'])) {
            if ($options['debug'] === true) {
                $options['debug'] = fopen('php://output', 'w');
            }
            $this->addDebugToBefore($options['debug']);
        }
    }

    /**
     * Transfers the files.
     */
    public function promise()
    {
        // If the promise has been created, just return it.
        if (!$this->promise) {
            // Create an upload/download promise for the transfer.
            $this->promise = $this->sourceMetadata['scheme'] === 'file'
                ? $this->createUploadPromise()
                : $this->createDownloadPromise();
        }

        return $this->promise;
    }

    /**
     * Transfers the files synchronously.
     */
    public function transfer()
    {
        $this->promise()->wait();
    }

    private function prepareTarget($targetPath)
    {
        $target = [
            'path'   => $this->normalizePath($targetPath),
            'scheme' => $this->determineScheme($targetPath),
        ];

        if ($target['scheme'] !== 's3' && $target['scheme'] !== 'file') {
            throw new \InvalidArgumentException('Scheme must be "s3" or "file".');
        }

        return $target;
    }

    /**
     * Creates an array that contains Bucket and Key by parsing the filename.
     *
     * @param string $path Path to parse.
     *
     * @return array
     */
    private function getS3Args($path)
    {
        $parts = explode('/', str_replace('s3://', '', $path), 2);
        $args = ['Bucket' => $parts[0]];
        if (isset($parts[1])) {
            $args['Key'] = $parts[1];
        }

        return $args;
    }

    /**
     * Parses the scheme from a filename.
     *
     * @param string $path Path to parse.
     *
     * @return string
     */
    private function determineScheme($path)
    {
        return !strpos($path, '://') ? 'file' : explode('://', $path)[0];
    }

    /**
     * Normalize a path so that it has UNIX-style directory separators and no trailing /
     *
     * @param string $path
     *
     * @return string
     */
    private function normalizePath($path)
    {
        return rtrim(str_replace('\\', '/', $path), '/');
    }

    private function createDownloadPromise()
    {
        $parts = $this->getS3Args($this->sourceMetadata['path']);
        $prefix = "s3://{$parts['Bucket']}/"
            . (isset($parts['Key']) ? $parts['Key'] . '/' : '');


        $commands = [];
        foreach ($this->getDownloadsIterator() as $object) {
            // Prepare the sink.
            $sink = $this->destination['path'] . '/'
                . preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $object);

            // Create the directory if needed.
            $dir = dirname($sink);
            if (!is_dir($dir) && !mkdir($dir, 0777, true)) {
                throw new \RuntimeException("Could not create dir: {$dir}");
            }

            // Create the command.
            $commands []= $this->client->getCommand(
                'GetObject',
                $this->getS3Args($object) + ['@http'  => ['sink'  => $sink]]
            );
        }

        // Create a GetObject command pool and return the promise.
        return (new Aws\CommandPool($this->client, $commands, [
            'concurrency' => $this->concurrency,
            'before'      => $this->before,
            'rejected'    => function ($reason, $idx, Promise\PromiseInterface $p) {
                $p->reject($reason);
            }
        ]))->promise();
    }

    private function createUploadPromise()
    {
        // Map each file into a promise that performs the actual transfer.
        $files = \Aws\map($this->getUploadsIterator(), function ($file) {
            return (filesize($file) >= $this->mupThreshold)
                ? $this->uploadMultipart($file)
                : $this->upload($file);
        });

        // Create an EachPromise, that will concurrently handle the upload
        // operations' yielded promises from the iterator.
        return Promise\each_limit_all($files, $this->concurrency);
    }

    /** @return Iterator */
    private function getUploadsIterator()
    {
        if (is_string($this->source)) {
            return Aws\filter(
                Aws\recursive_dir_iterator($this->sourceMetadata['path']),
                function ($file) { return !is_dir($file); }
            );
        }

        return $this->source;
    }

    /** @return Iterator */
    private function getDownloadsIterator()
    {
        if (is_string($this->source)) {
            $listArgs = $this->getS3Args($this->sourceMetadata['path']);
            if (isset($listArgs['Key'])) {
                $listArgs['Prefix'] = $listArgs['Key'] . '/';
                unset($listArgs['Key']);
            }

            $files = $this->client
                ->getPaginator('ListObjects', $listArgs)
                ->search('Contents[].Key');
            $files = Aws\map($files, function ($key) use ($listArgs) {
                return "s3://{$listArgs['Bucket']}/$key";
            });
            return Aws\filter($files, function ($key) {
                return substr($key, -1, 1) !== '/';
            });
        }

        return $this->source;
    }

    private function upload($filename)
    {
        $args = $this->s3Args;
        $args['SourceFile'] = $filename;
        $args['Key'] = $this->createS3Key($filename);
        $command = $this->client->getCommand('PutObject', $args);
        $this->before and call_user_func($this->before, $command);

        return $this->client->executeAsync($command);
    }

    private function uploadMultipart($filename)
    {
        $args = $this->s3Args;
        $args['Key'] = $this->createS3Key($filename);

        return (new MultipartUploader($this->client, $filename, [
            'bucket'          => $args['Bucket'],
            'key'             => $args['Key'],
            'before_initiate' => $this->before,
            'before_upload'   => $this->before,
            'before_complete' => $this->before,
            'concurrency'     => $this->concurrency,
        ]))->promise();
    }

    private function createS3Key($filename)
    {
        $filename = $this->normalizePath($filename);
        $relative_file_path = ltrim(
            preg_replace('#^' . preg_quote($this->sourceMetadata['path']) . '#', '', $filename),
            '/\\'
        );
        
        if (isset($this->s3Args['Key'])) {
            return rtrim($this->s3Args['Key'], '/').'/'.$relative_file_path;
        }

        return $relative_file_path;
    }

    private function addDebugToBefore($debug)
    {
        $before = $this->before;
        $sourcePath = $this->sourceMetadata['path'];
        $s3Args = $this->s3Args;

        $this->before = static function (
            CommandInterface $command
        ) use ($before, $debug, $sourcePath, $s3Args) {
            // Call the composed before function.
            $before and $before($command);

            // Determine the source and dest values based on operation.
            switch ($operation = $command->getName()) {
                case 'GetObject':
                    $source = "s3://{$command['Bucket']}/{$command['Key']}";
                    $dest = $command['@http']['sink'];
                    break;
                case 'PutObject':
                    $source = $command['SourceFile'];
                    $dest = "s3://{$command['Bucket']}/{$command['Key']}";
                    break;
                case 'UploadPart':
                    $part = $command['PartNumber'];
                case 'CreateMultipartUpload':
                case 'CompleteMultipartUpload':
                    $sourceKey = $command['Key'];
                    if (isset($s3Args['Key']) && strpos($sourceKey, $s3Args['Key']) === 0) {
                        $sourceKey = substr($sourceKey, strlen($s3Args['Key']) + 1);
                    }
                    $source = "{$sourcePath}/{$sourceKey}";
                    $dest = "s3://{$command['Bucket']}/{$command['Key']}";
                    break;
                default:
                    throw new \UnexpectedValueException(
                        "Transfer encountered an unexpected operation: {$operation}."
                    );
            }

            // Print the debugging message.
            $context = sprintf('%s -> %s (%s)', $source, $dest, $operation);
            if (isset($part)) {
                $context .= " : Part={$part}";
            }
            fwrite($debug, "Transferring {$context}\n");
        };
    }
}
<?php
namespace Aws;

/**
 * Builds AWS clients based on configuration settings.
 *
 * @method \Aws\Acm\AcmClient createAcm(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionAcm(array $args = [])
 * @method \Aws\ApiGateway\ApiGatewayClient createApiGateway(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionApiGateway(array $args = [])
 * @method \Aws\ApplicationAutoScaling\ApplicationAutoScalingClient createApplicationAutoScaling(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionApplicationAutoScaling(array $args = [])
 * @method \Aws\ApplicationDiscoveryService\ApplicationDiscoveryServiceClient createApplicationDiscoveryService(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionApplicationDiscoveryService(array $args = [])
 * @method \Aws\Appstream\AppstreamClient createAppstream(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionAppstream(array $args = [])
 * @method \Aws\AutoScaling\AutoScalingClient createAutoScaling(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionAutoScaling(array $args = [])
 * @method \Aws\Batch\BatchClient createBatch(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionBatch(array $args = [])
 * @method \Aws\Budgets\BudgetsClient createBudgets(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionBudgets(array $args = [])
 * @method \Aws\CloudDirectory\CloudDirectoryClient createCloudDirectory(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudDirectory(array $args = [])
 * @method \Aws\CloudFormation\CloudFormationClient createCloudFormation(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudFormation(array $args = [])
 * @method \Aws\CloudFront\CloudFrontClient createCloudFront(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudFront(array $args = [])
 * @method \Aws\CloudHsm\CloudHsmClient createCloudHsm(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudHsm(array $args = [])
 * @method \Aws\CloudSearch\CloudSearchClient createCloudSearch(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudSearch(array $args = [])
 * @method \Aws\CloudSearchDomain\CloudSearchDomainClient createCloudSearchDomain(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudSearchDomain(array $args = [])
 * @method \Aws\CloudTrail\CloudTrailClient createCloudTrail(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudTrail(array $args = [])
 * @method \Aws\CloudWatch\CloudWatchClient createCloudWatch(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudWatch(array $args = [])
 * @method \Aws\CloudWatchEvents\CloudWatchEventsClient createCloudWatchEvents(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudWatchEvents(array $args = [])
 * @method \Aws\CloudWatchLogs\CloudWatchLogsClient createCloudWatchLogs(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCloudWatchLogs(array $args = [])
 * @method \Aws\CodeBuild\CodeBuildClient createCodeBuild(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCodeBuild(array $args = [])
 * @method \Aws\CodeCommit\CodeCommitClient createCodeCommit(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCodeCommit(array $args = [])
 * @method \Aws\CodeDeploy\CodeDeployClient createCodeDeploy(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCodeDeploy(array $args = [])
 * @method \Aws\CodePipeline\CodePipelineClient createCodePipeline(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCodePipeline(array $args = [])
 * @method \Aws\CognitoIdentity\CognitoIdentityClient createCognitoIdentity(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCognitoIdentity(array $args = [])
 * @method \Aws\CognitoIdentityProvider\CognitoIdentityProviderClient createCognitoIdentityProvider(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCognitoIdentityProvider(array $args = [])
 * @method \Aws\CognitoSync\CognitoSyncClient createCognitoSync(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCognitoSync(array $args = [])
 * @method \Aws\ConfigService\ConfigServiceClient createConfigService(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionConfigService(array $args = [])
 * @method \Aws\CostandUsageReportService\CostandUsageReportServiceClient createCostandUsageReportService(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionCostandUsageReportService(array $args = [])
 * @method \Aws\DataPipeline\DataPipelineClient createDataPipeline(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionDataPipeline(array $args = [])
 * @method \Aws\DatabaseMigrationService\DatabaseMigrationServiceClient createDatabaseMigrationService(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionDatabaseMigrationService(array $args = [])
 * @method \Aws\DeviceFarm\DeviceFarmClient createDeviceFarm(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionDeviceFarm(array $args = [])
 * @method \Aws\DirectConnect\DirectConnectClient createDirectConnect(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionDirectConnect(array $args = [])
 * @method \Aws\DirectoryService\DirectoryServiceClient createDirectoryService(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionDirectoryService(array $args = [])
 * @method \Aws\DynamoDb\DynamoDbClient createDynamoDb(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionDynamoDb(array $args = [])
 * @method \Aws\DynamoDbStreams\DynamoDbStreamsClient createDynamoDbStreams(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionDynamoDbStreams(array $args = [])
 * @method \Aws\Ec2\Ec2Client createEc2(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionEc2(array $args = [])
 * @method \Aws\Ecr\EcrClient createEcr(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionEcr(array $args = [])
 * @method \Aws\Ecs\EcsClient createEcs(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionEcs(array $args = [])
 * @method \Aws\Efs\EfsClient createEfs(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionEfs(array $args = [])
 * @method \Aws\ElastiCache\ElastiCacheClient createElastiCache(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionElastiCache(array $args = [])
 * @method \Aws\ElasticBeanstalk\ElasticBeanstalkClient createElasticBeanstalk(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionElasticBeanstalk(array $args = [])
 * @method \Aws\ElasticLoadBalancing\ElasticLoadBalancingClient createElasticLoadBalancing(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionElasticLoadBalancing(array $args = [])
 * @method \Aws\ElasticLoadBalancingV2\ElasticLoadBalancingV2Client createElasticLoadBalancingV2(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionElasticLoadBalancingV2(array $args = [])
 * @method \Aws\ElasticTranscoder\ElasticTranscoderClient createElasticTranscoder(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionElasticTranscoder(array $args = [])
 * @method \Aws\ElasticsearchService\ElasticsearchServiceClient createElasticsearchService(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionElasticsearchService(array $args = [])
 * @method \Aws\Emr\EmrClient createEmr(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionEmr(array $args = [])
 * @method \Aws\Firehose\FirehoseClient createFirehose(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionFirehose(array $args = [])
 * @method \Aws\GameLift\GameLiftClient createGameLift(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionGameLift(array $args = [])
 * @method \Aws\Glacier\GlacierClient createGlacier(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionGlacier(array $args = [])
 * @method \Aws\Health\HealthClient createHealth(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionHealth(array $args = [])
 * @method \Aws\Iam\IamClient createIam(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionIam(array $args = [])
 * @method \Aws\ImportExport\ImportExportClient createImportExport(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionImportExport(array $args = [])
 * @method \Aws\Inspector\InspectorClient createInspector(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionInspector(array $args = [])
 * @method \Aws\Iot\IotClient createIot(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionIot(array $args = [])
 * @method \Aws\IotDataPlane\IotDataPlaneClient createIotDataPlane(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionIotDataPlane(array $args = [])
 * @method \Aws\Kinesis\KinesisClient createKinesis(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionKinesis(array $args = [])
 * @method \Aws\KinesisAnalytics\KinesisAnalyticsClient createKinesisAnalytics(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionKinesisAnalytics(array $args = [])
 * @method \Aws\Kms\KmsClient createKms(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionKms(array $args = [])
 * @method \Aws\Lambda\LambdaClient createLambda(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionLambda(array $args = [])
 * @method \Aws\LexRuntimeService\LexRuntimeServiceClient createLexRuntimeService(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionLexRuntimeService(array $args = [])
 * @method \Aws\Lightsail\LightsailClient createLightsail(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionLightsail(array $args = [])
 * @method \Aws\MachineLearning\MachineLearningClient createMachineLearning(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionMachineLearning(array $args = [])
 * @method \Aws\MarketplaceCommerceAnalytics\MarketplaceCommerceAnalyticsClient createMarketplaceCommerceAnalytics(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionMarketplaceCommerceAnalytics(array $args = [])
 * @method \Aws\MarketplaceMetering\MarketplaceMeteringClient createMarketplaceMetering(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionMarketplaceMetering(array $args = [])
 * @method \Aws\OpsWorks\OpsWorksClient createOpsWorks(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionOpsWorks(array $args = [])
 * @method \Aws\OpsWorksCM\OpsWorksCMClient createOpsWorksCM(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionOpsWorksCM(array $args = [])
 * @method \Aws\Pinpoint\PinpointClient createPinpoint(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionPinpoint(array $args = [])
 * @method \Aws\Polly\PollyClient createPolly(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionPolly(array $args = [])
 * @method \Aws\Rds\RdsClient createRds(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionRds(array $args = [])
 * @method \Aws\Redshift\RedshiftClient createRedshift(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionRedshift(array $args = [])
 * @method \Aws\Rekognition\RekognitionClient createRekognition(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionRekognition(array $args = [])
 * @method \Aws\Route53\Route53Client createRoute53(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionRoute53(array $args = [])
 * @method \Aws\Route53Domains\Route53DomainsClient createRoute53Domains(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionRoute53Domains(array $args = [])
 * @method \Aws\S3\S3Client createS3(array $args = [])
 * @method \Aws\S3\S3MultiRegionClient createMultiRegionS3(array $args = [])
 * @method \Aws\ServiceCatalog\ServiceCatalogClient createServiceCatalog(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionServiceCatalog(array $args = [])
 * @method \Aws\Ses\SesClient createSes(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSes(array $args = [])
 * @method \Aws\Sfn\SfnClient createSfn(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSfn(array $args = [])
 * @method \Aws\Shield\ShieldClient createShield(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionShield(array $args = [])
 * @method \Aws\Sms\SmsClient createSms(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSms(array $args = [])
 * @method \Aws\SnowBall\SnowBallClient createSnowBall(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSnowBall(array $args = [])
 * @method \Aws\Sns\SnsClient createSns(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSns(array $args = [])
 * @method \Aws\Sqs\SqsClient createSqs(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSqs(array $args = [])
 * @method \Aws\Ssm\SsmClient createSsm(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSsm(array $args = [])
 * @method \Aws\StorageGateway\StorageGatewayClient createStorageGateway(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionStorageGateway(array $args = [])
 * @method \Aws\Sts\StsClient createSts(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSts(array $args = [])
 * @method \Aws\Support\SupportClient createSupport(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSupport(array $args = [])
 * @method \Aws\Swf\SwfClient createSwf(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionSwf(array $args = [])
 * @method \Aws\Waf\WafClient createWaf(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionWaf(array $args = [])
 * @method \Aws\WafRegional\WafRegionalClient createWafRegional(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionWafRegional(array $args = [])
 * @method \Aws\WorkSpaces\WorkSpacesClient createWorkSpaces(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionWorkSpaces(array $args = [])
 * @method \Aws\XRay\XRayClient createXRay(array $args = [])
 * @method \Aws\MultiRegionClient createMultiRegionXRay(array $args = [])
 */
class Sdk
{
    const VERSION = '3.22.5';

    /** @var array Arguments for creating clients */
    private $args;

    /**
     * Constructs a new SDK object with an associative array of default
     * client settings.
     *
     * @param array $args
     *
     * @throws \InvalidArgumentException
     * @see Aws\AwsClient::__construct for a list of available options.
     */
    public function __construct(array $args = [])
    {
        $this->args = $args;

        if (!isset($args['handler']) && !isset($args['http_handler'])) {
            $this->args['http_handler'] = default_http_handler();
        }
    }

    public function __call($name, array $args)
    {
        $args = isset($args[0]) ? $args[0] : [];
        if (strpos($name, 'createMultiRegion') === 0) {
            return $this->createMultiRegionClient(substr($name, 17), $args);
        } elseif (strpos($name, 'create') === 0) {
            return $this->createClient(substr($name, 6), $args);
        }

        throw new \BadMethodCallException("Unknown method: {$name}.");
    }

    /**
     * Get a client by name using an array of constructor options.
     *
     * @param string $name Service name or namespace (e.g., DynamoDb, s3).
     * @param array  $args Arguments to configure the client.
     *
     * @return AwsClientInterface
     * @throws \InvalidArgumentException if any required options are missing or
     *                                   the service is not supported.
     * @see Aws\AwsClient::__construct for a list of available options for args.
     */
    public function createClient($name, array $args = [])
    {
        // Get information about the service from the manifest file.
        $service = manifest($name);
        $namespace = $service['namespace'];

        // Instantiate the client class.
        $client = "Aws\\{$namespace}\\{$namespace}Client";
        return new $client($this->mergeArgs($namespace, $service, $args));
    }

    public function createMultiRegionClient($name, array $args = [])
    {
        // Get information about the service from the manifest file.
        $service = manifest($name);
        $namespace = $service['namespace'];

        $klass = "Aws\\{$namespace}\\{$namespace}MultiRegionClient";
        $klass = class_exists($klass) ? $klass : 'Aws\\MultiRegionClient';

        return new $klass($this->mergeArgs($namespace, $service, $args));
    }

    private function mergeArgs($namespace, array $manifest, array $args = [])
    {
        // Merge provided args with stored, service-specific args.
        if (isset($this->args[$namespace])) {
            $args += $this->args[$namespace];
        }

        // Provide the endpoint prefix in the args.
        if (!isset($args['service'])) {
            $args['service'] = $manifest['endpoint'];
        }

        return $args + $this->args;
    }

    /**
     * Determine the endpoint prefix from a client namespace.
     *
     * @param string $name Namespace name
     *
     * @return string
     * @internal
     * @deprecated Use the `\Aws\manifest()` function instead.
     */
    public static function getEndpointPrefix($name)
    {
        return manifest($name)['endpoint'];
    }
}
<?php
namespace Aws\Signature;

use Aws\Credentials\CredentialsInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Provides anonymous client access (does not sign requests).
 */
class AnonymousSignature implements SignatureInterface
{
    public function signRequest(
        RequestInterface $request,
        CredentialsInterface $credentials
    ) {
        return $request;
    }

    public function presign(
        RequestInterface $request,
        CredentialsInterface $credentials,
        $expires
    ) {
        return $request;
    }
}
<?php
namespace Aws\Signature;

use Aws\Credentials\CredentialsInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Amazon S3 signature version 4 support.
 */
class S3SignatureV4 extends SignatureV4
{
    const UNSIGNED_PAYLOAD = 'UNSIGNED-PAYLOAD';
    
    /**
     * Always add a x-amz-content-sha-256 for data integrity.
     */
    public function signRequest(
        RequestInterface $request,
        CredentialsInterface $credentials
    ) {
        if (!$request->hasHeader('x-amz-content-sha256')) {
            $request = $request->withHeader(
                'X-Amz-Content-Sha256',
                $this->getPayload($request)
            );
        }

        return parent::signRequest($request, $credentials);
    }

    /**
     * Always add a x-amz-content-sha-256 for data integrity.
     */
    public function presign(
        RequestInterface $request,
        CredentialsInterface $credentials,
        $expires
    ) {
        if (!$request->hasHeader('x-amz-content-sha256')) {
            $request = $request->withHeader(
                'X-Amz-Content-Sha256',
                $this->getPresignedPayload($request)
            );
        }

        return parent::presign($request, $credentials, $expires);
    }

    /**
     * Override used to allow pre-signed URLs to be created for an
     * in-determinate request payload.
     */
    protected function getPresignedPayload(RequestInterface $request)
    {
        return self::UNSIGNED_PAYLOAD;
    }

    /**
     * Amazon S3 does not double-encode the path component in the canonical request
     */
    protected function createCanonicalizedPath($path)
    {
        return '/' . ltrim($path, '/');
    }
}
<?php
namespace Aws\Signature;

use Aws\Credentials\CredentialsInterface;
use Psr\Http\Message\RequestInterface;

/**
 * Interface used to provide interchangeable strategies for signing requests
 * using the various AWS signature protocols.
 */
interface SignatureInterface
{
    /**
     * Signs the specified request with an AWS signing protocol by using the
     * provided AWS account credentials and adding the required headers to the
     * request.
     *
     * @param RequestInterface     $request     Request to sign
     * @param CredentialsInterface $credentials Signing credentials
     *
     * @return RequestInterface Returns the modified request.
     */
    public function signRequest(
        RequestInterface $request,
        CredentialsInterface $credentials
    );

    /**
     * Create a pre-signed request.
     *
     * @param RequestInterface     $request     Request to sign
     * @param CredentialsInterface $credentials Credentials used to sign
     * @param int|string|\DateTime $expires The time at which the URL should
     *     expire. This can be a Unix timestamp, a PHP DateTime object, or a
     *     string that can be evaluated by strtotime.
     *
     * @return RequestInterface
     */
    public function presign(
        RequestInterface $request,
        CredentialsInterface $credentials,
        $expires
    );
}
<?php
namespace Aws\Signature;

use Aws\Exception\UnresolvedSignatureException;

/**
 * Signature providers.
 *
 * A signature provider is a function that accepts a version, service, and
 * region and returns a {@see SignatureInterface} object on success or NULL if
 * no signature can be created from the provided arguments.
 *
 * You can wrap your calls to a signature provider with the
 * {@see SignatureProvider::resolve} function to ensure that a signature object
 * is created. If a signature object is not created, then the resolve()
 * function will throw a {@see Aws\Exception\UnresolvedSignatureException}.
 *
 *     use Aws\Signature\SignatureProvider;
 *     $provider = SignatureProvider::defaultProvider();
 *     // Returns a SignatureInterface or NULL.
 *     $signer = $provider('v4', 's3', 'us-west-2');
 *     // Returns a SignatureInterface or throws.
 *     $signer = SignatureProvider::resolve($provider, 'no', 's3', 'foo');
 *
 * You can compose multiple providers into a single provider using
 * {@see Aws\or_chain}. This function accepts providers as arguments and
 * returns a new function that will invoke each provider until a non-null value
 * is returned.
 *
 *     $a = SignatureProvider::defaultProvider();
 *     $b = function ($version, $service, $region) {
 *         if ($version === 'foo') {
 *             return new MyFooSignature();
 *         }
 *     };
 *     $c = \Aws\or_chain($a, $b);
 *     $signer = $c('v4', 'abc', '123');     // $a handles this.
 *     $signer = $c('foo', 'abc', '123');    // $b handles this.
 *     $nullValue = $c('???', 'abc', '123'); // Neither can handle this.
 */
class SignatureProvider
{
    /**
     * Resolves and signature provider and ensures a non-null return value.
     *
     * @param callable $provider Provider function to invoke.
     * @param string   $version  Signature version.
     * @param string   $service  Service name.
     * @param string   $region   Region name.
     *
     * @return SignatureInterface
     * @throws UnresolvedSignatureException
     */
    public static function resolve(callable $provider, $version, $service, $region)
    {
        $result = $provider($version, $service, $region);
        if ($result instanceof SignatureInterface) {
            return $result;
        }

        throw new UnresolvedSignatureException(
            "Unable to resolve a signature for $version/$service/$region.\n"
            . "Valid signature versions include v4 and anonymous."
        );
    }

    /**
     * Default SDK signature provider.
     *
     * @return callable
     */
    public static function defaultProvider()
    {
        return self::memoize(self::version());
    }

    /**
     * Creates a signature provider that caches previously created signature
     * objects. The computed cache key is the concatenation of the version,
     * service, and region.
     *
     * @param callable $provider Signature provider to wrap.
     *
     * @return callable
     */
    public static function memoize(callable $provider)
    {
        $cache = [];
        return function ($version, $service, $region) use (&$cache, $provider) {
            $key = "($version)($service)($region)";
            if (!isset($cache[$key])) {
                $cache[$key] = $provider($version, $service, $region);
            }
            return $cache[$key];
        };
    }

    /**
     * Creates signature objects from known signature versions.
     *
     * This provider currently recognizes the following signature versions:
     *
     * - v4: Signature version 4.
     * - anonymous: Does not sign requests.
     *
     * @return callable
     */
    public static function version()
    {
        return function ($version, $service, $region) {
            switch ($version) {
                case 's3v4':
                case 'v4':
                    return $service === 's3'
                        ? new S3SignatureV4($service, $region)
                        : new SignatureV4($service, $region);
                case 'anonymous':
                    return new AnonymousSignature();
                default:
                    return null;
            }
        };
    }
}
<?php
namespace Aws\Signature;

/**
 * Provides signature calculation for SignatureV4.
 */
trait SignatureTrait
{
    /** @var array Cache of previously signed values */
    private $cache = [];

    /** @var int Size of the hash cache */
    private $cacheSize = 0;

    private function createScope($shortDate, $region, $service)
    {
        return "$shortDate/$region/$service/aws4_request";
    }

    private function getSigningKey($shortDate, $region, $service, $secretKey)
    {
        $k = $shortDate . '_' . $region . '_' . $service . '_' . $secretKey;

        if (!isset($this->cache[$k])) {
            // Clear the cache when it reaches 50 entries
            if (++$this->cacheSize > 50) {
                $this->cache = [];
                $this->cacheSize = 0;
            }

            $dateKey = hash_hmac(
                'sha256',
                $shortDate,
                "AWS4{$secretKey}",
                true
            );
            $regionKey = hash_hmac('sha256', $region, $dateKey, true);
            $serviceKey = hash_hmac('sha256', $service, $regionKey, true);
            $this->cache[$k] = hash_hmac(
                'sha256',
                'aws4_request',
                $serviceKey,
                true
            );
        }

        return $this->cache[$k];
    }
}
<?php
namespace Aws\Signature;

use Aws\Credentials\CredentialsInterface;
use Aws\Exception\CouldNotCreateChecksumException;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Signature Version 4
 * @link http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
 */
class SignatureV4 implements SignatureInterface
{
    use SignatureTrait;
    const ISO8601_BASIC = 'Ymd\THis\Z';

    /** @var string */
    private $service;

    /** @var string */
    private $region;

    /**
     * @param string $service Service name to use when signing
     * @param string $region  Region name to use when signing
     */
    public function __construct($service, $region)
    {
        $this->service = $service;
        $this->region = $region;
    }

    public function signRequest(
        RequestInterface $request,
        CredentialsInterface $credentials
    ) {
        $ldt = gmdate(self::ISO8601_BASIC);
        $sdt = substr($ldt, 0, 8);
        $parsed = $this->parseRequest($request);
        $parsed['headers']['X-Amz-Date'] = [$ldt];

        if ($token = $credentials->getSecurityToken()) {
            $parsed['headers']['X-Amz-Security-Token'] = [$token];
        }

        $cs = $this->createScope($sdt, $this->region, $this->service);
        $payload = $this->getPayload($request);
        $context = $this->createContext($parsed, $payload);
        $toSign = $this->createStringToSign($ldt, $cs, $context['creq']);
        $signingKey = $this->getSigningKey(
            $sdt,
            $this->region,
            $this->service,
            $credentials->getSecretKey()
        );
        $signature = hash_hmac('sha256', $toSign, $signingKey);
        $parsed['headers']['Authorization'] = [
            "AWS4-HMAC-SHA256 "
            . "Credential={$credentials->getAccessKeyId()}/{$cs}, "
            . "SignedHeaders={$context['headers']}, Signature={$signature}"
        ];

        return $this->buildRequest($parsed);
    }

    public function presign(
        RequestInterface $request,
        CredentialsInterface $credentials,
        $expires
    ) {
        $parsed = $this->createPresignedRequest($request, $credentials);
        $payload = $this->getPresignedPayload($request);
        $httpDate = gmdate(self::ISO8601_BASIC, time());
        $shortDate = substr($httpDate, 0, 8);
        $scope = $this->createScope($shortDate, $this->region, $this->service);
        $credential = $credentials->getAccessKeyId() . '/' . $scope;
        $parsed['query']['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
        $parsed['query']['X-Amz-Credential'] = $credential;
        $parsed['query']['X-Amz-Date'] = gmdate('Ymd\THis\Z', time());
        $parsed['query']['X-Amz-SignedHeaders'] = 'host';
        $parsed['query']['X-Amz-Expires'] = $this->convertExpires($expires);
        $context = $this->createContext($parsed, $payload);
        $stringToSign = $this->createStringToSign($httpDate, $scope, $context['creq']);
        $key = $this->getSigningKey(
            $shortDate,
            $this->region,
            $this->service,
            $credentials->getSecretKey()
        );
        $parsed['query']['X-Amz-Signature'] = hash_hmac('sha256', $stringToSign, $key);

        return $this->buildRequest($parsed);
    }

    /**
     * Converts a POST request to a GET request by moving POST fields into the
     * query string.
     *
     * Useful for pre-signing query protocol requests.
     *
     * @param RequestInterface $request Request to clone
     *
     * @return RequestInterface
     * @throws \InvalidArgumentException if the method is not POST
     */
    public static function convertPostToGet(RequestInterface $request)
    {
        if ($request->getMethod() !== 'POST') {
            throw new \InvalidArgumentException('Expected a POST request but '
                . 'received a ' . $request->getMethod() . ' request.');
        }

        $sr = $request->withMethod('GET')
            ->withBody(Psr7\stream_for(''))
            ->withoutHeader('Content-Type')
            ->withoutHeader('Content-Length');

        // Move POST fields to the query if they are present
        if ($request->getHeaderLine('Content-Type') === 'application/x-www-form-urlencoded') {
            $body = (string) $request->getBody();
            $sr = $sr->withUri($sr->getUri()->withQuery($body));
        }

        return $sr;
    }

    protected function getPayload(RequestInterface $request)
    {
        // Calculate the request signature payload
        if ($request->hasHeader('X-Amz-Content-Sha256')) {
            // Handle streaming operations (e.g. Glacier.UploadArchive)
            return $request->getHeaderLine('X-Amz-Content-Sha256');
        }

        if (!$request->getBody()->isSeekable()) {
            throw new CouldNotCreateChecksumException('sha256');
        }

        try {
            return Psr7\hash($request->getBody(), 'sha256');
        } catch (\Exception $e) {
            throw new CouldNotCreateChecksumException('sha256', $e);
        }
    }

    protected function getPresignedPayload(RequestInterface $request)
    {
        return $this->getPayload($request);
    }

    protected function createCanonicalizedPath($path)
    {
        $doubleEncoded = rawurlencode(ltrim($path, '/'));

        return '/' . str_replace('%2F', '/', $doubleEncoded);
    }

    private function createStringToSign($longDate, $credentialScope, $creq)
    {
        $hash = hash('sha256', $creq);

        return "AWS4-HMAC-SHA256\n{$longDate}\n{$credentialScope}\n{$hash}";
    }

    private function createPresignedRequest(
        RequestInterface $request,
        CredentialsInterface $credentials
    ) {
        $parsedRequest = $this->parseRequest($request);

        // Make sure to handle temporary credentials
        if ($token = $credentials->getSecurityToken()) {
            $parsedRequest['headers']['X-Amz-Security-Token'] = [$token];
        }

        return $this->moveHeadersToQuery($parsedRequest);
    }

    /**
     * @param array  $parsedRequest
     * @param string $payload Hash of the request payload
     * @return array Returns an array of context information
     */
    private function createContext(array $parsedRequest, $payload)
    {
        // The following headers are not signed because signing these headers
        // would potentially cause a signature mismatch when sending a request
        // through a proxy or if modified at the HTTP client level.
        static $blacklist = [
            'cache-control'       => true,
            'content-type'        => true,
            'content-length'      => true,
            'expect'              => true,
            'max-forwards'        => true,
            'pragma'              => true,
            'range'               => true,
            'te'                  => true,
            'if-match'            => true,
            'if-none-match'       => true,
            'if-modified-since'   => true,
            'if-unmodified-since' => true,
            'if-range'            => true,
            'accept'              => true,
            'authorization'       => true,
            'proxy-authorization' => true,
            'from'                => true,
            'referer'             => true,
            'user-agent'          => true,
            'x-amzn-trace-id'     => true
        ];

        // Normalize the path as required by SigV4
        $canon = $parsedRequest['method'] . "\n"
            . $this->createCanonicalizedPath($parsedRequest['path']) . "\n"
            . $this->getCanonicalizedQuery($parsedRequest['query']) . "\n";

        // Case-insensitively aggregate all of the headers.
        $aggregate = [];
        foreach ($parsedRequest['headers'] as $key => $values) {
            $key = strtolower($key);
            if (!isset($blacklist[$key])) {
                foreach ($values as $v) {
                    $aggregate[$key][] = $v;
                }
            }
        }

        ksort($aggregate);
        $canonHeaders = [];
        foreach ($aggregate as $k => $v) {
            if (count($v) > 0) {
                sort($v);
            }
            $canonHeaders[] = $k . ':' . preg_replace('/\s+/', ' ', implode(',', $v));
        }

        $signedHeadersString = implode(';', array_keys($aggregate));
        $canon .= implode("\n", $canonHeaders) . "\n\n"
            . $signedHeadersString . "\n"
            . $payload;

        return ['creq' => $canon, 'headers' => $signedHeadersString];
    }

    private function getCanonicalizedQuery(array $query)
    {
        unset($query['X-Amz-Signature']);

        if (!$query) {
            return '';
        }

        $qs = '';
        ksort($query);
        foreach ($query as $k => $v) {
            if (!is_array($v)) {
                $qs .= rawurlencode($k) . '=' . rawurlencode($v) . '&';
            } else {
                sort($v);
                foreach ($v as $value) {
                    $qs .= rawurlencode($k) . '=' . rawurlencode($value) . '&';
                }
            }
        }

        return substr($qs, 0, -1);
    }

    private function convertExpires($expires)
    {
        if ($expires instanceof \DateTime) {
            $expires = $expires->getTimestamp();
        } elseif (!is_numeric($expires)) {
            $expires = strtotime($expires);
        }

        $duration = $expires - time();

        // Ensure that the duration of the signature is not longer than a week
        if ($duration > 604800) {
            throw new \InvalidArgumentException('The expiration date of a '
                . 'signature version 4 presigned URL must be less than one '
                . 'week');
        }

        return $duration;
    }

    private function moveHeadersToQuery(array $parsedRequest)
    {
        foreach ($parsedRequest['headers'] as $name => $header) {
            $lname = strtolower($name);
            if (substr($lname, 0, 5) == 'x-amz') {
                $parsedRequest['query'][$name] = $header;
            }
            if ($lname !== 'host') {
                unset($parsedRequest['headers'][$name]);
            }
        }

        return $parsedRequest;
    }

    private function parseRequest(RequestInterface $request)
    {
        // Clean up any previously set headers.
        /** @var RequestInterface $request */
        $request = $request
            ->withoutHeader('X-Amz-Date')
            ->withoutHeader('Date')
            ->withoutHeader('Authorization');
        $uri = $request->getUri();

        return [
            'method'  => $request->getMethod(),
            'path'    => $uri->getPath(),
            'query'   => Psr7\parse_query($uri->getQuery()),
            'uri'     => $uri,
            'headers' => $request->getHeaders(),
            'body'    => $request->getBody(),
            'version' => $request->getProtocolVersion()
        ];
    }

    private function buildRequest(array $req)
    {
        if ($req['query']) {
            $req['uri'] = $req['uri']->withQuery(Psr7\build_query($req['query']));
        }

        return new Psr7\Request(
            $req['method'],
            $req['uri'],
            $req['headers'],
            $req['body'],
            $req['version']
        );
    }
}
<?php
namespace Aws;

use Aws\Exception\AwsException;
use GuzzleHttp\Promise\RejectedPromise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * Traces state changes between middlewares.
 */
class TraceMiddleware
{
    private $prevOutput;
    private $prevInput;
    private $config;

    private static $authHeaders = [
        'X-Amz-Security-Token' => '[TOKEN]',
    ];

    private static $authStrings = [
        // S3Signature
        '/AWSAccessKeyId=[A-Z0-9]{20}&/i' => 'AWSAccessKeyId=[KEY]&',
        // SignatureV4 Signature and S3Signature
        '/Signature=.+/i' => 'Signature=[SIGNATURE]',
        // SignatureV4 access key ID
        '/Credential=[A-Z0-9]{20}\//i' => 'Credential=[KEY]/',
        // S3 signatures
        '/AWS [A-Z0-9]{20}:.+/' => 'AWS AKI[KEY]:[SIGNATURE]',
        // STS Presigned URLs
        '/X-Amz-Security-Token=[^&]+/i' => 'X-Amz-Security-Token=[TOKEN]',
    ];

    /**
     * Configuration array can contain the following key value pairs.
     *
     * - logfn: (callable) Function that is invoked with log messages. By
     *   default, PHP's "echo" function will be utilized.
     * - stream_size: (int) When the size of a stream is greater than this
     *   number, the stream data will not be logged. Set to "0" to not log any
     *   stream data.
     * - scrub_auth: (bool) Set to false to disable the scrubbing of auth data
     *   from the logged messages.
     * - http: (bool) Set to false to disable the "debug" feature of lower
     *   level HTTP adapters (e.g., verbose curl output).
     * - auth_strings: (array) A mapping of authentication string regular
     *   expressions to scrubbed strings. These mappings are passed directly to
     *   preg_replace (e.g., preg_replace($key, $value, $debugOutput) if
     *   "scrub_auth" is set to true.
     * - auth_headers: (array) A mapping of header names known to contain
     *   sensitive data to what the scrubbed value should be. The value of any
     *   headers contained in this array will be replaced with the if
     *   "scrub_auth" is set to true.
     */
    public function __construct(array $config = [])
    {
        $this->config = $config + [
            'logfn'        => function ($value) { echo $value; },
            'stream_size'  => 524288,
            'scrub_auth'   => true,
            'http'         => true,
            'auth_strings' => [],
            'auth_headers' => [],
        ];

        $this->config['auth_strings'] += self::$authStrings;
        $this->config['auth_headers'] += self::$authHeaders;
    }

    public function __invoke($step, $name)
    {
        $this->prevOutput = $this->prevInput = [];

        return function (callable $next) use ($step, $name) {
            return function (
                CommandInterface $command,
                RequestInterface $request = null
            ) use ($next, $step, $name) {
                $this->createHttpDebug($command);
                $start = microtime(true);
                $this->stepInput([
                    'step'    => $step,
                    'name'    => $name,
                    'request' => $this->requestArray($request),
                    'command' => $this->commandArray($command)
                ]);

                return $next($command, $request)->then(
                    function ($value) use ($step, $name, $command, $start) {
                        $this->flushHttpDebug($command);
                        $this->stepOutput($start, [
                            'step'   => $step,
                            'name'   => $name,
                            'result' => $this->resultArray($value),
                            'error'  => null
                        ]);
                        return $value;
                    },
                    function ($reason) use ($step, $name, $start, $command) {
                        $this->flushHttpDebug($command);
                        $this->stepOutput($start, [
                            'step'   => $step,
                            'name'   => $name,
                            'result' => null,
                            'error'  => $this->exceptionArray($reason)
                        ]);
                        return new RejectedPromise($reason);
                    }
                );
            };
        };
    }

    private function stepInput($entry)
    {
        static $keys = ['command', 'request'];
        $this->compareStep($this->prevInput, $entry, '-> Entering', $keys);
        $this->write("\n");
        $this->prevInput = $entry;
    }

    private function stepOutput($start, $entry)
    {
        static $keys = ['result', 'error'];
        $this->compareStep($this->prevOutput, $entry, '<- Leaving', $keys);
        $totalTime = microtime(true) - $start;
        $this->write("  Inclusive step time: " . $totalTime . "\n\n");
        $this->prevOutput = $entry;
    }

    private function compareStep(array $a, array $b, $title, array $keys)
    {
        $changes = [];
        foreach ($keys as $key) {
            $av = isset($a[$key]) ? $a[$key] : null;
            $bv = isset($b[$key]) ? $b[$key] : null;
            $this->compareArray($av, $bv, $key, $changes);
        }
        $str = "\n{$title} step {$b['step']}, name '{$b['name']}'";
        $str .= "\n" . str_repeat('-', strlen($str) - 1) . "\n\n  ";
        $str .= $changes
            ? implode("\n  ", str_replace("\n", "\n  ", $changes))
            : 'no changes';
        $this->write($str . "\n");
    }

    private function commandArray(CommandInterface $cmd)
    {
        return [
            'instance' => spl_object_hash($cmd),
            'name'     => $cmd->getName(),
            'params'   => $cmd->toArray()
        ];
    }

    private function requestArray(RequestInterface $request = null)
    {
        return !$request ? [] : array_filter([
            'instance' => spl_object_hash($request),
            'method'   => $request->getMethod(),
            'headers'  => $this->redactHeaders($request->getHeaders()),
            'body'     => $this->streamStr($request->getBody()),
            'scheme'   => $request->getUri()->getScheme(),
            'port'     => $request->getUri()->getPort(),
            'path'     => $request->getUri()->getPath(),
            'query'    => $request->getUri()->getQuery(),
        ]);
    }

    private function responseArray(ResponseInterface $response = null)
    {
        return !$response ? [] : [
            'instance'   => spl_object_hash($response),
            'statusCode' => $response->getStatusCode(),
            'headers'    => $this->redactHeaders($response->getHeaders()),
            'body'       => $this->streamStr($response->getBody())
        ];
    }

    private function resultArray($value)
    {
        return $value instanceof ResultInterface
            ? [
                'instance' => spl_object_hash($value),
                'data'     => $value->toArray()
            ] : $value;
    }

    private function exceptionArray($e)
    {
        if (!($e instanceof \Exception)) {
            return $e;
        }

        $result = [
            'instance'   => spl_object_hash($e),
            'class'      => get_class($e),
            'message'    => $e->getMessage(),
            'file'       => $e->getFile(),
            'line'       => $e->getLine(),
            'trace'      => $e->getTraceAsString(),
        ];

        if ($e instanceof AwsException) {
            $result += [
                'type'       => $e->getAwsErrorType(),
                'code'       => $e->getAwsErrorCode(),
                'requestId'  => $e->getAwsRequestId(),
                'statusCode' => $e->getStatusCode(),
                'result'     => $this->resultArray($e->getResult()),
                'request'    => $this->requestArray($e->getRequest()),
                'response'   => $this->responseArray($e->getResponse()),
            ];
        }

        return $result;
    }

    private function compareArray($a, $b, $path, array &$diff)
    {
        if ($a === $b) {
            return;
        } elseif (is_array($a)) {
            $b = (array) $b;
            $keys = array_unique(array_merge(array_keys($a), array_keys($b)));
            foreach ($keys as $k) {
                if (!array_key_exists($k, $a)) {
                    $this->compareArray(null, $b[$k], "{$path}.{$k}", $diff);
                } elseif (!array_key_exists($k, $b)) {
                    $this->compareArray($a[$k], null, "{$path}.{$k}", $diff);
                } else {
                    $this->compareArray($a[$k], $b[$k], "{$path}.{$k}", $diff);
                }
            }
        } elseif ($a !== null && $b === null) {
            $diff[] = "{$path} was unset";
        } elseif ($a === null && $b !== null) {
            $diff[] = sprintf("%s was set to %s", $path, $this->str($b));
        } else {
            $diff[] = sprintf("%s changed from %s to %s", $path, $this->str($a), $this->str($b));
        }
    }

    private function str($value)
    {
        if (is_scalar($value)) {
            return (string) $value;
        } elseif ($value instanceof \Exception) {
            $value = $this->exceptionArray($value);
        }

        ob_start();
        var_dump($value);
        return ob_get_clean();
    }

    private function streamStr(StreamInterface $body)
    {
        return $body->getSize() < $this->config['stream_size']
            ? (string) $body
            : 'stream(size=' . $body->getSize() . ')';
    }

    private function createHttpDebug(CommandInterface $command)
    {
        if ($this->config['http'] && !isset($command['@http']['debug'])) {
            $command['@http']['debug'] = fopen('php://temp', 'w+');
        }
    }

    private function flushHttpDebug(CommandInterface $command)
    {
        if ($res = $command['@http']['debug']) {
            rewind($res);
            $this->write(stream_get_contents($res));
            fclose($res);
            $command['@http']['debug'] = null;
        }
    }

    private function write($value)
    {
        if ($this->config['scrub_auth']) {
            foreach ($this->config['auth_strings'] as $pattern => $replacement) {
                $value = preg_replace($pattern, $replacement, $value);
            }
        }

        call_user_func($this->config['logfn'], $value);
    }

    private function redactHeaders(array $headers)
    {
        if ($this->config['scrub_auth']) {
            $headers = $this->config['auth_headers'] + $headers;
        }

        return $headers;
    }
}
<?php
namespace Aws;

use Aws\Exception\AwsException;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\PromisorInterface;
use GuzzleHttp\Promise\RejectedPromise;

/**
 * "Waiters" are associated with an AWS resource (e.g., EC2 instance), and poll
 * that resource and until it is in a particular state.

 * The Waiter object produces a promise that is either a.) resolved once the
 * waiting conditions are met, or b.) rejected if the waiting conditions cannot
 * be met or has exceeded the number of allowed attempts at meeting the
 * conditions. You can use waiters in a blocking or non-blocking way, depending
 * on whether you call wait() on the promise.

 * The configuration for the waiter must include information about the operation
 * and the conditions for wait completion.
 */
class Waiter implements PromisorInterface
{
    /** @var AwsClientInterface Client used to execute each attempt. */
    private $client;

    /** @var string Name of the waiter. */
    private $name;

    /** @var array Params to use with each attempt operation. */
    private $args;

    /** @var array Waiter configuration. */
    private $config;

    /** @var array Default configuration options. */
    private static $defaults = ['initDelay' => 0, 'before' => null];

    /** @var array Required configuration options. */
    private static $required = [
        'acceptors',
        'delay',
        'maxAttempts',
        'operation',
    ];

    /**
     * The array of configuration options include:
     *
     * - acceptors: (array) Array of acceptor options
     * - delay: (int) Number of seconds to delay between attempts
     * - maxAttempts: (int) Maximum number of attempts before failing
     * - operation: (string) Name of the API operation to use for polling
     * - before: (callable) Invoked before attempts. Accepts command and tries.
     *
     * @param AwsClientInterface $client Client used to execute commands.
     * @param string             $name   Waiter name.
     * @param array              $args   Command arguments.
     * @param array              $config Waiter config that overrides defaults.
     *
     * @throws \InvalidArgumentException if the configuration is incomplete.
     */
    public function __construct(
        AwsClientInterface $client,
        $name,
        array $args = [],
        array $config = []
    ) {
        $this->client = $client;
        $this->name = $name;
        $this->args = $args;

        // Prepare and validate config.
        $this->config = $config + self::$defaults;
        foreach (self::$required as $key) {
            if (!isset($this->config[$key])) {
                throw new \InvalidArgumentException(
                    'The provided waiter configuration was incomplete.'
                );
            }
        }
        if ($this->config['before'] && !is_callable($this->config['before'])) {
            throw new \InvalidArgumentException(
                'The provided "before" callback is not callable.'
            );
        }
    }

    public function promise()
    {
        return Promise\coroutine(function () {
            $name = $this->config['operation'];
            for ($state = 'retry', $attempt = 1; $state === 'retry'; $attempt++) {
                // Execute the operation.
                $args = $this->getArgsForAttempt($attempt);
                $command = $this->client->getCommand($name, $args);
                try {
                    if ($this->config['before']) {
                        $this->config['before']($command, $attempt);
                    }
                    $result = (yield $this->client->executeAsync($command));
                } catch (AwsException $e) {
                    $result = $e;
                }

                // Determine the waiter's state and what to do next.
                $state = $this->determineState($result);
                if ($state === 'success') {
                    yield $command;
                } elseif ($state === 'failed') {
                    $msg = "The {$this->name} waiter entered a failure state.";
                    if ($result instanceof \Exception) {
                        $msg .= ' Reason: ' . $result->getMessage();
                    }
                    yield new RejectedPromise(new \RuntimeException($msg));
                } elseif ($state === 'retry'
                    && $attempt >= $this->config['maxAttempts']
                ) {
                    $state = 'failed';
                    yield new RejectedPromise(new \RuntimeException(
                        "The {$this->name} waiter failed after attempt #{$attempt}."
                    ));
                }
            }
        });
    }

    /**
     * Gets the operation arguments for the attempt, including the delay.
     *
     * @param $attempt Number of the current attempt.
     *
     * @return mixed integer
     */
    private function getArgsForAttempt($attempt)
    {
        $args = $this->args;

        // Determine the delay.
        $delay = ($attempt === 1)
            ? $this->config['initDelay']
            : $this->config['delay'];
        if (is_callable($delay)) {
            $delay = $delay($attempt);
        }

        // Set the delay. (Note: handlers except delay in milliseconds.)
        if (!isset($args['@http'])) {
            $args['@http'] = [];
        }
        $args['@http']['delay'] = $delay * 1000;

        return $args;
    }

    /**
     * Determines the state of the waiter attempt, based on the result of
     * polling the resource. A waiter can have the state of "success", "failed",
     * or "retry".
     *
     * @param mixed $result
     *
     * @return string Will be "success", "failed", or "retry"
     */
    private function determineState($result)
    {
        foreach ($this->config['acceptors'] as $acceptor) {
            $matcher = 'matches' . ucfirst($acceptor['matcher']);
            if ($this->{$matcher}($result, $acceptor)) {
                return $acceptor['state'];
            }
        }

        return $result instanceof \Exception ? 'failed' : 'retry';
    }

    /**
     * @param result $result   Result or exception.
     * @param array  $acceptor Acceptor configuration being checked.
     *
     * @return bool
     */
    private function matchesPath($result, array $acceptor)
    {
        return !($result instanceof ResultInterface)
            ? false
            : $acceptor['expected'] == $result->search($acceptor['argument']);
    }

    /**
     * @param result $result   Result or exception.
     * @param array  $acceptor Acceptor configuration being checked.
     *
     * @return bool
     */
    private function matchesPathAll($result, array $acceptor)
    {
        if (!($result instanceof ResultInterface)) {
            return false;
        }

        $actuals = $result->search($acceptor['argument']) ?: [];
        foreach ($actuals as $actual) {
            if ($actual != $acceptor['expected']) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param result $result   Result or exception.
     * @param array  $acceptor Acceptor configuration being checked.
     *
     * @return bool
     */
    private function matchesPathAny($result, array $acceptor)
    {
        if (!($result instanceof ResultInterface)) {
            return false;
        }

        $actuals = $result->search($acceptor['argument']) ?: [];
        foreach ($actuals as $actual) {
            if ($actual == $acceptor['expected']) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param result $result   Result or exception.
     * @param array  $acceptor Acceptor configuration being checked.
     *
     * @return bool
     */
    private function matchesStatus($result, array $acceptor)
    {
        if ($result instanceof ResultInterface) {
            return $acceptor['expected'] == $result['@metadata']['statusCode'];
        } elseif ($result instanceof AwsException && $response = $result->getResponse()) {
            return $acceptor['expected'] == $response->getStatusCode();
        } else {
            return false;
        }
    }

    /**
     * @param result $result   Result or exception.
     * @param array  $acceptor Acceptor configuration being checked.
     *
     * @return bool
     */
    private function matchesError($result, array $acceptor)
    {
        if ($result instanceof AwsException) {
            return $result->isConnectionError()
                || $result->getAwsErrorCode() == $acceptor['expected'];
        }

        return false;
    }
}
<?php
namespace Aws;

use Aws\Api\Parser\Exception\ParserException;
use GuzzleHttp\Promise;
use GuzzleHttp\Promise\FulfilledPromise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Converts an HTTP handler into a Command HTTP handler.
 *
 * HTTP handlers have the following signature:
 *     function(RequestInterface $request, array $options) : PromiseInterface
 *
 * The promise returned form an HTTP handler must resolve to a PSR-7 response
 * object when fulfilled or an error array when rejected. The error array
 * can contain the following data:
 *
 * - exception: (required, Exception) Exception that was encountered.
 * - response: (ResponseInterface) PSR-7 response that was received (if a
 *   response) was received.
 * - connection_error: (bool) True if the error is the result of failing to
 *   connect.
 */
class WrappedHttpHandler
{
    private $httpHandler;
    private $parser;
    private $errorParser;
    private $exceptionClass;
    private $collectStats;

    /**
     * @param callable $httpHandler    Function that accepts a request and array
     *                                 of request options and returns a promise
     *                                 that fulfills with a response or rejects
     *                                 with an error array.
     * @param callable $parser         Function that accepts a response object
     *                                 and returns an AWS result object.
     * @param callable $errorParser    Function that parses a response object
     *                                 into AWS error data.
     * @param string   $exceptionClass Exception class to throw.
     * @param bool     $collectStats   Whether to collect HTTP transfer
     *                                 information.
     */
    public function __construct(
        callable $httpHandler,
        callable $parser,
        callable $errorParser,
        $exceptionClass = 'Aws\Exception\AwsException',
        $collectStats = false
    ) {
        $this->httpHandler = $httpHandler;
        $this->parser = $parser;
        $this->errorParser = $errorParser;
        $this->exceptionClass = $exceptionClass;
        $this->collectStats = $collectStats;
    }

    /**
     * Calls the simpler HTTP specific handler and wraps the returned promise
     * with AWS specific values (e.g., a result object or AWS exception).
     *
     * @param CommandInterface $command Command being executed.
     * @param RequestInterface $request Request to send.
     *
     * @return Promise\PromiseInterface
     */
    public function __invoke(
        CommandInterface $command,
        RequestInterface $request
    ) {
        $fn = $this->httpHandler;
        $options = $command['@http'] ?: [];
        $stats = [];
        if ($this->collectStats) {
            $options['http_stats_receiver'] = static function (
                array $transferStats
            ) use (&$stats) {
                $stats = $transferStats;
            };
        } elseif (isset($options['http_stats_receiver'])) {
            throw new \InvalidArgumentException('Providing a custom HTTP stats'
                . ' receiver to Aws\WrappedHttpHandler is not supported.');
        }

        return Promise\promise_for($fn($request, $options))
            ->then(
                function (
                    ResponseInterface $res
                ) use ($command, $request, &$stats) {
                    return $this->parseResponse($command, $request, $res, $stats);
                },
                function ($err) use ($request, $command, &$stats) {
                    if (is_array($err)) {
                        $err = $this->parseError(
                            $err,
                            $request,
                            $command,
                            $stats
                        );
                    }
                    return new Promise\RejectedPromise($err);
                }
            );
    }

    /**
     * @param CommandInterface  $command
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param array             $stats
     *
     * @return ResultInterface
     */
    private function parseResponse(
        CommandInterface $command,
        RequestInterface $request,
        ResponseInterface $response,
        array $stats
    ) {
        $parser = $this->parser;
        $status = $response->getStatusCode();
        $result = $status < 300
            ? $parser($command, $response)
            : new Result();

        $metadata = [
            'statusCode'    => $status,
            'effectiveUri'  => (string) $request->getUri(),
            'headers'       => [],
            'transferStats' => [],
        ];
        if (!empty($stats)) {
            $metadata['transferStats']['http'] = [$stats];
        }

        // Bring headers into the metadata array.
        foreach ($response->getHeaders() as $name => $values) {
            $metadata['headers'][strtolower($name)] = $values[0];
        }

        $result['@metadata'] = $metadata;

        return $result;
    }

    /**
     * Parses a rejection into an AWS error.
     *
     * @param array            $err     Rejection error array.
     * @param RequestInterface $request Request that was sent.
     * @param CommandInterface $command Command being sent.
     * @param array            $stats   Transfer statistics
     *
     * @return \Exception
     */
    private function parseError(
        array $err,
        RequestInterface $request,
        CommandInterface $command,
        array $stats
    ) {
        if (!isset($err['exception'])) {
            throw new \RuntimeException('The HTTP handler was rejected without an "exception" key value pair.');
        }

        $serviceError = "AWS HTTP error: " . $err['exception']->getMessage();

        if (!isset($err['response'])) {
            $parts = ['response' => null];
        } else {
            try {
                $parts = call_user_func($this->errorParser, $err['response']);
                $serviceError .= " {$parts['code']} ({$parts['type']}): "
                    . "{$parts['message']} - " . $err['response']->getBody();
            } catch (ParserException $e) {
                $parts = [];
                $serviceError .= ' Unable to parse error information from '
                    . "response - {$e->getMessage()}";
            }

            $parts['response'] = $err['response'];
        }

        $parts['exception'] = $err['exception'];
        $parts['request'] = $request;
        $parts['connection_error'] = !empty($err['connection_error']);
        $parts['transfer_stats'] = $stats;

        return new $this->exceptionClass(
            sprintf(
                'Error executing "%s" on "%s"; %s',
                $command->getName(),
                $request->getUri(),
                $serviceError
            ),
            $command,
            $parts,
            $err['exception']
        );
    }
}
#!/usr/bin/env php
<?php

namespace Sabre\VObject;

// This sucks.. we have to try to find the composer autoloader. But chances
// are, we can't find it this way. So we'll do our bestest
$paths = [
    __DIR__ . '/../vendor/autoload.php',  // In case vobject is cloned directly
    __DIR__ . '/../../../autoload.php',   // In case vobject is a composer dependency.
];

foreach($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

if (!class_exists('Sabre\\VObject\\Version')) {
    fwrite(STDERR, "Composer autoloader could not be properly loaded.\n");
    die(1);
}

if ($argc < 2) {

    $version = Version::VERSION;

    $help = <<<HI
sabre/vobject $version
Usage:
    generate_vcards [count]

Options:
    count   The number of random vcards to generate

Examples:
    generate_vcards 1000 > testdata.vcf

HI;

    fwrite(STDERR, $help);
    exit(2);
}

$count = (int)$argv[1];
if ($count < 1) {
    fwrite(STDERR, "Count must be at least 1\n");
    exit(2);
}

fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n");
fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n");

/**
 * The following list is just some random data we compiled from various
 * sources online.
 *
 * Very little thought went into compiling this list, and certainly nothing
 * political or ethical.
 *
 * We would _love_ more additions to this to add more variation to this list.
 *
 * Send us PR's and don't be shy adding your own first and last name for fun.
 */

$sets = array(
    "nl" => array(
        "country" => "Netherlands",
        "boys" => array(
            "Anno",
            "Bram",
            "Daan",
            "Evert",
            "Finn",
            "Jayden",
            "Jens",
            "Jesse",
            "Levi",
            "Lucas",
            "Luuk",
            "Milan",
            "René",
            "Sem",
            "Sibrand",
            "Willem",
        ),
        "girls" => array(
            "Celia",
            "Emma",
            "Fenna",
            "Geke",
            "Inge",
            "Julia",
            "Lisa",
            "Lotte",
            "Mila",
            "Sara",
            "Sophie",
            "Tess",
            "Zoë",
        ),
        "last" => array(
            "Bakker",
            "Bos",
            "De Boer",
            "De Groot",
            "De Jong",
            "De Vries",
            "Jansen",
            "Janssen",
            "Meyer",
            "Mulder",
            "Peters",
            "Smit",
            "Van Dijk",
            "Van den Berg",
            "Visser",
            "Vos",
        ),
    ),
    "us" => array(
        "country" => "United States",
        "boys" => array(
            "Aiden",
            "Alexander",
            "Charles",
            "David",
            "Ethan",
            "Jacob",
            "James",
            "Jayden",
            "John",
            "Joseph",
            "Liam",
            "Mason",
            "Michael",
            "Noah",
            "Richard",
            "Robert",
            "Thomas",
            "William",
        ),
        "girls" => array(
            "Ava",
            "Barbara",
            "Chloe",
            "Dorothy",
            "Elizabeth",
            "Emily",
            "Emma",
            "Isabella",
            "Jennifer",
            "Lily",
            "Linda",
            "Margaret",
            "Maria",
            "Mary",
            "Mia",
            "Olivia",
            "Patricia",
            "Roxy",
            "Sophia",
            "Susan",
            "Zoe",
        ),
        "last" => array(
            "Smith",
            "Johnson",
            "Williams",
            "Jones",
            "Brown",
            "Davis",
            "Miller",
            "Wilson",
            "Moore",
            "Taylor",
            "Anderson",
            "Thomas",
            "Jackson",
            "White",
            "Harris",
            "Martin",
            "Thompson",
            "Garcia",
            "Martinez",
            "Robinson",
        ),
    ),
);

$current = 0;

$r = function($arr) {

    return $arr[mt_rand(0,count($arr)-1)];

};

$bdayStart = strtotime('-85 years');
$bdayEnd = strtotime('-20 years');

while($current < $count) {

    $current++;
    fwrite(STDERR, "\033[100D$current/$count");

    $country = array_rand($sets);
    $gender = mt_rand(0,1)?'girls':'boys';

    $vcard = new Component\VCard(array(
        'VERSION' => '4.0',
        'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']),
        'UID' => UUIDUtil::getUUID(),
    ));

    $bdayRatio = mt_rand(0,9);

    if($bdayRatio < 2) {
        // 20% has a birthday property with a full date
        $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd));
        $vcard->add('BDAY', $dt->format('Ymd'));

    } elseif ($bdayRatio < 3) {
        // 10% we only know the month and date of
        $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd));
        $vcard->add('BDAY', '--' . $dt->format('md'));
    }
    if ($result = $vcard->validate()) {
        ob_start();
        echo "\nWe produced an invalid vcard somehow!\n";
        foreach($result as $message) {
            echo "  " . $message['message'] . "\n";
        }
        fwrite(STDERR, ob_get_clean());
    }
    echo $vcard->serialize();

}

fwrite(STDERR,"\nDone.\n");
#!/usr/bin/env php
<?php
require __DIR__ . '/../vendor/autoload.php';

use JmesPath\Env;
use JmesPath\DebugRuntime;

$description = <<<EOT
Runs a JMESPath expression on the provided input or a test case.

Provide the JSON input and expression:
    echo '{}' | jp.php expression

Or provide the path to a compliance script, a suite, and test case number:
    jp.php --script path_to_script --suite test_suite_number --case test_case_number [expression]

EOT;

$args = [];
$currentKey = null;
for ($i = 1, $total = count($argv); $i < $total; $i++) {
    if ($i % 2) {
        if (substr($argv[$i], 0, 2) == '--') {
            $currentKey = str_replace('--', '', $argv[$i]);
        } else {
            $currentKey = trim($argv[$i]);
        }
    } else {
        $args[$currentKey] = $argv[$i];
        $currentKey = null;
    }
}

$expression = $currentKey;

if (isset($args['file']) || isset($args['suite']) || isset($args['case'])) {
    if (!isset($args['file']) || !isset($args['suite']) || !isset($args['case'])) {
        die($description);
    }
    // Manually run a compliance test
    $path = realpath($args['file']);
    file_exists($path) or die('File not found at ' . $path);
    $json = json_decode(file_get_contents($path), true);
    $set = $json[$args['suite']];
    $data = $set['given'];
    if (!isset($expression)) {
        $expression = $set['cases'][$args['case']]['expression'];
        echo "Expects\n=======\n";
        if (isset($set['cases'][$args['case']]['result'])) {
            echo json_encode($set['cases'][$args['case']]['result'], JSON_PRETTY_PRINT) . "\n\n";
        } elseif (isset($set['cases'][$args['case']]['error'])) {
            echo "{$set['cases'][$argv['case']]['error']} error\n\n";
        } else {
            echo "NULL\n\n";
        }
    }
} elseif (isset($expression)) {
    // Pass in an expression and STDIN as a standalone argument
    $data = json_decode(stream_get_contents(STDIN), true);
} else {
    die($description);
}

$runtime = new DebugRuntime(Env::createRuntime());
$runtime($expression, $data);
#!/usr/bin/env python

#
# Copyright (c) 2009-2010 Evert Pot
# All rights reserved.
# http://www.rooftopsolutions.nl/
#
# This utility is distributed along with SabreDAV
# license: http://sabre.io/license/ Modified BSD License

import os
from optparse import OptionParser
import time

def getfreespace(path):
    stat = os.statvfs(path)
    return stat.f_frsize * stat.f_bavail

def getbytesleft(path,threshold):
    return getfreespace(path)-threshold

def run(cacheDir, threshold, sleep=5, simulate=False, min_erase = 0):

    bytes = getbytesleft(cacheDir,threshold)
    if (bytes>0):
        print "Bytes to go before we hit threshold:", bytes
    else:
        print "Threshold exceeded with:", -bytes, "bytes"
        dir = os.listdir(cacheDir)
        dir2 = []
        for file in dir:
            path = cacheDir + '/' + file
            dir2.append({
                "path" : path,
                "atime": os.stat(path).st_atime,
                "size" : os.stat(path).st_size
            })

        dir2.sort(lambda x,y: int(x["atime"]-y["atime"]))

        filesunlinked = 0
        gainedspace = 0

        # Left is the amount of bytes that need to be freed up
        # The default is the 'min_erase setting'
        left = min_erase

        # If the min_erase setting is lower than the amount of bytes over
        # the threshold, we use that number instead.
        if left < -bytes :
            left = -bytes

        print "Need to delete at least:", left;

        for file in dir2:

            # Only deleting files if we're not simulating
            if not simulate: os.unlink(file["path"])
            left = int(left - file["size"])
            gainedspace = gainedspace + file["size"]
            filesunlinked = filesunlinked + 1

            if(left<0):
                break

        print "%d files deleted (%d bytes)" % (filesunlinked, gainedspace)


    time.sleep(sleep)



def main():
    parser = OptionParser(
        version="naturalselection v0.3",
        description="Cache directory manager. Deletes cache entries based on accesstime and free space thresholds.\n" +
            "This utility is distributed alongside SabreDAV.",
        usage="usage: %prog [options] cacheDirectory",
    )
    parser.add_option(
        '-s',
        dest="simulate",
        action="store_true",
        help="Don't actually make changes, but just simulate the behaviour",
    )
    parser.add_option(
        '-r','--runs',
        help="How many times to check before exiting. -1 is infinite, which is the default",
        type="int",
        dest="runs",
        default=-1
    )
    parser.add_option(
        '-n','--interval',
        help="Sleep time in seconds (default = 5)",
        type="int",
        dest="sleep",
        default=5
    )
    parser.add_option(
        '-l','--threshold',
        help="Threshold in bytes (default = 10737418240, which is 10GB)",
        type="int",
        dest="threshold",
        default=10737418240
    )
    parser.add_option(
        '-m', '--min-erase',
        help="Minimum number of bytes to erase when the threshold is reached. " +
            "Setting this option higher will reduce the number of times the cache directory will need to be scanned. " +
            "(the default is 1073741824, which is 1GB.)",
        type="int",
        dest="min_erase",
        default=1073741824
    )

    options,args = parser.parse_args()
    if len(args)<1:
        parser.error("This utility requires at least 1 argument")
    cacheDir = args[0]

    print "Natural Selection"
    print "Cache directory:", cacheDir
    free = getfreespace(cacheDir);
    print "Current free disk space:", free

    runs = options.runs;
    while runs!=0 :
        run(
            cacheDir,
            sleep=options.sleep,
            simulate=options.simulate,
            threshold=options.threshold,
            min_erase=options.min_erase
        )
        if runs>0:
            runs = runs - 1

if __name__ == '__main__' :
    main()
#!/bin/sh
php -S 0.0.0.0:8080 `dirname $0`/sabredav.php
#!/usr/bin/env php
<?php

namespace Sabre\VObject;

// This sucks.. we have to try to find the composer autoloader. But chances
// are, we can't find it this way. So we'll do our bestest
$paths = [
    __DIR__ . '/../vendor/autoload.php',  // In case vobject is cloned directly
    __DIR__ . '/../../../autoload.php',   // In case vobject is a composer dependency.
];

foreach($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

if (!class_exists('Sabre\\VObject\\Version')) {
    fwrite(STDERR, "Composer autoloader could not be loaded.\n");
    die(1);
}

$cli = new Cli();
exit($cli->main($argv));

{
    "name": "jquery",
    "version": "3.1.1",
    "description": "jQuery component",
    "license": "MIT",
    "keywords": [
        "jquery",
        "component"
    ],
    "main": "jquery.js",
    "ignore": [
        "component.json",
        "package.json",
        "composer.json"
    ]
}
{
    "name": "jquery",
    "repo": "components/jquery",
    "version": "3.1.1",
    "description": "jQuery component",
    "license": "MIT",
    "keywords": [
        "jquery",
        "component"
    ],
    "main": "jquery.js",
    "scripts": [
        "jquery.js",
        "jquery.min.js"
    ],
    "files": [
        "jquery.min.map"
    ]
}
{
    "name": "components/jquery",
    "description": "jQuery JavaScript Library",
    "type": "component",
    "homepage": "http://jquery.com",
    "license": "MIT",
    "support": {
        "irc": "irc://irc.freenode.org/jquery",
        "issues": "http://bugs.jquery.com",
        "forum": "http://forum.jquery.com",
        "wiki": "http://docs.jquery.com/",
        "source": "https://github.com/jquery/jquery"
    },
    "authors": [
        {
            "name": "John Resig",
            "email": "jeresig@gmail.com"
        }
    ],
    "extra": {
        "component": {
            "scripts": [
                "jquery.js"
            ],
            "files": [
                "jquery.min.js",
                "jquery.min.map"
            ]
        }
    }
}
/*!
 * jQuery JavaScript Library v3.1.1
 * https://jquery.com/
 *
 * Includes Sizzle.js
 * https://sizzlejs.com/
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license
 * https://jquery.org/license
 *
 * Date: 2016-09-22T22:30Z
 */
( function( global, factory ) {

	"use strict";

	if ( typeof module === "object" && typeof module.exports === "object" ) {

		// For CommonJS and CommonJS-like environments where a proper `window`
		// is present, execute the factory and get jQuery.
		// For environments that do not have a `window` with a `document`
		// (such as Node.js), expose a factory as module.exports.
		// This accentuates the need for the creation of a real `window`.
		// e.g. var jQuery = require("jquery")(window);
		// See ticket #14549 for more info.
		module.exports = global.document ?
			factory( global, true ) :
			function( w ) {
				if ( !w.document ) {
					throw new Error( "jQuery requires a window with a document" );
				}
				return factory( w );
			};
	} else {
		factory( global );
	}

// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
// enough that all such attempts are guarded in a try block.
"use strict";

var arr = [];

var document = window.document;

var getProto = Object.getPrototypeOf;

var slice = arr.slice;

var concat = arr.concat;

var push = arr.push;

var indexOf = arr.indexOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var fnToString = hasOwn.toString;

var ObjectFunctionString = fnToString.call( Object );

var support = {};



	function DOMEval( code, doc ) {
		doc = doc || document;

		var script = doc.createElement( "script" );

		script.text = code;
		doc.head.appendChild( script ).parentNode.removeChild( script );
	}
/* global Symbol */
// Defining this global in .eslintrc.json would create a danger of using the global
// unguarded in another place, it seems safer to define global only for this module



var
	version = "3.1.1",

	// Define a local copy of jQuery
	jQuery = function( selector, context ) {

		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery.fn.init( selector, context );
	},

	// Support: Android <=4.0 only
	// Make sure we trim BOM and NBSP
	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,

	// Matches dashed string for camelizing
	rmsPrefix = /^-ms-/,
	rdashAlpha = /-([a-z])/g,

	// Used by jQuery.camelCase as callback to replace()
	fcamelCase = function( all, letter ) {
		return letter.toUpperCase();
	};

jQuery.fn = jQuery.prototype = {

	// The current version of jQuery being used
	jquery: version,

	constructor: jQuery,

	// The default length of a jQuery object is 0
	length: 0,

	toArray: function() {
		return slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {

		// Return all the elements in a clean array
		if ( num == null ) {
			return slice.call( this );
		}

		// Return just the one element from the set
		return num < 0 ? this[ num + this.length ] : this[ num ];
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	each: function( callback ) {
		return jQuery.each( this, callback );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map( this, function( elem, i ) {
			return callback.call( elem, i, elem );
		} ) );
	},

	slice: function() {
		return this.pushStack( slice.apply( this, arguments ) );
	},

	first: function() {
		return this.eq( 0 );
	},

	last: function() {
		return this.eq( -1 );
	},

	eq: function( i ) {
		var len = this.length,
			j = +i + ( i < 0 ? len : 0 );
		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
	},

	end: function() {
		return this.prevObject || this.constructor();
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: push,
	sort: arr.sort,
	splice: arr.splice
};

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[ 0 ] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

		// Skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
		target = {};
	}

	// Extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {

		// Only deal with non-null/undefined values
		if ( ( options = arguments[ i ] ) != null ) {

			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
					( copyIsArray = jQuery.isArray( copy ) ) ) ) {

					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray( src ) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject( src ) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

jQuery.extend( {

	// Unique for each copy of jQuery on the page
	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

	// Assume jQuery is ready without the ready module
	isReady: true,

	error: function( msg ) {
		throw new Error( msg );
	},

	noop: function() {},

	isFunction: function( obj ) {
		return jQuery.type( obj ) === "function";
	},

	isArray: Array.isArray,

	isWindow: function( obj ) {
		return obj != null && obj === obj.window;
	},

	isNumeric: function( obj ) {

		// As of jQuery 3.0, isNumeric is limited to
		// strings and numbers (primitives or objects)
		// that can be coerced to finite numbers (gh-2662)
		var type = jQuery.type( obj );
		return ( type === "number" || type === "string" ) &&

			// parseFloat NaNs numeric-cast false positives ("")
			// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
			// subtraction forces infinities to NaN
			!isNaN( obj - parseFloat( obj ) );
	},

	isPlainObject: function( obj ) {
		var proto, Ctor;

		// Detect obvious negatives
		// Use toString instead of jQuery.type to catch host objects
		if ( !obj || toString.call( obj ) !== "[object Object]" ) {
			return false;
		}

		proto = getProto( obj );

		// Objects with no prototype (e.g., `Object.create( null )`) are plain
		if ( !proto ) {
			return true;
		}

		// Objects with prototype are plain iff they were constructed by a global Object function
		Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
		return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
	},

	isEmptyObject: function( obj ) {

		/* eslint-disable no-unused-vars */
		// See https://github.com/eslint/eslint/issues/6125
		var name;

		for ( name in obj ) {
			return false;
		}
		return true;
	},

	type: function( obj ) {
		if ( obj == null ) {
			return obj + "";
		}

		// Support: Android <=2.3 only (functionish RegExp)
		return typeof obj === "object" || typeof obj === "function" ?
			class2type[ toString.call( obj ) ] || "object" :
			typeof obj;
	},

	// Evaluates a script in a global context
	globalEval: function( code ) {
		DOMEval( code );
	},

	// Convert dashed to camelCase; used by the css and data modules
	// Support: IE <=9 - 11, Edge 12 - 13
	// Microsoft forgot to hump their vendor prefix (#9572)
	camelCase: function( string ) {
		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
	},

	each: function( obj, callback ) {
		var length, i = 0;

		if ( isArrayLike( obj ) ) {
			length = obj.length;
			for ( ; i < length; i++ ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		} else {
			for ( i in obj ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		}

		return obj;
	},

	// Support: Android <=4.0 only
	trim: function( text ) {
		return text == null ?
			"" :
			( text + "" ).replace( rtrim, "" );
	},

	// results is for internal usage only
	makeArray: function( arr, results ) {
		var ret = results || [];

		if ( arr != null ) {
			if ( isArrayLike( Object( arr ) ) ) {
				jQuery.merge( ret,
					typeof arr === "string" ?
					[ arr ] : arr
				);
			} else {
				push.call( ret, arr );
			}
		}

		return ret;
	},

	inArray: function( elem, arr, i ) {
		return arr == null ? -1 : indexOf.call( arr, elem, i );
	},

	// Support: Android <=4.0 only, PhantomJS 1 only
	// push.apply(_, arraylike) throws on ancient WebKit
	merge: function( first, second ) {
		var len = +second.length,
			j = 0,
			i = first.length;

		for ( ; j < len; j++ ) {
			first[ i++ ] = second[ j ];
		}

		first.length = i;

		return first;
	},

	grep: function( elems, callback, invert ) {
		var callbackInverse,
			matches = [],
			i = 0,
			length = elems.length,
			callbackExpect = !invert;

		// Go through the array, only saving the items
		// that pass the validator function
		for ( ; i < length; i++ ) {
			callbackInverse = !callback( elems[ i ], i );
			if ( callbackInverse !== callbackExpect ) {
				matches.push( elems[ i ] );
			}
		}

		return matches;
	},

	// arg is for internal usage only
	map: function( elems, callback, arg ) {
		var length, value,
			i = 0,
			ret = [];

		// Go through the array, translating each of the items to their new values
		if ( isArrayLike( elems ) ) {
			length = elems.length;
			for ( ; i < length; i++ ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}

		// Go through every key on the object,
		} else {
			for ( i in elems ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}
		}

		// Flatten any nested arrays
		return concat.apply( [], ret );
	},

	// A global GUID counter for objects
	guid: 1,

	// Bind a function to a context, optionally partially applying any
	// arguments.
	proxy: function( fn, context ) {
		var tmp, args, proxy;

		if ( typeof context === "string" ) {
			tmp = fn[ context ];
			context = fn;
			fn = tmp;
		}

		// Quick check to determine if target is callable, in the spec
		// this throws a TypeError, but we will just return undefined.
		if ( !jQuery.isFunction( fn ) ) {
			return undefined;
		}

		// Simulated bind
		args = slice.call( arguments, 2 );
		proxy = function() {
			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
		};

		// Set the guid of unique handler to the same of original handler, so it can be removed
		proxy.guid = fn.guid = fn.guid || jQuery.guid++;

		return proxy;
	},

	now: Date.now,

	// jQuery.support is not used in Core but other projects attach their
	// properties to it so it needs to exist.
	support: support
} );

if ( typeof Symbol === "function" ) {
	jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
}

// Populate the class2type map
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );

function isArrayLike( obj ) {

	// Support: real iOS 8.2 only (not reproducible in simulator)
	// `in` check used to prevent JIT error (gh-2145)
	// hasOwn isn't used here due to false negatives
	// regarding Nodelist length in IE
	var length = !!obj && "length" in obj && obj.length,
		type = jQuery.type( obj );

	if ( type === "function" || jQuery.isWindow( obj ) ) {
		return false;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
var Sizzle =
/*!
 * Sizzle CSS Selector Engine v2.3.3
 * https://sizzlejs.com/
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2016-08-08
 */
(function( window ) {

var i,
	support,
	Expr,
	getText,
	isXML,
	tokenize,
	compile,
	select,
	outermostContext,
	sortInput,
	hasDuplicate,

	// Local document vars
	setDocument,
	document,
	docElem,
	documentIsHTML,
	rbuggyQSA,
	rbuggyMatches,
	matches,
	contains,

	// Instance-specific data
	expando = "sizzle" + 1 * new Date(),
	preferredDoc = window.document,
	dirruns = 0,
	done = 0,
	classCache = createCache(),
	tokenCache = createCache(),
	compilerCache = createCache(),
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
		}
		return 0;
	},

	// Instance methods
	hasOwn = ({}).hasOwnProperty,
	arr = [],
	pop = arr.pop,
	push_native = arr.push,
	push = arr.push,
	slice = arr.slice,
	// Use a stripped-down indexOf as it's faster than native
	// https://jsperf.com/thor-indexof-vs-for/5
	indexOf = function( list, elem ) {
		var i = 0,
			len = list.length;
		for ( ; i < len; i++ ) {
			if ( list[i] === elem ) {
				return i;
			}
		}
		return -1;
	},

	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",

	// Regular expressions

	// http://www.w3.org/TR/css3-selectors/#whitespace
	whitespace = "[\\x20\\t\\r\\n\\f]",

	// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
	identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",

	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
		// Operator (capture 2)
		"*([*^$|!~]?=)" + whitespace +
		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
		"*\\]",

	pseudos = ":(" + identifier + ")(?:\\((" +
		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
		// 1. quoted (capture 3; capture 4 or capture 5)
		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
		// 2. simple (capture 6)
		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
		// 3. anything else (capture 2)
		".*" +
		")\\)|)",

	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
	rwhitespace = new RegExp( whitespace + "+", "g" ),
	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),

	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),

	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),

	rpseudo = new RegExp( pseudos ),
	ridentifier = new RegExp( "^" + identifier + "$" ),

	matchExpr = {
		"ID": new RegExp( "^#(" + identifier + ")" ),
		"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
		"TAG": new RegExp( "^(" + identifier + "|[*])" ),
		"ATTR": new RegExp( "^" + attributes ),
		"PSEUDO": new RegExp( "^" + pseudos ),
		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
		// For use in libraries implementing .is()
		// We use this for POS matching in `select`
		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
	},

	rinputs = /^(?:input|select|textarea|button)$/i,
	rheader = /^h\d$/i,

	rnative = /^[^{]+\{\s*\[native \w/,

	// Easily-parseable/retrievable ID or TAG or CLASS selectors
	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

	rsibling = /[+~]/,

	// CSS escapes
	// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
	funescape = function( _, escaped, escapedWhitespace ) {
		var high = "0x" + escaped - 0x10000;
		// NaN means non-codepoint
		// Support: Firefox<24
		// Workaround erroneous numeric interpretation of +"0x"
		return high !== high || escapedWhitespace ?
			escaped :
			high < 0 ?
				// BMP codepoint
				String.fromCharCode( high + 0x10000 ) :
				// Supplemental Plane codepoint (surrogate pair)
				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
	},

	// CSS string/identifier serialization
	// https://drafts.csswg.org/cssom/#common-serializing-idioms
	rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
	fcssescape = function( ch, asCodePoint ) {
		if ( asCodePoint ) {

			// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
			if ( ch === "\0" ) {
				return "\uFFFD";
			}

			// Control characters and (dependent upon position) numbers get escaped as code points
			return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
		}

		// Other potentially-special ASCII characters get backslash-escaped
		return "\\" + ch;
	},

	// Used for iframes
	// See setDocument()
	// Removing the function wrapper causes a "Permission Denied"
	// error in IE
	unloadHandler = function() {
		setDocument();
	},

	disabledAncestor = addCombinator(
		function( elem ) {
			return elem.disabled === true && ("form" in elem || "label" in elem);
		},
		{ dir: "parentNode", next: "legend" }
	);

// Optimize for push.apply( _, NodeList )
try {
	push.apply(
		(arr = slice.call( preferredDoc.childNodes )),
		preferredDoc.childNodes
	);
	// Support: Android<4.0
	// Detect silently failing push.apply
	arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
	push = { apply: arr.length ?

		// Leverage slice if possible
		function( target, els ) {
			push_native.apply( target, slice.call(els) );
		} :

		// Support: IE<9
		// Otherwise append directly
		function( target, els ) {
			var j = target.length,
				i = 0;
			// Can't trust NodeList.length
			while ( (target[j++] = els[i++]) ) {}
			target.length = j - 1;
		}
	};
}

function Sizzle( selector, context, results, seed ) {
	var m, i, elem, nid, match, groups, newSelector,
		newContext = context && context.ownerDocument,

		// nodeType defaults to 9, since context defaults to document
		nodeType = context ? context.nodeType : 9;

	results = results || [];

	// Return early from calls with invalid selector or context
	if ( typeof selector !== "string" || !selector ||
		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {

		return results;
	}

	// Try to shortcut find operations (as opposed to filters) in HTML documents
	if ( !seed ) {

		if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
			setDocument( context );
		}
		context = context || document;

		if ( documentIsHTML ) {

			// If the selector is sufficiently simple, try using a "get*By*" DOM method
			// (excepting DocumentFragment context, where the methods don't exist)
			if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {

				// ID selector
				if ( (m = match[1]) ) {

					// Document context
					if ( nodeType === 9 ) {
						if ( (elem = context.getElementById( m )) ) {

							// Support: IE, Opera, Webkit
							// TODO: identify versions
							// getElementById can match elements by name instead of ID
							if ( elem.id === m ) {
								results.push( elem );
								return results;
							}
						} else {
							return results;
						}

					// Element context
					} else {

						// Support: IE, Opera, Webkit
						// TODO: identify versions
						// getElementById can match elements by name instead of ID
						if ( newContext && (elem = newContext.getElementById( m )) &&
							contains( context, elem ) &&
							elem.id === m ) {

							results.push( elem );
							return results;
						}
					}

				// Type selector
				} else if ( match[2] ) {
					push.apply( results, context.getElementsByTagName( selector ) );
					return results;

				// Class selector
				} else if ( (m = match[3]) && support.getElementsByClassName &&
					context.getElementsByClassName ) {

					push.apply( results, context.getElementsByClassName( m ) );
					return results;
				}
			}

			// Take advantage of querySelectorAll
			if ( support.qsa &&
				!compilerCache[ selector + " " ] &&
				(!rbuggyQSA || !rbuggyQSA.test( selector )) ) {

				if ( nodeType !== 1 ) {
					newContext = context;
					newSelector = selector;

				// qSA looks outside Element context, which is not what we want
				// Thanks to Andrew Dupont for this workaround technique
				// Support: IE <=8
				// Exclude object elements
				} else if ( context.nodeName.toLowerCase() !== "object" ) {

					// Capture the context ID, setting it first if necessary
					if ( (nid = context.getAttribute( "id" )) ) {
						nid = nid.replace( rcssescape, fcssescape );
					} else {
						context.setAttribute( "id", (nid = expando) );
					}

					// Prefix every selector in the list
					groups = tokenize( selector );
					i = groups.length;
					while ( i-- ) {
						groups[i] = "#" + nid + " " + toSelector( groups[i] );
					}
					newSelector = groups.join( "," );

					// Expand context for sibling selectors
					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
						context;
				}

				if ( newSelector ) {
					try {
						push.apply( results,
							newContext.querySelectorAll( newSelector )
						);
						return results;
					} catch ( qsaError ) {
					} finally {
						if ( nid === expando ) {
							context.removeAttribute( "id" );
						}
					}
				}
			}
		}
	}

	// All others
	return select( selector.replace( rtrim, "$1" ), context, results, seed );
}

/**
 * Create key-value caches of limited size
 * @returns {function(string, object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
function createCache() {
	var keys = [];

	function cache( key, value ) {
		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
		if ( keys.push( key + " " ) > Expr.cacheLength ) {
			// Only keep the most recent entries
			delete cache[ keys.shift() ];
		}
		return (cache[ key + " " ] = value);
	}
	return cache;
}

/**
 * Mark a function for special use by Sizzle
 * @param {Function} fn The function to mark
 */
function markFunction( fn ) {
	fn[ expando ] = true;
	return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created element and returns a boolean result
 */
function assert( fn ) {
	var el = document.createElement("fieldset");

	try {
		return !!fn( el );
	} catch (e) {
		return false;
	} finally {
		// Remove from its parent by default
		if ( el.parentNode ) {
			el.parentNode.removeChild( el );
		}
		// release memory in IE
		el = null;
	}
}

/**
 * Adds the same handler for all of the specified attrs
 * @param {String} attrs Pipe-separated list of attributes
 * @param {Function} handler The method that will be applied
 */
function addHandle( attrs, handler ) {
	var arr = attrs.split("|"),
		i = arr.length;

	while ( i-- ) {
		Expr.attrHandle[ arr[i] ] = handler;
	}
}

/**
 * Checks document order of two siblings
 * @param {Element} a
 * @param {Element} b
 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
 */
function siblingCheck( a, b ) {
	var cur = b && a,
		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
			a.sourceIndex - b.sourceIndex;

	// Use IE sourceIndex if available on both nodes
	if ( diff ) {
		return diff;
	}

	// Check if b follows a
	if ( cur ) {
		while ( (cur = cur.nextSibling) ) {
			if ( cur === b ) {
				return -1;
			}
		}
	}

	return a ? 1 : -1;
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return name === "input" && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return (name === "input" || name === "button") && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for :enabled/:disabled
 * @param {Boolean} disabled true for :disabled; false for :enabled
 */
function createDisabledPseudo( disabled ) {

	// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
	return function( elem ) {

		// Only certain elements can match :enabled or :disabled
		// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
		// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
		if ( "form" in elem ) {

			// Check for inherited disabledness on relevant non-disabled elements:
			// * listed form-associated elements in a disabled fieldset
			//   https://html.spec.whatwg.org/multipage/forms.html#category-listed
			//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
			// * option elements in a disabled optgroup
			//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
			// All such elements have a "form" property.
			if ( elem.parentNode && elem.disabled === false ) {

				// Option elements defer to a parent optgroup if present
				if ( "label" in elem ) {
					if ( "label" in elem.parentNode ) {
						return elem.parentNode.disabled === disabled;
					} else {
						return elem.disabled === disabled;
					}
				}

				// Support: IE 6 - 11
				// Use the isDisabled shortcut property to check for disabled fieldset ancestors
				return elem.isDisabled === disabled ||

					// Where there is no isDisabled, check manually
					/* jshint -W018 */
					elem.isDisabled !== !disabled &&
						disabledAncestor( elem ) === disabled;
			}

			return elem.disabled === disabled;

		// Try to winnow out elements that can't be disabled before trusting the disabled property.
		// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
		// even exist on them, let alone have a boolean value.
		} else if ( "label" in elem ) {
			return elem.disabled === disabled;
		}

		// Remaining elements are neither :enabled nor :disabled
		return false;
	};
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo( fn ) {
	return markFunction(function( argument ) {
		argument = +argument;
		return markFunction(function( seed, matches ) {
			var j,
				matchIndexes = fn( [], seed.length, argument ),
				i = matchIndexes.length;

			// Match elements found at the specified indexes
			while ( i-- ) {
				if ( seed[ (j = matchIndexes[i]) ] ) {
					seed[j] = !(matches[j] = seed[j]);
				}
			}
		});
	});
}

/**
 * Checks a node for validity as a Sizzle context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext( context ) {
	return context && typeof context.getElementsByTagName !== "undefined" && context;
}

// Expose support vars for convenience
support = Sizzle.support = {};

/**
 * Detects XML nodes
 * @param {Element|Object} elem An element or a document
 * @returns {Boolean} True iff elem is a non-HTML XML node
 */
isXML = Sizzle.isXML = function( elem ) {
	// documentElement is verified for cases where it doesn't yet exist
	// (such as loading iframes in IE - #4833)
	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [doc] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
setDocument = Sizzle.setDocument = function( node ) {
	var hasCompare, subWindow,
		doc = node ? node.ownerDocument || node : preferredDoc;

	// Return early if doc is invalid or already selected
	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
		return document;
	}

	// Update global variables
	document = doc;
	docElem = document.documentElement;
	documentIsHTML = !isXML( document );

	// Support: IE 9-11, Edge
	// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
	if ( preferredDoc !== document &&
		(subWindow = document.defaultView) && subWindow.top !== subWindow ) {

		// Support: IE 11, Edge
		if ( subWindow.addEventListener ) {
			subWindow.addEventListener( "unload", unloadHandler, false );

		// Support: IE 9 - 10 only
		} else if ( subWindow.attachEvent ) {
			subWindow.attachEvent( "onunload", unloadHandler );
		}
	}

	/* Attributes
	---------------------------------------------------------------------- */

	// Support: IE<8
	// Verify that getAttribute really returns attributes and not properties
	// (excepting IE8 booleans)
	support.attributes = assert(function( el ) {
		el.className = "i";
		return !el.getAttribute("className");
	});

	/* getElement(s)By*
	---------------------------------------------------------------------- */

	// Check if getElementsByTagName("*") returns only elements
	support.getElementsByTagName = assert(function( el ) {
		el.appendChild( document.createComment("") );
		return !el.getElementsByTagName("*").length;
	});

	// Support: IE<9
	support.getElementsByClassName = rnative.test( document.getElementsByClassName );

	// Support: IE<10
	// Check if getElementById returns elements by name
	// The broken getElementById methods don't pick up programmatically-set names,
	// so use a roundabout getElementsByName test
	support.getById = assert(function( el ) {
		docElem.appendChild( el ).id = expando;
		return !document.getElementsByName || !document.getElementsByName( expando ).length;
	});

	// ID filter and find
	if ( support.getById ) {
		Expr.filter["ID"] = function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				return elem.getAttribute("id") === attrId;
			};
		};
		Expr.find["ID"] = function( id, context ) {
			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
				var elem = context.getElementById( id );
				return elem ? [ elem ] : [];
			}
		};
	} else {
		Expr.filter["ID"] =  function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				var node = typeof elem.getAttributeNode !== "undefined" &&
					elem.getAttributeNode("id");
				return node && node.value === attrId;
			};
		};

		// Support: IE 6 - 7 only
		// getElementById is not reliable as a find shortcut
		Expr.find["ID"] = function( id, context ) {
			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
				var node, i, elems,
					elem = context.getElementById( id );

				if ( elem ) {

					// Verify the id attribute
					node = elem.getAttributeNode("id");
					if ( node && node.value === id ) {
						return [ elem ];
					}

					// Fall back on getElementsByName
					elems = context.getElementsByName( id );
					i = 0;
					while ( (elem = elems[i++]) ) {
						node = elem.getAttributeNode("id");
						if ( node && node.value === id ) {
							return [ elem ];
						}
					}
				}

				return [];
			}
		};
	}

	// Tag
	Expr.find["TAG"] = support.getElementsByTagName ?
		function( tag, context ) {
			if ( typeof context.getElementsByTagName !== "undefined" ) {
				return context.getElementsByTagName( tag );

			// DocumentFragment nodes don't have gEBTN
			} else if ( support.qsa ) {
				return context.querySelectorAll( tag );
			}
		} :

		function( tag, context ) {
			var elem,
				tmp = [],
				i = 0,
				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
				results = context.getElementsByTagName( tag );

			// Filter out possible comments
			if ( tag === "*" ) {
				while ( (elem = results[i++]) ) {
					if ( elem.nodeType === 1 ) {
						tmp.push( elem );
					}
				}

				return tmp;
			}
			return results;
		};

	// Class
	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
			return context.getElementsByClassName( className );
		}
	};

	/* QSA/matchesSelector
	---------------------------------------------------------------------- */

	// QSA and matchesSelector support

	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
	rbuggyMatches = [];

	// qSa(:focus) reports false when true (Chrome 21)
	// We allow this because of a bug in IE8/9 that throws an error
	// whenever `document.activeElement` is accessed on an iframe
	// So, we allow :focus to pass through QSA all the time to avoid the IE error
	// See https://bugs.jquery.com/ticket/13378
	rbuggyQSA = [];

	if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
		// Build QSA regex
		// Regex strategy adopted from Diego Perini
		assert(function( el ) {
			// Select is set to empty string on purpose
			// This is to test IE's treatment of not explicitly
			// setting a boolean content attribute,
			// since its presence should be enough
			// https://bugs.jquery.com/ticket/12359
			docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
				"<select id='" + expando + "-\r\\' msallowcapture=''>" +
				"<option selected=''></option></select>";

			// Support: IE8, Opera 11-12.16
			// Nothing should be selected when empty strings follow ^= or $= or *=
			// The test attribute must be unknown in Opera but "safe" for WinRT
			// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
			if ( el.querySelectorAll("[msallowcapture^='']").length ) {
				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
			}

			// Support: IE8
			// Boolean attributes and "value" are not treated correctly
			if ( !el.querySelectorAll("[selected]").length ) {
				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
			}

			// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
			if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
				rbuggyQSA.push("~=");
			}

			// Webkit/Opera - :checked should return selected option elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			// IE8 throws error here and will not see later tests
			if ( !el.querySelectorAll(":checked").length ) {
				rbuggyQSA.push(":checked");
			}

			// Support: Safari 8+, iOS 8+
			// https://bugs.webkit.org/show_bug.cgi?id=136851
			// In-page `selector#id sibling-combinator selector` fails
			if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
				rbuggyQSA.push(".#.+[+~]");
			}
		});

		assert(function( el ) {
			el.innerHTML = "<a href='' disabled='disabled'></a>" +
				"<select disabled='disabled'><option/></select>";

			// Support: Windows 8 Native Apps
			// The type and name attributes are restricted during .innerHTML assignment
			var input = document.createElement("input");
			input.setAttribute( "type", "hidden" );
			el.appendChild( input ).setAttribute( "name", "D" );

			// Support: IE8
			// Enforce case-sensitivity of name attribute
			if ( el.querySelectorAll("[name=d]").length ) {
				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
			}

			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
			// IE8 throws error here and will not see later tests
			if ( el.querySelectorAll(":enabled").length !== 2 ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Support: IE9-11+
			// IE's :disabled selector does not pick up the children of disabled fieldsets
			docElem.appendChild( el ).disabled = true;
			if ( el.querySelectorAll(":disabled").length !== 2 ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Opera 10-11 does not throw on post-comma invalid pseudos
			el.querySelectorAll("*,:x");
			rbuggyQSA.push(",.*:");
		});
	}

	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
		docElem.webkitMatchesSelector ||
		docElem.mozMatchesSelector ||
		docElem.oMatchesSelector ||
		docElem.msMatchesSelector) )) ) {

		assert(function( el ) {
			// Check to see if it's possible to do matchesSelector
			// on a disconnected node (IE 9)
			support.disconnectedMatch = matches.call( el, "*" );

			// This should fail with an exception
			// Gecko does not error, returns false instead
			matches.call( el, "[s!='']:x" );
			rbuggyMatches.push( "!=", pseudos );
		});
	}

	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );

	/* Contains
	---------------------------------------------------------------------- */
	hasCompare = rnative.test( docElem.compareDocumentPosition );

	// Element contains another
	// Purposefully self-exclusive
	// As in, an element does not contain itself
	contains = hasCompare || rnative.test( docElem.contains ) ?
		function( a, b ) {
			var adown = a.nodeType === 9 ? a.documentElement : a,
				bup = b && b.parentNode;
			return a === bup || !!( bup && bup.nodeType === 1 && (
				adown.contains ?
					adown.contains( bup ) :
					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
			));
		} :
		function( a, b ) {
			if ( b ) {
				while ( (b = b.parentNode) ) {
					if ( b === a ) {
						return true;
					}
				}
			}
			return false;
		};

	/* Sorting
	---------------------------------------------------------------------- */

	// Document order sorting
	sortOrder = hasCompare ?
	function( a, b ) {

		// Flag for duplicate removal
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		// Sort on method existence if only one input has compareDocumentPosition
		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
		if ( compare ) {
			return compare;
		}

		// Calculate position if both inputs belong to the same document
		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
			a.compareDocumentPosition( b ) :

			// Otherwise we know they are disconnected
			1;

		// Disconnected nodes
		if ( compare & 1 ||
			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {

			// Choose the first element that is related to our preferred document
			if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
				return -1;
			}
			if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
				return 1;
			}

			// Maintain original order
			return sortInput ?
				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
				0;
		}

		return compare & 4 ? -1 : 1;
	} :
	function( a, b ) {
		// Exit early if the nodes are identical
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		var cur,
			i = 0,
			aup = a.parentNode,
			bup = b.parentNode,
			ap = [ a ],
			bp = [ b ];

		// Parentless nodes are either documents or disconnected
		if ( !aup || !bup ) {
			return a === document ? -1 :
				b === document ? 1 :
				aup ? -1 :
				bup ? 1 :
				sortInput ?
				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
				0;

		// If the nodes are siblings, we can do a quick check
		} else if ( aup === bup ) {
			return siblingCheck( a, b );
		}

		// Otherwise we need full lists of their ancestors for comparison
		cur = a;
		while ( (cur = cur.parentNode) ) {
			ap.unshift( cur );
		}
		cur = b;
		while ( (cur = cur.parentNode) ) {
			bp.unshift( cur );
		}

		// Walk down the tree looking for a discrepancy
		while ( ap[i] === bp[i] ) {
			i++;
		}

		return i ?
			// Do a sibling check if the nodes have a common ancestor
			siblingCheck( ap[i], bp[i] ) :

			// Otherwise nodes in our document sort first
			ap[i] === preferredDoc ? -1 :
			bp[i] === preferredDoc ? 1 :
			0;
	};

	return document;
};

Sizzle.matches = function( expr, elements ) {
	return Sizzle( expr, null, null, elements );
};

Sizzle.matchesSelector = function( elem, expr ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	// Make sure that attribute selectors are quoted
	expr = expr.replace( rattributeQuotes, "='$1']" );

	if ( support.matchesSelector && documentIsHTML &&
		!compilerCache[ expr + " " ] &&
		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {

		try {
			var ret = matches.call( elem, expr );

			// IE 9's matchesSelector returns false on disconnected nodes
			if ( ret || support.disconnectedMatch ||
					// As well, disconnected nodes are said to be in a document
					// fragment in IE 9
					elem.document && elem.document.nodeType !== 11 ) {
				return ret;
			}
		} catch (e) {}
	}

	return Sizzle( expr, document, null, [ elem ] ).length > 0;
};

Sizzle.contains = function( context, elem ) {
	// Set document vars if needed
	if ( ( context.ownerDocument || context ) !== document ) {
		setDocument( context );
	}
	return contains( context, elem );
};

Sizzle.attr = function( elem, name ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	var fn = Expr.attrHandle[ name.toLowerCase() ],
		// Don't get fooled by Object.prototype properties (jQuery #13807)
		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
			fn( elem, name, !documentIsHTML ) :
			undefined;

	return val !== undefined ?
		val :
		support.attributes || !documentIsHTML ?
			elem.getAttribute( name ) :
			(val = elem.getAttributeNode(name)) && val.specified ?
				val.value :
				null;
};

Sizzle.escape = function( sel ) {
	return (sel + "").replace( rcssescape, fcssescape );
};

Sizzle.error = function( msg ) {
	throw new Error( "Syntax error, unrecognized expression: " + msg );
};

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
Sizzle.uniqueSort = function( results ) {
	var elem,
		duplicates = [],
		j = 0,
		i = 0;

	// Unless we *know* we can detect duplicates, assume their presence
	hasDuplicate = !support.detectDuplicates;
	sortInput = !support.sortStable && results.slice( 0 );
	results.sort( sortOrder );

	if ( hasDuplicate ) {
		while ( (elem = results[i++]) ) {
			if ( elem === results[ i ] ) {
				j = duplicates.push( i );
			}
		}
		while ( j-- ) {
			results.splice( duplicates[ j ], 1 );
		}
	}

	// Clear input after sorting to release objects
	// See https://github.com/jquery/sizzle/pull/225
	sortInput = null;

	return results;
};

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
getText = Sizzle.getText = function( elem ) {
	var node,
		ret = "",
		i = 0,
		nodeType = elem.nodeType;

	if ( !nodeType ) {
		// If no nodeType, this is expected to be an array
		while ( (node = elem[i++]) ) {
			// Do not traverse comment nodes
			ret += getText( node );
		}
	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
		// Use textContent for elements
		// innerText usage removed for consistency of new lines (jQuery #11153)
		if ( typeof elem.textContent === "string" ) {
			return elem.textContent;
		} else {
			// Traverse its children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				ret += getText( elem );
			}
		}
	} else if ( nodeType === 3 || nodeType === 4 ) {
		return elem.nodeValue;
	}
	// Do not include comment or processing instruction nodes

	return ret;
};

Expr = Sizzle.selectors = {

	// Can be adjusted by the user
	cacheLength: 50,

	createPseudo: markFunction,

	match: matchExpr,

	attrHandle: {},

	find: {},

	relative: {
		">": { dir: "parentNode", first: true },
		" ": { dir: "parentNode" },
		"+": { dir: "previousSibling", first: true },
		"~": { dir: "previousSibling" }
	},

	preFilter: {
		"ATTR": function( match ) {
			match[1] = match[1].replace( runescape, funescape );

			// Move the given value to match[3] whether quoted or unquoted
			match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );

			if ( match[2] === "~=" ) {
				match[3] = " " + match[3] + " ";
			}

			return match.slice( 0, 4 );
		},

		"CHILD": function( match ) {
			/* matches from matchExpr["CHILD"]
				1 type (only|nth|...)
				2 what (child|of-type)
				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
				4 xn-component of xn+y argument ([+-]?\d*n|)
				5 sign of xn-component
				6 x of xn-component
				7 sign of y-component
				8 y of y-component
			*/
			match[1] = match[1].toLowerCase();

			if ( match[1].slice( 0, 3 ) === "nth" ) {
				// nth-* requires argument
				if ( !match[3] ) {
					Sizzle.error( match[0] );
				}

				// numeric x and y parameters for Expr.filter.CHILD
				// remember that false/true cast respectively to 0/1
				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );

			// other types prohibit arguments
			} else if ( match[3] ) {
				Sizzle.error( match[0] );
			}

			return match;
		},

		"PSEUDO": function( match ) {
			var excess,
				unquoted = !match[6] && match[2];

			if ( matchExpr["CHILD"].test( match[0] ) ) {
				return null;
			}

			// Accept quoted arguments as-is
			if ( match[3] ) {
				match[2] = match[4] || match[5] || "";

			// Strip excess characters from unquoted arguments
			} else if ( unquoted && rpseudo.test( unquoted ) &&
				// Get excess from tokenize (recursively)
				(excess = tokenize( unquoted, true )) &&
				// advance to the next closing parenthesis
				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {

				// excess is a negative index
				match[0] = match[0].slice( 0, excess );
				match[2] = unquoted.slice( 0, excess );
			}

			// Return only captures needed by the pseudo filter method (type and argument)
			return match.slice( 0, 3 );
		}
	},

	filter: {

		"TAG": function( nodeNameSelector ) {
			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
			return nodeNameSelector === "*" ?
				function() { return true; } :
				function( elem ) {
					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
				};
		},

		"CLASS": function( className ) {
			var pattern = classCache[ className + " " ];

			return pattern ||
				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
				classCache( className, function( elem ) {
					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
				});
		},

		"ATTR": function( name, operator, check ) {
			return function( elem ) {
				var result = Sizzle.attr( elem, name );

				if ( result == null ) {
					return operator === "!=";
				}
				if ( !operator ) {
					return true;
				}

				result += "";

				return operator === "=" ? result === check :
					operator === "!=" ? result !== check :
					operator === "^=" ? check && result.indexOf( check ) === 0 :
					operator === "*=" ? check && result.indexOf( check ) > -1 :
					operator === "$=" ? check && result.slice( -check.length ) === check :
					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
					false;
			};
		},

		"CHILD": function( type, what, argument, first, last ) {
			var simple = type.slice( 0, 3 ) !== "nth",
				forward = type.slice( -4 ) !== "last",
				ofType = what === "of-type";

			return first === 1 && last === 0 ?

				// Shortcut for :nth-*(n)
				function( elem ) {
					return !!elem.parentNode;
				} :

				function( elem, context, xml ) {
					var cache, uniqueCache, outerCache, node, nodeIndex, start,
						dir = simple !== forward ? "nextSibling" : "previousSibling",
						parent = elem.parentNode,
						name = ofType && elem.nodeName.toLowerCase(),
						useCache = !xml && !ofType,
						diff = false;

					if ( parent ) {

						// :(first|last|only)-(child|of-type)
						if ( simple ) {
							while ( dir ) {
								node = elem;
								while ( (node = node[ dir ]) ) {
									if ( ofType ?
										node.nodeName.toLowerCase() === name :
										node.nodeType === 1 ) {

										return false;
									}
								}
								// Reverse direction for :only-* (if we haven't yet done so)
								start = dir = type === "only" && !start && "nextSibling";
							}
							return true;
						}

						start = [ forward ? parent.firstChild : parent.lastChild ];

						// non-xml :nth-child(...) stores cache data on `parent`
						if ( forward && useCache ) {

							// Seek `elem` from a previously-cached index

							// ...in a gzip-friendly way
							node = parent;
							outerCache = node[ expando ] || (node[ expando ] = {});

							// Support: IE <9 only
							// Defend against cloned attroperties (jQuery gh-1709)
							uniqueCache = outerCache[ node.uniqueID ] ||
								(outerCache[ node.uniqueID ] = {});

							cache = uniqueCache[ type ] || [];
							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
							diff = nodeIndex && cache[ 2 ];
							node = nodeIndex && parent.childNodes[ nodeIndex ];

							while ( (node = ++nodeIndex && node && node[ dir ] ||

								// Fallback to seeking `elem` from the start
								(diff = nodeIndex = 0) || start.pop()) ) {

								// When found, cache indexes on `parent` and break
								if ( node.nodeType === 1 && ++diff && node === elem ) {
									uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
									break;
								}
							}

						} else {
							// Use previously-cached element index if available
							if ( useCache ) {
								// ...in a gzip-friendly way
								node = elem;
								outerCache = node[ expando ] || (node[ expando ] = {});

								// Support: IE <9 only
								// Defend against cloned attroperties (jQuery gh-1709)
								uniqueCache = outerCache[ node.uniqueID ] ||
									(outerCache[ node.uniqueID ] = {});

								cache = uniqueCache[ type ] || [];
								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
								diff = nodeIndex;
							}

							// xml :nth-child(...)
							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
							if ( diff === false ) {
								// Use the same loop as above to seek `elem` from the start
								while ( (node = ++nodeIndex && node && node[ dir ] ||
									(diff = nodeIndex = 0) || start.pop()) ) {

									if ( ( ofType ?
										node.nodeName.toLowerCase() === name :
										node.nodeType === 1 ) &&
										++diff ) {

										// Cache the index of each encountered element
										if ( useCache ) {
											outerCache = node[ expando ] || (node[ expando ] = {});

											// Support: IE <9 only
											// Defend against cloned attroperties (jQuery gh-1709)
											uniqueCache = outerCache[ node.uniqueID ] ||
												(outerCache[ node.uniqueID ] = {});

											uniqueCache[ type ] = [ dirruns, diff ];
										}

										if ( node === elem ) {
											break;
										}
									}
								}
							}
						}

						// Incorporate the offset, then check against cycle size
						diff -= last;
						return diff === first || ( diff % first === 0 && diff / first >= 0 );
					}
				};
		},

		"PSEUDO": function( pseudo, argument ) {
			// pseudo-class names are case-insensitive
			// http://www.w3.org/TR/selectors/#pseudo-classes
			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
			// Remember that setFilters inherits from pseudos
			var args,
				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
					Sizzle.error( "unsupported pseudo: " + pseudo );

			// The user may use createPseudo to indicate that
			// arguments are needed to create the filter function
			// just as Sizzle does
			if ( fn[ expando ] ) {
				return fn( argument );
			}

			// But maintain support for old signatures
			if ( fn.length > 1 ) {
				args = [ pseudo, pseudo, "", argument ];
				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
					markFunction(function( seed, matches ) {
						var idx,
							matched = fn( seed, argument ),
							i = matched.length;
						while ( i-- ) {
							idx = indexOf( seed, matched[i] );
							seed[ idx ] = !( matches[ idx ] = matched[i] );
						}
					}) :
					function( elem ) {
						return fn( elem, 0, args );
					};
			}

			return fn;
		}
	},

	pseudos: {
		// Potentially complex pseudos
		"not": markFunction(function( selector ) {
			// Trim the selector passed to compile
			// to avoid treating leading and trailing
			// spaces as combinators
			var input = [],
				results = [],
				matcher = compile( selector.replace( rtrim, "$1" ) );

			return matcher[ expando ] ?
				markFunction(function( seed, matches, context, xml ) {
					var elem,
						unmatched = matcher( seed, null, xml, [] ),
						i = seed.length;

					// Match elements unmatched by `matcher`
					while ( i-- ) {
						if ( (elem = unmatched[i]) ) {
							seed[i] = !(matches[i] = elem);
						}
					}
				}) :
				function( elem, context, xml ) {
					input[0] = elem;
					matcher( input, null, xml, results );
					// Don't keep the element (issue #299)
					input[0] = null;
					return !results.pop();
				};
		}),

		"has": markFunction(function( selector ) {
			return function( elem ) {
				return Sizzle( selector, elem ).length > 0;
			};
		}),

		"contains": markFunction(function( text ) {
			text = text.replace( runescape, funescape );
			return function( elem ) {
				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
			};
		}),

		// "Whether an element is represented by a :lang() selector
		// is based solely on the element's language value
		// being equal to the identifier C,
		// or beginning with the identifier C immediately followed by "-".
		// The matching of C against the element's language value is performed case-insensitively.
		// The identifier C does not have to be a valid language name."
		// http://www.w3.org/TR/selectors/#lang-pseudo
		"lang": markFunction( function( lang ) {
			// lang value must be a valid identifier
			if ( !ridentifier.test(lang || "") ) {
				Sizzle.error( "unsupported lang: " + lang );
			}
			lang = lang.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				var elemLang;
				do {
					if ( (elemLang = documentIsHTML ?
						elem.lang :
						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {

						elemLang = elemLang.toLowerCase();
						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
					}
				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
				return false;
			};
		}),

		// Miscellaneous
		"target": function( elem ) {
			var hash = window.location && window.location.hash;
			return hash && hash.slice( 1 ) === elem.id;
		},

		"root": function( elem ) {
			return elem === docElem;
		},

		"focus": function( elem ) {
			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
		},

		// Boolean properties
		"enabled": createDisabledPseudo( false ),
		"disabled": createDisabledPseudo( true ),

		"checked": function( elem ) {
			// In CSS3, :checked should return both checked and selected elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			var nodeName = elem.nodeName.toLowerCase();
			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
		},

		"selected": function( elem ) {
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		"empty": function( elem ) {
			// http://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
			//   but not by others (comment: 8; processing instruction: 7; etc.)
			// nodeType < 6 works because attributes (2) do not appear as children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeType < 6 ) {
					return false;
				}
			}
			return true;
		},

		"parent": function( elem ) {
			return !Expr.pseudos["empty"]( elem );
		},

		// Element/input types
		"header": function( elem ) {
			return rheader.test( elem.nodeName );
		},

		"input": function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		"button": function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && elem.type === "button" || name === "button";
		},

		"text": function( elem ) {
			var attr;
			return elem.nodeName.toLowerCase() === "input" &&
				elem.type === "text" &&

				// Support: IE<8
				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
		},

		// Position-in-collection
		"first": createPositionalPseudo(function() {
			return [ 0 ];
		}),

		"last": createPositionalPseudo(function( matchIndexes, length ) {
			return [ length - 1 ];
		}),

		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		}),

		"even": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"odd": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		})
	}
};

Expr.pseudos["nth"] = Expr.pseudos["eq"];

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
	Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
	Expr.pseudos[ i ] = createButtonPseudo( i );
}

// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
	var matched, match, tokens, type,
		soFar, groups, preFilters,
		cached = tokenCache[ selector + " " ];

	if ( cached ) {
		return parseOnly ? 0 : cached.slice( 0 );
	}

	soFar = selector;
	groups = [];
	preFilters = Expr.preFilter;

	while ( soFar ) {

		// Comma and first run
		if ( !matched || (match = rcomma.exec( soFar )) ) {
			if ( match ) {
				// Don't consume trailing commas as valid
				soFar = soFar.slice( match[0].length ) || soFar;
			}
			groups.push( (tokens = []) );
		}

		matched = false;

		// Combinators
		if ( (match = rcombinators.exec( soFar )) ) {
			matched = match.shift();
			tokens.push({
				value: matched,
				// Cast descendant combinators to space
				type: match[0].replace( rtrim, " " )
			});
			soFar = soFar.slice( matched.length );
		}

		// Filters
		for ( type in Expr.filter ) {
			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
				(match = preFilters[ type ]( match ))) ) {
				matched = match.shift();
				tokens.push({
					value: matched,
					type: type,
					matches: match
				});
				soFar = soFar.slice( matched.length );
			}
		}

		if ( !matched ) {
			break;
		}
	}

	// Return the length of the invalid excess
	// if we're just parsing
	// Otherwise, throw an error or return tokens
	return parseOnly ?
		soFar.length :
		soFar ?
			Sizzle.error( selector ) :
			// Cache the tokens
			tokenCache( selector, groups ).slice( 0 );
};

function toSelector( tokens ) {
	var i = 0,
		len = tokens.length,
		selector = "";
	for ( ; i < len; i++ ) {
		selector += tokens[i].value;
	}
	return selector;
}

function addCombinator( matcher, combinator, base ) {
	var dir = combinator.dir,
		skip = combinator.next,
		key = skip || dir,
		checkNonElements = base && key === "parentNode",
		doneName = done++;

	return combinator.first ?
		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( (elem = elem[ dir ]) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
			return false;
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var oldCache, uniqueCache, outerCache,
				newCache = [ dirruns, doneName ];

			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
			if ( xml ) {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						if ( matcher( elem, context, xml ) ) {
							return true;
						}
					}
				}
			} else {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						outerCache = elem[ expando ] || (elem[ expando ] = {});

						// Support: IE <9 only
						// Defend against cloned attroperties (jQuery gh-1709)
						uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});

						if ( skip && skip === elem.nodeName.toLowerCase() ) {
							elem = elem[ dir ] || elem;
						} else if ( (oldCache = uniqueCache[ key ]) &&
							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

							// Assign to newCache so results back-propagate to previous elements
							return (newCache[ 2 ] = oldCache[ 2 ]);
						} else {
							// Reuse newcache so results back-propagate to previous elements
							uniqueCache[ key ] = newCache;

							// A match means we're done; a fail means we have to keep checking
							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
								return true;
							}
						}
					}
				}
			}
			return false;
		};
}

function elementMatcher( matchers ) {
	return matchers.length > 1 ?
		function( elem, context, xml ) {
			var i = matchers.length;
			while ( i-- ) {
				if ( !matchers[i]( elem, context, xml ) ) {
					return false;
				}
			}
			return true;
		} :
		matchers[0];
}

function multipleContexts( selector, contexts, results ) {
	var i = 0,
		len = contexts.length;
	for ( ; i < len; i++ ) {
		Sizzle( selector, contexts[i], results );
	}
	return results;
}

function condense( unmatched, map, filter, context, xml ) {
	var elem,
		newUnmatched = [],
		i = 0,
		len = unmatched.length,
		mapped = map != null;

	for ( ; i < len; i++ ) {
		if ( (elem = unmatched[i]) ) {
			if ( !filter || filter( elem, context, xml ) ) {
				newUnmatched.push( elem );
				if ( mapped ) {
					map.push( i );
				}
			}
		}
	}

	return newUnmatched;
}

function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
	if ( postFilter && !postFilter[ expando ] ) {
		postFilter = setMatcher( postFilter );
	}
	if ( postFinder && !postFinder[ expando ] ) {
		postFinder = setMatcher( postFinder, postSelector );
	}
	return markFunction(function( seed, results, context, xml ) {
		var temp, i, elem,
			preMap = [],
			postMap = [],
			preexisting = results.length,

			// Get initial elements from seed or context
			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),

			// Prefilter to get matcher input, preserving a map for seed-results synchronization
			matcherIn = preFilter && ( seed || !selector ) ?
				condense( elems, preMap, preFilter, context, xml ) :
				elems,

			matcherOut = matcher ?
				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?

					// ...intermediate processing is necessary
					[] :

					// ...otherwise use results directly
					results :
				matcherIn;

		// Find primary matches
		if ( matcher ) {
			matcher( matcherIn, matcherOut, context, xml );
		}

		// Apply postFilter
		if ( postFilter ) {
			temp = condense( matcherOut, postMap );
			postFilter( temp, [], context, xml );

			// Un-match failing elements by moving them back to matcherIn
			i = temp.length;
			while ( i-- ) {
				if ( (elem = temp[i]) ) {
					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
				}
			}
		}

		if ( seed ) {
			if ( postFinder || preFilter ) {
				if ( postFinder ) {
					// Get the final matcherOut by condensing this intermediate into postFinder contexts
					temp = [];
					i = matcherOut.length;
					while ( i-- ) {
						if ( (elem = matcherOut[i]) ) {
							// Restore matcherIn since elem is not yet a final match
							temp.push( (matcherIn[i] = elem) );
						}
					}
					postFinder( null, (matcherOut = []), temp, xml );
				}

				// Move matched elements from seed to results to keep them synchronized
				i = matcherOut.length;
				while ( i-- ) {
					if ( (elem = matcherOut[i]) &&
						(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {

						seed[temp] = !(results[temp] = elem);
					}
				}
			}

		// Add elements to results, through postFinder if defined
		} else {
			matcherOut = condense(
				matcherOut === results ?
					matcherOut.splice( preexisting, matcherOut.length ) :
					matcherOut
			);
			if ( postFinder ) {
				postFinder( null, results, matcherOut, xml );
			} else {
				push.apply( results, matcherOut );
			}
		}
	});
}

function matcherFromTokens( tokens ) {
	var checkContext, matcher, j,
		len = tokens.length,
		leadingRelative = Expr.relative[ tokens[0].type ],
		implicitRelative = leadingRelative || Expr.relative[" "],
		i = leadingRelative ? 1 : 0,

		// The foundational matcher ensures that elements are reachable from top-level context(s)
		matchContext = addCombinator( function( elem ) {
			return elem === checkContext;
		}, implicitRelative, true ),
		matchAnyContext = addCombinator( function( elem ) {
			return indexOf( checkContext, elem ) > -1;
		}, implicitRelative, true ),
		matchers = [ function( elem, context, xml ) {
			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
				(checkContext = context).nodeType ?
					matchContext( elem, context, xml ) :
					matchAnyContext( elem, context, xml ) );
			// Avoid hanging onto element (issue #299)
			checkContext = null;
			return ret;
		} ];

	for ( ; i < len; i++ ) {
		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
		} else {
			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

			// Return special upon seeing a positional matcher
			if ( matcher[ expando ] ) {
				// Find the next relative operator (if any) for proper handling
				j = ++i;
				for ( ; j < len; j++ ) {
					if ( Expr.relative[ tokens[j].type ] ) {
						break;
					}
				}
				return setMatcher(
					i > 1 && elementMatcher( matchers ),
					i > 1 && toSelector(
						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
					).replace( rtrim, "$1" ),
					matcher,
					i < j && matcherFromTokens( tokens.slice( i, j ) ),
					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
					j < len && toSelector( tokens )
				);
			}
			matchers.push( matcher );
		}
	}

	return elementMatcher( matchers );
}

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
	var bySet = setMatchers.length > 0,
		byElement = elementMatchers.length > 0,
		superMatcher = function( seed, context, xml, results, outermost ) {
			var elem, j, matcher,
				matchedCount = 0,
				i = "0",
				unmatched = seed && [],
				setMatched = [],
				contextBackup = outermostContext,
				// We must always have either seed elements or outermost context
				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
				// Use integer dirruns iff this is the outermost matcher
				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
				len = elems.length;

			if ( outermost ) {
				outermostContext = context === document || context || outermost;
			}

			// Add elements passing elementMatchers directly to results
			// Support: IE<9, Safari
			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
				if ( byElement && elem ) {
					j = 0;
					if ( !context && elem.ownerDocument !== document ) {
						setDocument( elem );
						xml = !documentIsHTML;
					}
					while ( (matcher = elementMatchers[j++]) ) {
						if ( matcher( elem, context || document, xml) ) {
							results.push( elem );
							break;
						}
					}
					if ( outermost ) {
						dirruns = dirrunsUnique;
					}
				}

				// Track unmatched elements for set filters
				if ( bySet ) {
					// They will have gone through all possible matchers
					if ( (elem = !matcher && elem) ) {
						matchedCount--;
					}

					// Lengthen the array for every element, matched or not
					if ( seed ) {
						unmatched.push( elem );
					}
				}
			}

			// `i` is now the count of elements visited above, and adding it to `matchedCount`
			// makes the latter nonnegative.
			matchedCount += i;

			// Apply set filters to unmatched elements
			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
			// no element matchers and no seed.
			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
			// numerically zero.
			if ( bySet && i !== matchedCount ) {
				j = 0;
				while ( (matcher = setMatchers[j++]) ) {
					matcher( unmatched, setMatched, context, xml );
				}

				if ( seed ) {
					// Reintegrate element matches to eliminate the need for sorting
					if ( matchedCount > 0 ) {
						while ( i-- ) {
							if ( !(unmatched[i] || setMatched[i]) ) {
								setMatched[i] = pop.call( results );
							}
						}
					}

					// Discard index placeholder values to get only actual matches
					setMatched = condense( setMatched );
				}

				// Add matches to results
				push.apply( results, setMatched );

				// Seedless set matches succeeding multiple successful matchers stipulate sorting
				if ( outermost && !seed && setMatched.length > 0 &&
					( matchedCount + setMatchers.length ) > 1 ) {

					Sizzle.uniqueSort( results );
				}
			}

			// Override manipulation of globals by nested matchers
			if ( outermost ) {
				dirruns = dirrunsUnique;
				outermostContext = contextBackup;
			}

			return unmatched;
		};

	return bySet ?
		markFunction( superMatcher ) :
		superMatcher;
}

compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
	var i,
		setMatchers = [],
		elementMatchers = [],
		cached = compilerCache[ selector + " " ];

	if ( !cached ) {
		// Generate a function of recursive functions that can be used to check each element
		if ( !match ) {
			match = tokenize( selector );
		}
		i = match.length;
		while ( i-- ) {
			cached = matcherFromTokens( match[i] );
			if ( cached[ expando ] ) {
				setMatchers.push( cached );
			} else {
				elementMatchers.push( cached );
			}
		}

		// Cache the compiled function
		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );

		// Save selector and tokenization
		cached.selector = selector;
	}
	return cached;
};

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function( selector, context, results, seed ) {
	var i, tokens, token, type, find,
		compiled = typeof selector === "function" && selector,
		match = !seed && tokenize( (selector = compiled.selector || selector) );

	results = results || [];

	// Try to minimize operations if there is only one selector in the list and no seed
	// (the latter of which guarantees us context)
	if ( match.length === 1 ) {

		// Reduce context if the leading compound selector is an ID
		tokens = match[0] = match[0].slice( 0 );
		if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
				context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) {

			context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
			if ( !context ) {
				return results;

			// Precompiled matchers will still verify ancestry, so step up a level
			} else if ( compiled ) {
				context = context.parentNode;
			}

			selector = selector.slice( tokens.shift().value.length );
		}

		// Fetch a seed set for right-to-left matching
		i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
		while ( i-- ) {
			token = tokens[i];

			// Abort if we hit a combinator
			if ( Expr.relative[ (type = token.type) ] ) {
				break;
			}
			if ( (find = Expr.find[ type ]) ) {
				// Search, expanding context for leading sibling combinators
				if ( (seed = find(
					token.matches[0].replace( runescape, funescape ),
					rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
				)) ) {

					// If seed is empty or no tokens remain, we can return early
					tokens.splice( i, 1 );
					selector = seed.length && toSelector( tokens );
					if ( !selector ) {
						push.apply( results, seed );
						return results;
					}

					break;
				}
			}
		}
	}

	// Compile and execute a filtering function if one is not provided
	// Provide `match` to avoid retokenization if we modified the selector above
	( compiled || compile( selector, match ) )(
		seed,
		context,
		!documentIsHTML,
		results,
		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
	);
	return results;
};

// One-time assignments

// Sort stability
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;

// Support: Chrome 14-35+
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;

// Initialize against the default document
setDocument();

// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert(function( el ) {
	// Should return 1, but returns 4 (following)
	return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
});

// Support: IE<8
// Prevent attribute/property "interpolation"
// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( el ) {
	el.innerHTML = "<a href='#'></a>";
	return el.firstChild.getAttribute("href") === "#" ;
}) ) {
	addHandle( "type|href|height|width", function( elem, name, isXML ) {
		if ( !isXML ) {
			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
		}
	});
}

// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert(function( el ) {
	el.innerHTML = "<input/>";
	el.firstChild.setAttribute( "value", "" );
	return el.firstChild.getAttribute( "value" ) === "";
}) ) {
	addHandle( "value", function( elem, name, isXML ) {
		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
			return elem.defaultValue;
		}
	});
}

// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert(function( el ) {
	return el.getAttribute("disabled") == null;
}) ) {
	addHandle( booleans, function( elem, name, isXML ) {
		var val;
		if ( !isXML ) {
			return elem[ name ] === true ? name.toLowerCase() :
					(val = elem.getAttributeNode( name )) && val.specified ?
					val.value :
				null;
		}
	});
}

return Sizzle;

})( window );



jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;

// Deprecated
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
jQuery.escapeSelector = Sizzle.escape;




var dir = function( elem, dir, until ) {
	var matched = [],
		truncate = until !== undefined;

	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
		if ( elem.nodeType === 1 ) {
			if ( truncate && jQuery( elem ).is( until ) ) {
				break;
			}
			matched.push( elem );
		}
	}
	return matched;
};


var siblings = function( n, elem ) {
	var matched = [];

	for ( ; n; n = n.nextSibling ) {
		if ( n.nodeType === 1 && n !== elem ) {
			matched.push( n );
		}
	}

	return matched;
};


var rneedsContext = jQuery.expr.match.needsContext;

var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );



var risSimple = /^.[^:#\[\.,]*$/;

// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
	if ( jQuery.isFunction( qualifier ) ) {
		return jQuery.grep( elements, function( elem, i ) {
			return !!qualifier.call( elem, i, elem ) !== not;
		} );
	}

	// Single element
	if ( qualifier.nodeType ) {
		return jQuery.grep( elements, function( elem ) {
			return ( elem === qualifier ) !== not;
		} );
	}

	// Arraylike of elements (jQuery, arguments, Array)
	if ( typeof qualifier !== "string" ) {
		return jQuery.grep( elements, function( elem ) {
			return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
		} );
	}

	// Simple selector that can be filtered directly, removing non-Elements
	if ( risSimple.test( qualifier ) ) {
		return jQuery.filter( qualifier, elements, not );
	}

	// Complex selector, compare the two sets, removing non-Elements
	qualifier = jQuery.filter( qualifier, elements );
	return jQuery.grep( elements, function( elem ) {
		return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1;
	} );
}

jQuery.filter = function( expr, elems, not ) {
	var elem = elems[ 0 ];

	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	if ( elems.length === 1 && elem.nodeType === 1 ) {
		return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
	}

	return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
		return elem.nodeType === 1;
	} ) );
};

jQuery.fn.extend( {
	find: function( selector ) {
		var i, ret,
			len = this.length,
			self = this;

		if ( typeof selector !== "string" ) {
			return this.pushStack( jQuery( selector ).filter( function() {
				for ( i = 0; i < len; i++ ) {
					if ( jQuery.contains( self[ i ], this ) ) {
						return true;
					}
				}
			} ) );
		}

		ret = this.pushStack( [] );

		for ( i = 0; i < len; i++ ) {
			jQuery.find( selector, self[ i ], ret );
		}

		return len > 1 ? jQuery.uniqueSort( ret ) : ret;
	},
	filter: function( selector ) {
		return this.pushStack( winnow( this, selector || [], false ) );
	},
	not: function( selector ) {
		return this.pushStack( winnow( this, selector || [], true ) );
	},
	is: function( selector ) {
		return !!winnow(
			this,

			// If this is a positional/relative selector, check membership in the returned set
			// so $("p:first").is("p:last") won't return true for a doc with two "p".
			typeof selector === "string" && rneedsContext.test( selector ) ?
				jQuery( selector ) :
				selector || [],
			false
		).length;
	}
} );


// Initialize a jQuery object


// A central reference to the root jQuery(document)
var rootjQuery,

	// A simple way to check for HTML strings
	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
	// Strict HTML recognition (#11290: must start with <)
	// Shortcut simple #id case for speed
	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,

	init = jQuery.fn.init = function( selector, context, root ) {
		var match, elem;

		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}

		// Method init() accepts an alternate rootjQuery
		// so migrate can support jQuery.sub (gh-2101)
		root = root || rootjQuery;

		// Handle HTML strings
		if ( typeof selector === "string" ) {
			if ( selector[ 0 ] === "<" &&
				selector[ selector.length - 1 ] === ">" &&
				selector.length >= 3 ) {

				// Assume that strings that start and end with <> are HTML and skip the regex check
				match = [ null, selector, null ];

			} else {
				match = rquickExpr.exec( selector );
			}

			// Match html or make sure no context is specified for #id
			if ( match && ( match[ 1 ] || !context ) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[ 1 ] ) {
					context = context instanceof jQuery ? context[ 0 ] : context;

					// Option to run scripts is true for back-compat
					// Intentionally let the error be thrown if parseHTML is not present
					jQuery.merge( this, jQuery.parseHTML(
						match[ 1 ],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );

					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {

							// Properties of context are called as methods if possible
							if ( jQuery.isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );

							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}

					return this;

				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[ 2 ] );

					if ( elem ) {

						// Inject the element directly into the jQuery object
						this[ 0 ] = elem;
						this.length = 1;
					}
					return this;
				}

			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				return ( context || root ).find( selector );

			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			} else {
				return this.constructor( context ).find( selector );
			}

		// HANDLE: $(DOMElement)
		} else if ( selector.nodeType ) {
			this[ 0 ] = selector;
			this.length = 1;
			return this;

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) ) {
			return root.ready !== undefined ?
				root.ready( selector ) :

				// Execute immediately if ready is not present
				selector( jQuery );
		}

		return jQuery.makeArray( selector, this );
	};

// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;

// Initialize central reference
rootjQuery = jQuery( document );


var rparentsprev = /^(?:parents|prev(?:Until|All))/,

	// Methods guaranteed to produce a unique set when starting from a unique set
	guaranteedUnique = {
		children: true,
		contents: true,
		next: true,
		prev: true
	};

jQuery.fn.extend( {
	has: function( target ) {
		var targets = jQuery( target, this ),
			l = targets.length;

		return this.filter( function() {
			var i = 0;
			for ( ; i < l; i++ ) {
				if ( jQuery.contains( this, targets[ i ] ) ) {
					return true;
				}
			}
		} );
	},

	closest: function( selectors, context ) {
		var cur,
			i = 0,
			l = this.length,
			matched = [],
			targets = typeof selectors !== "string" && jQuery( selectors );

		// Positional selectors never match, since there's no _selection_ context
		if ( !rneedsContext.test( selectors ) ) {
			for ( ; i < l; i++ ) {
				for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {

					// Always skip document fragments
					if ( cur.nodeType < 11 && ( targets ?
						targets.index( cur ) > -1 :

						// Don't pass non-elements to Sizzle
						cur.nodeType === 1 &&
							jQuery.find.matchesSelector( cur, selectors ) ) ) {

						matched.push( cur );
						break;
					}
				}
			}
		}

		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
	},

	// Determine the position of an element within the set
	index: function( elem ) {

		// No argument, return index in parent
		if ( !elem ) {
			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
		}

		// Index in selector
		if ( typeof elem === "string" ) {
			return indexOf.call( jQuery( elem ), this[ 0 ] );
		}

		// Locate the position of the desired element
		return indexOf.call( this,

			// If it receives a jQuery object, the first element is used
			elem.jquery ? elem[ 0 ] : elem
		);
	},

	add: function( selector, context ) {
		return this.pushStack(
			jQuery.uniqueSort(
				jQuery.merge( this.get(), jQuery( selector, context ) )
			)
		);
	},

	addBack: function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter( selector )
		);
	}
} );

function sibling( cur, dir ) {
	while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
	return cur;
}

jQuery.each( {
	parent: function( elem ) {
		var parent = elem.parentNode;
		return parent && parent.nodeType !== 11 ? parent : null;
	},
	parents: function( elem ) {
		return dir( elem, "parentNode" );
	},
	parentsUntil: function( elem, i, until ) {
		return dir( elem, "parentNode", until );
	},
	next: function( elem ) {
		return sibling( elem, "nextSibling" );
	},
	prev: function( elem ) {
		return sibling( elem, "previousSibling" );
	},
	nextAll: function( elem ) {
		return dir( elem, "nextSibling" );
	},
	prevAll: function( elem ) {
		return dir( elem, "previousSibling" );
	},
	nextUntil: function( elem, i, until ) {
		return dir( elem, "nextSibling", until );
	},
	prevUntil: function( elem, i, until ) {
		return dir( elem, "previousSibling", until );
	},
	siblings: function( elem ) {
		return siblings( ( elem.parentNode || {} ).firstChild, elem );
	},
	children: function( elem ) {
		return siblings( elem.firstChild );
	},
	contents: function( elem ) {
		return elem.contentDocument || jQuery.merge( [], elem.childNodes );
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var matched = jQuery.map( this, fn, until );

		if ( name.slice( -5 ) !== "Until" ) {
			selector = until;
		}

		if ( selector && typeof selector === "string" ) {
			matched = jQuery.filter( selector, matched );
		}

		if ( this.length > 1 ) {

			// Remove duplicates
			if ( !guaranteedUnique[ name ] ) {
				jQuery.uniqueSort( matched );
			}

			// Reverse order for parents* and prev-derivatives
			if ( rparentsprev.test( name ) ) {
				matched.reverse();
			}
		}

		return this.pushStack( matched );
	};
} );
var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );



// Convert String-formatted options into Object-formatted ones
function createOptions( options ) {
	var object = {};
	jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
		object[ flag ] = true;
	} );
	return object;
}

/*
 * Create a callback list using the following parameters:
 *
 *	options: an optional list of space-separated options that will change how
 *			the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {

	// Convert options from String-formatted to Object-formatted if needed
	// (we check in cache first)
	options = typeof options === "string" ?
		createOptions( options ) :
		jQuery.extend( {}, options );

	var // Flag to know if list is currently firing
		firing,

		// Last fire value for non-forgettable lists
		memory,

		// Flag to know if list was already fired
		fired,

		// Flag to prevent firing
		locked,

		// Actual callback list
		list = [],

		// Queue of execution data for repeatable lists
		queue = [],

		// Index of currently firing callback (modified by add/remove as needed)
		firingIndex = -1,

		// Fire callbacks
		fire = function() {

			// Enforce single-firing
			locked = options.once;

			// Execute callbacks for all pending executions,
			// respecting firingIndex overrides and runtime changes
			fired = firing = true;
			for ( ; queue.length; firingIndex = -1 ) {
				memory = queue.shift();
				while ( ++firingIndex < list.length ) {

					// Run callback and check for early termination
					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
						options.stopOnFalse ) {

						// Jump to end and forget the data so .add doesn't re-fire
						firingIndex = list.length;
						memory = false;
					}
				}
			}

			// Forget the data if we're done with it
			if ( !options.memory ) {
				memory = false;
			}

			firing = false;

			// Clean up if we're done firing for good
			if ( locked ) {

				// Keep an empty list if we have data for future add calls
				if ( memory ) {
					list = [];

				// Otherwise, this object is spent
				} else {
					list = "";
				}
			}
		},

		// Actual Callbacks object
		self = {

			// Add a callback or a collection of callbacks to the list
			add: function() {
				if ( list ) {

					// If we have memory from a past run, we should fire after adding
					if ( memory && !firing ) {
						firingIndex = list.length - 1;
						queue.push( memory );
					}

					( function add( args ) {
						jQuery.each( args, function( _, arg ) {
							if ( jQuery.isFunction( arg ) ) {
								if ( !options.unique || !self.has( arg ) ) {
									list.push( arg );
								}
							} else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) {

								// Inspect recursively
								add( arg );
							}
						} );
					} )( arguments );

					if ( memory && !firing ) {
						fire();
					}
				}
				return this;
			},

			// Remove a callback from the list
			remove: function() {
				jQuery.each( arguments, function( _, arg ) {
					var index;
					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
						list.splice( index, 1 );

						// Handle firing indexes
						if ( index <= firingIndex ) {
							firingIndex--;
						}
					}
				} );
				return this;
			},

			// Check if a given callback is in the list.
			// If no argument is given, return whether or not list has callbacks attached.
			has: function( fn ) {
				return fn ?
					jQuery.inArray( fn, list ) > -1 :
					list.length > 0;
			},

			// Remove all callbacks from the list
			empty: function() {
				if ( list ) {
					list = [];
				}
				return this;
			},

			// Disable .fire and .add
			// Abort any current/pending executions
			// Clear all callbacks and values
			disable: function() {
				locked = queue = [];
				list = memory = "";
				return this;
			},
			disabled: function() {
				return !list;
			},

			// Disable .fire
			// Also disable .add unless we have memory (since it would have no effect)
			// Abort any pending executions
			lock: function() {
				locked = queue = [];
				if ( !memory && !firing ) {
					list = memory = "";
				}
				return this;
			},
			locked: function() {
				return !!locked;
			},

			// Call all callbacks with the given context and arguments
			fireWith: function( context, args ) {
				if ( !locked ) {
					args = args || [];
					args = [ context, args.slice ? args.slice() : args ];
					queue.push( args );
					if ( !firing ) {
						fire();
					}
				}
				return this;
			},

			// Call all the callbacks with the given arguments
			fire: function() {
				self.fireWith( this, arguments );
				return this;
			},

			// To know if the callbacks have already been called at least once
			fired: function() {
				return !!fired;
			}
		};

	return self;
};


function Identity( v ) {
	return v;
}
function Thrower( ex ) {
	throw ex;
}

function adoptValue( value, resolve, reject ) {
	var method;

	try {

		// Check for promise aspect first to privilege synchronous behavior
		if ( value && jQuery.isFunction( ( method = value.promise ) ) ) {
			method.call( value ).done( resolve ).fail( reject );

		// Other thenables
		} else if ( value && jQuery.isFunction( ( method = value.then ) ) ) {
			method.call( value, resolve, reject );

		// Other non-thenables
		} else {

			// Support: Android 4.0 only
			// Strict mode functions invoked without .call/.apply get global-object context
			resolve.call( undefined, value );
		}

	// For Promises/A+, convert exceptions into rejections
	// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
	// Deferred#then to conditionally suppress rejection.
	} catch ( value ) {

		// Support: Android 4.0 only
		// Strict mode functions invoked without .call/.apply get global-object context
		reject.call( undefined, value );
	}
}

jQuery.extend( {

	Deferred: function( func ) {
		var tuples = [

				// action, add listener, callbacks,
				// ... .then handlers, argument index, [final state]
				[ "notify", "progress", jQuery.Callbacks( "memory" ),
					jQuery.Callbacks( "memory" ), 2 ],
				[ "resolve", "done", jQuery.Callbacks( "once memory" ),
					jQuery.Callbacks( "once memory" ), 0, "resolved" ],
				[ "reject", "fail", jQuery.Callbacks( "once memory" ),
					jQuery.Callbacks( "once memory" ), 1, "rejected" ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				"catch": function( fn ) {
					return promise.then( null, fn );
				},

				// Keep pipe for back-compat
				pipe: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;

					return jQuery.Deferred( function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {

							// Map tuples (progress, done, fail) to arguments (done, fail, progress)
							var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];

							// deferred.progress(function() { bind to newDefer or newDefer.notify })
							// deferred.done(function() { bind to newDefer or newDefer.resolve })
							// deferred.fail(function() { bind to newDefer or newDefer.reject })
							deferred[ tuple[ 1 ] ]( function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise()
										.progress( newDefer.notify )
										.done( newDefer.resolve )
										.fail( newDefer.reject );
								} else {
									newDefer[ tuple[ 0 ] + "With" ](
										this,
										fn ? [ returned ] : arguments
									);
								}
							} );
						} );
						fns = null;
					} ).promise();
				},
				then: function( onFulfilled, onRejected, onProgress ) {
					var maxDepth = 0;
					function resolve( depth, deferred, handler, special ) {
						return function() {
							var that = this,
								args = arguments,
								mightThrow = function() {
									var returned, then;

									// Support: Promises/A+ section 2.3.3.3.3
									// https://promisesaplus.com/#point-59
									// Ignore double-resolution attempts
									if ( depth < maxDepth ) {
										return;
									}

									returned = handler.apply( that, args );

									// Support: Promises/A+ section 2.3.1
									// https://promisesaplus.com/#point-48
									if ( returned === deferred.promise() ) {
										throw new TypeError( "Thenable self-resolution" );
									}

									// Support: Promises/A+ sections 2.3.3.1, 3.5
									// https://promisesaplus.com/#point-54
									// https://promisesaplus.com/#point-75
									// Retrieve `then` only once
									then = returned &&

										// Support: Promises/A+ section 2.3.4
										// https://promisesaplus.com/#point-64
										// Only check objects and functions for thenability
										( typeof returned === "object" ||
											typeof returned === "function" ) &&
										returned.then;

									// Handle a returned thenable
									if ( jQuery.isFunction( then ) ) {

										// Special processors (notify) just wait for resolution
										if ( special ) {
											then.call(
												returned,
												resolve( maxDepth, deferred, Identity, special ),
												resolve( maxDepth, deferred, Thrower, special )
											);

										// Normal processors (resolve) also hook into progress
										} else {

											// ...and disregard older resolution values
											maxDepth++;

											then.call(
												returned,
												resolve( maxDepth, deferred, Identity, special ),
												resolve( maxDepth, deferred, Thrower, special ),
												resolve( maxDepth, deferred, Identity,
													deferred.notifyWith )
											);
										}

									// Handle all other returned values
									} else {

										// Only substitute handlers pass on context
										// and multiple values (non-spec behavior)
										if ( handler !== Identity ) {
											that = undefined;
											args = [ returned ];
										}

										// Process the value(s)
										// Default process is resolve
										( special || deferred.resolveWith )( that, args );
									}
								},

								// Only normal processors (resolve) catch and reject exceptions
								process = special ?
									mightThrow :
									function() {
										try {
											mightThrow();
										} catch ( e ) {

											if ( jQuery.Deferred.exceptionHook ) {
												jQuery.Deferred.exceptionHook( e,
													process.stackTrace );
											}

											// Support: Promises/A+ section 2.3.3.3.4.1
											// https://promisesaplus.com/#point-61
											// Ignore post-resolution exceptions
											if ( depth + 1 >= maxDepth ) {

												// Only substitute handlers pass on context
												// and multiple values (non-spec behavior)
												if ( handler !== Thrower ) {
													that = undefined;
													args = [ e ];
												}

												deferred.rejectWith( that, args );
											}
										}
									};

							// Support: Promises/A+ section 2.3.3.3.1
							// https://promisesaplus.com/#point-57
							// Re-resolve promises immediately to dodge false rejection from
							// subsequent errors
							if ( depth ) {
								process();
							} else {

								// Call an optional hook to record the stack, in case of exception
								// since it's otherwise lost when execution goes async
								if ( jQuery.Deferred.getStackHook ) {
									process.stackTrace = jQuery.Deferred.getStackHook();
								}
								window.setTimeout( process );
							}
						};
					}

					return jQuery.Deferred( function( newDefer ) {

						// progress_handlers.add( ... )
						tuples[ 0 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								jQuery.isFunction( onProgress ) ?
									onProgress :
									Identity,
								newDefer.notifyWith
							)
						);

						// fulfilled_handlers.add( ... )
						tuples[ 1 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								jQuery.isFunction( onFulfilled ) ?
									onFulfilled :
									Identity
							)
						);

						// rejected_handlers.add( ... )
						tuples[ 2 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								jQuery.isFunction( onRejected ) ?
									onRejected :
									Thrower
							)
						);
					} ).promise();
				},

				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
			deferred = {};

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			var list = tuple[ 2 ],
				stateString = tuple[ 5 ];

			// promise.progress = list.add
			// promise.done = list.add
			// promise.fail = list.add
			promise[ tuple[ 1 ] ] = list.add;

			// Handle state
			if ( stateString ) {
				list.add(
					function() {

						// state = "resolved" (i.e., fulfilled)
						// state = "rejected"
						state = stateString;
					},

					// rejected_callbacks.disable
					// fulfilled_callbacks.disable
					tuples[ 3 - i ][ 2 ].disable,

					// progress_callbacks.lock
					tuples[ 0 ][ 2 ].lock
				);
			}

			// progress_handlers.fire
			// fulfilled_handlers.fire
			// rejected_handlers.fire
			list.add( tuple[ 3 ].fire );

			// deferred.notify = function() { deferred.notifyWith(...) }
			// deferred.resolve = function() { deferred.resolveWith(...) }
			// deferred.reject = function() { deferred.rejectWith(...) }
			deferred[ tuple[ 0 ] ] = function() {
				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
				return this;
			};

			// deferred.notifyWith = list.fireWith
			// deferred.resolveWith = list.fireWith
			// deferred.rejectWith = list.fireWith
			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
		} );

		// Make the deferred a promise
		promise.promise( deferred );

		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}

		// All done!
		return deferred;
	},

	// Deferred helper
	when: function( singleValue ) {
		var

			// count of uncompleted subordinates
			remaining = arguments.length,

			// count of unprocessed arguments
			i = remaining,

			// subordinate fulfillment data
			resolveContexts = Array( i ),
			resolveValues = slice.call( arguments ),

			// the master Deferred
			master = jQuery.Deferred(),

			// subordinate callback factory
			updateFunc = function( i ) {
				return function( value ) {
					resolveContexts[ i ] = this;
					resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
					if ( !( --remaining ) ) {
						master.resolveWith( resolveContexts, resolveValues );
					}
				};
			};

		// Single- and empty arguments are adopted like Promise.resolve
		if ( remaining <= 1 ) {
			adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject );

			// Use .then() to unwrap secondary thenables (cf. gh-3000)
			if ( master.state() === "pending" ||
				jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {

				return master.then();
			}
		}

		// Multiple arguments are aggregated like Promise.all array elements
		while ( i-- ) {
			adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
		}

		return master.promise();
	}
} );


// These usually indicate a programmer mistake during development,
// warn about them ASAP rather than swallowing them by default.
var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;

jQuery.Deferred.exceptionHook = function( error, stack ) {

	// Support: IE 8 - 9 only
	// Console exists when dev tools are open, which can happen at any time
	if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
		window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
	}
};




jQuery.readyException = function( error ) {
	window.setTimeout( function() {
		throw error;
	} );
};




// The deferred used on DOM ready
var readyList = jQuery.Deferred();

jQuery.fn.ready = function( fn ) {

	readyList
		.then( fn )

		// Wrap jQuery.readyException in a function so that the lookup
		// happens at the time of error handling instead of callback
		// registration.
		.catch( function( error ) {
			jQuery.readyException( error );
		} );

	return this;
};

jQuery.extend( {

	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See #6781
	readyWait: 1,

	// Hold (or release) the ready event
	holdReady: function( hold ) {
		if ( hold ) {
			jQuery.readyWait++;
		} else {
			jQuery.ready( true );
		}
	},

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );
	}
} );

jQuery.ready.then = readyList.then;

// The ready event handler and self cleanup method
function completed() {
	document.removeEventListener( "DOMContentLoaded", completed );
	window.removeEventListener( "load", completed );
	jQuery.ready();
}

// Catch cases where $(document).ready() is called
// after the browser event has already occurred.
// Support: IE <=9 - 10 only
// Older IE sometimes signals "interactive" too soon
if ( document.readyState === "complete" ||
	( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {

	// Handle it asynchronously to allow scripts the opportunity to delay ready
	window.setTimeout( jQuery.ready );

} else {

	// Use the handy event callback
	document.addEventListener( "DOMContentLoaded", completed );

	// A fallback to window.onload, that will always work
	window.addEventListener( "load", completed );
}




// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
	var i = 0,
		len = elems.length,
		bulk = key == null;

	// Sets many values
	if ( jQuery.type( key ) === "object" ) {
		chainable = true;
		for ( i in key ) {
			access( elems, fn, i, key[ i ], true, emptyGet, raw );
		}

	// Sets one value
	} else if ( value !== undefined ) {
		chainable = true;

		if ( !jQuery.isFunction( value ) ) {
			raw = true;
		}

		if ( bulk ) {

			// Bulk operations run against the entire set
			if ( raw ) {
				fn.call( elems, value );
				fn = null;

			// ...except when executing function values
			} else {
				bulk = fn;
				fn = function( elem, key, value ) {
					return bulk.call( jQuery( elem ), value );
				};
			}
		}

		if ( fn ) {
			for ( ; i < len; i++ ) {
				fn(
					elems[ i ], key, raw ?
					value :
					value.call( elems[ i ], i, fn( elems[ i ], key ) )
				);
			}
		}
	}

	if ( chainable ) {
		return elems;
	}

	// Gets
	if ( bulk ) {
		return fn.call( elems );
	}

	return len ? fn( elems[ 0 ], key ) : emptyGet;
};
var acceptData = function( owner ) {

	// Accepts only:
	//  - Node
	//    - Node.ELEMENT_NODE
	//    - Node.DOCUMENT_NODE
	//  - Object
	//    - Any
	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};




function Data() {
	this.expando = jQuery.expando + Data.uid++;
}

Data.uid = 1;

Data.prototype = {

	cache: function( owner ) {

		// Check if the owner object already has a cache
		var value = owner[ this.expando ];

		// If not, create one
		if ( !value ) {
			value = {};

			// We can accept data for non-element nodes in modern browsers,
			// but we should not, see #8335.
			// Always return an empty object.
			if ( acceptData( owner ) ) {

				// If it is a node unlikely to be stringify-ed or looped over
				// use plain assignment
				if ( owner.nodeType ) {
					owner[ this.expando ] = value;

				// Otherwise secure it in a non-enumerable property
				// configurable must be true to allow the property to be
				// deleted when data is removed
				} else {
					Object.defineProperty( owner, this.expando, {
						value: value,
						configurable: true
					} );
				}
			}
		}

		return value;
	},
	set: function( owner, data, value ) {
		var prop,
			cache = this.cache( owner );

		// Handle: [ owner, key, value ] args
		// Always use camelCase key (gh-2257)
		if ( typeof data === "string" ) {
			cache[ jQuery.camelCase( data ) ] = value;

		// Handle: [ owner, { properties } ] args
		} else {

			// Copy the properties one-by-one to the cache object
			for ( prop in data ) {
				cache[ jQuery.camelCase( prop ) ] = data[ prop ];
			}
		}
		return cache;
	},
	get: function( owner, key ) {
		return key === undefined ?
			this.cache( owner ) :

			// Always use camelCase key (gh-2257)
			owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ];
	},
	access: function( owner, key, value ) {

		// In cases where either:
		//
		//   1. No key was specified
		//   2. A string key was specified, but no value provided
		//
		// Take the "read" path and allow the get method to determine
		// which value to return, respectively either:
		//
		//   1. The entire cache object
		//   2. The data stored at the key
		//
		if ( key === undefined ||
				( ( key && typeof key === "string" ) && value === undefined ) ) {

			return this.get( owner, key );
		}

		// When the key is not a string, or both a key and value
		// are specified, set or extend (existing objects) with either:
		//
		//   1. An object of properties
		//   2. A key and value
		//
		this.set( owner, key, value );

		// Since the "set" path can have two possible entry points
		// return the expected data based on which path was taken[*]
		return value !== undefined ? value : key;
	},
	remove: function( owner, key ) {
		var i,
			cache = owner[ this.expando ];

		if ( cache === undefined ) {
			return;
		}

		if ( key !== undefined ) {

			// Support array or space separated string of keys
			if ( jQuery.isArray( key ) ) {

				// If key is an array of keys...
				// We always set camelCase keys, so remove that.
				key = key.map( jQuery.camelCase );
			} else {
				key = jQuery.camelCase( key );

				// If a key with the spaces exists, use it.
				// Otherwise, create an array by matching non-whitespace
				key = key in cache ?
					[ key ] :
					( key.match( rnothtmlwhite ) || [] );
			}

			i = key.length;

			while ( i-- ) {
				delete cache[ key[ i ] ];
			}
		}

		// Remove the expando if there's no more data
		if ( key === undefined || jQuery.isEmptyObject( cache ) ) {

			// Support: Chrome <=35 - 45
			// Webkit & Blink performance suffers when deleting properties
			// from DOM nodes, so set to undefined instead
			// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
			if ( owner.nodeType ) {
				owner[ this.expando ] = undefined;
			} else {
				delete owner[ this.expando ];
			}
		}
	},
	hasData: function( owner ) {
		var cache = owner[ this.expando ];
		return cache !== undefined && !jQuery.isEmptyObject( cache );
	}
};
var dataPriv = new Data();

var dataUser = new Data();



//	Implementation Summary
//
//	1. Enforce API surface and semantic compatibility with 1.9.x branch
//	2. Improve the module's maintainability by reducing the storage
//		paths to a single mechanism.
//	3. Use the same single mechanism to support "private" and "user" data.
//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
//	5. Avoid exposing implementation details on user objects (eg. expando properties)
//	6. Provide a clear path for implementation upgrade to WeakMap in 2014

var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
	rmultiDash = /[A-Z]/g;

function getData( data ) {
	if ( data === "true" ) {
		return true;
	}

	if ( data === "false" ) {
		return false;
	}

	if ( data === "null" ) {
		return null;
	}

	// Only convert to a number if it doesn't change the string
	if ( data === +data + "" ) {
		return +data;
	}

	if ( rbrace.test( data ) ) {
		return JSON.parse( data );
	}

	return data;
}

function dataAttr( elem, key, data ) {
	var name;

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {
		name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = getData( data );
			} catch ( e ) {}

			// Make sure we set the data so it isn't changed later
			dataUser.set( elem, key, data );
		} else {
			data = undefined;
		}
	}
	return data;
}

jQuery.extend( {
	hasData: function( elem ) {
		return dataUser.hasData( elem ) || dataPriv.hasData( elem );
	},

	data: function( elem, name, data ) {
		return dataUser.access( elem, name, data );
	},

	removeData: function( elem, name ) {
		dataUser.remove( elem, name );
	},

	// TODO: Now that all calls to _data and _removeData have been replaced
	// with direct calls to dataPriv methods, these can be deprecated.
	_data: function( elem, name, data ) {
		return dataPriv.access( elem, name, data );
	},

	_removeData: function( elem, name ) {
		dataPriv.remove( elem, name );
	}
} );

jQuery.fn.extend( {
	data: function( key, value ) {
		var i, name, data,
			elem = this[ 0 ],
			attrs = elem && elem.attributes;

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = dataUser.get( elem );

				if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
					i = attrs.length;
					while ( i-- ) {

						// Support: IE 11 only
						// The attrs elements can be null (#14894)
						if ( attrs[ i ] ) {
							name = attrs[ i ].name;
							if ( name.indexOf( "data-" ) === 0 ) {
								name = jQuery.camelCase( name.slice( 5 ) );
								dataAttr( elem, name, data[ name ] );
							}
						}
					}
					dataPriv.set( elem, "hasDataAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each( function() {
				dataUser.set( this, key );
			} );
		}

		return access( this, function( value ) {
			var data;

			// The calling jQuery object (element matches) is not empty
			// (and therefore has an element appears at this[ 0 ]) and the
			// `value` parameter was not undefined. An empty jQuery object
			// will result in `undefined` for elem = this[ 0 ] which will
			// throw an exception if an attempt to read a data cache is made.
			if ( elem && value === undefined ) {

				// Attempt to get data from the cache
				// The key will always be camelCased in Data
				data = dataUser.get( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to "discover" the data in
				// HTML5 custom data-* attrs
				data = dataAttr( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// We tried really hard, but the data doesn't exist.
				return;
			}

			// Set the data...
			this.each( function() {

				// We always store the camelCased key
				dataUser.set( this, key, value );
			} );
		}, null, value, arguments.length > 1, null, true );
	},

	removeData: function( key ) {
		return this.each( function() {
			dataUser.remove( this, key );
		} );
	}
} );


jQuery.extend( {
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) {
			type = ( type || "fx" ) + "queue";
			queue = dataPriv.get( elem, type );

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || jQuery.isArray( data ) ) {
					queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};

		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {
			fn = queue.shift();
			startLength--;
		}

		if ( fn ) {

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {
				queue.unshift( "inprogress" );
			}

			// Clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {
			hooks.empty.fire();
		}
	},

	// Not public - generate a queueHooks object, or return the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
			empty: jQuery.Callbacks( "once memory" ).add( function() {
				dataPriv.remove( elem, [ type + "queue", key ] );
			} )
		} );
	}
} );

jQuery.fn.extend( {
	queue: function( type, data ) {
		var setter = 2;

		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
			setter--;
		}

		if ( arguments.length < setter ) {
			return jQuery.queue( this[ 0 ], type );
		}

		return data === undefined ?
			this :
			this.each( function() {
				var queue = jQuery.queue( this, type, data );

				// Ensure a hooks for this queue
				jQuery._queueHooks( this, type );

				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
					jQuery.dequeue( this, type );
				}
			} );
	},
	dequeue: function( type ) {
		return this.each( function() {
			jQuery.dequeue( this, type );
		} );
	},
	clearQueue: function( type ) {
		return this.queue( type || "fx", [] );
	},

	// Get a promise resolved when queues of a certain type
	// are emptied (fx is the type by default)
	promise: function( type, obj ) {
		var tmp,
			count = 1,
			defer = jQuery.Deferred(),
			elements = this,
			i = this.length,
			resolve = function() {
				if ( !( --count ) ) {
					defer.resolveWith( elements, [ elements ] );
				}
			};

		if ( typeof type !== "string" ) {
			obj = type;
			type = undefined;
		}
		type = type || "fx";

		while ( i-- ) {
			tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
			if ( tmp && tmp.empty ) {
				count++;
				tmp.empty.add( resolve );
			}
		}
		resolve();
		return defer.promise( obj );
	}
} );
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;

var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );


var cssExpand = [ "Top", "Right", "Bottom", "Left" ];

var isHiddenWithinTree = function( elem, el ) {

		// isHiddenWithinTree might be called from jQuery#filter function;
		// in that case, element will be second argument
		elem = el || elem;

		// Inline style trumps all
		return elem.style.display === "none" ||
			elem.style.display === "" &&

			// Otherwise, check computed style
			// Support: Firefox <=43 - 45
			// Disconnected elements can have computed display: none, so first confirm that elem is
			// in the document.
			jQuery.contains( elem.ownerDocument, elem ) &&

			jQuery.css( elem, "display" ) === "none";
	};

var swap = function( elem, options, callback, args ) {
	var ret, name,
		old = {};

	// Remember the old values, and insert the new ones
	for ( name in options ) {
		old[ name ] = elem.style[ name ];
		elem.style[ name ] = options[ name ];
	}

	ret = callback.apply( elem, args || [] );

	// Revert the old values
	for ( name in options ) {
		elem.style[ name ] = old[ name ];
	}

	return ret;
};




function adjustCSS( elem, prop, valueParts, tween ) {
	var adjusted,
		scale = 1,
		maxIterations = 20,
		currentValue = tween ?
			function() {
				return tween.cur();
			} :
			function() {
				return jQuery.css( elem, prop, "" );
			},
		initial = currentValue(),
		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),

		// Starting value computation is required for potential unit mismatches
		initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
			rcssNum.exec( jQuery.css( elem, prop ) );

	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {

		// Trust units reported by jQuery.css
		unit = unit || initialInUnit[ 3 ];

		// Make sure we update the tween properties later on
		valueParts = valueParts || [];

		// Iteratively approximate from a nonzero starting point
		initialInUnit = +initial || 1;

		do {

			// If previous iteration zeroed out, double until we get *something*.
			// Use string for doubling so we don't accidentally see scale as unchanged below
			scale = scale || ".5";

			// Adjust and apply
			initialInUnit = initialInUnit / scale;
			jQuery.style( elem, prop, initialInUnit + unit );

		// Update scale, tolerating zero or NaN from tween.cur()
		// Break the loop if scale is unchanged or perfect, or if we've just had enough.
		} while (
			scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations
		);
	}

	if ( valueParts ) {
		initialInUnit = +initialInUnit || +initial || 0;

		// Apply relative offset (+=/-=) if specified
		adjusted = valueParts[ 1 ] ?
			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
			+valueParts[ 2 ];
		if ( tween ) {
			tween.unit = unit;
			tween.start = initialInUnit;
			tween.end = adjusted;
		}
	}
	return adjusted;
}


var defaultDisplayMap = {};

function getDefaultDisplay( elem ) {
	var temp,
		doc = elem.ownerDocument,
		nodeName = elem.nodeName,
		display = defaultDisplayMap[ nodeName ];

	if ( display ) {
		return display;
	}

	temp = doc.body.appendChild( doc.createElement( nodeName ) );
	display = jQuery.css( temp, "display" );

	temp.parentNode.removeChild( temp );

	if ( display === "none" ) {
		display = "block";
	}
	defaultDisplayMap[ nodeName ] = display;

	return display;
}

function showHide( elements, show ) {
	var display, elem,
		values = [],
		index = 0,
		length = elements.length;

	// Determine new display value for elements that need to change
	for ( ; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}

		display = elem.style.display;
		if ( show ) {

			// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
			// check is required in this first loop unless we have a nonempty display value (either
			// inline or about-to-be-restored)
			if ( display === "none" ) {
				values[ index ] = dataPriv.get( elem, "display" ) || null;
				if ( !values[ index ] ) {
					elem.style.display = "";
				}
			}
			if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
				values[ index ] = getDefaultDisplay( elem );
			}
		} else {
			if ( display !== "none" ) {
				values[ index ] = "none";

				// Remember what we're overwriting
				dataPriv.set( elem, "display", display );
			}
		}
	}

	// Set the display of the elements in a second loop to avoid constant reflow
	for ( index = 0; index < length; index++ ) {
		if ( values[ index ] != null ) {
			elements[ index ].style.display = values[ index ];
		}
	}

	return elements;
}

jQuery.fn.extend( {
	show: function() {
		return showHide( this, true );
	},
	hide: function() {
		return showHide( this );
	},
	toggle: function( state ) {
		if ( typeof state === "boolean" ) {
			return state ? this.show() : this.hide();
		}

		return this.each( function() {
			if ( isHiddenWithinTree( this ) ) {
				jQuery( this ).show();
			} else {
				jQuery( this ).hide();
			}
		} );
	}
} );
var rcheckableType = ( /^(?:checkbox|radio)$/i );

var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i );

var rscriptType = ( /^$|\/(?:java|ecma)script/i );



// We have to close these tags to support XHTML (#13200)
var wrapMap = {

	// Support: IE <=9 only
	option: [ 1, "<select multiple='multiple'>", "</select>" ],

	// XHTML parsers do not magically insert elements in the
	// same way that tag soup parsers do. So we cannot shorten
	// this by omitting <tbody> or other required elements.
	thead: [ 1, "<table>", "</table>" ],
	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

	_default: [ 0, "", "" ]
};

// Support: IE <=9 only
wrapMap.optgroup = wrapMap.option;

wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;


function getAll( context, tag ) {

	// Support: IE <=9 - 11 only
	// Use typeof to avoid zero-argument method invocation on host objects (#15151)
	var ret;

	if ( typeof context.getElementsByTagName !== "undefined" ) {
		ret = context.getElementsByTagName( tag || "*" );

	} else if ( typeof context.querySelectorAll !== "undefined" ) {
		ret = context.querySelectorAll( tag || "*" );

	} else {
		ret = [];
	}

	if ( tag === undefined || tag && jQuery.nodeName( context, tag ) ) {
		return jQuery.merge( [ context ], ret );
	}

	return ret;
}


// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
	var i = 0,
		l = elems.length;

	for ( ; i < l; i++ ) {
		dataPriv.set(
			elems[ i ],
			"globalEval",
			!refElements || dataPriv.get( refElements[ i ], "globalEval" )
		);
	}
}


var rhtml = /<|&#?\w+;/;

function buildFragment( elems, context, scripts, selection, ignored ) {
	var elem, tmp, tag, wrap, contains, j,
		fragment = context.createDocumentFragment(),
		nodes = [],
		i = 0,
		l = elems.length;

	for ( ; i < l; i++ ) {
		elem = elems[ i ];

		if ( elem || elem === 0 ) {

			// Add nodes directly
			if ( jQuery.type( elem ) === "object" ) {

				// Support: Android <=4.0 only, PhantomJS 1 only
				// push.apply(_, arraylike) throws on ancient WebKit
				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );

			// Convert non-html into a text node
			} else if ( !rhtml.test( elem ) ) {
				nodes.push( context.createTextNode( elem ) );

			// Convert html into DOM nodes
			} else {
				tmp = tmp || fragment.appendChild( context.createElement( "div" ) );

				// Deserialize a standard representation
				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
				wrap = wrapMap[ tag ] || wrapMap._default;
				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];

				// Descend through wrappers to the right content
				j = wrap[ 0 ];
				while ( j-- ) {
					tmp = tmp.lastChild;
				}

				// Support: Android <=4.0 only, PhantomJS 1 only
				// push.apply(_, arraylike) throws on ancient WebKit
				jQuery.merge( nodes, tmp.childNodes );

				// Remember the top-level container
				tmp = fragment.firstChild;

				// Ensure the created nodes are orphaned (#12392)
				tmp.textContent = "";
			}
		}
	}

	// Remove wrapper from fragment
	fragment.textContent = "";

	i = 0;
	while ( ( elem = nodes[ i++ ] ) ) {

		// Skip elements already in the context collection (trac-4087)
		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
			if ( ignored ) {
				ignored.push( elem );
			}
			continue;
		}

		contains = jQuery.contains( elem.ownerDocument, elem );

		// Append to fragment
		tmp = getAll( fragment.appendChild( elem ), "script" );

		// Preserve script evaluation history
		if ( contains ) {
			setGlobalEval( tmp );
		}

		// Capture executables
		if ( scripts ) {
			j = 0;
			while ( ( elem = tmp[ j++ ] ) ) {
				if ( rscriptType.test( elem.type || "" ) ) {
					scripts.push( elem );
				}
			}
		}
	}

	return fragment;
}


( function() {
	var fragment = document.createDocumentFragment(),
		div = fragment.appendChild( document.createElement( "div" ) ),
		input = document.createElement( "input" );

	// Support: Android 4.0 - 4.3 only
	// Check state lost if the name is set (#11217)
	// Support: Windows Web Apps (WWA)
	// `name` and `type` must use .setAttribute for WWA (#14901)
	input.setAttribute( "type", "radio" );
	input.setAttribute( "checked", "checked" );
	input.setAttribute( "name", "t" );

	div.appendChild( input );

	// Support: Android <=4.1 only
	// Older WebKit doesn't clone checked state correctly in fragments
	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;

	// Support: IE <=11 only
	// Make sure textarea (and checkbox) defaultValue is properly cloned
	div.innerHTML = "<textarea>x</textarea>";
	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
} )();
var documentElement = document.documentElement;



var
	rkeyEvent = /^key/,
	rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
	rtypenamespace = /^([^.]*)(?:\.(.+)|)/;

function returnTrue() {
	return true;
}

function returnFalse() {
	return false;
}

// Support: IE <=9 only
// See #13393 for more info
function safeActiveElement() {
	try {
		return document.activeElement;
	} catch ( err ) { }
}

function on( elem, types, selector, data, fn, one ) {
	var origFn, type;

	// Types can be a map of types/handlers
	if ( typeof types === "object" ) {

		// ( types-Object, selector, data )
		if ( typeof selector !== "string" ) {

			// ( types-Object, data )
			data = data || selector;
			selector = undefined;
		}
		for ( type in types ) {
			on( elem, type, selector, data, types[ type ], one );
		}
		return elem;
	}

	if ( data == null && fn == null ) {

		// ( types, fn )
		fn = selector;
		data = selector = undefined;
	} else if ( fn == null ) {
		if ( typeof selector === "string" ) {

			// ( types, selector, fn )
			fn = data;
			data = undefined;
		} else {

			// ( types, data, fn )
			fn = data;
			data = selector;
			selector = undefined;
		}
	}
	if ( fn === false ) {
		fn = returnFalse;
	} else if ( !fn ) {
		return elem;
	}

	if ( one === 1 ) {
		origFn = fn;
		fn = function( event ) {

			// Can use an empty set, since event contains the info
			jQuery().off( event );
			return origFn.apply( this, arguments );
		};

		// Use same guid so caller can remove using origFn
		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
	}
	return elem.each( function() {
		jQuery.event.add( this, types, fn, data, selector );
	} );
}

/*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
jQuery.event = {

	global: {},

	add: function( elem, types, handler, data, selector ) {

		var handleObjIn, eventHandle, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = dataPriv.get( elem );

		// Don't attach events to noData or text/comment nodes (but allow plain objects)
		if ( !elemData ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// Ensure that invalid selectors throw exceptions at attach time
		// Evaluate against documentElement in case elem is a non-element node (e.g., document)
		if ( selector ) {
			jQuery.find.matchesSelector( documentElement, selector );
		}

		// Make sure that the handler has a unique ID, used to find/remove it later
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// Init the element's event structure and main handler, if this is the first
		if ( !( events = elemData.events ) ) {
			events = elemData.events = {};
		}
		if ( !( eventHandle = elemData.handle ) ) {
			eventHandle = elemData.handle = function( e ) {

				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
			};
		}

		// Handle multiple events separated by a space
		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// There *must* be a type, no attaching namespace-only handlers
			if ( !type ) {
				continue;
			}

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// handleObj is passed to all event handlers
			handleObj = jQuery.extend( {
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				namespace: namespaces.join( "." )
			}, handleObjIn );

			// Init the event handler queue if we're the first
			if ( !( handlers = events[ type ] ) ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// Only use addEventListener if the special events handler returns false
				if ( !special.setup ||
					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {

					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

	},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {

		var j, origCount, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );

		if ( !elemData || !( events = elemData.events ) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// Unbind all events (on this namespace, if provided) for the element
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector ? special.delegateType : special.bindType ) || type;
			handlers = events[ type ] || [];
			tmp = tmp[ 2 ] &&
				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			while ( j-- ) {
				handleObj = handlers[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector ||
						selector === "**" && handleObj.selector ) ) {
					handlers.splice( j, 1 );

					if ( handleObj.selector ) {
						handlers.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown ||
					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {

					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove data and the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			dataPriv.remove( elem, "handle events" );
		}
	},

	dispatch: function( nativeEvent ) {

		// Make a writable jQuery.Event from the native event object
		var event = jQuery.event.fix( nativeEvent );

		var i, j, ret, matched, handleObj, handlerQueue,
			args = new Array( arguments.length ),
			handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
			special = jQuery.event.special[ event.type ] || {};

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[ 0 ] = event;

		for ( i = 1; i < arguments.length; i++ ) {
			args[ i ] = arguments[ i ];
		}

		event.delegateTarget = this;

		// Call the preDispatch hook for the mapped type, and let it bail if desired
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// Determine handlers
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// Run delegates first; they may want to stop propagation beneath us
		i = 0;
		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			while ( ( handleObj = matched.handlers[ j++ ] ) &&
				!event.isImmediatePropagationStopped() ) {

				// Triggered event must either 1) have no namespace, or 2) have namespace(s)
				// a subset or equal to those in the bound event (both can have no namespace).
				if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {

					event.handleObj = handleObj;
					event.data = handleObj.data;

					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
						handleObj.handler ).apply( matched.elem, args );

					if ( ret !== undefined ) {
						if ( ( event.result = ret ) === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		// Call the postDispatch hook for the mapped type
		if ( special.postDispatch ) {
			special.postDispatch.call( this, event );
		}

		return event.result;
	},

	handlers: function( event, handlers ) {
		var i, handleObj, sel, matchedHandlers, matchedSelectors,
			handlerQueue = [],
			delegateCount = handlers.delegateCount,
			cur = event.target;

		// Find delegate handlers
		if ( delegateCount &&

			// Support: IE <=9
			// Black-hole SVG <use> instance trees (trac-13180)
			cur.nodeType &&

			// Support: Firefox <=42
			// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
			// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
			// Support: IE 11 only
			// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
			!( event.type === "click" && event.button >= 1 ) ) {

			for ( ; cur !== this; cur = cur.parentNode || this ) {

				// Don't check non-elements (#13208)
				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
				if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
					matchedHandlers = [];
					matchedSelectors = {};
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// Don't conflict with Object.prototype properties (#13203)
						sel = handleObj.selector + " ";

						if ( matchedSelectors[ sel ] === undefined ) {
							matchedSelectors[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) > -1 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						if ( matchedSelectors[ sel ] ) {
							matchedHandlers.push( handleObj );
						}
					}
					if ( matchedHandlers.length ) {
						handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
					}
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		cur = this;
		if ( delegateCount < handlers.length ) {
			handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
		}

		return handlerQueue;
	},

	addProp: function( name, hook ) {
		Object.defineProperty( jQuery.Event.prototype, name, {
			enumerable: true,
			configurable: true,

			get: jQuery.isFunction( hook ) ?
				function() {
					if ( this.originalEvent ) {
							return hook( this.originalEvent );
					}
				} :
				function() {
					if ( this.originalEvent ) {
							return this.originalEvent[ name ];
					}
				},

			set: function( value ) {
				Object.defineProperty( this, name, {
					enumerable: true,
					configurable: true,
					writable: true,
					value: value
				} );
			}
		} );
	},

	fix: function( originalEvent ) {
		return originalEvent[ jQuery.expando ] ?
			originalEvent :
			new jQuery.Event( originalEvent );
	},

	special: {
		load: {

			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		focus: {

			// Fire native event if possible so blur/focus sequence is correct
			trigger: function() {
				if ( this !== safeActiveElement() && this.focus ) {
					this.focus();
					return false;
				}
			},
			delegateType: "focusin"
		},
		blur: {
			trigger: function() {
				if ( this === safeActiveElement() && this.blur ) {
					this.blur();
					return false;
				}
			},
			delegateType: "focusout"
		},
		click: {

			// For checkbox, fire native event so checked state will be right
			trigger: function() {
				if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
					this.click();
					return false;
				}
			},

			// For cross-browser consistency, don't fire native .click() on links
			_default: function( event ) {
				return jQuery.nodeName( event.target, "a" );
			}
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	}
};

jQuery.removeEvent = function( elem, type, handle ) {

	// This "if" is needed for plain objects
	if ( elem.removeEventListener ) {
		elem.removeEventListener( type, handle );
	}
};

jQuery.Event = function( src, props ) {

	// Allow instantiation without the 'new' keyword
	if ( !( this instanceof jQuery.Event ) ) {
		return new jQuery.Event( src, props );
	}

	// Event object
	if ( src && src.type ) {
		this.originalEvent = src;
		this.type = src.type;

		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = src.defaultPrevented ||
				src.defaultPrevented === undefined &&

				// Support: Android <=2.3 only
				src.returnValue === false ?
			returnTrue :
			returnFalse;

		// Create target properties
		// Support: Safari <=6 - 7 only
		// Target should not be a text node (#504, #13143)
		this.target = ( src.target && src.target.nodeType === 3 ) ?
			src.target.parentNode :
			src.target;

		this.currentTarget = src.currentTarget;
		this.relatedTarget = src.relatedTarget;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || jQuery.now();

	// Mark it as fixed
	this[ jQuery.expando ] = true;
};

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	constructor: jQuery.Event,
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse,
	isSimulated: false,

	preventDefault: function() {
		var e = this.originalEvent;

		this.isDefaultPrevented = returnTrue;

		if ( e && !this.isSimulated ) {
			e.preventDefault();
		}
	},
	stopPropagation: function() {
		var e = this.originalEvent;

		this.isPropagationStopped = returnTrue;

		if ( e && !this.isSimulated ) {
			e.stopPropagation();
		}
	},
	stopImmediatePropagation: function() {
		var e = this.originalEvent;

		this.isImmediatePropagationStopped = returnTrue;

		if ( e && !this.isSimulated ) {
			e.stopImmediatePropagation();
		}

		this.stopPropagation();
	}
};

// Includes all common event props including KeyEvent and MouseEvent specific props
jQuery.each( {
	altKey: true,
	bubbles: true,
	cancelable: true,
	changedTouches: true,
	ctrlKey: true,
	detail: true,
	eventPhase: true,
	metaKey: true,
	pageX: true,
	pageY: true,
	shiftKey: true,
	view: true,
	"char": true,
	charCode: true,
	key: true,
	keyCode: true,
	button: true,
	buttons: true,
	clientX: true,
	clientY: true,
	offsetX: true,
	offsetY: true,
	pointerId: true,
	pointerType: true,
	screenX: true,
	screenY: true,
	targetTouches: true,
	toElement: true,
	touches: true,

	which: function( event ) {
		var button = event.button;

		// Add which for key events
		if ( event.which == null && rkeyEvent.test( event.type ) ) {
			return event.charCode != null ? event.charCode : event.keyCode;
		}

		// Add which for click: 1 === left; 2 === middle; 3 === right
		if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
			if ( button & 1 ) {
				return 1;
			}

			if ( button & 2 ) {
				return 3;
			}

			if ( button & 4 ) {
				return 2;
			}

			return 0;
		}

		return event.which;
	}
}, jQuery.event.addProp );

// Create mouseenter/leave events using mouseover/out and event-time checks
// so that event delegation works in jQuery.
// Do the same for pointerenter/pointerleave and pointerover/pointerout
//
// Support: Safari 7 only
// Safari sends mouseenter too often; see:
// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
// for the description of the bug (it existed in older Chrome versions as well).
jQuery.each( {
	mouseenter: "mouseover",
	mouseleave: "mouseout",
	pointerenter: "pointerover",
	pointerleave: "pointerout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		delegateType: fix,
		bindType: fix,

		handle: function( event ) {
			var ret,
				target = this,
				related = event.relatedTarget,
				handleObj = event.handleObj;

			// For mouseenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
				event.type = handleObj.origType;
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
} );

jQuery.fn.extend( {

	on: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn );
	},
	one: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn, 1 );
	},
	off: function( types, selector, fn ) {
		var handleObj, type;
		if ( types && types.preventDefault && types.handleObj ) {

			// ( event )  dispatched jQuery.Event
			handleObj = types.handleObj;
			jQuery( types.delegateTarget ).off(
				handleObj.namespace ?
					handleObj.origType + "." + handleObj.namespace :
					handleObj.origType,
				handleObj.selector,
				handleObj.handler
			);
			return this;
		}
		if ( typeof types === "object" ) {

			// ( types-object [, selector] )
			for ( type in types ) {
				this.off( type, selector, types[ type ] );
			}
			return this;
		}
		if ( selector === false || typeof selector === "function" ) {

			// ( types [, fn] )
			fn = selector;
			selector = undefined;
		}
		if ( fn === false ) {
			fn = returnFalse;
		}
		return this.each( function() {
			jQuery.event.remove( this, types, fn, selector );
		} );
	}
} );


var

	/* eslint-disable max-len */

	// See https://github.com/eslint/eslint/issues/3229
	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,

	/* eslint-enable */

	// Support: IE <=10 - 11, Edge 12 - 13
	// In IE/Edge using regex groups here causes severe slowdowns.
	// See https://connect.microsoft.com/IE/feedback/details/1736512/
	rnoInnerhtml = /<script|<style|<link/i,

	// checked="checked" or checked
	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
	rscriptTypeMasked = /^true\/(.*)/,
	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;

function manipulationTarget( elem, content ) {
	if ( jQuery.nodeName( elem, "table" ) &&
		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {

		return elem.getElementsByTagName( "tbody" )[ 0 ] || elem;
	}

	return elem;
}

// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
	elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
	return elem;
}
function restoreScript( elem ) {
	var match = rscriptTypeMasked.exec( elem.type );

	if ( match ) {
		elem.type = match[ 1 ];
	} else {
		elem.removeAttribute( "type" );
	}

	return elem;
}

function cloneCopyEvent( src, dest ) {
	var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;

	if ( dest.nodeType !== 1 ) {
		return;
	}

	// 1. Copy private data: events, handlers, etc.
	if ( dataPriv.hasData( src ) ) {
		pdataOld = dataPriv.access( src );
		pdataCur = dataPriv.set( dest, pdataOld );
		events = pdataOld.events;

		if ( events ) {
			delete pdataCur.handle;
			pdataCur.events = {};

			for ( type in events ) {
				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
					jQuery.event.add( dest, type, events[ type ][ i ] );
				}
			}
		}
	}

	// 2. Copy user data
	if ( dataUser.hasData( src ) ) {
		udataOld = dataUser.access( src );
		udataCur = jQuery.extend( {}, udataOld );

		dataUser.set( dest, udataCur );
	}
}

// Fix IE bugs, see support tests
function fixInput( src, dest ) {
	var nodeName = dest.nodeName.toLowerCase();

	// Fails to persist the checked state of a cloned checkbox or radio button.
	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
		dest.checked = src.checked;

	// Fails to return the selected option to the default selected state when cloning options
	} else if ( nodeName === "input" || nodeName === "textarea" ) {
		dest.defaultValue = src.defaultValue;
	}
}

function domManip( collection, args, callback, ignored ) {

	// Flatten any nested arrays
	args = concat.apply( [], args );

	var fragment, first, scripts, hasScripts, node, doc,
		i = 0,
		l = collection.length,
		iNoClone = l - 1,
		value = args[ 0 ],
		isFunction = jQuery.isFunction( value );

	// We can't cloneNode fragments that contain checked, in WebKit
	if ( isFunction ||
			( l > 1 && typeof value === "string" &&
				!support.checkClone && rchecked.test( value ) ) ) {
		return collection.each( function( index ) {
			var self = collection.eq( index );
			if ( isFunction ) {
				args[ 0 ] = value.call( this, index, self.html() );
			}
			domManip( self, args, callback, ignored );
		} );
	}

	if ( l ) {
		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
		first = fragment.firstChild;

		if ( fragment.childNodes.length === 1 ) {
			fragment = first;
		}

		// Require either new content or an interest in ignored elements to invoke the callback
		if ( first || ignored ) {
			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
			hasScripts = scripts.length;

			// Use the original fragment for the last item
			// instead of the first because it can end up
			// being emptied incorrectly in certain situations (#8070).
			for ( ; i < l; i++ ) {
				node = fragment;

				if ( i !== iNoClone ) {
					node = jQuery.clone( node, true, true );

					// Keep references to cloned scripts for later restoration
					if ( hasScripts ) {

						// Support: Android <=4.0 only, PhantomJS 1 only
						// push.apply(_, arraylike) throws on ancient WebKit
						jQuery.merge( scripts, getAll( node, "script" ) );
					}
				}

				callback.call( collection[ i ], node, i );
			}

			if ( hasScripts ) {
				doc = scripts[ scripts.length - 1 ].ownerDocument;

				// Reenable scripts
				jQuery.map( scripts, restoreScript );

				// Evaluate executable scripts on first document insertion
				for ( i = 0; i < hasScripts; i++ ) {
					node = scripts[ i ];
					if ( rscriptType.test( node.type || "" ) &&
						!dataPriv.access( node, "globalEval" ) &&
						jQuery.contains( doc, node ) ) {

						if ( node.src ) {

							// Optional AJAX dependency, but won't run scripts if not present
							if ( jQuery._evalUrl ) {
								jQuery._evalUrl( node.src );
							}
						} else {
							DOMEval( node.textContent.replace( rcleanScript, "" ), doc );
						}
					}
				}
			}
		}
	}

	return collection;
}

function remove( elem, selector, keepData ) {
	var node,
		nodes = selector ? jQuery.filter( selector, elem ) : elem,
		i = 0;

	for ( ; ( node = nodes[ i ] ) != null; i++ ) {
		if ( !keepData && node.nodeType === 1 ) {
			jQuery.cleanData( getAll( node ) );
		}

		if ( node.parentNode ) {
			if ( keepData && jQuery.contains( node.ownerDocument, node ) ) {
				setGlobalEval( getAll( node, "script" ) );
			}
			node.parentNode.removeChild( node );
		}
	}

	return elem;
}

jQuery.extend( {
	htmlPrefilter: function( html ) {
		return html.replace( rxhtmlTag, "<$1></$2>" );
	},

	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
		var i, l, srcElements, destElements,
			clone = elem.cloneNode( true ),
			inPage = jQuery.contains( elem.ownerDocument, elem );

		// Fix IE cloning issues
		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
				!jQuery.isXMLDoc( elem ) ) {

			// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
			destElements = getAll( clone );
			srcElements = getAll( elem );

			for ( i = 0, l = srcElements.length; i < l; i++ ) {
				fixInput( srcElements[ i ], destElements[ i ] );
			}
		}

		// Copy the events from the original to the clone
		if ( dataAndEvents ) {
			if ( deepDataAndEvents ) {
				srcElements = srcElements || getAll( elem );
				destElements = destElements || getAll( clone );

				for ( i = 0, l = srcElements.length; i < l; i++ ) {
					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
				}
			} else {
				cloneCopyEvent( elem, clone );
			}
		}

		// Preserve script evaluation history
		destElements = getAll( clone, "script" );
		if ( destElements.length > 0 ) {
			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
		}

		// Return the cloned set
		return clone;
	},

	cleanData: function( elems ) {
		var data, elem, type,
			special = jQuery.event.special,
			i = 0;

		for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
			if ( acceptData( elem ) ) {
				if ( ( data = elem[ dataPriv.expando ] ) ) {
					if ( data.events ) {
						for ( type in data.events ) {
							if ( special[ type ] ) {
								jQuery.event.remove( elem, type );

							// This is a shortcut to avoid jQuery.event.remove's overhead
							} else {
								jQuery.removeEvent( elem, type, data.handle );
							}
						}
					}

					// Support: Chrome <=35 - 45+
					// Assign undefined instead of using delete, see Data#remove
					elem[ dataPriv.expando ] = undefined;
				}
				if ( elem[ dataUser.expando ] ) {

					// Support: Chrome <=35 - 45+
					// Assign undefined instead of using delete, see Data#remove
					elem[ dataUser.expando ] = undefined;
				}
			}
		}
	}
} );

jQuery.fn.extend( {
	detach: function( selector ) {
		return remove( this, selector, true );
	},

	remove: function( selector ) {
		return remove( this, selector );
	},

	text: function( value ) {
		return access( this, function( value ) {
			return value === undefined ?
				jQuery.text( this ) :
				this.empty().each( function() {
					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
						this.textContent = value;
					}
				} );
		}, null, value, arguments.length );
	},

	append: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.appendChild( elem );
			}
		} );
	},

	prepend: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.insertBefore( elem, target.firstChild );
			}
		} );
	},

	before: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this );
			}
		} );
	},

	after: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this.nextSibling );
			}
		} );
	},

	empty: function() {
		var elem,
			i = 0;

		for ( ; ( elem = this[ i ] ) != null; i++ ) {
			if ( elem.nodeType === 1 ) {

				// Prevent memory leaks
				jQuery.cleanData( getAll( elem, false ) );

				// Remove any remaining nodes
				elem.textContent = "";
			}
		}

		return this;
	},

	clone: function( dataAndEvents, deepDataAndEvents ) {
		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

		return this.map( function() {
			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
		} );
	},

	html: function( value ) {
		return access( this, function( value ) {
			var elem = this[ 0 ] || {},
				i = 0,
				l = this.length;

			if ( value === undefined && elem.nodeType === 1 ) {
				return elem.innerHTML;
			}

			// See if we can take a shortcut and just use innerHTML
			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {

				value = jQuery.htmlPrefilter( value );

				try {
					for ( ; i < l; i++ ) {
						elem = this[ i ] || {};

						// Remove element nodes and prevent memory leaks
						if ( elem.nodeType === 1 ) {
							jQuery.cleanData( getAll( elem, false ) );
							elem.innerHTML = value;
						}
					}

					elem = 0;

				// If using innerHTML throws an exception, use the fallback method
				} catch ( e ) {}
			}

			if ( elem ) {
				this.empty().append( value );
			}
		}, null, value, arguments.length );
	},

	replaceWith: function() {
		var ignored = [];

		// Make the changes, replacing each non-ignored context element with the new content
		return domManip( this, arguments, function( elem ) {
			var parent = this.parentNode;

			if ( jQuery.inArray( this, ignored ) < 0 ) {
				jQuery.cleanData( getAll( this ) );
				if ( parent ) {
					parent.replaceChild( elem, this );
				}
			}

		// Force callback invocation
		}, ignored );
	}
} );

jQuery.each( {
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function( name, original ) {
	jQuery.fn[ name ] = function( selector ) {
		var elems,
			ret = [],
			insert = jQuery( selector ),
			last = insert.length - 1,
			i = 0;

		for ( ; i <= last; i++ ) {
			elems = i === last ? this : this.clone( true );
			jQuery( insert[ i ] )[ original ]( elems );

			// Support: Android <=4.0 only, PhantomJS 1 only
			// .get() because push.apply(_, arraylike) throws on ancient WebKit
			push.apply( ret, elems.get() );
		}

		return this.pushStack( ret );
	};
} );
var rmargin = ( /^margin/ );

var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );

var getStyles = function( elem ) {

		// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
		// IE throws on elements created in popups
		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
		var view = elem.ownerDocument.defaultView;

		if ( !view || !view.opener ) {
			view = window;
		}

		return view.getComputedStyle( elem );
	};



( function() {

	// Executing both pixelPosition & boxSizingReliable tests require only one layout
	// so they're executed at the same time to save the second computation.
	function computeStyleTests() {

		// This is a singleton, we need to execute it only once
		if ( !div ) {
			return;
		}

		div.style.cssText =
			"box-sizing:border-box;" +
			"position:relative;display:block;" +
			"margin:auto;border:1px;padding:1px;" +
			"top:1%;width:50%";
		div.innerHTML = "";
		documentElement.appendChild( container );

		var divStyle = window.getComputedStyle( div );
		pixelPositionVal = divStyle.top !== "1%";

		// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
		reliableMarginLeftVal = divStyle.marginLeft === "2px";
		boxSizingReliableVal = divStyle.width === "4px";

		// Support: Android 4.0 - 4.3 only
		// Some styles come back with percentage values, even though they shouldn't
		div.style.marginRight = "50%";
		pixelMarginRightVal = divStyle.marginRight === "4px";

		documentElement.removeChild( container );

		// Nullify the div so it wouldn't be stored in the memory and
		// it will also be a sign that checks already performed
		div = null;
	}

	var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal,
		container = document.createElement( "div" ),
		div = document.createElement( "div" );

	// Finish early in limited (non-browser) environments
	if ( !div.style ) {
		return;
	}

	// Support: IE <=9 - 11 only
	// Style of cloned element affects source element cloned (#8908)
	div.style.backgroundClip = "content-box";
	div.cloneNode( true ).style.backgroundClip = "";
	support.clearCloneStyle = div.style.backgroundClip === "content-box";

	container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" +
		"padding:0;margin-top:1px;position:absolute";
	container.appendChild( div );

	jQuery.extend( support, {
		pixelPosition: function() {
			computeStyleTests();
			return pixelPositionVal;
		},
		boxSizingReliable: function() {
			computeStyleTests();
			return boxSizingReliableVal;
		},
		pixelMarginRight: function() {
			computeStyleTests();
			return pixelMarginRightVal;
		},
		reliableMarginLeft: function() {
			computeStyleTests();
			return reliableMarginLeftVal;
		}
	} );
} )();


function curCSS( elem, name, computed ) {
	var width, minWidth, maxWidth, ret,
		style = elem.style;

	computed = computed || getStyles( elem );

	// Support: IE <=9 only
	// getPropertyValue is only needed for .css('filter') (#12537)
	if ( computed ) {
		ret = computed.getPropertyValue( name ) || computed[ name ];

		if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
			ret = jQuery.style( elem, name );
		}

		// A tribute to the "awesome hack by Dean Edwards"
		// Android Browser returns percentage for some values,
		// but width seems to be reliably pixels.
		// This is against the CSSOM draft spec:
		// https://drafts.csswg.org/cssom/#resolved-values
		if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) {

			// Remember the original values
			width = style.width;
			minWidth = style.minWidth;
			maxWidth = style.maxWidth;

			// Put in the new values to get a computed value out
			style.minWidth = style.maxWidth = style.width = ret;
			ret = computed.width;

			// Revert the changed values
			style.width = width;
			style.minWidth = minWidth;
			style.maxWidth = maxWidth;
		}
	}

	return ret !== undefined ?

		// Support: IE <=9 - 11 only
		// IE returns zIndex value as an integer.
		ret + "" :
		ret;
}


function addGetHookIf( conditionFn, hookFn ) {

	// Define the hook, we'll check on the first run if it's really needed.
	return {
		get: function() {
			if ( conditionFn() ) {

				// Hook not needed (or it's not possible to use it due
				// to missing dependency), remove it.
				delete this.get;
				return;
			}

			// Hook needed; redefine it so that the support test is not executed again.
			return ( this.get = hookFn ).apply( this, arguments );
		}
	};
}


var

	// Swappable if display is none or starts with table
	// except "table", "table-cell", or "table-caption"
	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
	cssNormalTransform = {
		letterSpacing: "0",
		fontWeight: "400"
	},

	cssPrefixes = [ "Webkit", "Moz", "ms" ],
	emptyStyle = document.createElement( "div" ).style;

// Return a css property mapped to a potentially vendor prefixed property
function vendorPropName( name ) {

	// Shortcut for names that are not vendor prefixed
	if ( name in emptyStyle ) {
		return name;
	}

	// Check for vendor prefixed names
	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in emptyStyle ) {
			return name;
		}
	}
}

function setPositiveNumber( elem, value, subtract ) {

	// Any relative (+/-) values have already been
	// normalized at this point
	var matches = rcssNum.exec( value );
	return matches ?

		// Guard against undefined "subtract", e.g., when used as in cssHooks
		Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
		value;
}

function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
	var i,
		val = 0;

	// If we already have the right measurement, avoid augmentation
	if ( extra === ( isBorderBox ? "border" : "content" ) ) {
		i = 4;

	// Otherwise initialize for horizontal or vertical properties
	} else {
		i = name === "width" ? 1 : 0;
	}

	for ( ; i < 4; i += 2 ) {

		// Both box models exclude margin, so add it if we want it
		if ( extra === "margin" ) {
			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
		}

		if ( isBorderBox ) {

			// border-box includes padding, so remove it if we want content
			if ( extra === "content" ) {
				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
			}

			// At this point, extra isn't border nor margin, so remove border
			if ( extra !== "margin" ) {
				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		} else {

			// At this point, extra isn't content, so add padding
			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

			// At this point, extra isn't content nor padding, so add border
			if ( extra !== "padding" ) {
				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		}
	}

	return val;
}

function getWidthOrHeight( elem, name, extra ) {

	// Start with offset property, which is equivalent to the border-box value
	var val,
		valueIsBorderBox = true,
		styles = getStyles( elem ),
		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

	// Support: IE <=11 only
	// Running getBoundingClientRect on a disconnected node
	// in IE throws an error.
	if ( elem.getClientRects().length ) {
		val = elem.getBoundingClientRect()[ name ];
	}

	// Some non-html elements return undefined for offsetWidth, so check for null/undefined
	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
	if ( val <= 0 || val == null ) {

		// Fall back to computed then uncomputed css if necessary
		val = curCSS( elem, name, styles );
		if ( val < 0 || val == null ) {
			val = elem.style[ name ];
		}

		// Computed unit is not pixels. Stop here and return.
		if ( rnumnonpx.test( val ) ) {
			return val;
		}

		// Check for style in case a browser which returns unreliable values
		// for getComputedStyle silently falls back to the reliable elem.style
		valueIsBorderBox = isBorderBox &&
			( support.boxSizingReliable() || val === elem.style[ name ] );

		// Normalize "", auto, and prepare for extra
		val = parseFloat( val ) || 0;
	}

	// Use the active box-sizing model to add/subtract irrelevant styles
	return ( val +
		augmentWidthOrHeight(
			elem,
			name,
			extra || ( isBorderBox ? "border" : "content" ),
			valueIsBorderBox,
			styles
		)
	) + "px";
}

jQuery.extend( {

	// Add in style property hooks for overriding the default
	// behavior of getting and setting a style property
	cssHooks: {
		opacity: {
			get: function( elem, computed ) {
				if ( computed ) {

					// We should always get a number back from opacity
					var ret = curCSS( elem, "opacity" );
					return ret === "" ? "1" : ret;
				}
			}
		}
	},

	// Don't automatically add "px" to these possibly-unitless properties
	cssNumber: {
		"animationIterationCount": true,
		"columnCount": true,
		"fillOpacity": true,
		"flexGrow": true,
		"flexShrink": true,
		"fontWeight": true,
		"lineHeight": true,
		"opacity": true,
		"order": true,
		"orphans": true,
		"widows": true,
		"zIndex": true,
		"zoom": true
	},

	// Add in properties whose names you wish to fix before
	// setting or getting the value
	cssProps: {
		"float": "cssFloat"
	},

	// Get and set the style property on a DOM Node
	style: function( elem, name, value, extra ) {

		// Don't set styles on text and comment nodes
		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
			return;
		}

		// Make sure that we're working with the right name
		var ret, type, hooks,
			origName = jQuery.camelCase( name ),
			style = elem.style;

		name = jQuery.cssProps[ origName ] ||
			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

		// Gets hook for the prefixed version, then unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// Check if we're setting a value
		if ( value !== undefined ) {
			type = typeof value;

			// Convert "+=" or "-=" to relative numbers (#7345)
			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
				value = adjustCSS( elem, name, ret );

				// Fixes bug #9237
				type = "number";
			}

			// Make sure that null and NaN values aren't set (#7116)
			if ( value == null || value !== value ) {
				return;
			}

			// If a number was passed in, add the unit (except for certain CSS properties)
			if ( type === "number" ) {
				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
			}

			// background-* props affect original clone's values
			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
				style[ name ] = "inherit";
			}

			// If a hook was provided, use that value, otherwise just set the specified value
			if ( !hooks || !( "set" in hooks ) ||
				( value = hooks.set( elem, value, extra ) ) !== undefined ) {

				style[ name ] = value;
			}

		} else {

			// If a hook was provided get the non-computed value from there
			if ( hooks && "get" in hooks &&
				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {

				return ret;
			}

			// Otherwise just get the value from the style object
			return style[ name ];
		}
	},

	css: function( elem, name, extra, styles ) {
		var val, num, hooks,
			origName = jQuery.camelCase( name );

		// Make sure that we're working with the right name
		name = jQuery.cssProps[ origName ] ||
			( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName );

		// Try prefixed name followed by the unprefixed name
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// If a hook was provided get the computed value from there
		if ( hooks && "get" in hooks ) {
			val = hooks.get( elem, true, extra );
		}

		// Otherwise, if a way to get the computed value exists, use that
		if ( val === undefined ) {
			val = curCSS( elem, name, styles );
		}

		// Convert "normal" to computed value
		if ( val === "normal" && name in cssNormalTransform ) {
			val = cssNormalTransform[ name ];
		}

		// Make numeric if forced or a qualifier was provided and val looks numeric
		if ( extra === "" || extra ) {
			num = parseFloat( val );
			return extra === true || isFinite( num ) ? num || 0 : val;
		}
		return val;
	}
} );

jQuery.each( [ "height", "width" ], function( i, name ) {
	jQuery.cssHooks[ name ] = {
		get: function( elem, computed, extra ) {
			if ( computed ) {

				// Certain elements can have dimension info if we invisibly show them
				// but it must have a current display style that would benefit
				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&

					// Support: Safari 8+
					// Table columns in Safari have non-zero offsetWidth & zero
					// getBoundingClientRect().width unless display is changed.
					// Support: IE <=11 only
					// Running getBoundingClientRect on a disconnected node
					// in IE throws an error.
					( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
						swap( elem, cssShow, function() {
							return getWidthOrHeight( elem, name, extra );
						} ) :
						getWidthOrHeight( elem, name, extra );
			}
		},

		set: function( elem, value, extra ) {
			var matches,
				styles = extra && getStyles( elem ),
				subtract = extra && augmentWidthOrHeight(
					elem,
					name,
					extra,
					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
					styles
				);

			// Convert to pixels if value adjustment is needed
			if ( subtract && ( matches = rcssNum.exec( value ) ) &&
				( matches[ 3 ] || "px" ) !== "px" ) {

				elem.style[ name ] = value;
				value = jQuery.css( elem, name );
			}

			return setPositiveNumber( elem, value, subtract );
		}
	};
} );

jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
	function( elem, computed ) {
		if ( computed ) {
			return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
				elem.getBoundingClientRect().left -
					swap( elem, { marginLeft: 0 }, function() {
						return elem.getBoundingClientRect().left;
					} )
				) + "px";
		}
	}
);

// These hooks are used by animate to expand properties
jQuery.each( {
	margin: "",
	padding: "",
	border: "Width"
}, function( prefix, suffix ) {
	jQuery.cssHooks[ prefix + suffix ] = {
		expand: function( value ) {
			var i = 0,
				expanded = {},

				// Assumes a single number if not a string
				parts = typeof value === "string" ? value.split( " " ) : [ value ];

			for ( ; i < 4; i++ ) {
				expanded[ prefix + cssExpand[ i ] + suffix ] =
					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
			}

			return expanded;
		}
	};

	if ( !rmargin.test( prefix ) ) {
		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
	}
} );

jQuery.fn.extend( {
	css: function( name, value ) {
		return access( this, function( elem, name, value ) {
			var styles, len,
				map = {},
				i = 0;

			if ( jQuery.isArray( name ) ) {
				styles = getStyles( elem );
				len = name.length;

				for ( ; i < len; i++ ) {
					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
				}

				return map;
			}

			return value !== undefined ?
				jQuery.style( elem, name, value ) :
				jQuery.css( elem, name );
		}, name, value, arguments.length > 1 );
	}
} );


function Tween( elem, options, prop, end, easing ) {
	return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;

Tween.prototype = {
	constructor: Tween,
	init: function( elem, options, prop, end, easing, unit ) {
		this.elem = elem;
		this.prop = prop;
		this.easing = easing || jQuery.easing._default;
		this.options = options;
		this.start = this.now = this.cur();
		this.end = end;
		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
	},
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];

		return hooks && hooks.get ?
			hooks.get( this ) :
			Tween.propHooks._default.get( this );
	},
	run: function( percent ) {
		var eased,
			hooks = Tween.propHooks[ this.prop ];

		if ( this.options.duration ) {
			this.pos = eased = jQuery.easing[ this.easing ](
				percent, this.options.duration * percent, 0, 1, this.options.duration
			);
		} else {
			this.pos = eased = percent;
		}
		this.now = ( this.end - this.start ) * eased + this.start;

		if ( this.options.step ) {
			this.options.step.call( this.elem, this.now, this );
		}

		if ( hooks && hooks.set ) {
			hooks.set( this );
		} else {
			Tween.propHooks._default.set( this );
		}
		return this;
	}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
	_default: {
		get: function( tween ) {
			var result;

			// Use a property on the element directly when it is not a DOM element,
			// or when there is no matching style property that exists.
			if ( tween.elem.nodeType !== 1 ||
				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
				return tween.elem[ tween.prop ];
			}

			// Passing an empty string as a 3rd parameter to .css will automatically
			// attempt a parseFloat and fallback to a string if the parse fails.
			// Simple values such as "10px" are parsed to Float;
			// complex values such as "rotate(1rad)" are returned as-is.
			result = jQuery.css( tween.elem, tween.prop, "" );

			// Empty strings, null, undefined and "auto" are converted to 0.
			return !result || result === "auto" ? 0 : result;
		},
		set: function( tween ) {

			// Use step hook for back compat.
			// Use cssHook if its there.
			// Use .style if available and use plain properties where available.
			if ( jQuery.fx.step[ tween.prop ] ) {
				jQuery.fx.step[ tween.prop ]( tween );
			} else if ( tween.elem.nodeType === 1 &&
				( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null ||
					jQuery.cssHooks[ tween.prop ] ) ) {
				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
			} else {
				tween.elem[ tween.prop ] = tween.now;
			}
		}
	}
};

// Support: IE <=9 only
// Panic based approach to setting things on disconnected nodes
Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
	set: function( tween ) {
		if ( tween.elem.nodeType && tween.elem.parentNode ) {
			tween.elem[ tween.prop ] = tween.now;
		}
	}
};

jQuery.easing = {
	linear: function( p ) {
		return p;
	},
	swing: function( p ) {
		return 0.5 - Math.cos( p * Math.PI ) / 2;
	},
	_default: "swing"
};

jQuery.fx = Tween.prototype.init;

// Back compat <1.8 extension point
jQuery.fx.step = {};




var
	fxNow, timerId,
	rfxtypes = /^(?:toggle|show|hide)$/,
	rrun = /queueHooks$/;

function raf() {
	if ( timerId ) {
		window.requestAnimationFrame( raf );
		jQuery.fx.tick();
	}
}

// Animations created synchronously will run synchronously
function createFxNow() {
	window.setTimeout( function() {
		fxNow = undefined;
	} );
	return ( fxNow = jQuery.now() );
}

// Generate parameters to create a standard animation
function genFx( type, includeWidth ) {
	var which,
		i = 0,
		attrs = { height: type };

	// If we include width, step value is 1 to do all cssExpand values,
	// otherwise step value is 2 to skip over Left and Right
	includeWidth = includeWidth ? 1 : 0;
	for ( ; i < 4; i += 2 - includeWidth ) {
		which = cssExpand[ i ];
		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
	}

	if ( includeWidth ) {
		attrs.opacity = attrs.width = type;
	}

	return attrs;
}

function createTween( value, prop, animation ) {
	var tween,
		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
		index = 0,
		length = collection.length;
	for ( ; index < length; index++ ) {
		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {

			// We're done with this property
			return tween;
		}
	}
}

function defaultPrefilter( elem, props, opts ) {
	var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
		isBox = "width" in props || "height" in props,
		anim = this,
		orig = {},
		style = elem.style,
		hidden = elem.nodeType && isHiddenWithinTree( elem ),
		dataShow = dataPriv.get( elem, "fxshow" );

	// Queue-skipping animations hijack the fx hooks
	if ( !opts.queue ) {
		hooks = jQuery._queueHooks( elem, "fx" );
		if ( hooks.unqueued == null ) {
			hooks.unqueued = 0;
			oldfire = hooks.empty.fire;
			hooks.empty.fire = function() {
				if ( !hooks.unqueued ) {
					oldfire();
				}
			};
		}
		hooks.unqueued++;

		anim.always( function() {

			// Ensure the complete handler is called before this completes
			anim.always( function() {
				hooks.unqueued--;
				if ( !jQuery.queue( elem, "fx" ).length ) {
					hooks.empty.fire();
				}
			} );
		} );
	}

	// Detect show/hide animations
	for ( prop in props ) {
		value = props[ prop ];
		if ( rfxtypes.test( value ) ) {
			delete props[ prop ];
			toggle = toggle || value === "toggle";
			if ( value === ( hidden ? "hide" : "show" ) ) {

				// Pretend to be hidden if this is a "show" and
				// there is still data from a stopped show/hide
				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
					hidden = true;

				// Ignore all other no-op show/hide data
				} else {
					continue;
				}
			}
			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
		}
	}

	// Bail out if this is a no-op like .hide().hide()
	propTween = !jQuery.isEmptyObject( props );
	if ( !propTween && jQuery.isEmptyObject( orig ) ) {
		return;
	}

	// Restrict "overflow" and "display" styles during box animations
	if ( isBox && elem.nodeType === 1 ) {

		// Support: IE <=9 - 11, Edge 12 - 13
		// Record all 3 overflow attributes because IE does not infer the shorthand
		// from identically-valued overflowX and overflowY
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];

		// Identify a display type, preferring old show/hide data over the CSS cascade
		restoreDisplay = dataShow && dataShow.display;
		if ( restoreDisplay == null ) {
			restoreDisplay = dataPriv.get( elem, "display" );
		}
		display = jQuery.css( elem, "display" );
		if ( display === "none" ) {
			if ( restoreDisplay ) {
				display = restoreDisplay;
			} else {

				// Get nonempty value(s) by temporarily forcing visibility
				showHide( [ elem ], true );
				restoreDisplay = elem.style.display || restoreDisplay;
				display = jQuery.css( elem, "display" );
				showHide( [ elem ] );
			}
		}

		// Animate inline elements as inline-block
		if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
			if ( jQuery.css( elem, "float" ) === "none" ) {

				// Restore the original display value at the end of pure show/hide animations
				if ( !propTween ) {
					anim.done( function() {
						style.display = restoreDisplay;
					} );
					if ( restoreDisplay == null ) {
						display = style.display;
						restoreDisplay = display === "none" ? "" : display;
					}
				}
				style.display = "inline-block";
			}
		}
	}

	if ( opts.overflow ) {
		style.overflow = "hidden";
		anim.always( function() {
			style.overflow = opts.overflow[ 0 ];
			style.overflowX = opts.overflow[ 1 ];
			style.overflowY = opts.overflow[ 2 ];
		} );
	}

	// Implement show/hide animations
	propTween = false;
	for ( prop in orig ) {

		// General show/hide setup for this element animation
		if ( !propTween ) {
			if ( dataShow ) {
				if ( "hidden" in dataShow ) {
					hidden = dataShow.hidden;
				}
			} else {
				dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
			}

			// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
			if ( toggle ) {
				dataShow.hidden = !hidden;
			}

			// Show elements before animating them
			if ( hidden ) {
				showHide( [ elem ], true );
			}

			/* eslint-disable no-loop-func */

			anim.done( function() {

			/* eslint-enable no-loop-func */

				// The final step of a "hide" animation is actually hiding the element
				if ( !hidden ) {
					showHide( [ elem ] );
				}
				dataPriv.remove( elem, "fxshow" );
				for ( prop in orig ) {
					jQuery.style( elem, prop, orig[ prop ] );
				}
			} );
		}

		// Per-property setup
		propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
		if ( !( prop in dataShow ) ) {
			dataShow[ prop ] = propTween.start;
			if ( hidden ) {
				propTween.end = propTween.start;
				propTween.start = 0;
			}
		}
	}
}

function propFilter( props, specialEasing ) {
	var index, name, easing, value, hooks;

	// camelCase, specialEasing and expand cssHook pass
	for ( index in props ) {
		name = jQuery.camelCase( index );
		easing = specialEasing[ name ];
		value = props[ index ];
		if ( jQuery.isArray( value ) ) {
			easing = value[ 1 ];
			value = props[ index ] = value[ 0 ];
		}

		if ( index !== name ) {
			props[ name ] = value;
			delete props[ index ];
		}

		hooks = jQuery.cssHooks[ name ];
		if ( hooks && "expand" in hooks ) {
			value = hooks.expand( value );
			delete props[ name ];

			// Not quite $.extend, this won't overwrite existing keys.
			// Reusing 'index' because we have the correct "name"
			for ( index in value ) {
				if ( !( index in props ) ) {
					props[ index ] = value[ index ];
					specialEasing[ index ] = easing;
				}
			}
		} else {
			specialEasing[ name ] = easing;
		}
	}
}

function Animation( elem, properties, options ) {
	var result,
		stopped,
		index = 0,
		length = Animation.prefilters.length,
		deferred = jQuery.Deferred().always( function() {

			// Don't match elem in the :animated selector
			delete tick.elem;
		} ),
		tick = function() {
			if ( stopped ) {
				return false;
			}
			var currentTime = fxNow || createFxNow(),
				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),

				// Support: Android 2.3 only
				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
				temp = remaining / animation.duration || 0,
				percent = 1 - temp,
				index = 0,
				length = animation.tweens.length;

			for ( ; index < length; index++ ) {
				animation.tweens[ index ].run( percent );
			}

			deferred.notifyWith( elem, [ animation, percent, remaining ] );

			if ( percent < 1 && length ) {
				return remaining;
			} else {
				deferred.resolveWith( elem, [ animation ] );
				return false;
			}
		},
		animation = deferred.promise( {
			elem: elem,
			props: jQuery.extend( {}, properties ),
			opts: jQuery.extend( true, {
				specialEasing: {},
				easing: jQuery.easing._default
			}, options ),
			originalProperties: properties,
			originalOptions: options,
			startTime: fxNow || createFxNow(),
			duration: options.duration,
			tweens: [],
			createTween: function( prop, end ) {
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
						animation.opts.specialEasing[ prop ] || animation.opts.easing );
				animation.tweens.push( tween );
				return tween;
			},
			stop: function( gotoEnd ) {
				var index = 0,

					// If we are going to the end, we want to run all the tweens
					// otherwise we skip this part
					length = gotoEnd ? animation.tweens.length : 0;
				if ( stopped ) {
					return this;
				}
				stopped = true;
				for ( ; index < length; index++ ) {
					animation.tweens[ index ].run( 1 );
				}

				// Resolve when we played the last frame; otherwise, reject
				if ( gotoEnd ) {
					deferred.notifyWith( elem, [ animation, 1, 0 ] );
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		} ),
		props = animation.props;

	propFilter( props, animation.opts.specialEasing );

	for ( ; index < length; index++ ) {
		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
		if ( result ) {
			if ( jQuery.isFunction( result.stop ) ) {
				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
					jQuery.proxy( result.stop, result );
			}
			return result;
		}
	}

	jQuery.map( props, createTween, animation );

	if ( jQuery.isFunction( animation.opts.start ) ) {
		animation.opts.start.call( elem, animation );
	}

	jQuery.fx.timer(
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		} )
	);

	// attach callbacks from options
	return animation.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );
}

jQuery.Animation = jQuery.extend( Animation, {

	tweeners: {
		"*": [ function( prop, value ) {
			var tween = this.createTween( prop, value );
			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
			return tween;
		} ]
	},

	tweener: function( props, callback ) {
		if ( jQuery.isFunction( props ) ) {
			callback = props;
			props = [ "*" ];
		} else {
			props = props.match( rnothtmlwhite );
		}

		var prop,
			index = 0,
			length = props.length;

		for ( ; index < length; index++ ) {
			prop = props[ index ];
			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
			Animation.tweeners[ prop ].unshift( callback );
		}
	},

	prefilters: [ defaultPrefilter ],

	prefilter: function( callback, prepend ) {
		if ( prepend ) {
			Animation.prefilters.unshift( callback );
		} else {
			Animation.prefilters.push( callback );
		}
	}
} );

jQuery.speed = function( speed, easing, fn ) {
	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
		complete: fn || !fn && easing ||
			jQuery.isFunction( speed ) && speed,
		duration: speed,
		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
	};

	// Go to the end state if fx are off or if document is hidden
	if ( jQuery.fx.off || document.hidden ) {
		opt.duration = 0;

	} else {
		if ( typeof opt.duration !== "number" ) {
			if ( opt.duration in jQuery.fx.speeds ) {
				opt.duration = jQuery.fx.speeds[ opt.duration ];

			} else {
				opt.duration = jQuery.fx.speeds._default;
			}
		}
	}

	// Normalize opt.queue - true/undefined/null -> "fx"
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	// Queueing
	opt.old = opt.complete;

	opt.complete = function() {
		if ( jQuery.isFunction( opt.old ) ) {
			opt.old.call( this );
		}

		if ( opt.queue ) {
			jQuery.dequeue( this, opt.queue );
		}
	};

	return opt;
};

jQuery.fn.extend( {
	fadeTo: function( speed, to, easing, callback ) {

		// Show any hidden elements after setting opacity to 0
		return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()

			// Animate to the value specified
			.end().animate( { opacity: to }, speed, easing, callback );
	},
	animate: function( prop, speed, easing, callback ) {
		var empty = jQuery.isEmptyObject( prop ),
			optall = jQuery.speed( speed, easing, callback ),
			doAnimation = function() {

				// Operate on a copy of prop so per-property easing won't be lost
				var anim = Animation( this, jQuery.extend( {}, prop ), optall );

				// Empty animations, or finishing resolves immediately
				if ( empty || dataPriv.get( this, "finish" ) ) {
					anim.stop( true );
				}
			};
			doAnimation.finish = doAnimation;

		return empty || optall.queue === false ?
			this.each( doAnimation ) :
			this.queue( optall.queue, doAnimation );
	},
	stop: function( type, clearQueue, gotoEnd ) {
		var stopQueue = function( hooks ) {
			var stop = hooks.stop;
			delete hooks.stop;
			stop( gotoEnd );
		};

		if ( typeof type !== "string" ) {
			gotoEnd = clearQueue;
			clearQueue = type;
			type = undefined;
		}
		if ( clearQueue && type !== false ) {
			this.queue( type || "fx", [] );
		}

		return this.each( function() {
			var dequeue = true,
				index = type != null && type + "queueHooks",
				timers = jQuery.timers,
				data = dataPriv.get( this );

			if ( index ) {
				if ( data[ index ] && data[ index ].stop ) {
					stopQueue( data[ index ] );
				}
			} else {
				for ( index in data ) {
					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
						stopQueue( data[ index ] );
					}
				}
			}

			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this &&
					( type == null || timers[ index ].queue === type ) ) {

					timers[ index ].anim.stop( gotoEnd );
					dequeue = false;
					timers.splice( index, 1 );
				}
			}

			// Start the next in the queue if the last step wasn't forced.
			// Timers currently will call their complete callbacks, which
			// will dequeue but only if they were gotoEnd.
			if ( dequeue || !gotoEnd ) {
				jQuery.dequeue( this, type );
			}
		} );
	},
	finish: function( type ) {
		if ( type !== false ) {
			type = type || "fx";
		}
		return this.each( function() {
			var index,
				data = dataPriv.get( this ),
				queue = data[ type + "queue" ],
				hooks = data[ type + "queueHooks" ],
				timers = jQuery.timers,
				length = queue ? queue.length : 0;

			// Enable finishing flag on private data
			data.finish = true;

			// Empty the queue first
			jQuery.queue( this, type, [] );

			if ( hooks && hooks.stop ) {
				hooks.stop.call( this, true );
			}

			// Look for any active animations, and finish them
			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
					timers[ index ].anim.stop( true );
					timers.splice( index, 1 );
				}
			}

			// Look for any animations in the old queue and finish them
			for ( index = 0; index < length; index++ ) {
				if ( queue[ index ] && queue[ index ].finish ) {
					queue[ index ].finish.call( this );
				}
			}

			// Turn off finishing flag
			delete data.finish;
		} );
	}
} );

jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
	var cssFn = jQuery.fn[ name ];
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return speed == null || typeof speed === "boolean" ?
			cssFn.apply( this, arguments ) :
			this.animate( genFx( name, true ), speed, easing, callback );
	};
} );

// Generate shortcuts for custom animations
jQuery.each( {
	slideDown: genFx( "show" ),
	slideUp: genFx( "hide" ),
	slideToggle: genFx( "toggle" ),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" },
	fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return this.animate( props, speed, easing, callback );
	};
} );

jQuery.timers = [];
jQuery.fx.tick = function() {
	var timer,
		i = 0,
		timers = jQuery.timers;

	fxNow = jQuery.now();

	for ( ; i < timers.length; i++ ) {
		timer = timers[ i ];

		// Checks the timer has not already been removed
		if ( !timer() && timers[ i ] === timer ) {
			timers.splice( i--, 1 );
		}
	}

	if ( !timers.length ) {
		jQuery.fx.stop();
	}
	fxNow = undefined;
};

jQuery.fx.timer = function( timer ) {
	jQuery.timers.push( timer );
	if ( timer() ) {
		jQuery.fx.start();
	} else {
		jQuery.timers.pop();
	}
};

jQuery.fx.interval = 13;
jQuery.fx.start = function() {
	if ( !timerId ) {
		timerId = window.requestAnimationFrame ?
			window.requestAnimationFrame( raf ) :
			window.setInterval( jQuery.fx.tick, jQuery.fx.interval );
	}
};

jQuery.fx.stop = function() {
	if ( window.cancelAnimationFrame ) {
		window.cancelAnimationFrame( timerId );
	} else {
		window.clearInterval( timerId );
	}

	timerId = null;
};

jQuery.fx.speeds = {
	slow: 600,
	fast: 200,

	// Default speed
	_default: 400
};


// Based off of the plugin by Clint Helfers, with permission.
// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
jQuery.fn.delay = function( time, type ) {
	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
	type = type || "fx";

	return this.queue( type, function( next, hooks ) {
		var timeout = window.setTimeout( next, time );
		hooks.stop = function() {
			window.clearTimeout( timeout );
		};
	} );
};


( function() {
	var input = document.createElement( "input" ),
		select = document.createElement( "select" ),
		opt = select.appendChild( document.createElement( "option" ) );

	input.type = "checkbox";

	// Support: Android <=4.3 only
	// Default value for a checkbox should be "on"
	support.checkOn = input.value !== "";

	// Support: IE <=11 only
	// Must access selectedIndex to make default options select
	support.optSelected = opt.selected;

	// Support: IE <=11 only
	// An input loses its value after becoming a radio
	input = document.createElement( "input" );
	input.value = "t";
	input.type = "radio";
	support.radioValue = input.value === "t";
} )();


var boolHook,
	attrHandle = jQuery.expr.attrHandle;

jQuery.fn.extend( {
	attr: function( name, value ) {
		return access( this, jQuery.attr, name, value, arguments.length > 1 );
	},

	removeAttr: function( name ) {
		return this.each( function() {
			jQuery.removeAttr( this, name );
		} );
	}
} );

jQuery.extend( {
	attr: function( elem, name, value ) {
		var ret, hooks,
			nType = elem.nodeType;

		// Don't get/set attributes on text, comment and attribute nodes
		if ( nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		// Fallback to prop when attributes are not supported
		if ( typeof elem.getAttribute === "undefined" ) {
			return jQuery.prop( elem, name, value );
		}

		// Attribute hooks are determined by the lowercase version
		// Grab necessary hook if one is defined
		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
			hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
				( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
		}

		if ( value !== undefined ) {
			if ( value === null ) {
				jQuery.removeAttr( elem, name );
				return;
			}

			if ( hooks && "set" in hooks &&
				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
				return ret;
			}

			elem.setAttribute( name, value + "" );
			return value;
		}

		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
			return ret;
		}

		ret = jQuery.find.attr( elem, name );

		// Non-existent attributes return null, we normalize to undefined
		return ret == null ? undefined : ret;
	},

	attrHooks: {
		type: {
			set: function( elem, value ) {
				if ( !support.radioValue && value === "radio" &&
					jQuery.nodeName( elem, "input" ) ) {
					var val = elem.value;
					elem.setAttribute( "type", value );
					if ( val ) {
						elem.value = val;
					}
					return value;
				}
			}
		}
	},

	removeAttr: function( elem, value ) {
		var name,
			i = 0,

			// Attribute names can contain non-HTML whitespace characters
			// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
			attrNames = value && value.match( rnothtmlwhite );

		if ( attrNames && elem.nodeType === 1 ) {
			while ( ( name = attrNames[ i++ ] ) ) {
				elem.removeAttribute( name );
			}
		}
	}
} );

// Hooks for boolean attributes
boolHook = {
	set: function( elem, value, name ) {
		if ( value === false ) {

			// Remove boolean attributes when set to false
			jQuery.removeAttr( elem, name );
		} else {
			elem.setAttribute( name, name );
		}
		return name;
	}
};

jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
	var getter = attrHandle[ name ] || jQuery.find.attr;

	attrHandle[ name ] = function( elem, name, isXML ) {
		var ret, handle,
			lowercaseName = name.toLowerCase();

		if ( !isXML ) {

			// Avoid an infinite loop by temporarily removing this function from the getter
			handle = attrHandle[ lowercaseName ];
			attrHandle[ lowercaseName ] = ret;
			ret = getter( elem, name, isXML ) != null ?
				lowercaseName :
				null;
			attrHandle[ lowercaseName ] = handle;
		}
		return ret;
	};
} );




var rfocusable = /^(?:input|select|textarea|button)$/i,
	rclickable = /^(?:a|area)$/i;

jQuery.fn.extend( {
	prop: function( name, value ) {
		return access( this, jQuery.prop, name, value, arguments.length > 1 );
	},

	removeProp: function( name ) {
		return this.each( function() {
			delete this[ jQuery.propFix[ name ] || name ];
		} );
	}
} );

jQuery.extend( {
	prop: function( elem, name, value ) {
		var ret, hooks,
			nType = elem.nodeType;

		// Don't get/set properties on text, comment and attribute nodes
		if ( nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {

			// Fix name and attach hooks
			name = jQuery.propFix[ name ] || name;
			hooks = jQuery.propHooks[ name ];
		}

		if ( value !== undefined ) {
			if ( hooks && "set" in hooks &&
				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
				return ret;
			}

			return ( elem[ name ] = value );
		}

		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
			return ret;
		}

		return elem[ name ];
	},

	propHooks: {
		tabIndex: {
			get: function( elem ) {

				// Support: IE <=9 - 11 only
				// elem.tabIndex doesn't always return the
				// correct value when it hasn't been explicitly set
				// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
				// Use proper attribute retrieval(#12072)
				var tabindex = jQuery.find.attr( elem, "tabindex" );

				if ( tabindex ) {
					return parseInt( tabindex, 10 );
				}

				if (
					rfocusable.test( elem.nodeName ) ||
					rclickable.test( elem.nodeName ) &&
					elem.href
				) {
					return 0;
				}

				return -1;
			}
		}
	},

	propFix: {
		"for": "htmlFor",
		"class": "className"
	}
} );

// Support: IE <=11 only
// Accessing the selectedIndex property
// forces the browser to respect setting selected
// on the option
// The getter ensures a default option is selected
// when in an optgroup
// eslint rule "no-unused-expressions" is disabled for this code
// since it considers such accessions noop
if ( !support.optSelected ) {
	jQuery.propHooks.selected = {
		get: function( elem ) {

			/* eslint no-unused-expressions: "off" */

			var parent = elem.parentNode;
			if ( parent && parent.parentNode ) {
				parent.parentNode.selectedIndex;
			}
			return null;
		},
		set: function( elem ) {

			/* eslint no-unused-expressions: "off" */

			var parent = elem.parentNode;
			if ( parent ) {
				parent.selectedIndex;

				if ( parent.parentNode ) {
					parent.parentNode.selectedIndex;
				}
			}
		}
	};
}

jQuery.each( [
	"tabIndex",
	"readOnly",
	"maxLength",
	"cellSpacing",
	"cellPadding",
	"rowSpan",
	"colSpan",
	"useMap",
	"frameBorder",
	"contentEditable"
], function() {
	jQuery.propFix[ this.toLowerCase() ] = this;
} );




	// Strip and collapse whitespace according to HTML spec
	// https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace
	function stripAndCollapse( value ) {
		var tokens = value.match( rnothtmlwhite ) || [];
		return tokens.join( " " );
	}


function getClass( elem ) {
	return elem.getAttribute && elem.getAttribute( "class" ) || "";
}

jQuery.fn.extend( {
	addClass: function( value ) {
		var classes, elem, cur, curValue, clazz, j, finalValue,
			i = 0;

		if ( jQuery.isFunction( value ) ) {
			return this.each( function( j ) {
				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		if ( typeof value === "string" && value ) {
			classes = value.match( rnothtmlwhite ) || [];

			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );
				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

				if ( cur ) {
					j = 0;
					while ( ( clazz = classes[ j++ ] ) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}

					// Only assign if different to avoid unneeded rendering.
					finalValue = stripAndCollapse( cur );
					if ( curValue !== finalValue ) {
						elem.setAttribute( "class", finalValue );
					}
				}
			}
		}

		return this;
	},

	removeClass: function( value ) {
		var classes, elem, cur, curValue, clazz, j, finalValue,
			i = 0;

		if ( jQuery.isFunction( value ) ) {
			return this.each( function( j ) {
				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		if ( !arguments.length ) {
			return this.attr( "class", "" );
		}

		if ( typeof value === "string" && value ) {
			classes = value.match( rnothtmlwhite ) || [];

			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );

				// This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

				if ( cur ) {
					j = 0;
					while ( ( clazz = classes[ j++ ] ) ) {

						// Remove *all* instances
						while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
							cur = cur.replace( " " + clazz + " ", " " );
						}
					}

					// Only assign if different to avoid unneeded rendering.
					finalValue = stripAndCollapse( cur );
					if ( curValue !== finalValue ) {
						elem.setAttribute( "class", finalValue );
					}
				}
			}
		}

		return this;
	},

	toggleClass: function( value, stateVal ) {
		var type = typeof value;

		if ( typeof stateVal === "boolean" && type === "string" ) {
			return stateVal ? this.addClass( value ) : this.removeClass( value );
		}

		if ( jQuery.isFunction( value ) ) {
			return this.each( function( i ) {
				jQuery( this ).toggleClass(
					value.call( this, i, getClass( this ), stateVal ),
					stateVal
				);
			} );
		}

		return this.each( function() {
			var className, i, self, classNames;

			if ( type === "string" ) {

				// Toggle individual class names
				i = 0;
				self = jQuery( this );
				classNames = value.match( rnothtmlwhite ) || [];

				while ( ( className = classNames[ i++ ] ) ) {

					// Check each className given, space separated list
					if ( self.hasClass( className ) ) {
						self.removeClass( className );
					} else {
						self.addClass( className );
					}
				}

			// Toggle whole class name
			} else if ( value === undefined || type === "boolean" ) {
				className = getClass( this );
				if ( className ) {

					// Store className if set
					dataPriv.set( this, "__className__", className );
				}

				// If the element has a class name or if we're passed `false`,
				// then remove the whole classname (if there was one, the above saved it).
				// Otherwise bring back whatever was previously saved (if anything),
				// falling back to the empty string if nothing was stored.
				if ( this.setAttribute ) {
					this.setAttribute( "class",
						className || value === false ?
						"" :
						dataPriv.get( this, "__className__" ) || ""
					);
				}
			}
		} );
	},

	hasClass: function( selector ) {
		var className, elem,
			i = 0;

		className = " " + selector + " ";
		while ( ( elem = this[ i++ ] ) ) {
			if ( elem.nodeType === 1 &&
				( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
					return true;
			}
		}

		return false;
	}
} );




var rreturn = /\r/g;

jQuery.fn.extend( {
	val: function( value ) {
		var hooks, ret, isFunction,
			elem = this[ 0 ];

		if ( !arguments.length ) {
			if ( elem ) {
				hooks = jQuery.valHooks[ elem.type ] ||
					jQuery.valHooks[ elem.nodeName.toLowerCase() ];

				if ( hooks &&
					"get" in hooks &&
					( ret = hooks.get( elem, "value" ) ) !== undefined
				) {
					return ret;
				}

				ret = elem.value;

				// Handle most common string cases
				if ( typeof ret === "string" ) {
					return ret.replace( rreturn, "" );
				}

				// Handle cases where value is null/undef or number
				return ret == null ? "" : ret;
			}

			return;
		}

		isFunction = jQuery.isFunction( value );

		return this.each( function( i ) {
			var val;

			if ( this.nodeType !== 1 ) {
				return;
			}

			if ( isFunction ) {
				val = value.call( this, i, jQuery( this ).val() );
			} else {
				val = value;
			}

			// Treat null/undefined as ""; convert numbers to string
			if ( val == null ) {
				val = "";

			} else if ( typeof val === "number" ) {
				val += "";

			} else if ( jQuery.isArray( val ) ) {
				val = jQuery.map( val, function( value ) {
					return value == null ? "" : value + "";
				} );
			}

			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

			// If set returns undefined, fall back to normal setting
			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
				this.value = val;
			}
		} );
	}
} );

jQuery.extend( {
	valHooks: {
		option: {
			get: function( elem ) {

				var val = jQuery.find.attr( elem, "value" );
				return val != null ?
					val :

					// Support: IE <=10 - 11 only
					// option.text throws exceptions (#14686, #14858)
					// Strip and collapse whitespace
					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
					stripAndCollapse( jQuery.text( elem ) );
			}
		},
		select: {
			get: function( elem ) {
				var value, option, i,
					options = elem.options,
					index = elem.selectedIndex,
					one = elem.type === "select-one",
					values = one ? null : [],
					max = one ? index + 1 : options.length;

				if ( index < 0 ) {
					i = max;

				} else {
					i = one ? index : 0;
				}

				// Loop through all the selected options
				for ( ; i < max; i++ ) {
					option = options[ i ];

					// Support: IE <=9 only
					// IE8-9 doesn't update selected after form reset (#2551)
					if ( ( option.selected || i === index ) &&

							// Don't return options that are disabled or in a disabled optgroup
							!option.disabled &&
							( !option.parentNode.disabled ||
								!jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {

						// Get the specific value for the option
						value = jQuery( option ).val();

						// We don't need an array for one selects
						if ( one ) {
							return value;
						}

						// Multi-Selects return an array
						values.push( value );
					}
				}

				return values;
			},

			set: function( elem, value ) {
				var optionSet, option,
					options = elem.options,
					values = jQuery.makeArray( value ),
					i = options.length;

				while ( i-- ) {
					option = options[ i ];

					/* eslint-disable no-cond-assign */

					if ( option.selected =
						jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
					) {
						optionSet = true;
					}

					/* eslint-enable no-cond-assign */
				}

				// Force browsers to behave consistently when non-matching value is set
				if ( !optionSet ) {
					elem.selectedIndex = -1;
				}
				return values;
			}
		}
	}
} );

// Radios and checkboxes getter/setter
jQuery.each( [ "radio", "checkbox" ], function() {
	jQuery.valHooks[ this ] = {
		set: function( elem, value ) {
			if ( jQuery.isArray( value ) ) {
				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
			}
		}
	};
	if ( !support.checkOn ) {
		jQuery.valHooks[ this ].get = function( elem ) {
			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
		};
	}
} );




// Return jQuery for attributes-only inclusion


var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/;

jQuery.extend( jQuery.event, {

	trigger: function( event, data, elem, onlyHandlers ) {

		var i, cur, tmp, bubbleType, ontype, handle, special,
			eventPath = [ elem || document ],
			type = hasOwn.call( event, "type" ) ? event.type : event,
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];

		cur = tmp = elem = elem || document;

		// Don't do events on text and comment nodes
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

		if ( type.indexOf( "." ) > -1 ) {

			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split( "." );
			type = namespaces.shift();
			namespaces.sort();
		}
		ontype = type.indexOf( ":" ) < 0 && "on" + type;

		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
		event.isTrigger = onlyHandlers ? 2 : 3;
		event.namespace = namespaces.join( "." );
		event.rnamespace = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
			null;

		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}

		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );

		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push( cur );
				tmp = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === ( elem.ownerDocument || document ) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// Fire handlers on the event path
		i = 0;
		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {

			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;

			// jQuery handler
			handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
				dataPriv.get( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}

			// Native handler
			handle = ontype && cur[ ontype ];
			if ( handle && handle.apply && acceptData( cur ) ) {
				event.result = handle.apply( cur, data );
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}
		event.type = type;

		// If nobody prevented the default action, do it now
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if ( ( !special._default ||
				special._default.apply( eventPath.pop(), data ) === false ) &&
				acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name as the event.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];

					if ( tmp ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;
					elem[ type ]();
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	},

	// Piggyback on a donor event to simulate a different one
	// Used only for `focus(in | out)` events
	simulate: function( type, elem, event ) {
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{
				type: type,
				isSimulated: true
			}
		);

		jQuery.event.trigger( e, null, elem );
	}

} );

jQuery.fn.extend( {

	trigger: function( type, data ) {
		return this.each( function() {
			jQuery.event.trigger( type, data, this );
		} );
	},
	triggerHandler: function( type, data ) {
		var elem = this[ 0 ];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );
		}
	}
} );


jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup contextmenu" ).split( " " ),
	function( i, name ) {

	// Handle event binding
	jQuery.fn[ name ] = function( data, fn ) {
		return arguments.length > 0 ?
			this.on( name, null, data, fn ) :
			this.trigger( name );
	};
} );

jQuery.fn.extend( {
	hover: function( fnOver, fnOut ) {
		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
	}
} );




support.focusin = "onfocusin" in window;


// Support: Firefox <=44
// Firefox doesn't have focus(in | out) events
// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
//
// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
// focus(in | out) events fire after focus & blur events,
// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
if ( !support.focusin ) {
	jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {

		// Attach a single capturing handler on the document while someone wants focusin/focusout
		var handler = function( event ) {
			jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
		};

		jQuery.event.special[ fix ] = {
			setup: function() {
				var doc = this.ownerDocument || this,
					attaches = dataPriv.access( doc, fix );

				if ( !attaches ) {
					doc.addEventListener( orig, handler, true );
				}
				dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
			},
			teardown: function() {
				var doc = this.ownerDocument || this,
					attaches = dataPriv.access( doc, fix ) - 1;

				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					dataPriv.remove( doc, fix );

				} else {
					dataPriv.access( doc, fix, attaches );
				}
			}
		};
	} );
}
var location = window.location;

var nonce = jQuery.now();

var rquery = ( /\?/ );



// Cross-browser xml parsing
jQuery.parseXML = function( data ) {
	var xml;
	if ( !data || typeof data !== "string" ) {
		return null;
	}

	// Support: IE 9 - 11 only
	// IE throws on parseFromString with invalid input.
	try {
		xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
	} catch ( e ) {
		xml = undefined;
	}

	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
		jQuery.error( "Invalid XML: " + data );
	}
	return xml;
};


var
	rbracket = /\[\]$/,
	rCRLF = /\r?\n/g,
	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
	rsubmittable = /^(?:input|select|textarea|keygen)/i;

function buildParams( prefix, obj, traditional, add ) {
	var name;

	if ( jQuery.isArray( obj ) ) {

		// Serialize array item.
		jQuery.each( obj, function( i, v ) {
			if ( traditional || rbracket.test( prefix ) ) {

				// Treat each array item as a scalar.
				add( prefix, v );

			} else {

				// Item is non-scalar (array or object), encode its numeric index.
				buildParams(
					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
					v,
					traditional,
					add
				);
			}
		} );

	} else if ( !traditional && jQuery.type( obj ) === "object" ) {

		// Serialize object item.
		for ( name in obj ) {
			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
		}

	} else {

		// Serialize scalar item.
		add( prefix, obj );
	}
}

// Serialize an array of form elements or a set of
// key/values into a query string
jQuery.param = function( a, traditional ) {
	var prefix,
		s = [],
		add = function( key, valueOrFunction ) {

			// If value is a function, invoke it and use its return value
			var value = jQuery.isFunction( valueOrFunction ) ?
				valueOrFunction() :
				valueOrFunction;

			s[ s.length ] = encodeURIComponent( key ) + "=" +
				encodeURIComponent( value == null ? "" : value );
		};

	// If an array was passed in, assume that it is an array of form elements.
	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {

		// Serialize the form elements
		jQuery.each( a, function() {
			add( this.name, this.value );
		} );

	} else {

		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for ( prefix in a ) {
			buildParams( prefix, a[ prefix ], traditional, add );
		}
	}

	// Return the resulting serialization
	return s.join( "&" );
};

jQuery.fn.extend( {
	serialize: function() {
		return jQuery.param( this.serializeArray() );
	},
	serializeArray: function() {
		return this.map( function() {

			// Can add propHook for "elements" to filter or add form elements
			var elements = jQuery.prop( this, "elements" );
			return elements ? jQuery.makeArray( elements ) : this;
		} )
		.filter( function() {
			var type = this.type;

			// Use .is( ":disabled" ) so that fieldset[disabled] works
			return this.name && !jQuery( this ).is( ":disabled" ) &&
				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
				( this.checked || !rcheckableType.test( type ) );
		} )
		.map( function( i, elem ) {
			var val = jQuery( this ).val();

			if ( val == null ) {
				return null;
			}

			if ( jQuery.isArray( val ) ) {
				return jQuery.map( val, function( val ) {
					return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
				} );
			}

			return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
		} ).get();
	}
} );


var
	r20 = /%20/g,
	rhash = /#.*$/,
	rantiCache = /([?&])_=[^&]*/,
	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,

	// #7653, #8125, #8152: local protocol detection
	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
	rnoContent = /^(?:GET|HEAD)$/,
	rprotocol = /^\/\//,

	/* Prefilters
	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
	 * 2) These are called:
	 *    - BEFORE asking for a transport
	 *    - AFTER param serialization (s.data is a string if s.processData is true)
	 * 3) key is the dataType
	 * 4) the catchall symbol "*" can be used
	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
	 */
	prefilters = {},

	/* Transports bindings
	 * 1) key is the dataType
	 * 2) the catchall symbol "*" can be used
	 * 3) selection will start with transport dataType and THEN go to "*" if needed
	 */
	transports = {},

	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
	allTypes = "*/".concat( "*" ),

	// Anchor tag for parsing the document origin
	originAnchor = document.createElement( "a" );
	originAnchor.href = location.href;

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {

		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}

		var dataType,
			i = 0,
			dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];

		if ( jQuery.isFunction( func ) ) {

			// For each dataType in the dataTypeExpression
			while ( ( dataType = dataTypes[ i++ ] ) ) {

				// Prepend if requested
				if ( dataType[ 0 ] === "+" ) {
					dataType = dataType.slice( 1 ) || "*";
					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );

				// Otherwise append
				} else {
					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
				}
			}
		}
	};
}

// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {

	var inspected = {},
		seekingTransport = ( structure === transports );

	function inspect( dataType ) {
		var selected;
		inspected[ dataType ] = true;
		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
			if ( typeof dataTypeOrTransport === "string" &&
				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {

				options.dataTypes.unshift( dataTypeOrTransport );
				inspect( dataTypeOrTransport );
				return false;
			} else if ( seekingTransport ) {
				return !( selected = dataTypeOrTransport );
			}
		} );
		return selected;
	}

	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes #9887
function ajaxExtend( target, src ) {
	var key, deep,
		flatOptions = jQuery.ajaxSettings.flatOptions || {};

	for ( key in src ) {
		if ( src[ key ] !== undefined ) {
			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
		}
	}
	if ( deep ) {
		jQuery.extend( true, target, deep );
	}

	return target;
}

/* Handles responses to an ajax request:
 * - finds the right dataType (mediates between content-type and expected dataType)
 * - returns the corresponding response
 */
function ajaxHandleResponses( s, jqXHR, responses ) {

	var ct, type, finalDataType, firstDataType,
		contents = s.contents,
		dataTypes = s.dataTypes;

	// Remove auto dataType and get content-type in the process
	while ( dataTypes[ 0 ] === "*" ) {
		dataTypes.shift();
		if ( ct === undefined ) {
			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
		}
	}

	// Check if we're dealing with a known content-type
	if ( ct ) {
		for ( type in contents ) {
			if ( contents[ type ] && contents[ type ].test( ct ) ) {
				dataTypes.unshift( type );
				break;
			}
		}
	}

	// Check to see if we have a response for the expected dataType
	if ( dataTypes[ 0 ] in responses ) {
		finalDataType = dataTypes[ 0 ];
	} else {

		// Try convertible dataTypes
		for ( type in responses ) {
			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
				finalDataType = type;
				break;
			}
			if ( !firstDataType ) {
				firstDataType = type;
			}
		}

		// Or just use first one
		finalDataType = finalDataType || firstDataType;
	}

	// If we found a dataType
	// We add the dataType to the list if needed
	// and return the corresponding response
	if ( finalDataType ) {
		if ( finalDataType !== dataTypes[ 0 ] ) {
			dataTypes.unshift( finalDataType );
		}
		return responses[ finalDataType ];
	}
}

/* Chain conversions given the request and the original response
 * Also sets the responseXXX fields on the jqXHR instance
 */
function ajaxConvert( s, response, jqXHR, isSuccess ) {
	var conv2, current, conv, tmp, prev,
		converters = {},

		// Work with a copy of dataTypes in case we need to modify it for conversion
		dataTypes = s.dataTypes.slice();

	// Create converters map with lowercased keys
	if ( dataTypes[ 1 ] ) {
		for ( conv in s.converters ) {
			converters[ conv.toLowerCase() ] = s.converters[ conv ];
		}
	}

	current = dataTypes.shift();

	// Convert to each sequential dataType
	while ( current ) {

		if ( s.responseFields[ current ] ) {
			jqXHR[ s.responseFields[ current ] ] = response;
		}

		// Apply the dataFilter if provided
		if ( !prev && isSuccess && s.dataFilter ) {
			response = s.dataFilter( response, s.dataType );
		}

		prev = current;
		current = dataTypes.shift();

		if ( current ) {

			// There's only work to do if current dataType is non-auto
			if ( current === "*" ) {

				current = prev;

			// Convert response if prev dataType is non-auto and differs from current
			} else if ( prev !== "*" && prev !== current ) {

				// Seek a direct converter
				conv = converters[ prev + " " + current ] || converters[ "* " + current ];

				// If none found, seek a pair
				if ( !conv ) {
					for ( conv2 in converters ) {

						// If conv2 outputs current
						tmp = conv2.split( " " );
						if ( tmp[ 1 ] === current ) {

							// If prev can be converted to accepted input
							conv = converters[ prev + " " + tmp[ 0 ] ] ||
								converters[ "* " + tmp[ 0 ] ];
							if ( conv ) {

								// Condense equivalence converters
								if ( conv === true ) {
									conv = converters[ conv2 ];

								// Otherwise, insert the intermediate dataType
								} else if ( converters[ conv2 ] !== true ) {
									current = tmp[ 0 ];
									dataTypes.unshift( tmp[ 1 ] );
								}
								break;
							}
						}
					}
				}

				// Apply converter (if not an equivalence)
				if ( conv !== true ) {

					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s.throws ) {
						response = conv( response );
					} else {
						try {
							response = conv( response );
						} catch ( e ) {
							return {
								state: "parsererror",
								error: conv ? e : "No conversion from " + prev + " to " + current
							};
						}
					}
				}
			}
		}
	}

	return { state: "success", data: response };
}

jQuery.extend( {

	// Counter for holding the number of active queries
	active: 0,

	// Last-Modified header cache for next request
	lastModified: {},
	etag: {},

	ajaxSettings: {
		url: location.href,
		type: "GET",
		isLocal: rlocalProtocol.test( location.protocol ),
		global: true,
		processData: true,
		async: true,
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",

		/*
		timeout: 0,
		data: null,
		dataType: null,
		username: null,
		password: null,
		cache: null,
		throws: false,
		traditional: false,
		headers: {},
		*/

		accepts: {
			"*": allTypes,
			text: "text/plain",
			html: "text/html",
			xml: "application/xml, text/xml",
			json: "application/json, text/javascript"
		},

		contents: {
			xml: /\bxml\b/,
			html: /\bhtml/,
			json: /\bjson\b/
		},

		responseFields: {
			xml: "responseXML",
			text: "responseText",
			json: "responseJSON"
		},

		// Data converters
		// Keys separate source (or catchall "*") and destination types with a single space
		converters: {

			// Convert anything to text
			"* text": String,

			// Text to html (true = no transformation)
			"text html": true,

			// Evaluate text as a json expression
			"text json": JSON.parse,

			// Parse text as xml
			"text xml": jQuery.parseXML
		},

		// For options that shouldn't be deep extended:
		// you can add your own custom options here if
		// and when you create one that shouldn't be
		// deep extended (see ajaxExtend)
		flatOptions: {
			url: true,
			context: true
		}
	},

	// Creates a full fledged settings object into target
	// with both ajaxSettings and settings fields.
	// If target is omitted, writes into ajaxSettings.
	ajaxSetup: function( target, settings ) {
		return settings ?

			// Building a settings object
			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

			// Extending ajaxSettings
			ajaxExtend( jQuery.ajaxSettings, target );
	},

	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
	ajaxTransport: addToPrefiltersOrTransports( transports ),

	// Main method
	ajax: function( url, options ) {

		// If url is an object, simulate pre-1.5 signature
		if ( typeof url === "object" ) {
			options = url;
			url = undefined;
		}

		// Force options to be an object
		options = options || {};

		var transport,

			// URL without anti-cache param
			cacheURL,

			// Response headers
			responseHeadersString,
			responseHeaders,

			// timeout handle
			timeoutTimer,

			// Url cleanup var
			urlAnchor,

			// Request state (becomes false upon send and true upon completion)
			completed,

			// To know if global events are to be dispatched
			fireGlobals,

			// Loop variable
			i,

			// uncached part of the url
			uncached,

			// Create the final options object
			s = jQuery.ajaxSetup( {}, options ),

			// Callbacks context
			callbackContext = s.context || s,

			// Context for global events is callbackContext if it is a DOM node or jQuery collection
			globalEventContext = s.context &&
				( callbackContext.nodeType || callbackContext.jquery ) ?
					jQuery( callbackContext ) :
					jQuery.event,

			// Deferreds
			deferred = jQuery.Deferred(),
			completeDeferred = jQuery.Callbacks( "once memory" ),

			// Status-dependent callbacks
			statusCode = s.statusCode || {},

			// Headers (they are sent all at once)
			requestHeaders = {},
			requestHeadersNames = {},

			// Default abort message
			strAbort = "canceled",

			// Fake xhr
			jqXHR = {
				readyState: 0,

				// Builds headers hashtable if needed
				getResponseHeader: function( key ) {
					var match;
					if ( completed ) {
						if ( !responseHeaders ) {
							responseHeaders = {};
							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
								responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ];
							}
						}
						match = responseHeaders[ key.toLowerCase() ];
					}
					return match == null ? null : match;
				},

				// Raw string
				getAllResponseHeaders: function() {
					return completed ? responseHeadersString : null;
				},

				// Caches the header
				setRequestHeader: function( name, value ) {
					if ( completed == null ) {
						name = requestHeadersNames[ name.toLowerCase() ] =
							requestHeadersNames[ name.toLowerCase() ] || name;
						requestHeaders[ name ] = value;
					}
					return this;
				},

				// Overrides response content-type header
				overrideMimeType: function( type ) {
					if ( completed == null ) {
						s.mimeType = type;
					}
					return this;
				},

				// Status-dependent callbacks
				statusCode: function( map ) {
					var code;
					if ( map ) {
						if ( completed ) {

							// Execute the appropriate callbacks
							jqXHR.always( map[ jqXHR.status ] );
						} else {

							// Lazy-add the new callbacks in a way that preserves old ones
							for ( code in map ) {
								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
							}
						}
					}
					return this;
				},

				// Cancel the request
				abort: function( statusText ) {
					var finalText = statusText || strAbort;
					if ( transport ) {
						transport.abort( finalText );
					}
					done( 0, finalText );
					return this;
				}
			};

		// Attach deferreds
		deferred.promise( jqXHR );

		// Add protocol if not provided (prefilters might expect it)
		// Handle falsy url in the settings object (#10093: consistency with old signature)
		// We also use the url parameter if available
		s.url = ( ( url || s.url || location.href ) + "" )
			.replace( rprotocol, location.protocol + "//" );

		// Alias method option to type as per ticket #12004
		s.type = options.method || options.type || s.method || s.type;

		// Extract dataTypes list
		s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];

		// A cross-domain request is in order when the origin doesn't match the current origin.
		if ( s.crossDomain == null ) {
			urlAnchor = document.createElement( "a" );

			// Support: IE <=8 - 11, Edge 12 - 13
			// IE throws exception on accessing the href property if url is malformed,
			// e.g. http://example.com:80x/
			try {
				urlAnchor.href = s.url;

				// Support: IE <=8 - 11 only
				// Anchor's host property isn't correctly set when s.url is relative
				urlAnchor.href = urlAnchor.href;
				s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
					urlAnchor.protocol + "//" + urlAnchor.host;
			} catch ( e ) {

				// If there is an error parsing the URL, assume it is crossDomain,
				// it can be rejected by the transport if it is invalid
				s.crossDomain = true;
			}
		}

		// Convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" ) {
			s.data = jQuery.param( s.data, s.traditional );
		}

		// Apply prefilters
		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

		// If request was aborted inside a prefilter, stop there
		if ( completed ) {
			return jqXHR;
		}

		// We can fire global events as of now if asked to
		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
		fireGlobals = jQuery.event && s.global;

		// Watch for a new set of requests
		if ( fireGlobals && jQuery.active++ === 0 ) {
			jQuery.event.trigger( "ajaxStart" );
		}

		// Uppercase the type
		s.type = s.type.toUpperCase();

		// Determine if request has content
		s.hasContent = !rnoContent.test( s.type );

		// Save the URL in case we're toying with the If-Modified-Since
		// and/or If-None-Match header later on
		// Remove hash to simplify url manipulation
		cacheURL = s.url.replace( rhash, "" );

		// More options handling for requests with no content
		if ( !s.hasContent ) {

			// Remember the hash so we can put it back
			uncached = s.url.slice( cacheURL.length );

			// If data is available, append data to url
			if ( s.data ) {
				cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;

				// #9682: remove data so that it's not used in an eventual retry
				delete s.data;
			}

			// Add or update anti-cache param if needed
			if ( s.cache === false ) {
				cacheURL = cacheURL.replace( rantiCache, "$1" );
				uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached;
			}

			// Put hash and anti-cache on the URL that will be requested (gh-1732)
			s.url = cacheURL + uncached;

		// Change '%20' to '+' if this is encoded form body content (gh-2658)
		} else if ( s.data && s.processData &&
			( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
			s.data = s.data.replace( r20, "+" );
		}

		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
		if ( s.ifModified ) {
			if ( jQuery.lastModified[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
			}
			if ( jQuery.etag[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
			}
		}

		// Set the correct header, if data is being sent
		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
			jqXHR.setRequestHeader( "Content-Type", s.contentType );
		}

		// Set the Accepts header for the server, depending on the dataType
		jqXHR.setRequestHeader(
			"Accept",
			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
				s.accepts[ s.dataTypes[ 0 ] ] +
					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
				s.accepts[ "*" ]
		);

		// Check for headers option
		for ( i in s.headers ) {
			jqXHR.setRequestHeader( i, s.headers[ i ] );
		}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend &&
			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {

			// Abort if not done already and return
			return jqXHR.abort();
		}

		// Aborting is no longer a cancellation
		strAbort = "abort";

		// Install callbacks on deferreds
		completeDeferred.add( s.complete );
		jqXHR.done( s.success );
		jqXHR.fail( s.error );

		// Get transport
		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

		// If no transport, we auto-abort
		if ( !transport ) {
			done( -1, "No Transport" );
		} else {
			jqXHR.readyState = 1;

			// Send global event
			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
			}

			// If request was aborted inside ajaxSend, stop there
			if ( completed ) {
				return jqXHR;
			}

			// Timeout
			if ( s.async && s.timeout > 0 ) {
				timeoutTimer = window.setTimeout( function() {
					jqXHR.abort( "timeout" );
				}, s.timeout );
			}

			try {
				completed = false;
				transport.send( requestHeaders, done );
			} catch ( e ) {

				// Rethrow post-completion exceptions
				if ( completed ) {
					throw e;
				}

				// Propagate others as results
				done( -1, e );
			}
		}

		// Callback for when everything is done
		function done( status, nativeStatusText, responses, headers ) {
			var isSuccess, success, error, response, modified,
				statusText = nativeStatusText;

			// Ignore repeat invocations
			if ( completed ) {
				return;
			}

			completed = true;

			// Clear timeout if it exists
			if ( timeoutTimer ) {
				window.clearTimeout( timeoutTimer );
			}

			// Dereference transport for early garbage collection
			// (no matter how long the jqXHR object will be used)
			transport = undefined;

			// Cache response headers
			responseHeadersString = headers || "";

			// Set readyState
			jqXHR.readyState = status > 0 ? 4 : 0;

			// Determine if successful
			isSuccess = status >= 200 && status < 300 || status === 304;

			// Get response data
			if ( responses ) {
				response = ajaxHandleResponses( s, jqXHR, responses );
			}

			// Convert no matter what (that way responseXXX fields are always set)
			response = ajaxConvert( s, response, jqXHR, isSuccess );

			// If successful, handle type chaining
			if ( isSuccess ) {

				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
				if ( s.ifModified ) {
					modified = jqXHR.getResponseHeader( "Last-Modified" );
					if ( modified ) {
						jQuery.lastModified[ cacheURL ] = modified;
					}
					modified = jqXHR.getResponseHeader( "etag" );
					if ( modified ) {
						jQuery.etag[ cacheURL ] = modified;
					}
				}

				// if no content
				if ( status === 204 || s.type === "HEAD" ) {
					statusText = "nocontent";

				// if not modified
				} else if ( status === 304 ) {
					statusText = "notmodified";

				// If we have data, let's convert it
				} else {
					statusText = response.state;
					success = response.data;
					error = response.error;
					isSuccess = !error;
				}
			} else {

				// Extract error from statusText and normalize for non-aborts
				error = statusText;
				if ( status || !statusText ) {
					statusText = "error";
					if ( status < 0 ) {
						status = 0;
					}
				}
			}

			// Set data for the fake xhr object
			jqXHR.status = status;
			jqXHR.statusText = ( nativeStatusText || statusText ) + "";

			// Success/Error
			if ( isSuccess ) {
				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
			} else {
				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
			}

			// Status-dependent callbacks
			jqXHR.statusCode( statusCode );
			statusCode = undefined;

			if ( fireGlobals ) {
				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
					[ jqXHR, s, isSuccess ? success : error ] );
			}

			// Complete
			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );

				// Handle the global AJAX counter
				if ( !( --jQuery.active ) ) {
					jQuery.event.trigger( "ajaxStop" );
				}
			}
		}

		return jqXHR;
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get( url, data, callback, "json" );
	},

	getScript: function( url, callback ) {
		return jQuery.get( url, undefined, callback, "script" );
	}
} );

jQuery.each( [ "get", "post" ], function( i, method ) {
	jQuery[ method ] = function( url, data, callback, type ) {

		// Shift arguments if data argument was omitted
		if ( jQuery.isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = undefined;
		}

		// The url can be an options object (which then must have .url)
		return jQuery.ajax( jQuery.extend( {
			url: url,
			type: method,
			dataType: type,
			data: data,
			success: callback
		}, jQuery.isPlainObject( url ) && url ) );
	};
} );


jQuery._evalUrl = function( url ) {
	return jQuery.ajax( {
		url: url,

		// Make this explicit, since user can override this through ajaxSetup (#11264)
		type: "GET",
		dataType: "script",
		cache: true,
		async: false,
		global: false,
		"throws": true
	} );
};


jQuery.fn.extend( {
	wrapAll: function( html ) {
		var wrap;

		if ( this[ 0 ] ) {
			if ( jQuery.isFunction( html ) ) {
				html = html.call( this[ 0 ] );
			}

			// The elements to wrap the target around
			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );

			if ( this[ 0 ].parentNode ) {
				wrap.insertBefore( this[ 0 ] );
			}

			wrap.map( function() {
				var elem = this;

				while ( elem.firstElementChild ) {
					elem = elem.firstElementChild;
				}

				return elem;
			} ).append( this );
		}

		return this;
	},

	wrapInner: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each( function( i ) {
				jQuery( this ).wrapInner( html.call( this, i ) );
			} );
		}

		return this.each( function() {
			var self = jQuery( this ),
				contents = self.contents();

			if ( contents.length ) {
				contents.wrapAll( html );

			} else {
				self.append( html );
			}
		} );
	},

	wrap: function( html ) {
		var isFunction = jQuery.isFunction( html );

		return this.each( function( i ) {
			jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html );
		} );
	},

	unwrap: function( selector ) {
		this.parent( selector ).not( "body" ).each( function() {
			jQuery( this ).replaceWith( this.childNodes );
		} );
		return this;
	}
} );


jQuery.expr.pseudos.hidden = function( elem ) {
	return !jQuery.expr.pseudos.visible( elem );
};
jQuery.expr.pseudos.visible = function( elem ) {
	return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
};




jQuery.ajaxSettings.xhr = function() {
	try {
		return new window.XMLHttpRequest();
	} catch ( e ) {}
};

var xhrSuccessStatus = {

		// File protocol always yields status code 0, assume 200
		0: 200,

		// Support: IE <=9 only
		// #1450: sometimes IE returns 1223 when it should be 204
		1223: 204
	},
	xhrSupported = jQuery.ajaxSettings.xhr();

support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
support.ajax = xhrSupported = !!xhrSupported;

jQuery.ajaxTransport( function( options ) {
	var callback, errorCallback;

	// Cross domain only allowed if supported through XMLHttpRequest
	if ( support.cors || xhrSupported && !options.crossDomain ) {
		return {
			send: function( headers, complete ) {
				var i,
					xhr = options.xhr();

				xhr.open(
					options.type,
					options.url,
					options.async,
					options.username,
					options.password
				);

				// Apply custom fields if provided
				if ( options.xhrFields ) {
					for ( i in options.xhrFields ) {
						xhr[ i ] = options.xhrFields[ i ];
					}
				}

				// Override mime type if needed
				if ( options.mimeType && xhr.overrideMimeType ) {
					xhr.overrideMimeType( options.mimeType );
				}

				// X-Requested-With header
				// For cross-domain requests, seeing as conditions for a preflight are
				// akin to a jigsaw puzzle, we simply never set it to be sure.
				// (it can always be set on a per-request basis or even using ajaxSetup)
				// For same-domain requests, won't change header if already provided.
				if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
					headers[ "X-Requested-With" ] = "XMLHttpRequest";
				}

				// Set headers
				for ( i in headers ) {
					xhr.setRequestHeader( i, headers[ i ] );
				}

				// Callback
				callback = function( type ) {
					return function() {
						if ( callback ) {
							callback = errorCallback = xhr.onload =
								xhr.onerror = xhr.onabort = xhr.onreadystatechange = null;

							if ( type === "abort" ) {
								xhr.abort();
							} else if ( type === "error" ) {

								// Support: IE <=9 only
								// On a manual native abort, IE9 throws
								// errors on any property access that is not readyState
								if ( typeof xhr.status !== "number" ) {
									complete( 0, "error" );
								} else {
									complete(

										// File: protocol always yields status 0; see #8605, #14207
										xhr.status,
										xhr.statusText
									);
								}
							} else {
								complete(
									xhrSuccessStatus[ xhr.status ] || xhr.status,
									xhr.statusText,

									// Support: IE <=9 only
									// IE9 has no XHR2 but throws on binary (trac-11426)
									// For XHR2 non-text, let the caller handle it (gh-2498)
									( xhr.responseType || "text" ) !== "text"  ||
									typeof xhr.responseText !== "string" ?
										{ binary: xhr.response } :
										{ text: xhr.responseText },
									xhr.getAllResponseHeaders()
								);
							}
						}
					};
				};

				// Listen to events
				xhr.onload = callback();
				errorCallback = xhr.onerror = callback( "error" );

				// Support: IE 9 only
				// Use onreadystatechange to replace onabort
				// to handle uncaught aborts
				if ( xhr.onabort !== undefined ) {
					xhr.onabort = errorCallback;
				} else {
					xhr.onreadystatechange = function() {

						// Check readyState before timeout as it changes
						if ( xhr.readyState === 4 ) {

							// Allow onerror to be called first,
							// but that will not handle a native abort
							// Also, save errorCallback to a variable
							// as xhr.onerror cannot be accessed
							window.setTimeout( function() {
								if ( callback ) {
									errorCallback();
								}
							} );
						}
					};
				}

				// Create the abort callback
				callback = callback( "abort" );

				try {

					// Do send the request (this may raise an exception)
					xhr.send( options.hasContent && options.data || null );
				} catch ( e ) {

					// #14683: Only rethrow if this hasn't been notified as an error yet
					if ( callback ) {
						throw e;
					}
				}
			},

			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
} );




// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
jQuery.ajaxPrefilter( function( s ) {
	if ( s.crossDomain ) {
		s.contents.script = false;
	}
} );

// Install script dataType
jQuery.ajaxSetup( {
	accepts: {
		script: "text/javascript, application/javascript, " +
			"application/ecmascript, application/x-ecmascript"
	},
	contents: {
		script: /\b(?:java|ecma)script\b/
	},
	converters: {
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}
} );

// Handle cache's special case and crossDomain
jQuery.ajaxPrefilter( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {
		s.type = "GET";
	}
} );

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function( s ) {

	// This transport only deals with cross domain requests
	if ( s.crossDomain ) {
		var script, callback;
		return {
			send: function( _, complete ) {
				script = jQuery( "<script>" ).prop( {
					charset: s.scriptCharset,
					src: s.url
				} ).on(
					"load error",
					callback = function( evt ) {
						script.remove();
						callback = null;
						if ( evt ) {
							complete( evt.type === "error" ? 404 : 200, evt.type );
						}
					}
				);

				// Use native DOM manipulation to avoid our domManip AJAX trickery
				document.head.appendChild( script[ 0 ] );
			},
			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
} );




var oldCallbacks = [],
	rjsonp = /(=)\?(?=&|$)|\?\?/;

// Default jsonp settings
jQuery.ajaxSetup( {
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
		this[ callback ] = true;
		return callback;
	}
} );

// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
			"url" :
			typeof s.data === "string" &&
				( s.contentType || "" )
					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
				rjsonp.test( s.data ) && "data"
		);

	// Handle iff the expected data type is "jsonp" or we have a parameter to set
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		// Get callback name, remembering preexisting value associated with it
		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;

		// Insert callback into url or form data
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}

		// Use data converter to retrieve json after script execution
		s.converters[ "script json" ] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};

		// Force json dataType
		s.dataTypes[ 0 ] = "json";

		// Install callback
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};

		// Clean-up function (fires after converters)
		jqXHR.always( function() {

			// If previous value didn't exist - remove it
			if ( overwritten === undefined ) {
				jQuery( window ).removeProp( callbackName );

			// Otherwise restore preexisting value
			} else {
				window[ callbackName ] = overwritten;
			}

			// Save back as free
			if ( s[ callbackName ] ) {

				// Make sure that re-using the options doesn't screw things around
				s.jsonpCallback = originalSettings.jsonpCallback;

				// Save the callback name for future use
				oldCallbacks.push( callbackName );
			}

			// Call if it was a function and we have a response
			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}

			responseContainer = overwritten = undefined;
		} );

		// Delegate to script
		return "script";
	}
} );




// Support: Safari 8 only
// In Safari 8 documents created via document.implementation.createHTMLDocument
// collapse sibling forms: the second one becomes a child of the first one.
// Because of that, this security measure has to be disabled in Safari 8.
// https://bugs.webkit.org/show_bug.cgi?id=137337
support.createHTMLDocument = ( function() {
	var body = document.implementation.createHTMLDocument( "" ).body;
	body.innerHTML = "<form></form><form></form>";
	return body.childNodes.length === 2;
} )();


// Argument "data" should be string of html
// context (optional): If specified, the fragment will be created in this context,
// defaults to document
// keepScripts (optional): If true, will include scripts passed in the html string
jQuery.parseHTML = function( data, context, keepScripts ) {
	if ( typeof data !== "string" ) {
		return [];
	}
	if ( typeof context === "boolean" ) {
		keepScripts = context;
		context = false;
	}

	var base, parsed, scripts;

	if ( !context ) {

		// Stop scripts or inline event handlers from being executed immediately
		// by using document.implementation
		if ( support.createHTMLDocument ) {
			context = document.implementation.createHTMLDocument( "" );

			// Set the base href for the created document
			// so any parsed elements with URLs
			// are based on the document's URL (gh-2965)
			base = context.createElement( "base" );
			base.href = document.location.href;
			context.head.appendChild( base );
		} else {
			context = document;
		}
	}

	parsed = rsingleTag.exec( data );
	scripts = !keepScripts && [];

	// Single tag
	if ( parsed ) {
		return [ context.createElement( parsed[ 1 ] ) ];
	}

	parsed = buildFragment( [ data ], context, scripts );

	if ( scripts && scripts.length ) {
		jQuery( scripts ).remove();
	}

	return jQuery.merge( [], parsed.childNodes );
};


/**
 * Load a url into a page
 */
jQuery.fn.load = function( url, params, callback ) {
	var selector, type, response,
		self = this,
		off = url.indexOf( " " );

	if ( off > -1 ) {
		selector = stripAndCollapse( url.slice( off ) );
		url = url.slice( 0, off );
	}

	// If it's a function
	if ( jQuery.isFunction( params ) ) {

		// We assume that it's the callback
		callback = params;
		params = undefined;

	// Otherwise, build a param string
	} else if ( params && typeof params === "object" ) {
		type = "POST";
	}

	// If we have elements to modify, make the request
	if ( self.length > 0 ) {
		jQuery.ajax( {
			url: url,

			// If "type" variable is undefined, then "GET" method will be used.
			// Make value of this field explicit since
			// user can override it through ajaxSetup method
			type: type || "GET",
			dataType: "html",
			data: params
		} ).done( function( responseText ) {

			// Save response for use in complete callback
			response = arguments;

			self.html( selector ?

				// If a selector was specified, locate the right elements in a dummy div
				// Exclude scripts to avoid IE 'Permission Denied' errors
				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :

				// Otherwise use the full result
				responseText );

		// If the request succeeds, this function gets "data", "status", "jqXHR"
		// but they are ignored because response was set above.
		// If it fails, this function gets "jqXHR", "status", "error"
		} ).always( callback && function( jqXHR, status ) {
			self.each( function() {
				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
			} );
		} );
	}

	return this;
};




// Attach a bunch of functions for handling common AJAX events
jQuery.each( [
	"ajaxStart",
	"ajaxStop",
	"ajaxComplete",
	"ajaxError",
	"ajaxSuccess",
	"ajaxSend"
], function( i, type ) {
	jQuery.fn[ type ] = function( fn ) {
		return this.on( type, fn );
	};
} );




jQuery.expr.pseudos.animated = function( elem ) {
	return jQuery.grep( jQuery.timers, function( fn ) {
		return elem === fn.elem;
	} ).length;
};




/**
 * Gets a window from an element
 */
function getWindow( elem ) {
	return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
}

jQuery.offset = {
	setOffset: function( elem, options, i ) {
		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
			position = jQuery.css( elem, "position" ),
			curElem = jQuery( elem ),
			props = {};

		// Set position first, in-case top/left are set even on static elem
		if ( position === "static" ) {
			elem.style.position = "relative";
		}

		curOffset = curElem.offset();
		curCSSTop = jQuery.css( elem, "top" );
		curCSSLeft = jQuery.css( elem, "left" );
		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
			( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;

		// Need to be able to calculate position if either
		// top or left is auto and position is either absolute or fixed
		if ( calculatePosition ) {
			curPosition = curElem.position();
			curTop = curPosition.top;
			curLeft = curPosition.left;

		} else {
			curTop = parseFloat( curCSSTop ) || 0;
			curLeft = parseFloat( curCSSLeft ) || 0;
		}

		if ( jQuery.isFunction( options ) ) {

			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
		}

		if ( options.top != null ) {
			props.top = ( options.top - curOffset.top ) + curTop;
		}
		if ( options.left != null ) {
			props.left = ( options.left - curOffset.left ) + curLeft;
		}

		if ( "using" in options ) {
			options.using.call( elem, props );

		} else {
			curElem.css( props );
		}
	}
};

jQuery.fn.extend( {
	offset: function( options ) {

		// Preserve chaining for setter
		if ( arguments.length ) {
			return options === undefined ?
				this :
				this.each( function( i ) {
					jQuery.offset.setOffset( this, options, i );
				} );
		}

		var docElem, win, rect, doc,
			elem = this[ 0 ];

		if ( !elem ) {
			return;
		}

		// Support: IE <=11 only
		// Running getBoundingClientRect on a
		// disconnected node in IE throws an error
		if ( !elem.getClientRects().length ) {
			return { top: 0, left: 0 };
		}

		rect = elem.getBoundingClientRect();

		// Make sure element is not hidden (display: none)
		if ( rect.width || rect.height ) {
			doc = elem.ownerDocument;
			win = getWindow( doc );
			docElem = doc.documentElement;

			return {
				top: rect.top + win.pageYOffset - docElem.clientTop,
				left: rect.left + win.pageXOffset - docElem.clientLeft
			};
		}

		// Return zeros for disconnected and hidden elements (gh-2310)
		return rect;
	},

	position: function() {
		if ( !this[ 0 ] ) {
			return;
		}

		var offsetParent, offset,
			elem = this[ 0 ],
			parentOffset = { top: 0, left: 0 };

		// Fixed elements are offset from window (parentOffset = {top:0, left: 0},
		// because it is its only offset parent
		if ( jQuery.css( elem, "position" ) === "fixed" ) {

			// Assume getBoundingClientRect is there when computed position is fixed
			offset = elem.getBoundingClientRect();

		} else {

			// Get *real* offsetParent
			offsetParent = this.offsetParent();

			// Get correct offsets
			offset = this.offset();
			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
				parentOffset = offsetParent.offset();
			}

			// Add offsetParent borders
			parentOffset = {
				top: parentOffset.top + jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ),
				left: parentOffset.left + jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true )
			};
		}

		// Subtract parent offsets and element margins
		return {
			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
		};
	},

	// This method will return documentElement in the following cases:
	// 1) For the element inside the iframe without offsetParent, this method will return
	//    documentElement of the parent window
	// 2) For the hidden or detached element
	// 3) For body or html element, i.e. in case of the html node - it will return itself
	//
	// but those exceptions were never presented as a real life use-cases
	// and might be considered as more preferable results.
	//
	// This logic, however, is not guaranteed and can change at any point in the future
	offsetParent: function() {
		return this.map( function() {
			var offsetParent = this.offsetParent;

			while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
				offsetParent = offsetParent.offsetParent;
			}

			return offsetParent || documentElement;
		} );
	}
} );

// Create scrollLeft and scrollTop methods
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
	var top = "pageYOffset" === prop;

	jQuery.fn[ method ] = function( val ) {
		return access( this, function( elem, method, val ) {
			var win = getWindow( elem );

			if ( val === undefined ) {
				return win ? win[ prop ] : elem[ method ];
			}

			if ( win ) {
				win.scrollTo(
					!top ? val : win.pageXOffset,
					top ? val : win.pageYOffset
				);

			} else {
				elem[ method ] = val;
			}
		}, method, val, arguments.length );
	};
} );

// Support: Safari <=7 - 9.1, Chrome <=37 - 49
// Add the top/left cssHooks using jQuery.fn.position
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
// getComputedStyle returns percent when specified for top/left/bottom/right;
// rather than make the css module depend on the offset module, just check for it here
jQuery.each( [ "top", "left" ], function( i, prop ) {
	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
		function( elem, computed ) {
			if ( computed ) {
				computed = curCSS( elem, prop );

				// If curCSS returns percentage, fallback to offset
				return rnumnonpx.test( computed ) ?
					jQuery( elem ).position()[ prop ] + "px" :
					computed;
			}
		}
	);
} );


// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
		function( defaultExtra, funcName ) {

		// Margin is only for outerHeight, outerWidth
		jQuery.fn[ funcName ] = function( margin, value ) {
			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );

			return access( this, function( elem, type, value ) {
				var doc;

				if ( jQuery.isWindow( elem ) ) {

					// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
					return funcName.indexOf( "outer" ) === 0 ?
						elem[ "inner" + name ] :
						elem.document.documentElement[ "client" + name ];
				}

				// Get document width or height
				if ( elem.nodeType === 9 ) {
					doc = elem.documentElement;

					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
					// whichever is greatest
					return Math.max(
						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
						elem.body[ "offset" + name ], doc[ "offset" + name ],
						doc[ "client" + name ]
					);
				}

				return value === undefined ?

					// Get width or height on the element, requesting but not forcing parseFloat
					jQuery.css( elem, type, extra ) :

					// Set width or height on the element
					jQuery.style( elem, type, value, extra );
			}, type, chainable ? margin : undefined, chainable );
		};
	} );
} );


jQuery.fn.extend( {

	bind: function( types, data, fn ) {
		return this.on( types, null, data, fn );
	},
	unbind: function( types, fn ) {
		return this.off( types, null, fn );
	},

	delegate: function( selector, types, data, fn ) {
		return this.on( types, selector, data, fn );
	},
	undelegate: function( selector, types, fn ) {

		// ( namespace ) or ( selector, types [, fn] )
		return arguments.length === 1 ?
			this.off( selector, "**" ) :
			this.off( types, selector || "**", fn );
	}
} );

jQuery.parseJSON = JSON.parse;




// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.

// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

if ( typeof define === "function" && define.amd ) {
	define( "jquery", [], function() {
		return jQuery;
	} );
}




var

	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,

	// Map over the $ in case of overwrite
	_$ = window.$;

jQuery.noConflict = function( deep ) {
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}

	if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	return jQuery;
};

// Expose jQuery and $ identifiers, even in AMD
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566)
if ( !noGlobal ) {
	window.jQuery = window.$ = jQuery;
}





return jQuery;
} );
/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */
!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=r.isArray(d)))?(e?(e=!1,f=c&&r.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,M,e),g(f,c,N,e)):(f++,j.call(a,g(f,c,M,e),g(f,c,N,e),g(f,c,M,c.notifyWith))):(d!==M&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R),
a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},T=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function U(){this.expando=r.expando+U.uid++}U.uid=1,U.prototype={cache:function(a){var b=a[this.expando];return b||(b={},T(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){r.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(K)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var V=new U,W=new U,X=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Y=/[A-Z]/g;function Z(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:X.test(a)?JSON.parse(a):a)}function $(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Y,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=Z(c)}catch(e){}W.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return W.hasData(a)||V.hasData(a)},data:function(a,b,c){return W.access(a,b,c)},removeData:function(a,b){W.remove(a,b)},_data:function(a,b,c){return V.access(a,b,c)},_removeData:function(a,b){V.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=W.get(f),1===f.nodeType&&!V.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),$(f,d,e[d])));V.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){W.set(this,a)}):S(this,function(b){var c;if(f&&void 0===b){if(c=W.get(f,a),void 0!==c)return c;if(c=$(f,a),void 0!==c)return c}else this.each(function(){W.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=V.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var _=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,aa=new RegExp("^(?:([+-])=|)("+_+")([a-z%]*)$","i"),ba=["Top","Right","Bottom","Left"],ca=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},da=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function ea(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&aa.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var fa={};function ga(a){var b,c=a.ownerDocument,d=a.nodeName,e=fa[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),fa[d]=e,e)}function ha(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=V.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&ca(d)&&(e[f]=ga(d))):"none"!==c&&(e[f]="none",V.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ha(this,!0)},hide:function(){return ha(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){ca(this)?r(this).show():r(this).hide()})}});var ia=/^(?:checkbox|radio)$/i,ja=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c<d;c++)V.set(a[c],"globalEval",!b||V.get(b[c],"globalEval"))}var oa=/<|&#?\w+;/;function pa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(oa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ja.exec(f)||["",""])[1].toLowerCase(),i=la[h]||la._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==wa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===wa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&r.nodeName(this,"input"))return this.click(),!1},_default:function(a){return r.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ua:va,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:va,isPropagationStopped:va,isImmediatePropagationStopped:va,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ua,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ua,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ua,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&ra.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&sa.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return xa(this,a,b,c,d)},one:function(a,b,c,d){return xa(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=va),this.each(function(){r.event.remove(this,a,c,b)})}});var ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/<script|<style|<link/i,Aa=/checked\s*(?:[^=]|=\s*.checked.)/i,Ba=/^true\/(.*)/,Ca=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}W.hasData(a)&&(h=W.access(a),i=r.extend({},h),W.set(b,i))}}function Ha(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ia.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ia(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,ma(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Fa),l=0;l<i;l++)j=h[l],ka.test(j.type||"")&&!V.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Ca,""),k))}return a}function Ja(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(ma(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&na(ma(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(ya,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);if(b)if(c)for(f=f||ma(a),g=g||ma(h),d=0,e=f.length;d<e;d++)Ga(f[d],g[d]);else Ga(a,h);return g=ma(h,"script"),g.length>0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(ma(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ia(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(ma(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var Ka=/^margin/,La=new RegExp("^("+_+")(?!px)[a-z%]+$","i"),Ma=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",qa.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,qa.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Na(a,b,c){var d,e,f,g,h=a.style;return c=c||Ma(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&La.test(g)&&Ka.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Oa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Pa=/^(none|table(?!-c[ea]).+)/,Qa={position:"absolute",visibility:"hidden",display:"block"},Ra={letterSpacing:"0",fontWeight:"400"},Sa=["Webkit","Moz","ms"],Ta=d.createElement("div").style;function Ua(a){if(a in Ta)return a;var b=a[0].toUpperCase()+a.slice(1),c=Sa.length;while(c--)if(a=Sa[c]+b,a in Ta)return a}function Va(a,b,c){var d=aa.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Wa(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ba[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ba[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ba[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ba[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ba[f]+"Width",!0,e)));return g}function Xa(a,b,c){var d,e=!0,f=Ma(a),g="border-box"===r.css(a,"boxSizing",!1,f);if(a.getClientRects().length&&(d=a.getBoundingClientRect()[b]),d<=0||null==d){if(d=Na(a,b,f),(d<0||null==d)&&(d=a.style[b]),La.test(d))return d;e=g&&(o.boxSizingReliable()||d===a.style[b]),d=parseFloat(d)||0}return d+Wa(a,b,c||(g?"border":"content"),e,f)+"px"}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Na(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=a.style;return b=r.cssProps[h]||(r.cssProps[h]=Ua(h)||h),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=aa.exec(c))&&e[1]&&(c=ea(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b);return b=r.cssProps[h]||(r.cssProps[h]=Ua(h)||h),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Na(a,b,d)),"normal"===e&&b in Ra&&(e=Ra[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Pa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?Xa(a,b,d):da(a,Qa,function(){return Xa(a,b,d)})},set:function(a,c,d){var e,f=d&&Ma(a),g=d&&Wa(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=aa.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Va(a,c,g)}}}),r.cssHooks.marginLeft=Oa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Na(a,"marginLeft"))||a.getBoundingClientRect().left-da(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ba[d]+b]=f[d]||f[d-2]||f[0];return e}},Ka.test(a)||(r.cssHooks[a+b].set=Va)}),r.fn.extend({css:function(a,b){return S(this,function(a,b,c){var d,e,f={},g=0;if(r.isArray(b)){for(d=Ma(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f<g;f++)if(d=e[f].call(c,b,a))return d}function fb(a,b,c){var d,e,f,g,h,i,j,k,l="width"in b||"height"in b,m=this,n={},o=a.style,p=a.nodeType&&ca(a),q=V.get(a,"fxshow");c.queue||(g=r._queueHooks(a,"fx"),null==g.unqueued&&(g.unqueued=0,h=g.empty.fire,g.empty.fire=function(){g.unqueued||h()}),g.unqueued++,m.always(function(){m.always(function(){g.unqueued--,r.queue(a,"fx").length||g.empty.fire()})}));for(d in b)if(e=b[d],_a.test(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}n[d]=q&&q[d]||r.style(a,d)}if(i=!r.isEmptyObject(b),i||!r.isEmptyObject(n)){l&&1===a.nodeType&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=q&&q.display,null==j&&(j=V.get(a,"display")),k=r.css(a,"display"),"none"===k&&(j?k=j:(ha([a],!0),j=a.style.display||j,k=r.css(a,"display"),ha([a]))),("inline"===k||"inline-block"===k&&null!=j)&&"none"===r.css(a,"float")&&(i||(m.done(function(){o.display=j}),null==j&&(k=o.display,j="none"===k?"":k)),o.display="inline-block")),c.overflow&&(o.overflow="hidden",m.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]})),i=!1;for(d in n)i||(q?"hidden"in q&&(p=q.hidden):q=V.access(a,"fxshow",{display:j}),f&&(q.hidden=!p),p&&ha([a],!0),m.done(function(){p||ha([a]),V.remove(a,"fxshow");for(d in n)r.style(a,d,n[d])})),i=eb(p?q[d]:0,d,m),d in q||(q[d]=i.start,p&&(i.end=i.start,i.start=0))}}function gb(a,b){var c,d,e,f,g;for(c in a)if(d=r.camelCase(c),e=b[d],f=a[c],r.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=r.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function hb(a,b,c){var d,e,f=0,g=hb.prefilters.length,h=r.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Za||cb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;g<i;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),f<1&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:r.extend({},b),opts:r.extend(!0,{specialEasing:{},easing:r.easing._default},c),originalProperties:b,originalOptions:c,startTime:Za||cb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=r.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;c<d;c++)j.tweens[c].run(1);return b?(h.notifyWith(a,[j,1,0]),h.resolveWith(a,[j,b])):h.rejectWith(a,[j,b]),this}}),k=j.props;for(gb(k,j.opts.specialEasing);f<g;f++)if(d=hb.prefilters[f].call(j,a,k,j.opts))return r.isFunction(d.stop)&&(r._queueHooks(j.elem,j.opts.queue).stop=r.proxy(d.stop,d)),d;return r.map(k,eb,j),r.isFunction(j.opts.start)&&j.opts.start.call(a,j),r.fx.timer(r.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}r.Animation=r.extend(hb,{tweeners:{"*":[function(a,b){var c=this.createTween(a,b);return ea(c.elem,a,aa.exec(b),c),c}]},tweener:function(a,b){r.isFunction(a)?(b=a,a=["*"]):a=a.match(K);for(var c,d=0,e=a.length;d<e;d++)c=a[d],hb.tweeners[c]=hb.tweeners[c]||[],hb.tweeners[c].unshift(b)},prefilters:[fb],prefilter:function(a,b){b?hb.prefilters.unshift(a):hb.prefilters.push(a)}}),r.speed=function(a,b,c){var e=a&&"object"==typeof a?r.extend({},a):{complete:c||!c&&b||r.isFunction(a)&&a,duration:a,easing:c&&b||b&&!r.isFunction(b)&&b};return r.fx.off||d.hidden?e.duration=0:"number"!=typeof e.duration&&(e.duration in r.fx.speeds?e.duration=r.fx.speeds[e.duration]:e.duration=r.fx.speeds._default),null!=e.queue&&e.queue!==!0||(e.queue="fx"),e.old=e.complete,e.complete=function(){r.isFunction(e.old)&&e.old.call(this),e.queue&&r.dequeue(this,e.queue)},e},r.fn.extend({fadeTo:function(a,b,c,d){return this.filter(ca).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=r.isEmptyObject(a),f=r.speed(b,c,d),g=function(){var b=hb(this,r.extend({},a),f);(e||V.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=r.timers,g=V.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&ab.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));!b&&c||r.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=V.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=r.timers,g=d?d.length:0;for(c.finish=!0,r.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;b<g;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),r.each(["toggle","show","hide"],function(a,b){var c=r.fn[b];r.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(db(b,!0),a,d,e)}}),r.each({slideDown:db("show"),slideUp:db("hide"),slideToggle:db("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){r.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),r.timers=[],r.fx.tick=function(){var a,b=0,c=r.timers;for(Za=r.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||r.fx.stop(),Za=void 0},r.fx.timer=function(a){r.timers.push(a),a()?r.fx.start():r.timers.pop()},r.fx.interval=13,r.fx.start=function(){$a||($a=a.requestAnimationFrame?a.requestAnimationFrame(bb):a.setInterval(r.fx.tick,r.fx.interval))},r.fx.stop=function(){a.cancelAnimationFrame?a.cancelAnimationFrame($a):a.clearInterval($a),$a=null},r.fx.speeds={slow:600,fast:200,_default:400},r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var ib,jb=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return S(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)),
void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!r.nodeName(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("<script>").prop({charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&f("error"===a.type?404:200,a.type)}),d.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Qb=[],Rb=/(=)\?(?=&|$)|\?\?/;r.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Qb.pop()||r.expando+"_"+rb++;return this[a]=!0,a}}),r.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Rb.test(b.url)?"url":"string"==typeof b.data&&0===(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Rb.test(b.data)&&"data");if(h||"jsonp"===b.dataTypes[0])return e=b.jsonpCallback=r.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Rb,"$1"+e):b.jsonp!==!1&&(b.url+=(sb.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||r.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){void 0===f?r(a).removeProp(e):a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Qb.push(e)),g&&r.isFunction(f)&&f(g[0]),g=f=void 0}),"script"}),o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=B.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=pa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.fn.load=function(a,b,c){var d,e,f,g=this,h=a.indexOf(" ");return h>-1&&(d=mb(a.slice(h)),a=a.slice(0,h)),r.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&r.ajax({url:a,type:e||"GET",dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?r("<div>").append(r.parseHTML(a)).find(d):a)}).always(c&&function(a,b){g.each(function(){c.apply(this,f||[a.responseText,b,a])})}),this},r.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){r.fn[b]=function(a){return this.on(b,a)}}),r.expr.pseudos.animated=function(a){return r.grep(r.timers,function(b){return a===b.elem}).length};function Sb(a){return r.isWindow(a)?a:9===a.nodeType&&a.defaultView}r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),d.width||d.height?(e=f.ownerDocument,c=Sb(e),b=e.documentElement,{top:d.top+c.pageYOffset-b.clientTop,left:d.left+c.pageXOffset-b.clientLeft}):d):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),r.nodeName(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||qa})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return S(this,function(a,d,e){var f=Sb(a);return void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Oa(o.pixelPosition,function(a,c){if(c)return c=Na(a,b),La.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return S(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.parseJSON=JSON.parse,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var Tb=a.jQuery,Ub=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=Ub),b&&a.jQuery===r&&(a.jQuery=Tb),r},b||(a.jQuery=a.$=r),r});
{"version":3,"sources":["jquery.js"],"names":["global","factory","module","exports","document","w","Error","window","this","noGlobal","arr","getProto","Object","getPrototypeOf","slice","concat","push","indexOf","class2type","toString","hasOwn","hasOwnProperty","fnToString","ObjectFunctionString","call","support","DOMEval","code","doc","script","createElement","text","head","appendChild","parentNode","removeChild","version","jQuery","selector","context","fn","init","rtrim","rmsPrefix","rdashAlpha","fcamelCase","all","letter","toUpperCase","prototype","jquery","constructor","length","toArray","get","num","pushStack","elems","ret","merge","prevObject","each","callback","map","elem","i","apply","arguments","first","eq","last","len","j","end","sort","splice","extend","options","name","src","copy","copyIsArray","clone","target","deep","isFunction","isPlainObject","isArray","undefined","expando","Math","random","replace","isReady","error","msg","noop","obj","type","Array","isWindow","isNumeric","isNaN","parseFloat","proto","Ctor","isEmptyObject","globalEval","camelCase","string","nodeName","toLowerCase","isArrayLike","trim","makeArray","results","inArray","second","grep","invert","callbackInverse","matches","callbackExpect","arg","value","guid","proxy","tmp","args","now","Date","Symbol","iterator","split","Sizzle","Expr","getText","isXML","tokenize","compile","select","outermostContext","sortInput","hasDuplicate","setDocument","docElem","documentIsHTML","rbuggyQSA","rbuggyMatches","contains","preferredDoc","dirruns","done","classCache","createCache","tokenCache","compilerCache","sortOrder","a","b","pop","push_native","list","booleans","whitespace","identifier","attributes","pseudos","rwhitespace","RegExp","rcomma","rcombinators","rattributeQuotes","rpseudo","ridentifier","matchExpr","ID","CLASS","TAG","ATTR","PSEUDO","CHILD","bool","needsContext","rinputs","rheader","rnative","rquickExpr","rsibling","runescape","funescape","_","escaped","escapedWhitespace","high","String","fromCharCode","rcssescape","fcssescape","ch","asCodePoint","charCodeAt","unloadHandler","disabledAncestor","addCombinator","disabled","dir","next","childNodes","nodeType","e","els","seed","m","nid","match","groups","newSelector","newContext","ownerDocument","exec","getElementById","id","getElementsByTagName","getElementsByClassName","qsa","test","getAttribute","setAttribute","toSelector","join","testContext","querySelectorAll","qsaError","removeAttribute","keys","cache","key","cacheLength","shift","markFunction","assert","el","addHandle","attrs","handler","attrHandle","siblingCheck","cur","diff","sourceIndex","nextSibling","createInputPseudo","createButtonPseudo","createDisabledPseudo","isDisabled","createPositionalPseudo","argument","matchIndexes","documentElement","node","hasCompare","subWindow","defaultView","top","addEventListener","attachEvent","className","createComment","getById","getElementsByName","filter","attrId","find","getAttributeNode","tag","innerHTML","input","matchesSelector","webkitMatchesSelector","mozMatchesSelector","oMatchesSelector","msMatchesSelector","disconnectedMatch","compareDocumentPosition","adown","bup","compare","sortDetached","aup","ap","bp","unshift","expr","elements","attr","val","specified","escape","sel","uniqueSort","duplicates","detectDuplicates","sortStable","textContent","firstChild","nodeValue","selectors","createPseudo","relative",">"," ","+","~","preFilter","excess","unquoted","nodeNameSelector","pattern","operator","check","result","what","simple","forward","ofType","xml","uniqueCache","outerCache","nodeIndex","start","parent","useCache","lastChild","uniqueID","pseudo","setFilters","idx","matched","not","matcher","unmatched","has","innerText","lang","elemLang","hash","location","root","focus","activeElement","hasFocus","href","tabIndex","enabled","checked","selected","selectedIndex","empty","header","button","even","odd","lt","gt","radio","checkbox","file","password","image","submit","reset","filters","parseOnly","tokens","soFar","preFilters","cached","combinator","base","skip","checkNonElements","doneName","oldCache","newCache","elementMatcher","matchers","multipleContexts","contexts","condense","newUnmatched","mapped","setMatcher","postFilter","postFinder","postSelector","temp","preMap","postMap","preexisting","matcherIn","matcherOut","matcherFromTokens","checkContext","leadingRelative","implicitRelative","matchContext","matchAnyContext","matcherFromGroupMatchers","elementMatchers","setMatchers","bySet","byElement","superMatcher","outermost","matchedCount","setMatched","contextBackup","dirrunsUnique","token","compiled","defaultValue","unique","isXMLDoc","escapeSelector","until","truncate","is","siblings","n","rneedsContext","rsingleTag","risSimple","winnow","qualifier","self","rootjQuery","parseHTML","ready","rparentsprev","guaranteedUnique","children","contents","prev","targets","l","closest","index","prevAll","add","addBack","sibling","parents","parentsUntil","nextAll","nextUntil","prevUntil","contentDocument","reverse","rnothtmlwhite","createOptions","object","flag","Callbacks","firing","memory","fired","locked","queue","firingIndex","fire","once","stopOnFalse","remove","disable","lock","fireWith","Identity","v","Thrower","ex","adoptValue","resolve","reject","method","promise","fail","then","Deferred","func","tuples","state","always","deferred","catch","pipe","fns","newDefer","tuple","returned","progress","notify","onFulfilled","onRejected","onProgress","maxDepth","depth","special","that","mightThrow","TypeError","notifyWith","resolveWith","process","exceptionHook","stackTrace","rejectWith","getStackHook","setTimeout","stateString","when","singleValue","remaining","resolveContexts","resolveValues","master","updateFunc","rerrorNames","stack","console","warn","message","readyException","readyList","readyWait","holdReady","hold","wait","completed","removeEventListener","readyState","doScroll","access","chainable","emptyGet","raw","bulk","acceptData","owner","Data","uid","defineProperty","configurable","set","data","prop","hasData","dataPriv","dataUser","rbrace","rmultiDash","getData","JSON","parse","dataAttr","removeData","_data","_removeData","dequeue","startLength","hooks","_queueHooks","stop","setter","clearQueue","count","defer","pnum","source","rcssNum","cssExpand","isHiddenWithinTree","style","display","css","swap","old","adjustCSS","valueParts","tween","adjusted","scale","maxIterations","currentValue","initial","unit","cssNumber","initialInUnit","defaultDisplayMap","getDefaultDisplay","body","showHide","show","values","hide","toggle","rcheckableType","rtagName","rscriptType","wrapMap","option","thead","col","tr","td","_default","optgroup","tbody","tfoot","colgroup","caption","th","getAll","setGlobalEval","refElements","rhtml","buildFragment","scripts","selection","ignored","wrap","fragment","createDocumentFragment","nodes","htmlPrefilter","createTextNode","div","checkClone","cloneNode","noCloneChecked","rkeyEvent","rmouseEvent","rtypenamespace","returnTrue","returnFalse","safeActiveElement","err","on","types","one","origFn","event","off","handleObjIn","eventHandle","events","t","handleObj","handlers","namespaces","origType","elemData","handle","triggered","dispatch","delegateType","bindType","namespace","delegateCount","setup","mappedTypes","origCount","teardown","removeEvent","nativeEvent","fix","handlerQueue","delegateTarget","preDispatch","isPropagationStopped","currentTarget","isImmediatePropagationStopped","rnamespace","preventDefault","stopPropagation","postDispatch","matchedHandlers","matchedSelectors","addProp","hook","Event","enumerable","originalEvent","writable","load","noBubble","trigger","blur","click","beforeunload","returnValue","props","isDefaultPrevented","defaultPrevented","relatedTarget","timeStamp","isSimulated","stopImmediatePropagation","altKey","bubbles","cancelable","changedTouches","ctrlKey","detail","eventPhase","metaKey","pageX","pageY","shiftKey","view","char","charCode","keyCode","buttons","clientX","clientY","offsetX","offsetY","pointerId","pointerType","screenX","screenY","targetTouches","toElement","touches","which","mouseenter","mouseleave","pointerenter","pointerleave","orig","related","rxhtmlTag","rnoInnerhtml","rchecked","rscriptTypeMasked","rcleanScript","manipulationTarget","content","disableScript","restoreScript","cloneCopyEvent","dest","pdataOld","pdataCur","udataOld","udataCur","fixInput","domManip","collection","hasScripts","iNoClone","html","_evalUrl","keepData","cleanData","dataAndEvents","deepDataAndEvents","srcElements","destElements","inPage","detach","append","prepend","insertBefore","before","after","replaceWith","replaceChild","appendTo","prependTo","insertAfter","replaceAll","original","insert","rmargin","rnumnonpx","getStyles","opener","getComputedStyle","computeStyleTests","cssText","container","divStyle","pixelPositionVal","reliableMarginLeftVal","marginLeft","boxSizingReliableVal","width","marginRight","pixelMarginRightVal","backgroundClip","clearCloneStyle","pixelPosition","boxSizingReliable","pixelMarginRight","reliableMarginLeft","curCSS","computed","minWidth","maxWidth","getPropertyValue","addGetHookIf","conditionFn","hookFn","rdisplayswap","cssShow","position","visibility","cssNormalTransform","letterSpacing","fontWeight","cssPrefixes","emptyStyle","vendorPropName","capName","setPositiveNumber","subtract","max","augmentWidthOrHeight","extra","isBorderBox","styles","getWidthOrHeight","valueIsBorderBox","getClientRects","getBoundingClientRect","cssHooks","opacity","animationIterationCount","columnCount","fillOpacity","flexGrow","flexShrink","lineHeight","order","orphans","widows","zIndex","zoom","cssProps","float","origName","isFinite","left","margin","padding","border","prefix","suffix","expand","expanded","parts","Tween","easing","propHooks","run","percent","eased","duration","pos","step","fx","scrollTop","scrollLeft","linear","p","swing","cos","PI","fxNow","timerId","rfxtypes","rrun","raf","requestAnimationFrame","tick","createFxNow","genFx","includeWidth","height","createTween","animation","Animation","tweeners","defaultPrefilter","opts","oldfire","propTween","restoreDisplay","isBox","anim","hidden","dataShow","unqueued","overflow","overflowX","overflowY","propFilter","specialEasing","properties","stopped","prefilters","currentTime","startTime","tweens","originalProperties","originalOptions","gotoEnd","timer","complete","*","tweener","prefilter","speed","opt","speeds","fadeTo","to","animate","optall","doAnimation","finish","stopQueue","timers","cssFn","slideDown","slideUp","slideToggle","fadeIn","fadeOut","fadeToggle","interval","setInterval","cancelAnimationFrame","clearInterval","slow","fast","delay","time","timeout","clearTimeout","checkOn","optSelected","radioValue","boolHook","removeAttr","nType","attrHooks","attrNames","getter","lowercaseName","rfocusable","rclickable","removeProp","propFix","tabindex","parseInt","for","class","stripAndCollapse","getClass","addClass","classes","curValue","clazz","finalValue","removeClass","toggleClass","stateVal","classNames","hasClass","rreturn","valHooks","optionSet","rfocusMorph","onlyHandlers","bubbleType","ontype","eventPath","isTrigger","parentWindow","simulate","triggerHandler","hover","fnOver","fnOut","focusin","attaches","nonce","rquery","parseXML","DOMParser","parseFromString","rbracket","rCRLF","rsubmitterTypes","rsubmittable","buildParams","traditional","param","s","valueOrFunction","encodeURIComponent","serialize","serializeArray","r20","rhash","rantiCache","rheaders","rlocalProtocol","rnoContent","rprotocol","transports","allTypes","originAnchor","addToPrefiltersOrTransports","structure","dataTypeExpression","dataType","dataTypes","inspectPrefiltersOrTransports","jqXHR","inspected","seekingTransport","inspect","prefilterOrFactory","dataTypeOrTransport","ajaxExtend","flatOptions","ajaxSettings","ajaxHandleResponses","responses","ct","finalDataType","firstDataType","mimeType","getResponseHeader","converters","ajaxConvert","response","isSuccess","conv2","current","conv","responseFields","dataFilter","active","lastModified","etag","url","isLocal","protocol","processData","async","contentType","accepts","json","* text","text html","text json","text xml","ajaxSetup","settings","ajaxPrefilter","ajaxTransport","ajax","transport","cacheURL","responseHeadersString","responseHeaders","timeoutTimer","urlAnchor","fireGlobals","uncached","callbackContext","globalEventContext","completeDeferred","statusCode","requestHeaders","requestHeadersNames","strAbort","getAllResponseHeaders","setRequestHeader","overrideMimeType","status","abort","statusText","finalText","crossDomain","host","hasContent","ifModified","headers","beforeSend","success","send","nativeStatusText","modified","getJSON","getScript","throws","wrapAll","firstElementChild","wrapInner","unwrap","visible","offsetWidth","offsetHeight","xhr","XMLHttpRequest","xhrSuccessStatus","0","1223","xhrSupported","cors","errorCallback","open","username","xhrFields","onload","onerror","onabort","onreadystatechange","responseType","responseText","binary","text script","charset","scriptCharset","evt","oldCallbacks","rjsonp","jsonp","jsonpCallback","originalSettings","callbackName","overwritten","responseContainer","jsonProp","createHTMLDocument","implementation","keepScripts","parsed","params","animated","getWindow","offset","setOffset","curPosition","curLeft","curCSSTop","curTop","curOffset","curCSSLeft","calculatePosition","curElem","using","win","rect","pageYOffset","clientTop","pageXOffset","clientLeft","offsetParent","parentOffset","scrollTo","Height","Width","","defaultExtra","funcName","bind","unbind","delegate","undelegate","parseJSON","define","amd","_jQuery","_$","$","noConflict"],"mappings":";CAaA,SAAYA,EAAQC,GAEnB,YAEuB,iBAAXC,SAAiD,gBAAnBA,QAAOC,QAShDD,OAAOC,QAAUH,EAAOI,SACvBH,EAASD,GAAQ,GACjB,SAAUK,GACT,IAAMA,EAAED,SACP,KAAM,IAAIE,OAAO,2CAElB,OAAOL,GAASI,IAGlBJ,EAASD,IAIY,mBAAXO,QAAyBA,OAASC,KAAM,SAAUD,EAAQE,GAMtE,YAEA,IAAIC,MAEAN,EAAWG,EAAOH,SAElBO,EAAWC,OAAOC,eAElBC,EAAQJ,EAAII,MAEZC,EAASL,EAAIK,OAEbC,EAAON,EAAIM,KAEXC,EAAUP,EAAIO,QAEdC,KAEAC,EAAWD,EAAWC,SAEtBC,EAASF,EAAWG,eAEpBC,EAAaF,EAAOD,SAEpBI,EAAuBD,EAAWE,KAAMZ,QAExCa,IAIH,SAASC,GAASC,EAAMC,GACvBA,EAAMA,GAAOxB,CAEb,IAAIyB,GAASD,EAAIE,cAAe,SAEhCD,GAAOE,KAAOJ,EACdC,EAAII,KAAKC,YAAaJ,GAASK,WAAWC,YAAaN,GAQzD,GACCO,GAAU,QAGVC,EAAS,SAAUC,EAAUC,GAI5B,MAAO,IAAIF,GAAOG,GAAGC,KAAMH,EAAUC,IAKtCG,EAAQ,qCAGRC,EAAY,QACZC,EAAa,YAGbC,EAAa,SAAUC,EAAKC,GAC3B,MAAOA,GAAOC,cAGhBX,GAAOG,GAAKH,EAAOY,WAGlBC,OAAQd,EAERe,YAAad,EAGbe,OAAQ,EAERC,QAAS,WACR,MAAOvC,GAAMU,KAAMhB,OAKpB8C,IAAK,SAAUC,GAGd,MAAY,OAAPA,EACGzC,EAAMU,KAAMhB,MAIb+C,EAAM,EAAI/C,KAAM+C,EAAM/C,KAAK4C,QAAW5C,KAAM+C,IAKpDC,UAAW,SAAUC,GAGpB,GAAIC,GAAMrB,EAAOsB,MAAOnD,KAAK2C,cAAeM,EAM5C,OAHAC,GAAIE,WAAapD,KAGVkD,GAIRG,KAAM,SAAUC,GACf,MAAOzB,GAAOwB,KAAMrD,KAAMsD,IAG3BC,IAAK,SAAUD,GACd,MAAOtD,MAAKgD,UAAWnB,EAAO0B,IAAKvD,KAAM,SAAUwD,EAAMC,GACxD,MAAOH,GAAStC,KAAMwC,EAAMC,EAAGD,OAIjClD,MAAO,WACN,MAAON,MAAKgD,UAAW1C,EAAMoD,MAAO1D,KAAM2D,aAG3CC,MAAO,WACN,MAAO5D,MAAK6D,GAAI,IAGjBC,KAAM,WACL,MAAO9D,MAAK6D,QAGbA,GAAI,SAAUJ,GACb,GAAIM,GAAM/D,KAAK4C,OACdoB,GAAKP,GAAMA,EAAI,EAAIM,EAAM,EAC1B,OAAO/D,MAAKgD,UAAWgB,GAAK,GAAKA,EAAID,GAAQ/D,KAAMgE,SAGpDC,IAAK,WACJ,MAAOjE,MAAKoD,YAAcpD,KAAK2C,eAKhCnC,KAAMA,EACN0D,KAAMhE,EAAIgE,KACVC,OAAQjE,EAAIiE,QAGbtC,EAAOuC,OAASvC,EAAOG,GAAGoC,OAAS,WAClC,GAAIC,GAASC,EAAMC,EAAKC,EAAMC,EAAaC,EAC1CC,EAAShB,UAAW,OACpBF,EAAI,EACJb,EAASe,UAAUf,OACnBgC,GAAO,CAsBR,KAnBuB,iBAAXD,KACXC,EAAOD,EAGPA,EAAShB,UAAWF,OACpBA,KAIsB,gBAAXkB,IAAwB9C,EAAOgD,WAAYF,KACtDA,MAIIlB,IAAMb,IACV+B,EAAS3E,KACTyD,KAGOA,EAAIb,EAAQa,IAGnB,GAAqC,OAA9BY,EAAUV,UAAWF,IAG3B,IAAMa,IAAQD,GACbE,EAAMI,EAAQL,GACdE,EAAOH,EAASC,GAGXK,IAAWH,IAKXI,GAAQJ,IAAU3C,EAAOiD,cAAeN,KAC1CC,EAAc5C,EAAOkD,QAASP,MAE3BC,GACJA,GAAc,EACdC,EAAQH,GAAO1C,EAAOkD,QAASR,GAAQA,MAGvCG,EAAQH,GAAO1C,EAAOiD,cAAeP,GAAQA,KAI9CI,EAAQL,GAASzC,EAAOuC,OAAQQ,EAAMF,EAAOF,IAGzBQ,SAATR,IACXG,EAAQL,GAASE,GAOrB,OAAOG,IAGR9C,EAAOuC,QAGNa,QAAS,UAAarD,EAAUsD,KAAKC,UAAWC,QAAS,MAAO,IAGhEC,SAAS,EAETC,MAAO,SAAUC,GAChB,KAAM,IAAIzF,OAAOyF,IAGlBC,KAAM,aAENX,WAAY,SAAUY,GACrB,MAA8B,aAAvB5D,EAAO6D,KAAMD,IAGrBV,QAASY,MAAMZ,QAEfa,SAAU,SAAUH,GACnB,MAAc,OAAPA,GAAeA,IAAQA,EAAI1F,QAGnC8F,UAAW,SAAUJ,GAKpB,GAAIC,GAAO7D,EAAO6D,KAAMD,EACxB,QAAkB,WAATC,GAA8B,WAATA,KAK5BI,MAAOL,EAAMM,WAAYN,KAG5BX,cAAe,SAAUW,GACxB,GAAIO,GAAOC,CAIX,UAAMR,GAAgC,oBAAzB9E,EAASK,KAAMyE,QAI5BO,EAAQ7F,EAAUsF,MAQlBQ,EAAOrF,EAAOI,KAAMgF,EAAO,gBAAmBA,EAAMrD,YAC7B,kBAATsD,IAAuBnF,EAAWE,KAAMiF,KAAWlF,KAGlEmF,cAAe,SAAUT,GAIxB,GAAInB,EAEJ,KAAMA,IAAQmB,GACb,OAAO,CAER,QAAO,GAGRC,KAAM,SAAUD,GACf,MAAY,OAAPA,EACGA,EAAM,GAIQ,gBAARA,IAAmC,kBAARA,GACxC/E,EAAYC,EAASK,KAAMyE,KAAW,eAC/BA,IAITU,WAAY,SAAUhF,GACrBD,EAASC,IAMViF,UAAW,SAAUC,GACpB,MAAOA,GAAOjB,QAASjD,EAAW,OAAQiD,QAAShD,EAAYC,IAGhEiE,SAAU,SAAU9C,EAAMc,GACzB,MAAOd,GAAK8C,UAAY9C,EAAK8C,SAASC,gBAAkBjC,EAAKiC,eAG9DlD,KAAM,SAAUoC,EAAKnC,GACpB,GAAIV,GAAQa,EAAI,CAEhB,IAAK+C,EAAaf,IAEjB,IADA7C,EAAS6C,EAAI7C,OACLa,EAAIb,EAAQa,IACnB,GAAKH,EAAStC,KAAMyE,EAAKhC,GAAKA,EAAGgC,EAAKhC,OAAU,EAC/C,UAIF,KAAMA,IAAKgC,GACV,GAAKnC,EAAStC,KAAMyE,EAAKhC,GAAKA,EAAGgC,EAAKhC,OAAU,EAC/C,KAKH,OAAOgC,IAIRgB,KAAM,SAAUlF,GACf,MAAe,OAARA,EACN,IACEA,EAAO,IAAK6D,QAASlD,EAAO,KAIhCwE,UAAW,SAAUxG,EAAKyG,GACzB,GAAIzD,GAAMyD,KAaV,OAXY,OAAPzG,IACCsG,EAAapG,OAAQF,IACzB2B,EAAOsB,MAAOD,EACE,gBAARhD,IACLA,GAAQA,GAGXM,EAAKQ,KAAMkC,EAAKhD,IAIXgD,GAGR0D,QAAS,SAAUpD,EAAMtD,EAAKuD,GAC7B,MAAc,OAAPvD,KAAmBO,EAAQO,KAAMd,EAAKsD,EAAMC,IAKpDN,MAAO,SAAUS,EAAOiD,GAKvB,IAJA,GAAI9C,IAAO8C,EAAOjE,OACjBoB,EAAI,EACJP,EAAIG,EAAMhB,OAEHoB,EAAID,EAAKC,IAChBJ,EAAOH,KAAQoD,EAAQ7C,EAKxB,OAFAJ,GAAMhB,OAASa,EAERG,GAGRkD,KAAM,SAAU7D,EAAOK,EAAUyD,GAShC,IARA,GAAIC,GACHC,KACAxD,EAAI,EACJb,EAASK,EAAML,OACfsE,GAAkBH,EAIXtD,EAAIb,EAAQa,IACnBuD,GAAmB1D,EAAUL,EAAOQ,GAAKA,GACpCuD,IAAoBE,GACxBD,EAAQzG,KAAMyC,EAAOQ,GAIvB,OAAOwD,IAIR1D,IAAK,SAAUN,EAAOK,EAAU6D,GAC/B,GAAIvE,GAAQwE,EACX3D,EAAI,EACJP,IAGD,IAAKsD,EAAavD,GAEjB,IADAL,EAASK,EAAML,OACPa,EAAIb,EAAQa,IACnB2D,EAAQ9D,EAAUL,EAAOQ,GAAKA,EAAG0D,GAEnB,MAATC,GACJlE,EAAI1C,KAAM4G,OAMZ,KAAM3D,IAAKR,GACVmE,EAAQ9D,EAAUL,EAAOQ,GAAKA,EAAG0D,GAEnB,MAATC,GACJlE,EAAI1C,KAAM4G,EAMb,OAAO7G,GAAOmD,SAAWR,IAI1BmE,KAAM,EAINC,MAAO,SAAUtF,EAAID,GACpB,GAAIwF,GAAKC,EAAMF,CAUf,IARwB,gBAAZvF,KACXwF,EAAMvF,EAAID,GACVA,EAAUC,EACVA,EAAKuF,GAKA1F,EAAOgD,WAAY7C,GAazB,MARAwF,GAAOlH,EAAMU,KAAM2C,UAAW,GAC9B2D,EAAQ,WACP,MAAOtF,GAAG0B,MAAO3B,GAAW/B,KAAMwH,EAAKjH,OAAQD,EAAMU,KAAM2C,cAI5D2D,EAAMD,KAAOrF,EAAGqF,KAAOrF,EAAGqF,MAAQxF,EAAOwF,OAElCC,GAGRG,IAAKC,KAAKD,IAIVxG,QAASA,IAGa,kBAAX0G,UACX9F,EAAOG,GAAI2F,OAAOC,UAAa1H,EAAKyH,OAAOC,WAI5C/F,EAAOwB,KAAM,uEAAuEwE,MAAO,KAC3F,SAAUpE,EAAGa,GACZ5D,EAAY,WAAa4D,EAAO,KAAQA,EAAKiC,eAG9C,SAASC,GAAaf,GAMrB,GAAI7C,KAAW6C,GAAO,UAAYA,IAAOA,EAAI7C,OAC5C8C,EAAO7D,EAAO6D,KAAMD,EAErB,OAAc,aAATC,IAAuB7D,EAAO+D,SAAUH,KAI7B,UAATC,GAA+B,IAAX9C,GACR,gBAAXA,IAAuBA,EAAS,GAAOA,EAAS,IAAO6C,IAEhE,GAAIqC,GAWJ,SAAW/H,GAEX,GAAI0D,GACHxC,EACA8G,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EAGAC,EACA5I,EACA6I,EACAC,EACAC,EACAC,EACA3B,EACA4B,EAGA5D,EAAU,SAAW,EAAI,GAAIyC,MAC7BoB,EAAe/I,EAAOH,SACtBmJ,EAAU,EACVC,EAAO,EACPC,EAAaC,KACbC,EAAaD,KACbE,EAAgBF,KAChBG,EAAY,SAAUC,EAAGC,GAIxB,MAHKD,KAAMC,IACVhB,GAAe,GAET,GAIR3H,KAAcC,eACdX,KACAsJ,EAAMtJ,EAAIsJ,IACVC,EAAcvJ,EAAIM,KAClBA,EAAON,EAAIM,KACXF,EAAQJ,EAAII,MAGZG,EAAU,SAAUiJ,EAAMlG,GAGzB,IAFA,GAAIC,GAAI,EACPM,EAAM2F,EAAK9G,OACJa,EAAIM,EAAKN,IAChB,GAAKiG,EAAKjG,KAAOD,EAChB,MAAOC,EAGT,WAGDkG,EAAW,6HAKXC,EAAa,sBAGbC,EAAa,gCAGbC,EAAa,MAAQF,EAAa,KAAOC,EAAa,OAASD,EAE9D,gBAAkBA,EAElB,2DAA6DC,EAAa,OAASD,EACnF,OAEDG,EAAU,KAAOF,EAAa,wFAKAC,EAAa,eAM3CE,EAAc,GAAIC,QAAQL,EAAa,IAAK,KAC5C1H,EAAQ,GAAI+H,QAAQ,IAAML,EAAa,8BAAgCA,EAAa,KAAM,KAE1FM,EAAS,GAAID,QAAQ,IAAML,EAAa,KAAOA,EAAa,KAC5DO,EAAe,GAAIF,QAAQ,IAAML,EAAa,WAAaA,EAAa,IAAMA,EAAa,KAE3FQ,EAAmB,GAAIH,QAAQ,IAAML,EAAa,iBAAmBA,EAAa,OAAQ,KAE1FS,EAAU,GAAIJ,QAAQF,GACtBO,EAAc,GAAIL,QAAQ,IAAMJ,EAAa,KAE7CU,GACCC,GAAM,GAAIP,QAAQ,MAAQJ,EAAa,KACvCY,MAAS,GAAIR,QAAQ,QAAUJ,EAAa,KAC5Ca,IAAO,GAAIT,QAAQ,KAAOJ,EAAa,SACvCc,KAAQ,GAAIV,QAAQ,IAAMH,GAC1Bc,OAAU,GAAIX,QAAQ,IAAMF,GAC5Bc,MAAS,GAAIZ,QAAQ,yDAA2DL,EAC/E,+BAAiCA,EAAa,cAAgBA,EAC9D,aAAeA,EAAa,SAAU,KACvCkB,KAAQ,GAAIb,QAAQ,OAASN,EAAW,KAAM,KAG9CoB,aAAgB,GAAId,QAAQ,IAAML,EAAa,mDAC9CA,EAAa,mBAAqBA,EAAa,mBAAoB,MAGrEoB,EAAU,sCACVC,EAAU,SAEVC,EAAU,yBAGVC,EAAa,mCAEbC,EAAW,OAIXC,EAAY,GAAIpB,QAAQ,qBAAuBL,EAAa,MAAQA,EAAa,OAAQ,MACzF0B,GAAY,SAAUC,EAAGC,EAASC,GACjC,GAAIC,GAAO,KAAOF,EAAU,KAI5B,OAAOE,KAASA,GAAQD,EACvBD,EACAE,EAAO,EAENC,OAAOC,aAAcF,EAAO,OAE5BC,OAAOC,aAAcF,GAAQ,GAAK,MAAe,KAAPA,EAAe,QAK5DG,GAAa,sDACbC,GAAa,SAAUC,EAAIC,GAC1B,MAAKA,GAGQ,OAAPD,EACG,SAIDA,EAAGzL,MAAO,MAAU,KAAOyL,EAAGE,WAAYF,EAAGnJ,OAAS,GAAIjC,SAAU,IAAO,IAI5E,KAAOoL,GAOfG,GAAgB,WACf1D,KAGD2D,GAAmBC,GAClB,SAAU5I,GACT,MAAOA,GAAK6I,YAAa,IAAS,QAAU7I,IAAQ,SAAWA,MAE9D8I,IAAK,aAAcC,KAAM,UAI7B,KACC/L,EAAKkD,MACHxD,EAAMI,EAAMU,KAAM8H,EAAa0D,YAChC1D,EAAa0D,YAIdtM,EAAK4I,EAAa0D,WAAW5J,QAAS6J,SACrC,MAAQC,IACTlM,GAASkD,MAAOxD,EAAI0C,OAGnB,SAAU+B,EAAQgI,GACjBlD,EAAY/F,MAAOiB,EAAQrE,EAAMU,KAAK2L,KAKvC,SAAUhI,EAAQgI,GACjB,GAAI3I,GAAIW,EAAO/B,OACda,EAAI,CAEL,OAASkB,EAAOX,KAAO2I,EAAIlJ,MAC3BkB,EAAO/B,OAASoB,EAAI,IAKvB,QAAS8D,IAAQhG,EAAUC,EAAS4E,EAASiG,GAC5C,GAAIC,GAAGpJ,EAAGD,EAAMsJ,EAAKC,EAAOC,EAAQC,EACnCC,EAAanL,GAAWA,EAAQoL,cAGhCV,EAAW1K,EAAUA,EAAQ0K,SAAW,CAKzC,IAHA9F,EAAUA,MAGe,gBAAb7E,KAA0BA,GACxB,IAAb2K,GAA+B,IAAbA,GAA+B,KAAbA,EAEpC,MAAO9F,EAIR,KAAMiG,KAEE7K,EAAUA,EAAQoL,eAAiBpL,EAAU+G,KAAmBlJ,GACtE4I,EAAazG,GAEdA,EAAUA,GAAWnC,EAEhB8I,GAAiB,CAIrB,GAAkB,KAAb+D,IAAoBM,EAAQ5B,EAAWiC,KAAMtL,IAGjD,GAAM+K,EAAIE,EAAM,IAGf,GAAkB,IAAbN,EAAiB,CACrB,KAAMjJ,EAAOzB,EAAQsL,eAAgBR,IAUpC,MAAOlG,EALP,IAAKnD,EAAK8J,KAAOT,EAEhB,MADAlG,GAAQnG,KAAMgD,GACPmD,MAYT,IAAKuG,IAAe1J,EAAO0J,EAAWG,eAAgBR,KACrDhE,EAAU9G,EAASyB,IACnBA,EAAK8J,KAAOT,EAGZ,MADAlG,GAAQnG,KAAMgD,GACPmD,MAKH,CAAA,GAAKoG,EAAM,GAEjB,MADAvM,GAAKkD,MAAOiD,EAAS5E,EAAQwL,qBAAsBzL,IAC5C6E,CAGD,KAAMkG,EAAIE,EAAM,KAAO9L,EAAQuM,wBACrCzL,EAAQyL,uBAGR,MADAhN,GAAKkD,MAAOiD,EAAS5E,EAAQyL,uBAAwBX,IAC9ClG,EAKT,GAAK1F,EAAQwM,MACXrE,EAAetH,EAAW,QACzB6G,IAAcA,EAAU+E,KAAM5L,IAAc,CAE9C,GAAkB,IAAb2K,EACJS,EAAanL,EACbkL,EAAcnL,MAMR,IAAwC,WAAnCC,EAAQuE,SAASC,cAA6B,EAGnDuG,EAAM/K,EAAQ4L,aAAc,OACjCb,EAAMA,EAAI1H,QAASyG,GAAYC,IAE/B/J,EAAQ6L,aAAc,KAAOd,EAAM7H,GAIpC+H,EAAS9E,EAAUpG,GACnB2B,EAAIuJ,EAAOpK,MACX,OAAQa,IACPuJ,EAAOvJ,GAAK,IAAMqJ,EAAM,IAAMe,GAAYb,EAAOvJ,GAElDwJ,GAAcD,EAAOc,KAAM,KAG3BZ,EAAa9B,EAASsC,KAAM5L,IAAciM,GAAahM,EAAQL,aAC9DK,EAGF,GAAKkL,EACJ,IAIC,MAHAzM,GAAKkD,MAAOiD,EACXuG,EAAWc,iBAAkBf,IAEvBtG,EACN,MAAQsH,IACR,QACInB,IAAQ7H,GACZlD,EAAQmM,gBAAiB,QAS/B,MAAO9F,GAAQtG,EAASsD,QAASlD,EAAO,MAAQH,EAAS4E,EAASiG,GASnE,QAAS1D,MACR,GAAIiF,KAEJ,SAASC,GAAOC,EAAKjH,GAMpB,MAJK+G,GAAK3N,KAAM6N,EAAM,KAAQtG,EAAKuG,mBAE3BF,GAAOD,EAAKI,SAEZH,EAAOC,EAAM,KAAQjH,EAE9B,MAAOgH,GAOR,QAASI,IAAcxM,GAEtB,MADAA,GAAIiD,IAAY,EACTjD,EAOR,QAASyM,IAAQzM,GAChB,GAAI0M,GAAK9O,EAAS0B,cAAc,WAEhC,KACC,QAASU,EAAI0M,GACZ,MAAOhC,GACR,OAAO,EACN,QAEIgC,EAAGhN,YACPgN,EAAGhN,WAAWC,YAAa+M,GAG5BA,EAAK,MASP,QAASC,IAAWC,EAAOC,GAC1B,GAAI3O,GAAM0O,EAAM/G,MAAM,KACrBpE,EAAIvD,EAAI0C,MAET,OAAQa,IACPsE,EAAK+G,WAAY5O,EAAIuD,IAAOoL,EAU9B,QAASE,IAAczF,EAAGC,GACzB,GAAIyF,GAAMzF,GAAKD,EACd2F,EAAOD,GAAsB,IAAf1F,EAAEmD,UAAiC,IAAflD,EAAEkD,UACnCnD,EAAE4F,YAAc3F,EAAE2F,WAGpB,IAAKD,EACJ,MAAOA,EAIR,IAAKD,EACJ,MAASA,EAAMA,EAAIG,YAClB,GAAKH,IAAQzF,EACZ,QAKH,OAAOD,GAAI,KAOZ,QAAS8F,IAAmB1J,GAC3B,MAAO,UAAUlC,GAChB,GAAIc,GAAOd,EAAK8C,SAASC,aACzB,OAAgB,UAATjC,GAAoBd,EAAKkC,OAASA,GAQ3C,QAAS2J,IAAoB3J,GAC5B,MAAO,UAAUlC,GAChB,GAAIc,GAAOd,EAAK8C,SAASC,aACzB,QAAiB,UAATjC,GAA6B,WAATA,IAAsBd,EAAKkC,OAASA,GAQlE,QAAS4J,IAAsBjD,GAG9B,MAAO,UAAU7I,GAKhB,MAAK,QAAUA,GASTA,EAAK9B,YAAc8B,EAAK6I,YAAa,EAGpC,SAAW7I,GACV,SAAWA,GAAK9B,WACb8B,EAAK9B,WAAW2K,WAAaA,EAE7B7I,EAAK6I,WAAaA,EAMpB7I,EAAK+L,aAAelD,GAI1B7I,EAAK+L,cAAgBlD,GACpBF,GAAkB3I,KAAW6I,EAGzB7I,EAAK6I,WAAaA,EAKd,SAAW7I,IACfA,EAAK6I,WAAaA,GAY5B,QAASmD,IAAwBxN,GAChC,MAAOwM,IAAa,SAAUiB,GAE7B,MADAA,IAAYA,EACLjB,GAAa,SAAU5B,EAAM3F,GACnC,GAAIjD,GACH0L,EAAe1N,KAAQ4K,EAAKhK,OAAQ6M,GACpChM,EAAIiM,EAAa9M,MAGlB,OAAQa,IACFmJ,EAAO5I,EAAI0L,EAAajM,MAC5BmJ,EAAK5I,KAAOiD,EAAQjD,GAAK4I,EAAK5I,SAYnC,QAAS+J,IAAahM,GACrB,MAAOA,IAAmD,mBAAjCA,GAAQwL,sBAAwCxL,EAI1Ed,EAAU6G,GAAO7G,WAOjBgH,EAAQH,GAAOG,MAAQ,SAAUzE,GAGhC,GAAImM,GAAkBnM,IAASA,EAAK2J,eAAiB3J,GAAMmM,eAC3D,SAAOA,GAA+C,SAA7BA,EAAgBrJ,UAQ1CkC,EAAcV,GAAOU,YAAc,SAAUoH,GAC5C,GAAIC,GAAYC,EACf1O,EAAMwO,EAAOA,EAAKzC,eAAiByC,EAAO9G,CAG3C,OAAK1H,KAAQxB,GAA6B,IAAjBwB,EAAIqL,UAAmBrL,EAAIuO,iBAKpD/P,EAAWwB,EACXqH,EAAU7I,EAAS+P,gBACnBjH,GAAkBT,EAAOrI,GAIpBkJ,IAAiBlJ,IACpBkQ,EAAYlQ,EAASmQ,cAAgBD,EAAUE,MAAQF,IAGnDA,EAAUG,iBACdH,EAAUG,iBAAkB,SAAU/D,IAAe,GAG1C4D,EAAUI,aACrBJ,EAAUI,YAAa,WAAYhE,KAUrCjL,EAAQ6I,WAAa2E,GAAO,SAAUC,GAErC,MADAA,GAAGyB,UAAY,KACPzB,EAAGf,aAAa,eAOzB1M,EAAQsM,qBAAuBkB,GAAO,SAAUC,GAE/C,MADAA,GAAGjN,YAAa7B,EAASwQ,cAAc,MAC/B1B,EAAGnB,qBAAqB,KAAK3K,SAItC3B,EAAQuM,uBAAyBtC,EAAQwC,KAAM9N,EAAS4N,wBAMxDvM,EAAQoP,QAAU5B,GAAO,SAAUC,GAElC,MADAjG,GAAQhH,YAAaiN,GAAKpB,GAAKrI,GACvBrF,EAAS0Q,oBAAsB1Q,EAAS0Q,kBAAmBrL,GAAUrC,SAIzE3B,EAAQoP,SACZtI,EAAKwI,OAAW,GAAI,SAAUjD,GAC7B,GAAIkD,GAASlD,EAAGlI,QAASiG,EAAWC,GACpC,OAAO,UAAU9H,GAChB,MAAOA,GAAKmK,aAAa,QAAU6C,IAGrCzI,EAAK0I,KAAS,GAAI,SAAUnD,EAAIvL,GAC/B,GAAuC,mBAA3BA,GAAQsL,gBAAkC3E,EAAiB,CACtE,GAAIlF,GAAOzB,EAAQsL,eAAgBC,EACnC,OAAO9J,IAASA,UAIlBuE,EAAKwI,OAAW,GAAK,SAAUjD,GAC9B,GAAIkD,GAASlD,EAAGlI,QAASiG,EAAWC,GACpC,OAAO,UAAU9H,GAChB,GAAIoM,GAAwC,mBAA1BpM,GAAKkN,kBACtBlN,EAAKkN,iBAAiB,KACvB,OAAOd,IAAQA,EAAKxI,QAAUoJ,IAMhCzI,EAAK0I,KAAS,GAAI,SAAUnD,EAAIvL,GAC/B,GAAuC,mBAA3BA,GAAQsL,gBAAkC3E,EAAiB,CACtE,GAAIkH,GAAMnM,EAAGR,EACZO,EAAOzB,EAAQsL,eAAgBC,EAEhC,IAAK9J,EAAO,CAIX,GADAoM,EAAOpM,EAAKkN,iBAAiB,MACxBd,GAAQA,EAAKxI,QAAUkG,EAC3B,OAAS9J,EAIVP,GAAQlB,EAAQuO,kBAAmBhD,GACnC7J,EAAI,CACJ,OAASD,EAAOP,EAAMQ,KAErB,GADAmM,EAAOpM,EAAKkN,iBAAiB,MACxBd,GAAQA,EAAKxI,QAAUkG,EAC3B,OAAS9J,GAKZ,YAMHuE,EAAK0I,KAAU,IAAIxP,EAAQsM,qBAC1B,SAAUoD,EAAK5O,GACd,MAA6C,mBAAjCA,GAAQwL,qBACZxL,EAAQwL,qBAAsBoD,GAG1B1P,EAAQwM,IACZ1L,EAAQiM,iBAAkB2C,GAD3B,QAKR,SAAUA,EAAK5O,GACd,GAAIyB,GACH+D,KACA9D,EAAI,EAEJkD,EAAU5E,EAAQwL,qBAAsBoD,EAGzC,IAAa,MAARA,EAAc,CAClB,MAASnN,EAAOmD,EAAQlD,KACA,IAAlBD,EAAKiJ,UACTlF,EAAI/G,KAAMgD,EAIZ,OAAO+D,GAER,MAAOZ,IAIToB,EAAK0I,KAAY,MAAIxP,EAAQuM,wBAA0B,SAAU2C,EAAWpO,GAC3E,GAA+C,mBAAnCA,GAAQyL,wBAA0C9E,EAC7D,MAAO3G,GAAQyL,uBAAwB2C,IAUzCvH,KAOAD,MAEM1H,EAAQwM,IAAMvC,EAAQwC,KAAM9N,EAASoO,qBAG1CS,GAAO,SAAUC,GAMhBjG,EAAQhH,YAAaiN,GAAKkC,UAAY,UAAY3L,EAAU,qBAC1CA,EAAU,kEAOvByJ,EAAGV,iBAAiB,wBAAwBpL,QAChD+F,EAAUnI,KAAM,SAAWoJ,EAAa,gBAKnC8E,EAAGV,iBAAiB,cAAcpL,QACvC+F,EAAUnI,KAAM,MAAQoJ,EAAa,aAAeD,EAAW,KAI1D+E,EAAGV,iBAAkB,QAAU/I,EAAU,MAAOrC,QACrD+F,EAAUnI,KAAK,MAMVkO,EAAGV,iBAAiB,YAAYpL,QACrC+F,EAAUnI,KAAK,YAMVkO,EAAGV,iBAAkB,KAAO/I,EAAU,MAAOrC,QAClD+F,EAAUnI,KAAK,cAIjBiO,GAAO,SAAUC,GAChBA,EAAGkC,UAAY,mFAKf,IAAIC,GAAQjR,EAAS0B,cAAc,QACnCuP,GAAMjD,aAAc,OAAQ,UAC5Bc,EAAGjN,YAAaoP,GAAQjD,aAAc,OAAQ,KAIzCc,EAAGV,iBAAiB,YAAYpL,QACpC+F,EAAUnI,KAAM,OAASoJ,EAAa,eAKS,IAA3C8E,EAAGV,iBAAiB,YAAYpL,QACpC+F,EAAUnI,KAAM,WAAY,aAK7BiI,EAAQhH,YAAaiN,GAAKrC,UAAW,EACY,IAA5CqC,EAAGV,iBAAiB,aAAapL,QACrC+F,EAAUnI,KAAM,WAAY,aAI7BkO,EAAGV,iBAAiB,QACpBrF,EAAUnI,KAAK,YAIXS,EAAQ6P,gBAAkB5F,EAAQwC,KAAOzG,EAAUwB,EAAQxB,SAChEwB,EAAQsI,uBACRtI,EAAQuI,oBACRvI,EAAQwI,kBACRxI,EAAQyI,qBAERzC,GAAO,SAAUC,GAGhBzN,EAAQkQ,kBAAoBlK,EAAQjG,KAAM0N,EAAI,KAI9CzH,EAAQjG,KAAM0N,EAAI,aAClB9F,EAAcpI,KAAM,KAAMuJ,KAI5BpB,EAAYA,EAAU/F,QAAU,GAAIqH,QAAQtB,EAAUmF,KAAK,MAC3DlF,EAAgBA,EAAchG,QAAU,GAAIqH,QAAQrB,EAAckF,KAAK,MAIvE+B,EAAa3E,EAAQwC,KAAMjF,EAAQ2I,yBAKnCvI,EAAWgH,GAAc3E,EAAQwC,KAAMjF,EAAQI,UAC9C,SAAUS,EAAGC,GACZ,GAAI8H,GAAuB,IAAf/H,EAAEmD,SAAiBnD,EAAEqG,gBAAkBrG,EAClDgI,EAAM/H,GAAKA,EAAE7H,UACd,OAAO4H,KAAMgI,MAAWA,GAAwB,IAAjBA,EAAI7E,YAClC4E,EAAMxI,SACLwI,EAAMxI,SAAUyI,GAChBhI,EAAE8H,yBAA8D,GAAnC9H,EAAE8H,wBAAyBE,MAG3D,SAAUhI,EAAGC,GACZ,GAAKA,EACJ,MAASA,EAAIA,EAAE7H,WACd,GAAK6H,IAAMD,EACV,OAAO,CAIV,QAAO,GAOTD,EAAYwG,EACZ,SAAUvG,EAAGC,GAGZ,GAAKD,IAAMC,EAEV,MADAhB,IAAe,EACR,CAIR,IAAIgJ,IAAWjI,EAAE8H,yBAA2B7H,EAAE6H,uBAC9C,OAAKG,GACGA,GAIRA,GAAYjI,EAAE6D,eAAiB7D,MAAUC,EAAE4D,eAAiB5D,GAC3DD,EAAE8H,wBAAyB7H,GAG3B,EAGc,EAAVgI,IACFtQ,EAAQuQ,cAAgBjI,EAAE6H,wBAAyB9H,KAAQiI,EAGxDjI,IAAM1J,GAAY0J,EAAE6D,gBAAkBrE,GAAgBD,EAASC,EAAcQ,MAG7EC,IAAM3J,GAAY2J,EAAE4D,gBAAkBrE,GAAgBD,EAASC,EAAcS,GAC1E,EAIDjB,EACJ7H,EAAS6H,EAAWgB,GAAM7I,EAAS6H,EAAWiB,GAChD,EAGe,EAAVgI,KAAmB,IAE3B,SAAUjI,EAAGC,GAEZ,GAAKD,IAAMC,EAEV,MADAhB,IAAe,EACR,CAGR,IAAIyG,GACHvL,EAAI,EACJgO,EAAMnI,EAAE5H,WACR4P,EAAM/H,EAAE7H,WACRgQ,GAAOpI,GACPqI,GAAOpI,EAGR,KAAMkI,IAAQH,EACb,MAAOhI,KAAM1J,KACZ2J,IAAM3J,EAAW,EACjB6R,KACAH,EAAM,EACNhJ,EACE7H,EAAS6H,EAAWgB,GAAM7I,EAAS6H,EAAWiB,GAChD,CAGK,IAAKkI,IAAQH,EACnB,MAAOvC,IAAczF,EAAGC,EAIzByF,GAAM1F,CACN,OAAS0F,EAAMA,EAAItN,WAClBgQ,EAAGE,QAAS5C,EAEbA,GAAMzF,CACN,OAASyF,EAAMA,EAAItN,WAClBiQ,EAAGC,QAAS5C,EAIb,OAAQ0C,EAAGjO,KAAOkO,EAAGlO,GACpBA,GAGD,OAAOA,GAENsL,GAAc2C,EAAGjO,GAAIkO,EAAGlO,IAGxBiO,EAAGjO,KAAOqF,KACV6I,EAAGlO,KAAOqF,EAAe,EACzB,GAGKlJ,GA3YCA,GA8YTkI,GAAOb,QAAU,SAAU4K,EAAMC,GAChC,MAAOhK,IAAQ+J,EAAM,KAAM,KAAMC,IAGlChK,GAAOgJ,gBAAkB,SAAUtN,EAAMqO,GASxC,IAPOrO,EAAK2J,eAAiB3J,KAAW5D,GACvC4I,EAAahF,GAIdqO,EAAOA,EAAKzM,QAASgF,EAAkB,UAElCnJ,EAAQ6P,iBAAmBpI,IAC9BU,EAAeyI,EAAO,QACpBjJ,IAAkBA,EAAc8E,KAAMmE,OACtClJ,IAAkBA,EAAU+E,KAAMmE,IAErC,IACC,GAAI3O,GAAM+D,EAAQjG,KAAMwC,EAAMqO,EAG9B,IAAK3O,GAAOjC,EAAQkQ,mBAGlB3N,EAAK5D,UAAuC,KAA3B4D,EAAK5D,SAAS6M,SAChC,MAAOvJ,GAEP,MAAOwJ,IAGV,MAAO5E,IAAQ+J,EAAMjS,EAAU,MAAQ4D,IAASZ,OAAS,GAG1DkF,GAAOe,SAAW,SAAU9G,EAASyB,GAKpC,OAHOzB,EAAQoL,eAAiBpL,KAAcnC,GAC7C4I,EAAazG,GAEP8G,EAAU9G,EAASyB,IAG3BsE,GAAOiK,KAAO,SAAUvO,EAAMc,IAEtBd,EAAK2J,eAAiB3J,KAAW5D,GACvC4I,EAAahF,EAGd,IAAIxB,GAAK+F,EAAK+G,WAAYxK,EAAKiC,eAE9ByL,EAAMhQ,GAAMpB,EAAOI,KAAM+G,EAAK+G,WAAYxK,EAAKiC,eAC9CvE,EAAIwB,EAAMc,GAAOoE,GACjB1D,MAEF,OAAeA,UAARgN,EACNA,EACA/Q,EAAQ6I,aAAepB,EACtBlF,EAAKmK,aAAcrJ,IAClB0N,EAAMxO,EAAKkN,iBAAiBpM,KAAU0N,EAAIC,UAC1CD,EAAI5K,MACJ,MAGJU,GAAOoK,OAAS,SAAUC,GACzB,OAAQA,EAAM,IAAI/M,QAASyG,GAAYC,KAGxChE,GAAOxC,MAAQ,SAAUC,GACxB,KAAM,IAAIzF,OAAO,0CAA4CyF,IAO9DuC,GAAOsK,WAAa,SAAUzL,GAC7B,GAAInD,GACH6O,KACArO,EAAI,EACJP,EAAI,CAOL,IAJA8E,GAAgBtH,EAAQqR,iBACxBhK,GAAarH,EAAQsR,YAAc5L,EAAQrG,MAAO,GAClDqG,EAAQzC,KAAMmF,GAETd,EAAe,CACnB,MAAS/E,EAAOmD,EAAQlD,KAClBD,IAASmD,EAASlD,KACtBO,EAAIqO,EAAW7R,KAAMiD,GAGvB,OAAQO,IACP2C,EAAQxC,OAAQkO,EAAYrO,GAAK,GAQnC,MAFAsE,GAAY,KAEL3B,GAORqB,EAAUF,GAAOE,QAAU,SAAUxE,GACpC,GAAIoM,GACH1M,EAAM,GACNO,EAAI,EACJgJ,EAAWjJ,EAAKiJ,QAEjB,IAAMA,GAMC,GAAkB,IAAbA,GAA+B,IAAbA,GAA+B,KAAbA,EAAkB,CAGjE,GAAiC,gBAArBjJ,GAAKgP,YAChB,MAAOhP,GAAKgP,WAGZ,KAAMhP,EAAOA,EAAKiP,WAAYjP,EAAMA,EAAOA,EAAK2L,YAC/CjM,GAAO8E,EAASxE,OAGZ,IAAkB,IAAbiJ,GAA+B,IAAbA,EAC7B,MAAOjJ,GAAKkP,cAhBZ,OAAS9C,EAAOpM,EAAKC,KAEpBP,GAAO8E,EAAS4H,EAkBlB,OAAO1M,IAGR6E,EAAOD,GAAO6K,WAGbrE,YAAa,GAEbsE,aAAcpE,GAEdzB,MAAOxC,EAEPuE,cAEA2B,QAEAoC,UACCC,KAAOxG,IAAK,aAAc1I,OAAO,GACjCmP,KAAOzG,IAAK,cACZ0G,KAAO1G,IAAK,kBAAmB1I,OAAO,GACtCqP,KAAO3G,IAAK,oBAGb4G,WACCvI,KAAQ,SAAUoC,GAUjB,MATAA,GAAM,GAAKA,EAAM,GAAG3H,QAASiG,EAAWC,IAGxCyB,EAAM,IAAOA,EAAM,IAAMA,EAAM,IAAMA,EAAM,IAAM,IAAK3H,QAASiG,EAAWC,IAExD,OAAbyB,EAAM,KACVA,EAAM,GAAK,IAAMA,EAAM,GAAK,KAGtBA,EAAMzM,MAAO,EAAG,IAGxBuK,MAAS,SAAUkC,GA6BlB,MAlBAA,GAAM,GAAKA,EAAM,GAAGxG,cAEY,QAA3BwG,EAAM,GAAGzM,MAAO,EAAG,IAEjByM,EAAM,IACXjF,GAAOxC,MAAOyH,EAAM,IAKrBA,EAAM,KAAQA,EAAM,GAAKA,EAAM,IAAMA,EAAM,IAAM,GAAK,GAAmB,SAAbA,EAAM,IAA8B,QAAbA,EAAM,KACzFA,EAAM,KAAUA,EAAM,GAAKA,EAAM,IAAqB,QAAbA,EAAM,KAGpCA,EAAM,IACjBjF,GAAOxC,MAAOyH,EAAM,IAGdA,GAGRnC,OAAU,SAAUmC,GACnB,GAAIoG,GACHC,GAAYrG,EAAM,IAAMA,EAAM,EAE/B,OAAKxC,GAAiB,MAAEmD,KAAMX,EAAM,IAC5B,MAIHA,EAAM,GACVA,EAAM,GAAKA,EAAM,IAAMA,EAAM,IAAM,GAGxBqG,GAAY/I,EAAQqD,KAAM0F,KAEpCD,EAASjL,EAAUkL,GAAU,MAE7BD,EAASC,EAAS3S,QAAS,IAAK2S,EAASxQ,OAASuQ,GAAWC,EAASxQ,UAGvEmK,EAAM,GAAKA,EAAM,GAAGzM,MAAO,EAAG6S,GAC9BpG,EAAM,GAAKqG,EAAS9S,MAAO,EAAG6S,IAIxBpG,EAAMzM,MAAO,EAAG,MAIzBiQ,QAEC7F,IAAO,SAAU2I,GAChB,GAAI/M,GAAW+M,EAAiBjO,QAASiG,EAAWC,IAAY/E,aAChE,OAA4B,MAArB8M,EACN,WAAa,OAAO,GACpB,SAAU7P,GACT,MAAOA,GAAK8C,UAAY9C,EAAK8C,SAASC,gBAAkBD,IAI3DmE,MAAS,SAAU0F,GAClB,GAAImD,GAAUrK,EAAYkH,EAAY,IAEtC,OAAOmD,KACLA,EAAU,GAAIrJ,QAAQ,MAAQL,EAAa,IAAMuG,EAAY,IAAMvG,EAAa,SACjFX,EAAYkH,EAAW,SAAU3M,GAChC,MAAO8P,GAAQ5F,KAAgC,gBAAnBlK,GAAK2M,WAA0B3M,EAAK2M,WAA0C,mBAAtB3M,GAAKmK,cAAgCnK,EAAKmK,aAAa,UAAY,OAI1JhD,KAAQ,SAAUrG,EAAMiP,EAAUC,GACjC,MAAO,UAAUhQ,GAChB,GAAIiQ,GAAS3L,GAAOiK,KAAMvO,EAAMc,EAEhC,OAAe,OAAVmP,EACgB,OAAbF,GAEFA,IAINE,GAAU,GAEU,MAAbF,EAAmBE,IAAWD,EACvB,OAAbD,EAAoBE,IAAWD,EAClB,OAAbD,EAAoBC,GAAqC,IAA5BC,EAAOhT,QAAS+S,GAChC,OAAbD,EAAoBC,GAASC,EAAOhT,QAAS+S,MAChC,OAAbD,EAAoBC,GAASC,EAAOnT,OAAQkT,EAAM5Q,UAAa4Q,EAClD,OAAbD,GAAsB,IAAME,EAAOrO,QAAS4E,EAAa,KAAQ,KAAMvJ,QAAS+S,MACnE,OAAbD,IAAoBE,IAAWD,GAASC,EAAOnT,MAAO,EAAGkT,EAAM5Q,OAAS,KAAQ4Q,EAAQ,QAK3F3I,MAAS,SAAUnF,EAAMgO,EAAMjE,EAAU7L,EAAOE,GAC/C,GAAI6P,GAAgC,QAAvBjO,EAAKpF,MAAO,EAAG,GAC3BsT,EAA+B,SAArBlO,EAAKpF,UACfuT,EAAkB,YAATH,CAEV,OAAiB,KAAV9P,GAAwB,IAATE,EAGrB,SAAUN,GACT,QAASA,EAAK9B,YAGf,SAAU8B,EAAMzB,EAAS+R,GACxB,GAAI1F,GAAO2F,EAAaC,EAAYpE,EAAMqE,EAAWC,EACpD5H,EAAMqH,IAAWC,EAAU,cAAgB,kBAC3CO,EAAS3Q,EAAK9B,WACd4C,EAAOuP,GAAUrQ,EAAK8C,SAASC,cAC/B6N,GAAYN,IAAQD,EACpB5E,GAAO,CAER,IAAKkF,EAAS,CAGb,GAAKR,EAAS,CACb,MAAQrH,EAAM,CACbsD,EAAOpM,CACP,OAASoM,EAAOA,EAAMtD,GACrB,GAAKuH,EACJjE,EAAKtJ,SAASC,gBAAkBjC,EACd,IAAlBsL,EAAKnD,SAEL,OAAO,CAITyH,GAAQ5H,EAAe,SAAT5G,IAAoBwO,GAAS,cAE5C,OAAO,EAMR,GAHAA,GAAUN,EAAUO,EAAO1B,WAAa0B,EAAOE,WAG1CT,GAAWQ,EAAW,CAK1BxE,EAAOuE,EACPH,EAAapE,EAAM3K,KAAc2K,EAAM3K,OAIvC8O,EAAcC,EAAYpE,EAAK0E,YAC7BN,EAAYpE,EAAK0E,cAEnBlG,EAAQ2F,EAAarO,OACrBuO,EAAY7F,EAAO,KAAQrF,GAAWqF,EAAO,GAC7Ca,EAAOgF,GAAa7F,EAAO,GAC3BwB,EAAOqE,GAAaE,EAAO3H,WAAYyH,EAEvC,OAASrE,IAASqE,GAAarE,GAAQA,EAAMtD,KAG3C2C,EAAOgF,EAAY,IAAMC,EAAM1K,MAGhC,GAAuB,IAAlBoG,EAAKnD,YAAoBwC,GAAQW,IAASpM,EAAO,CACrDuQ,EAAarO,IAAWqD,EAASkL,EAAWhF,EAC5C,YAuBF,IAjBKmF,IAEJxE,EAAOpM,EACPwQ,EAAapE,EAAM3K,KAAc2K,EAAM3K,OAIvC8O,EAAcC,EAAYpE,EAAK0E,YAC7BN,EAAYpE,EAAK0E,cAEnBlG,EAAQ2F,EAAarO,OACrBuO,EAAY7F,EAAO,KAAQrF,GAAWqF,EAAO,GAC7Ca,EAAOgF,GAKHhF,KAAS,EAEb,MAASW,IAASqE,GAAarE,GAAQA,EAAMtD,KAC3C2C,EAAOgF,EAAY,IAAMC,EAAM1K,MAEhC,IAAOqK,EACNjE,EAAKtJ,SAASC,gBAAkBjC,EACd,IAAlBsL,EAAKnD,aACHwC,IAGGmF,IACJJ,EAAapE,EAAM3K,KAAc2K,EAAM3K,OAIvC8O,EAAcC,EAAYpE,EAAK0E,YAC7BN,EAAYpE,EAAK0E,cAEnBP,EAAarO,IAAWqD,EAASkG,IAG7BW,IAASpM,GACb,KASL,OADAyL,IAAQnL,EACDmL,IAASrL,GAAWqL,EAAOrL,IAAU,GAAKqL,EAAOrL,GAAS,KAKrEgH,OAAU,SAAU2J,EAAQ9E,GAK3B,GAAIjI,GACHxF,EAAK+F,EAAKgC,QAASwK,IAAYxM,EAAKyM,WAAYD,EAAOhO,gBACtDuB,GAAOxC,MAAO,uBAAyBiP,EAKzC,OAAKvS,GAAIiD,GACDjD,EAAIyN,GAIPzN,EAAGY,OAAS,GAChB4E,GAAS+M,EAAQA,EAAQ,GAAI9E,GACtB1H,EAAKyM,WAAW3T,eAAgB0T,EAAOhO,eAC7CiI,GAAa,SAAU5B,EAAM3F,GAC5B,GAAIwN,GACHC,EAAU1S,EAAI4K,EAAM6C,GACpBhM,EAAIiR,EAAQ9R,MACb,OAAQa,IACPgR,EAAMhU,EAASmM,EAAM8H,EAAQjR,IAC7BmJ,EAAM6H,KAAWxN,EAASwN,GAAQC,EAAQjR,MAG5C,SAAUD,GACT,MAAOxB,GAAIwB,EAAM,EAAGgE,KAIhBxF,IAIT+H,SAEC4K,IAAOnG,GAAa,SAAU1M,GAI7B,GAAI+O,MACHlK,KACAiO,EAAUzM,EAASrG,EAASsD,QAASlD,EAAO,MAE7C,OAAO0S,GAAS3P,GACfuJ,GAAa,SAAU5B,EAAM3F,EAASlF,EAAS+R,GAC9C,GAAItQ,GACHqR,EAAYD,EAAShI,EAAM,KAAMkH,MACjCrQ,EAAImJ,EAAKhK,MAGV,OAAQa,KACDD,EAAOqR,EAAUpR,MACtBmJ,EAAKnJ,KAAOwD,EAAQxD,GAAKD,MAI5B,SAAUA,EAAMzB,EAAS+R,GAKxB,MAJAjD,GAAM,GAAKrN,EACXoR,EAAS/D,EAAO,KAAMiD,EAAKnN,GAE3BkK,EAAM,GAAK,MACHlK,EAAQ6C,SAInBsL,IAAOtG,GAAa,SAAU1M,GAC7B,MAAO,UAAU0B,GAChB,MAAOsE,IAAQhG,EAAU0B,GAAOZ,OAAS,KAI3CiG,SAAY2F,GAAa,SAAUjN,GAElC,MADAA,GAAOA,EAAK6D,QAASiG,EAAWC,IACzB,SAAU9H,GAChB,OAASA,EAAKgP,aAAehP,EAAKuR,WAAa/M,EAASxE,IAAS/C,QAASc,SAW5EyT,KAAQxG,GAAc,SAAUwG,GAM/B,MAJM1K,GAAYoD,KAAKsH,GAAQ,KAC9BlN,GAAOxC,MAAO,qBAAuB0P,GAEtCA,EAAOA,EAAK5P,QAASiG,EAAWC,IAAY/E,cACrC,SAAU/C,GAChB,GAAIyR,EACJ,GACC,IAAMA,EAAWvM,EAChBlF,EAAKwR,KACLxR,EAAKmK,aAAa,aAAenK,EAAKmK,aAAa,QAGnD,MADAsH,GAAWA,EAAS1O,cACb0O,IAAaD,GAA2C,IAAnCC,EAASxU,QAASuU,EAAO,YAE5CxR,EAAOA,EAAK9B,aAAiC,IAAlB8B,EAAKiJ,SAC3C,QAAO,KAKT9H,OAAU,SAAUnB,GACnB,GAAI0R,GAAOnV,EAAOoV,UAAYpV,EAAOoV,SAASD,IAC9C,OAAOA,IAAQA,EAAK5U,MAAO,KAAQkD,EAAK8J,IAGzC8H,KAAQ,SAAU5R,GACjB,MAAOA,KAASiF,GAGjB4M,MAAS,SAAU7R,GAClB,MAAOA,KAAS5D,EAAS0V,iBAAmB1V,EAAS2V,UAAY3V,EAAS2V,gBAAkB/R,EAAKkC,MAAQlC,EAAKgS,OAAShS,EAAKiS,WAI7HC,QAAWpG,IAAsB,GACjCjD,SAAYiD,IAAsB,GAElCqG,QAAW,SAAUnS,GAGpB,GAAI8C,GAAW9C,EAAK8C,SAASC,aAC7B,OAAqB,UAAbD,KAA0B9C,EAAKmS,SAA0B,WAAbrP,KAA2B9C,EAAKoS,UAGrFA,SAAY,SAAUpS,GAOrB,MAJKA,GAAK9B,YACT8B,EAAK9B,WAAWmU,cAGVrS,EAAKoS,YAAa,GAI1BE,MAAS,SAAUtS,GAKlB,IAAMA,EAAOA,EAAKiP,WAAYjP,EAAMA,EAAOA,EAAK2L,YAC/C,GAAK3L,EAAKiJ,SAAW,EACpB,OAAO,CAGT,QAAO,GAGR0H,OAAU,SAAU3Q,GACnB,OAAQuE,EAAKgC,QAAe,MAAGvG,IAIhCuS,OAAU,SAAUvS,GACnB,MAAOyH,GAAQyC,KAAMlK,EAAK8C,WAG3BuK,MAAS,SAAUrN,GAClB,MAAOwH,GAAQ0C,KAAMlK,EAAK8C,WAG3B0P,OAAU,SAAUxS,GACnB,GAAIc,GAAOd,EAAK8C,SAASC,aACzB,OAAgB,UAATjC,GAAkC,WAAdd,EAAKkC,MAA8B,WAATpB,GAGtD/C,KAAQ,SAAUiC,GACjB,GAAIuO,EACJ,OAAuC,UAAhCvO,EAAK8C,SAASC,eACN,SAAd/C,EAAKkC,OAImC,OAArCqM,EAAOvO,EAAKmK,aAAa,UAA2C,SAAvBoE,EAAKxL,gBAIvD3C,MAAS4L,GAAuB,WAC/B,OAAS,KAGV1L,KAAQ0L,GAAuB,SAAUE,EAAc9M,GACtD,OAASA,EAAS,KAGnBiB,GAAM2L,GAAuB,SAAUE,EAAc9M,EAAQ6M,GAC5D,OAASA,EAAW,EAAIA,EAAW7M,EAAS6M,KAG7CwG,KAAQzG,GAAuB,SAAUE,EAAc9M,GAEtD,IADA,GAAIa,GAAI,EACAA,EAAIb,EAAQa,GAAK,EACxBiM,EAAalP,KAAMiD,EAEpB,OAAOiM,KAGRwG,IAAO1G,GAAuB,SAAUE,EAAc9M,GAErD,IADA,GAAIa,GAAI,EACAA,EAAIb,EAAQa,GAAK,EACxBiM,EAAalP,KAAMiD,EAEpB,OAAOiM,KAGRyG,GAAM3G,GAAuB,SAAUE,EAAc9M,EAAQ6M,GAE5D,IADA,GAAIhM,GAAIgM,EAAW,EAAIA,EAAW7M,EAAS6M,IACjChM,GAAK,GACdiM,EAAalP,KAAMiD,EAEpB,OAAOiM,KAGR0G,GAAM5G,GAAuB,SAAUE,EAAc9M,EAAQ6M,GAE5D,IADA,GAAIhM,GAAIgM,EAAW,EAAIA,EAAW7M,EAAS6M,IACjChM,EAAIb,GACb8M,EAAalP,KAAMiD,EAEpB,OAAOiM,OAKV3H,EAAKgC,QAAa,IAAIhC,EAAKgC,QAAY,EAGvC,KAAMtG,KAAO4S,OAAO,EAAMC,UAAU,EAAMC,MAAM,EAAMC,UAAU,EAAMC,OAAO,GAC5E1O,EAAKgC,QAAStG,GAAM2L,GAAmB3L,EAExC,KAAMA,KAAOiT,QAAQ,EAAMC,OAAO,GACjC5O,EAAKgC,QAAStG,GAAM4L,GAAoB5L,EAIzC,SAAS+Q,OACTA,GAAW/R,UAAYsF,EAAK6O,QAAU7O,EAAKgC,QAC3ChC,EAAKyM,WAAa,GAAIA,IAEtBtM,EAAWJ,GAAOI,SAAW,SAAUpG,EAAU+U,GAChD,GAAInC,GAAS3H,EAAO+J,EAAQpR,EAC3BqR,EAAO/J,EAAQgK,EACfC,EAAS9N,EAAYrH,EAAW,IAEjC,IAAKmV,EACJ,MAAOJ,GAAY,EAAII,EAAO3W,MAAO,EAGtCyW,GAAQjV,EACRkL,KACAgK,EAAajP,EAAKmL,SAElB,OAAQ6D,EAAQ,CAGTrC,KAAY3H,EAAQ7C,EAAOkD,KAAM2J,MACjChK,IAEJgK,EAAQA,EAAMzW,MAAOyM,EAAM,GAAGnK,SAAYmU,GAE3C/J,EAAOxM,KAAOsW,OAGfpC,GAAU,GAGJ3H,EAAQ5C,EAAaiD,KAAM2J,MAChCrC,EAAU3H,EAAMwB,QAChBuI,EAAOtW,MACN4G,MAAOsN,EAEPhP,KAAMqH,EAAM,GAAG3H,QAASlD,EAAO,OAEhC6U,EAAQA,EAAMzW,MAAOoU,EAAQ9R,QAI9B,KAAM8C,IAAQqC,GAAKwI,SACZxD,EAAQxC,EAAW7E,GAAO0H,KAAM2J,KAAcC,EAAYtR,MAC9DqH,EAAQiK,EAAYtR,GAAQqH,MAC7B2H,EAAU3H,EAAMwB,QAChBuI,EAAOtW,MACN4G,MAAOsN,EACPhP,KAAMA,EACNuB,QAAS8F,IAEVgK,EAAQA,EAAMzW,MAAOoU,EAAQ9R,QAI/B,KAAM8R,EACL,MAOF,MAAOmC,GACNE,EAAMnU,OACNmU,EACCjP,GAAOxC,MAAOxD,GAEdqH,EAAYrH,EAAUkL,GAAS1M,MAAO,GAGzC,SAASuN,IAAYiJ,GAIpB,IAHA,GAAIrT,GAAI,EACPM,EAAM+S,EAAOlU,OACbd,EAAW,GACJ2B,EAAIM,EAAKN,IAChB3B,GAAYgV,EAAOrT,GAAG2D,KAEvB,OAAOtF,GAGR,QAASsK,IAAewI,EAASsC,EAAYC,GAC5C,GAAI7K,GAAM4K,EAAW5K,IACpB8K,EAAOF,EAAW3K,KAClB8B,EAAM+I,GAAQ9K,EACd+K,EAAmBF,GAAgB,eAAR9I,EAC3BiJ,EAAWtO,GAEZ,OAAOkO,GAAWtT,MAEjB,SAAUJ,EAAMzB,EAAS+R,GACxB,MAAStQ,EAAOA,EAAM8I,GACrB,GAAuB,IAAlB9I,EAAKiJ,UAAkB4K,EAC3B,MAAOzC,GAASpR,EAAMzB,EAAS+R,EAGjC,QAAO,GAIR,SAAUtQ,EAAMzB,EAAS+R,GACxB,GAAIyD,GAAUxD,EAAaC,EAC1BwD,GAAazO,EAASuO,EAGvB,IAAKxD,GACJ,MAAStQ,EAAOA,EAAM8I,GACrB,IAAuB,IAAlB9I,EAAKiJ,UAAkB4K,IACtBzC,EAASpR,EAAMzB,EAAS+R,GAC5B,OAAO,MAKV,OAAStQ,EAAOA,EAAM8I,GACrB,GAAuB,IAAlB9I,EAAKiJ,UAAkB4K,EAO3B,GANArD,EAAaxQ,EAAMyB,KAAczB,EAAMyB,OAIvC8O,EAAcC,EAAYxQ,EAAK8Q,YAAeN,EAAYxQ,EAAK8Q,cAE1D8C,GAAQA,IAAS5T,EAAK8C,SAASC,cACnC/C,EAAOA,EAAM8I,IAAS9I,MAChB,CAAA,IAAM+T,EAAWxD,EAAa1F,KACpCkJ,EAAU,KAAQxO,GAAWwO,EAAU,KAAQD,EAG/C,MAAQE,GAAU,GAAMD,EAAU,EAMlC,IAHAxD,EAAa1F,GAAQmJ,EAGfA,EAAU,GAAM5C,EAASpR,EAAMzB,EAAS+R,GAC7C,OAAO,EAMZ,OAAO,GAIV,QAAS2D,IAAgBC,GACxB,MAAOA,GAAS9U,OAAS,EACxB,SAAUY,EAAMzB,EAAS+R,GACxB,GAAIrQ,GAAIiU,EAAS9U,MACjB,OAAQa,IACP,IAAMiU,EAASjU,GAAID,EAAMzB,EAAS+R,GACjC,OAAO,CAGT,QAAO,GAER4D,EAAS,GAGX,QAASC,IAAkB7V,EAAU8V,EAAUjR,GAG9C,IAFA,GAAIlD,GAAI,EACPM,EAAM6T,EAAShV,OACRa,EAAIM,EAAKN,IAChBqE,GAAQhG,EAAU8V,EAASnU,GAAIkD,EAEhC,OAAOA,GAGR,QAASkR,IAAUhD,EAAWtR,EAAKgN,EAAQxO,EAAS+R,GAOnD,IANA,GAAItQ,GACHsU,KACArU,EAAI,EACJM,EAAM8Q,EAAUjS,OAChBmV,EAAgB,MAAPxU,EAEFE,EAAIM,EAAKN,KACVD,EAAOqR,EAAUpR,MAChB8M,IAAUA,EAAQ/M,EAAMzB,EAAS+R,KACtCgE,EAAatX,KAAMgD,GACduU,GACJxU,EAAI/C,KAAMiD,IAMd,OAAOqU,GAGR,QAASE,IAAY9E,EAAWpR,EAAU8S,EAASqD,EAAYC,EAAYC,GAO1E,MANKF,KAAeA,EAAYhT,KAC/BgT,EAAaD,GAAYC,IAErBC,IAAeA,EAAYjT,KAC/BiT,EAAaF,GAAYE,EAAYC,IAE/B3J,GAAa,SAAU5B,EAAMjG,EAAS5E,EAAS+R,GACrD,GAAIsE,GAAM3U,EAAGD,EACZ6U,KACAC,KACAC,EAAc5R,EAAQ/D,OAGtBK,EAAQ2J,GAAQ+K,GAAkB7V,GAAY,IAAKC,EAAQ0K,UAAa1K,GAAYA,MAGpFyW,GAAYtF,IAAetG,GAAS9K,EAEnCmB,EADA4U,GAAU5U,EAAOoV,EAAQnF,EAAWnR,EAAS+R,GAG9C2E,EAAa7D,EAEZsD,IAAgBtL,EAAOsG,EAAYqF,GAAeN,MAMjDtR,EACD6R,CAQF,IALK5D,GACJA,EAAS4D,EAAWC,EAAY1W,EAAS+R,GAIrCmE,EAAa,CACjBG,EAAOP,GAAUY,EAAYH,GAC7BL,EAAYG,KAAUrW,EAAS+R,GAG/BrQ,EAAI2U,EAAKxV,MACT,OAAQa,KACDD,EAAO4U,EAAK3U,MACjBgV,EAAYH,EAAQ7U,MAAS+U,EAAWF,EAAQ7U,IAAOD,IAK1D,GAAKoJ,GACJ,GAAKsL,GAAchF,EAAY,CAC9B,GAAKgF,EAAa,CAEjBE,KACA3U,EAAIgV,EAAW7V,MACf,OAAQa,KACDD,EAAOiV,EAAWhV,KAEvB2U,EAAK5X,KAAOgY,EAAU/U,GAAKD,EAG7B0U,GAAY,KAAOO,KAAkBL,EAAMtE,GAI5CrQ,EAAIgV,EAAW7V,MACf,OAAQa,KACDD,EAAOiV,EAAWhV,MACtB2U,EAAOF,EAAazX,EAASmM,EAAMpJ,GAAS6U,EAAO5U,SAEpDmJ,EAAKwL,KAAUzR,EAAQyR,GAAQ5U,SAOlCiV,GAAaZ,GACZY,IAAe9R,EACd8R,EAAWtU,OAAQoU,EAAaE,EAAW7V,QAC3C6V,GAEGP,EACJA,EAAY,KAAMvR,EAAS8R,EAAY3E,GAEvCtT,EAAKkD,MAAOiD,EAAS8R,KAMzB,QAASC,IAAmB5B,GAwB3B,IAvBA,GAAI6B,GAAc/D,EAAS5Q,EAC1BD,EAAM+S,EAAOlU,OACbgW,EAAkB7Q,EAAK8K,SAAUiE,EAAO,GAAGpR,MAC3CmT,EAAmBD,GAAmB7Q,EAAK8K,SAAS,KACpDpP,EAAImV,EAAkB,EAAI,EAG1BE,EAAe1M,GAAe,SAAU5I,GACvC,MAAOA,KAASmV,GACdE,GAAkB,GACrBE,EAAkB3M,GAAe,SAAU5I,GAC1C,MAAO/C,GAASkY,EAAcnV,OAC5BqV,GAAkB,GACrBnB,GAAa,SAAUlU,EAAMzB,EAAS+R,GACrC,GAAI5Q,IAAS0V,IAAqB9E,GAAO/R,IAAYsG,MACnDsQ,EAAe5W,GAAS0K,SACxBqM,EAActV,EAAMzB,EAAS+R,GAC7BiF,EAAiBvV,EAAMzB,EAAS+R,GAGlC,OADA6E,GAAe,KACRzV,IAGDO,EAAIM,EAAKN,IAChB,GAAMmR,EAAU7M,EAAK8K,SAAUiE,EAAOrT,GAAGiC,MACxCgS,GAAatL,GAAcqL,GAAgBC,GAAY9C,QACjD,CAIN,GAHAA,EAAU7M,EAAKwI,OAAQuG,EAAOrT,GAAGiC,MAAOhC,MAAO,KAAMoT,EAAOrT,GAAGwD,SAG1D2N,EAAS3P,GAAY,CAGzB,IADAjB,IAAMP,EACEO,EAAID,EAAKC,IAChB,GAAK+D,EAAK8K,SAAUiE,EAAO9S,GAAG0B,MAC7B,KAGF,OAAOsS,IACNvU,EAAI,GAAKgU,GAAgBC,GACzBjU,EAAI,GAAKoK,GAERiJ,EAAOxW,MAAO,EAAGmD,EAAI,GAAIlD,QAAS6G,MAAgC,MAAzB0P,EAAQrT,EAAI,GAAIiC,KAAe,IAAM,MAC7EN,QAASlD,EAAO,MAClB0S,EACAnR,EAAIO,GAAK0U,GAAmB5B,EAAOxW,MAAOmD,EAAGO,IAC7CA,EAAID,GAAO2U,GAAoB5B,EAASA,EAAOxW,MAAO0D,IACtDA,EAAID,GAAO8J,GAAYiJ,IAGzBY,EAASlX,KAAMoU,GAIjB,MAAO6C,IAAgBC,GAGxB,QAASsB,IAA0BC,EAAiBC,GACnD,GAAIC,GAAQD,EAAYtW,OAAS,EAChCwW,EAAYH,EAAgBrW,OAAS,EACrCyW,EAAe,SAAUzM,EAAM7K,EAAS+R,EAAKnN,EAAS2S,GACrD,GAAI9V,GAAMQ,EAAG4Q,EACZ2E,EAAe,EACf9V,EAAI,IACJoR,EAAYjI,MACZ4M,KACAC,EAAgBpR,EAEhBpF,EAAQ2J,GAAQwM,GAAarR,EAAK0I,KAAU,IAAG,IAAK6I,GAEpDI,EAAiB3Q,GAA4B,MAAjB0Q,EAAwB,EAAIvU,KAAKC,UAAY,GACzEpB,EAAMd,EAAML,MASb,KAPK0W,IACJjR,EAAmBtG,IAAYnC,GAAYmC,GAAWuX,GAM/C7V,IAAMM,GAA4B,OAApBP,EAAOP,EAAMQ,IAAaA,IAAM,CACrD,GAAK2V,GAAa5V,EAAO,CACxBQ,EAAI,EACEjC,GAAWyB,EAAK2J,gBAAkBvN,IACvC4I,EAAahF,GACbsQ,GAAOpL,EAER,OAASkM,EAAUqE,EAAgBjV,KAClC,GAAK4Q,EAASpR,EAAMzB,GAAWnC,EAAUkU,GAAO,CAC/CnN,EAAQnG,KAAMgD,EACd,OAGG8V,IACJvQ,EAAU2Q,GAKPP,KAEE3V,GAAQoR,GAAWpR,IACxB+V,IAII3M,GACJiI,EAAUrU,KAAMgD,IAgBnB,GATA+V,GAAgB9V,EASX0V,GAAS1V,IAAM8V,EAAe,CAClCvV,EAAI,CACJ,OAAS4Q,EAAUsE,EAAYlV,KAC9B4Q,EAASC,EAAW2E,EAAYzX,EAAS+R,EAG1C,IAAKlH,EAAO,CAEX,GAAK2M,EAAe,EACnB,MAAQ9V,IACAoR,EAAUpR,IAAM+V,EAAW/V,KACjC+V,EAAW/V,GAAK+F,EAAIxI,KAAM2F,GAM7B6S,GAAa3B,GAAU2B,GAIxBhZ,EAAKkD,MAAOiD,EAAS6S,GAGhBF,IAAc1M,GAAQ4M,EAAW5W,OAAS,GAC5C2W,EAAeL,EAAYtW,OAAW,GAExCkF,GAAOsK,WAAYzL,GAUrB,MALK2S,KACJvQ,EAAU2Q,EACVrR,EAAmBoR,GAGb5E,EAGT,OAAOsE,GACN3K,GAAc6K,GACdA,EA+KF,MA5KAlR,GAAUL,GAAOK,QAAU,SAAUrG,EAAUiL,GAC9C,GAAItJ,GACHyV,KACAD,KACAhC,EAAS7N,EAAetH,EAAW,IAEpC,KAAMmV,EAAS,CAERlK,IACLA,EAAQ7E,EAAUpG,IAEnB2B,EAAIsJ,EAAMnK,MACV,OAAQa,IACPwT,EAASyB,GAAmB3L,EAAMtJ,IAC7BwT,EAAQhS,GACZiU,EAAY1Y,KAAMyW,GAElBgC,EAAgBzY,KAAMyW,EAKxBA,GAAS7N,EAAetH,EAAUkX,GAA0BC,EAAiBC,IAG7EjC,EAAOnV,SAAWA,EAEnB,MAAOmV,IAYR7O,EAASN,GAAOM,OAAS,SAAUtG,EAAUC,EAAS4E,EAASiG,GAC9D,GAAInJ,GAAGqT,EAAQ6C,EAAOjU,EAAM+K,EAC3BmJ,EAA+B,kBAAb9X,IAA2BA,EAC7CiL,GAASH,GAAQ1E,EAAWpG,EAAW8X,EAAS9X,UAAYA,EAM7D,IAJA6E,EAAUA,MAIY,IAAjBoG,EAAMnK,OAAe,CAIzB,GADAkU,EAAS/J,EAAM,GAAKA,EAAM,GAAGzM,MAAO,GAC/BwW,EAAOlU,OAAS,GAAkC,QAA5B+W,EAAQ7C,EAAO,IAAIpR,MACvB,IAArB3D,EAAQ0K,UAAkB/D,GAAkBX,EAAK8K,SAAUiE,EAAO,GAAGpR,MAAS,CAG/E,GADA3D,GAAYgG,EAAK0I,KAAS,GAAGkJ,EAAM1S,QAAQ,GAAG7B,QAAQiG,EAAWC,IAAYvJ,QAAkB,IACzFA,EACL,MAAO4E,EAGIiT,KACX7X,EAAUA,EAAQL,YAGnBI,EAAWA,EAASxB,MAAOwW,EAAOvI,QAAQnH,MAAMxE,QAIjDa,EAAI8G,EAAwB,aAAEmD,KAAM5L,GAAa,EAAIgV,EAAOlU,MAC5D,OAAQa,IAAM,CAIb,GAHAkW,EAAQ7C,EAAOrT,GAGVsE,EAAK8K,SAAWnN,EAAOiU,EAAMjU,MACjC,KAED,KAAM+K,EAAO1I,EAAK0I,KAAM/K,MAEjBkH,EAAO6D,EACZkJ,EAAM1S,QAAQ,GAAG7B,QAASiG,EAAWC,IACrCF,EAASsC,KAAMoJ,EAAO,GAAGpR,OAAUqI,GAAahM,EAAQL,aAAgBK,IACpE,CAKJ,GAFA+U,EAAO3S,OAAQV,EAAG,GAClB3B,EAAW8K,EAAKhK,QAAUiL,GAAYiJ,IAChChV,EAEL,MADAtB,GAAKkD,MAAOiD,EAASiG,GACdjG,CAGR,SAeJ,OAPEiT,GAAYzR,EAASrG,EAAUiL,IAChCH,EACA7K,GACC2G,EACD/B,GACC5E,GAAWqJ,EAASsC,KAAM5L,IAAciM,GAAahM,EAAQL,aAAgBK,GAExE4E,GAMR1F,EAAQsR,WAAatN,EAAQ4C,MAAM,IAAI3D,KAAMmF,GAAYyE,KAAK,MAAQ7I,EAItEhE,EAAQqR,mBAAqB/J,EAG7BC,IAIAvH,EAAQuQ,aAAe/C,GAAO,SAAUC,GAEvC,MAA0E,GAAnEA,EAAG0C,wBAAyBxR,EAAS0B,cAAc,eAMrDmN,GAAO,SAAUC,GAEtB,MADAA,GAAGkC,UAAY,mBAC+B,MAAvClC,EAAG+D,WAAW9E,aAAa,WAElCgB,GAAW,yBAA0B,SAAUnL,EAAMc,EAAM2D,GAC1D,IAAMA,EACL,MAAOzE,GAAKmK,aAAcrJ,EAA6B,SAAvBA,EAAKiC,cAA2B,EAAI,KAOjEtF,EAAQ6I,YAAe2E,GAAO,SAAUC,GAG7C,MAFAA,GAAGkC,UAAY,WACflC,EAAG+D,WAAW7E,aAAc,QAAS,IACY,KAA1Cc,EAAG+D,WAAW9E,aAAc,YAEnCgB,GAAW,QAAS,SAAUnL,EAAMc,EAAM2D,GACzC,IAAMA,GAAyC,UAAhCzE,EAAK8C,SAASC,cAC5B,MAAO/C,GAAKqW,eAOTpL,GAAO,SAAUC,GACtB,MAAsC,OAA/BA,EAAGf,aAAa,eAEvBgB,GAAWhF,EAAU,SAAUnG,EAAMc,EAAM2D,GAC1C,GAAI+J,EACJ,KAAM/J,EACL,MAAOzE,GAAMc,MAAW,EAAOA,EAAKiC,eACjCyL,EAAMxO,EAAKkN,iBAAkBpM,KAAW0N,EAAIC,UAC7CD,EAAI5K,MACL,OAKGU,IAEH/H,EAIJ8B,GAAO4O,KAAO3I,EACdjG,EAAOgQ,KAAO/J,EAAO6K,UAGrB9Q,EAAOgQ,KAAM,KAAQhQ,EAAOgQ,KAAK9H,QACjClI,EAAOuQ,WAAavQ,EAAOiY,OAAShS,EAAOsK,WAC3CvQ,EAAON,KAAOuG,EAAOE,QACrBnG,EAAOkY,SAAWjS,EAAOG,MACzBpG,EAAOgH,SAAWf,EAAOe,SACzBhH,EAAOmY,eAAiBlS,EAAOoK,MAK/B,IAAI5F,GAAM,SAAU9I,EAAM8I,EAAK2N,GAC9B,GAAIvF,MACHwF,EAAqBlV,SAAViV,CAEZ,QAAUzW,EAAOA,EAAM8I,KAA6B,IAAlB9I,EAAKiJ,SACtC,GAAuB,IAAlBjJ,EAAKiJ,SAAiB,CAC1B,GAAKyN,GAAYrY,EAAQ2B,GAAO2W,GAAIF,GACnC,KAEDvF,GAAQlU,KAAMgD,GAGhB,MAAOkR,IAIJ0F,EAAW,SAAUC,EAAG7W,GAG3B,IAFA,GAAIkR,MAEI2F,EAAGA,EAAIA,EAAElL,YACI,IAAfkL,EAAE5N,UAAkB4N,IAAM7W,GAC9BkR,EAAQlU,KAAM6Z,EAIhB,OAAO3F,IAIJ4F,EAAgBzY,EAAOgQ,KAAK9E,MAAMhC,aAElCwP,EAAa,kEAIbC,EAAY,gBAGhB,SAASC,GAAQ3I,EAAU4I,EAAW/F,GACrC,MAAK9S,GAAOgD,WAAY6V,GAChB7Y,EAAOiF,KAAMgL,EAAU,SAAUtO,EAAMC,GAC7C,QAASiX,EAAU1Z,KAAMwC,EAAMC,EAAGD,KAAWmR,IAK1C+F,EAAUjO,SACP5K,EAAOiF,KAAMgL,EAAU,SAAUtO,GACvC,MAASA,KAASkX,IAAgB/F,IAKV,gBAAd+F,GACJ7Y,EAAOiF,KAAMgL,EAAU,SAAUtO,GACvC,MAAS/C,GAAQO,KAAM0Z,EAAWlX,QAAkBmR,IAKjD6F,EAAU9M,KAAMgN,GACb7Y,EAAO0O,OAAQmK,EAAW5I,EAAU6C,IAI5C+F,EAAY7Y,EAAO0O,OAAQmK,EAAW5I,GAC/BjQ,EAAOiF,KAAMgL,EAAU,SAAUtO,GACvC,MAAS/C,GAAQO,KAAM0Z,EAAWlX,QAAkBmR,GAAyB,IAAlBnR,EAAKiJ,YAIlE5K,EAAO0O,OAAS,SAAUsB,EAAM5O,EAAO0R,GACtC,GAAInR,GAAOP,EAAO,EAMlB,OAJK0R,KACJ9C,EAAO,QAAUA,EAAO,KAGH,IAAjB5O,EAAML,QAAkC,IAAlBY,EAAKiJ,SACxB5K,EAAO4O,KAAKK,gBAAiBtN,EAAMqO,IAAWrO,MAG/C3B,EAAO4O,KAAKxJ,QAAS4K,EAAMhQ,EAAOiF,KAAM7D,EAAO,SAAUO,GAC/D,MAAyB,KAAlBA,EAAKiJ,aAId5K,EAAOG,GAAGoC,QACTqM,KAAM,SAAU3O,GACf,GAAI2B,GAAGP,EACNa,EAAM/D,KAAK4C,OACX+X,EAAO3a,IAER,IAAyB,gBAAb8B,GACX,MAAO9B,MAAKgD,UAAWnB,EAAQC,GAAWyO,OAAQ,WACjD,IAAM9M,EAAI,EAAGA,EAAIM,EAAKN,IACrB,GAAK5B,EAAOgH,SAAU8R,EAAMlX,GAAKzD,MAChC,OAAO,IAQX,KAFAkD,EAAMlD,KAAKgD,cAELS,EAAI,EAAGA,EAAIM,EAAKN,IACrB5B,EAAO4O,KAAM3O,EAAU6Y,EAAMlX,GAAKP,EAGnC,OAAOa,GAAM,EAAIlC,EAAOuQ,WAAYlP,GAAQA,GAE7CqN,OAAQ,SAAUzO,GACjB,MAAO9B,MAAKgD,UAAWyX,EAAQza,KAAM8B,OAAgB,KAEtD6S,IAAK,SAAU7S,GACd,MAAO9B,MAAKgD,UAAWyX,EAAQza,KAAM8B,OAAgB,KAEtDqY,GAAI,SAAUrY,GACb,QAAS2Y,EACRza,KAIoB,gBAAb8B,IAAyBwY,EAAc5M,KAAM5L,GACnDD,EAAQC,GACRA,OACD,GACCc,SASJ,IAAIgY,GAMHzP,EAAa,sCAEblJ,EAAOJ,EAAOG,GAAGC,KAAO,SAAUH,EAAUC,EAASqT,GACpD,GAAIrI,GAAOvJ,CAGX,KAAM1B,EACL,MAAO9B,KAQR,IAHAoV,EAAOA,GAAQwF,EAGU,gBAAb9Y,GAAwB,CAanC,GAPCiL,EALsB,MAAlBjL,EAAU,IACsB,MAApCA,EAAUA,EAASc,OAAS,IAC5Bd,EAASc,QAAU,GAGT,KAAMd,EAAU,MAGlBqJ,EAAWiC,KAAMtL,IAIrBiL,IAAWA,EAAO,IAAQhL,EA6CxB,OAAMA,GAAWA,EAAQW,QACtBX,GAAWqT,GAAO3E,KAAM3O,GAK1B9B,KAAK2C,YAAaZ,GAAU0O,KAAM3O,EAhDzC,IAAKiL,EAAO,GAAM,CAYjB,GAXAhL,EAAUA,YAAmBF,GAASE,EAAS,GAAMA,EAIrDF,EAAOsB,MAAOnD,KAAM6B,EAAOgZ,UAC1B9N,EAAO,GACPhL,GAAWA,EAAQ0K,SAAW1K,EAAQoL,eAAiBpL,EAAUnC,GACjE,IAII2a,EAAW7M,KAAMX,EAAO,KAASlL,EAAOiD,cAAe/C,GAC3D,IAAMgL,IAAShL,GAGTF,EAAOgD,WAAY7E,KAAM+M,IAC7B/M,KAAM+M,GAAShL,EAASgL,IAIxB/M,KAAK+R,KAAMhF,EAAOhL,EAASgL,GAK9B,OAAO/M,MAYP,MARAwD,GAAO5D,EAASyN,eAAgBN,EAAO,IAElCvJ,IAGJxD,KAAM,GAAMwD,EACZxD,KAAK4C,OAAS,GAER5C,KAcH,MAAK8B,GAAS2K,UACpBzM,KAAM,GAAM8B,EACZ9B,KAAK4C,OAAS,EACP5C,MAII6B,EAAOgD,WAAY/C,GACRkD,SAAfoQ,EAAK0F,MACX1F,EAAK0F,MAAOhZ,GAGZA,EAAUD,GAGLA,EAAO6E,UAAW5E,EAAU9B,MAIrCiC,GAAKQ,UAAYZ,EAAOG,GAGxB4Y,EAAa/Y,EAAQjC,EAGrB,IAAImb,GAAe,iCAGlBC,GACCC,UAAU,EACVC,UAAU,EACV3O,MAAM,EACN4O,MAAM,EAGRtZ,GAAOG,GAAGoC,QACT0Q,IAAK,SAAUnQ,GACd,GAAIyW,GAAUvZ,EAAQ8C,EAAQ3E,MAC7Bqb,EAAID,EAAQxY,MAEb,OAAO5C,MAAKuQ,OAAQ,WAEnB,IADA,GAAI9M,GAAI,EACAA,EAAI4X,EAAG5X,IACd,GAAK5B,EAAOgH,SAAU7I,KAAMob,EAAS3X,IACpC,OAAO,KAMX6X,QAAS,SAAU3I,EAAW5Q,GAC7B,GAAIiN,GACHvL,EAAI,EACJ4X,EAAIrb,KAAK4C,OACT8R,KACA0G,EAA+B,gBAAdzI,IAA0B9Q,EAAQ8Q,EAGpD,KAAM2H,EAAc5M,KAAMiF,GACzB,KAAQlP,EAAI4X,EAAG5X,IACd,IAAMuL,EAAMhP,KAAMyD,GAAKuL,GAAOA,IAAQjN,EAASiN,EAAMA,EAAItN,WAGxD,GAAKsN,EAAIvC,SAAW,KAAQ2O,EAC3BA,EAAQG,MAAOvM,MAGE,IAAjBA,EAAIvC,UACH5K,EAAO4O,KAAKK,gBAAiB9B,EAAK2D,IAAgB,CAEnD+B,EAAQlU,KAAMwO,EACd,OAMJ,MAAOhP,MAAKgD,UAAW0R,EAAQ9R,OAAS,EAAIf,EAAOuQ,WAAYsC,GAAYA,IAI5E6G,MAAO,SAAU/X,GAGhB,MAAMA,GAKe,gBAATA,GACJ/C,EAAQO,KAAMa,EAAQ2B,GAAQxD,KAAM,IAIrCS,EAAQO,KAAMhB,KAGpBwD,EAAKd,OAASc,EAAM,GAAMA,GAZjBxD,KAAM,IAAOA,KAAM,GAAI0B,WAAe1B,KAAK4D,QAAQ4X,UAAU5Y,WAgBxE6Y,IAAK,SAAU3Z,EAAUC,GACxB,MAAO/B,MAAKgD,UACXnB,EAAOuQ,WACNvQ,EAAOsB,MAAOnD,KAAK8C,MAAOjB,EAAQC,EAAUC,OAK/C2Z,QAAS,SAAU5Z,GAClB,MAAO9B,MAAKyb,IAAiB,MAAZ3Z,EAChB9B,KAAKoD,WAAapD,KAAKoD,WAAWmN,OAAQzO,MAK7C,SAAS6Z,GAAS3M,EAAK1C,GACtB,OAAU0C,EAAMA,EAAK1C,KAA4B,IAAjB0C,EAAIvC,UACpC,MAAOuC,GAGRnN,EAAOwB,MACN8Q,OAAQ,SAAU3Q,GACjB,GAAI2Q,GAAS3Q,EAAK9B,UAClB,OAAOyS,IAA8B,KAApBA,EAAO1H,SAAkB0H,EAAS,MAEpDyH,QAAS,SAAUpY,GAClB,MAAO8I,GAAK9I,EAAM,eAEnBqY,aAAc,SAAUrY,EAAMC,EAAGwW,GAChC,MAAO3N,GAAK9I,EAAM,aAAcyW,IAEjC1N,KAAM,SAAU/I,GACf,MAAOmY,GAASnY,EAAM,gBAEvB2X,KAAM,SAAU3X,GACf,MAAOmY,GAASnY,EAAM,oBAEvBsY,QAAS,SAAUtY,GAClB,MAAO8I,GAAK9I,EAAM,gBAEnBgY,QAAS,SAAUhY,GAClB,MAAO8I,GAAK9I,EAAM,oBAEnBuY,UAAW,SAAUvY,EAAMC,EAAGwW,GAC7B,MAAO3N,GAAK9I,EAAM,cAAeyW,IAElC+B,UAAW,SAAUxY,EAAMC,EAAGwW,GAC7B,MAAO3N,GAAK9I,EAAM,kBAAmByW,IAEtCG,SAAU,SAAU5W,GACnB,MAAO4W,IAAY5W,EAAK9B,gBAAmB+Q,WAAYjP,IAExDyX,SAAU,SAAUzX,GACnB,MAAO4W,GAAU5W,EAAKiP,aAEvByI,SAAU,SAAU1X,GACnB,MAAOA,GAAKyY,iBAAmBpa,EAAOsB,SAAWK,EAAKgJ,cAErD,SAAUlI,EAAMtC,GAClBH,EAAOG,GAAIsC,GAAS,SAAU2V,EAAOnY,GACpC,GAAI4S,GAAU7S,EAAO0B,IAAKvD,KAAMgC,EAAIiY,EAuBpC,OArB0B,UAArB3V,EAAKhE,YACTwB,EAAWmY,GAGPnY,GAAgC,gBAAbA,KACvB4S,EAAU7S,EAAO0O,OAAQzO,EAAU4S,IAG/B1U,KAAK4C,OAAS,IAGZoY,EAAkB1W,IACvBzC,EAAOuQ,WAAYsC,GAIfqG,EAAarN,KAAMpJ,IACvBoQ,EAAQwH,WAIHlc,KAAKgD,UAAW0R,KAGzB,IAAIyH,GAAgB,mBAKpB,SAASC,GAAe/X,GACvB,GAAIgY,KAIJ,OAHAxa,GAAOwB,KAAMgB,EAAQ0I,MAAOoP,OAAuB,SAAU5Q,EAAG+Q,GAC/DD,EAAQC,IAAS,IAEXD,EAyBRxa,EAAO0a,UAAY,SAAUlY,GAI5BA,EAA6B,gBAAZA,GAChB+X,EAAe/X,GACfxC,EAAOuC,UAAYC,EAEpB,IACCmY,GAGAC,EAGAC,EAGAC,EAGAjT,KAGAkT,KAGAC,KAGAC,EAAO,WAQN,IALAH,EAAStY,EAAQ0Y,KAIjBL,EAAQF,GAAS,EACTI,EAAMha,OAAQia,KAAmB,CACxCJ,EAASG,EAAMrO,OACf,SAAUsO,EAAcnT,EAAK9G,OAGvB8G,EAAMmT,GAAcnZ,MAAO+Y,EAAQ,GAAKA,EAAQ,OAAU,GAC9DpY,EAAQ2Y,cAGRH,EAAcnT,EAAK9G,OACnB6Z,GAAS,GAMNpY,EAAQoY,SACbA,GAAS,GAGVD,GAAS,EAGJG,IAIHjT,EADI+S,KAKG,KAMV9B,GAGCc,IAAK,WA2BJ,MA1BK/R,KAGC+S,IAAWD,IACfK,EAAcnT,EAAK9G,OAAS,EAC5Bga,EAAMpc,KAAMic,IAGb,QAAWhB,GAAKjU,GACf3F,EAAOwB,KAAMmE,EAAM,SAAU+D,EAAGpE,GAC1BtF,EAAOgD,WAAYsC,GACjB9C,EAAQyV,QAAWa,EAAK7F,IAAK3N,IAClCuC,EAAKlJ,KAAM2G,GAEDA,GAAOA,EAAIvE,QAAiC,WAAvBf,EAAO6D,KAAMyB,IAG7CsU,EAAKtU,MAGHxD,WAEA8Y,IAAWD,GACfM,KAGK9c,MAIRid,OAAQ,WAYP,MAXApb,GAAOwB,KAAMM,UAAW,SAAU4H,EAAGpE,GACpC,GAAIoU,EACJ,QAAUA,EAAQ1Z,EAAO+E,QAASO,EAAKuC,EAAM6R,OAC5C7R,EAAKvF,OAAQoX,EAAO,GAGfA,GAASsB,GACbA,MAII7c,MAKR8U,IAAK,SAAU9S,GACd,MAAOA,GACNH,EAAO+E,QAAS5E,EAAI0H,MACpBA,EAAK9G,OAAS,GAIhBkT,MAAO,WAIN,MAHKpM,KACJA,MAEM1J,MAMRkd,QAAS,WAGR,MAFAP,GAASC,KACTlT,EAAO+S,EAAS,GACTzc,MAERqM,SAAU,WACT,OAAQ3C,GAMTyT,KAAM,WAKL,MAJAR,GAASC,KACHH,GAAWD,IAChB9S,EAAO+S,EAAS,IAEVzc,MAER2c,OAAQ,WACP,QAASA,GAIVS,SAAU,SAAUrb,EAASyF,GAS5B,MARMmV,KACLnV,EAAOA,MACPA,GAASzF,EAASyF,EAAKlH,MAAQkH,EAAKlH,QAAUkH,GAC9CoV,EAAMpc,KAAMgH,GACNgV,GACLM,KAGK9c,MAIR8c,KAAM,WAEL,MADAnC,GAAKyC,SAAUpd,KAAM2D,WACd3D,MAIR0c,MAAO,WACN,QAASA,GAIZ,OAAO/B,GAIR,SAAS0C,GAAUC,GAClB,MAAOA,GAER,QAASC,GAASC,GACjB,KAAMA,GAGP,QAASC,GAAYrW,EAAOsW,EAASC,GACpC,GAAIC,EAEJ,KAGMxW,GAASvF,EAAOgD,WAAc+Y,EAASxW,EAAMyW,SACjDD,EAAO5c,KAAMoG,GAAQ4B,KAAM0U,GAAUI,KAAMH,GAGhCvW,GAASvF,EAAOgD,WAAc+Y,EAASxW,EAAM2W,MACxDH,EAAO5c,KAAMoG,EAAOsW,EAASC,GAO7BD,EAAQ1c,KAAMgE,OAAWoC,GAMzB,MAAQA,GAITuW,EAAO3c,KAAMgE,OAAWoC,IAI1BvF,EAAOuC,QAEN4Z,SAAU,SAAUC,GACnB,GAAIC,KAIA,SAAU,WAAYrc,EAAO0a,UAAW,UACzC1a,EAAO0a,UAAW,UAAY,IAC7B,UAAW,OAAQ1a,EAAO0a,UAAW,eACtC1a,EAAO0a,UAAW,eAAiB,EAAG,aACrC,SAAU,OAAQ1a,EAAO0a,UAAW,eACrC1a,EAAO0a,UAAW,eAAiB,EAAG,aAExC4B,EAAQ,UACRN,GACCM,MAAO,WACN,MAAOA,IAERC,OAAQ,WAEP,MADAC,GAASrV,KAAMrF,WAAYma,KAAMna,WAC1B3D,MAERse,QAAS,SAAUtc,GAClB,MAAO6b,GAAQE,KAAM,KAAM/b,IAI5Buc,KAAM,WACL,GAAIC,GAAM7a,SAEV,OAAO9B,GAAOmc,SAAU,SAAUS,GACjC5c,EAAOwB,KAAM6a,EAAQ,SAAUza,EAAGib,GAGjC,GAAI1c,GAAKH,EAAOgD,WAAY2Z,EAAKE,EAAO,MAAWF,EAAKE,EAAO,GAK/DL,GAAUK,EAAO,IAAO,WACvB,GAAIC,GAAW3c,GAAMA,EAAG0B,MAAO1D,KAAM2D,UAChCgb,IAAY9c,EAAOgD,WAAY8Z,EAASd,SAC5Cc,EAASd,UACPe,SAAUH,EAASI,QACnB7V,KAAMyV,EAASf,SACfI,KAAMW,EAASd,QAEjBc,EAAUC,EAAO,GAAM,QACtB1e,KACAgC,GAAO2c,GAAahb,eAKxB6a,EAAM,OACHX,WAELE,KAAM,SAAUe,EAAaC,EAAYC,GACxC,GAAIC,GAAW,CACf,SAASvB,GAASwB,EAAOb,EAAUxP,EAASsQ,GAC3C,MAAO,YACN,GAAIC,GAAOpf,KACVwH,EAAO7D,UACP0b,EAAa,WACZ,GAAIV,GAAUZ,CAKd,MAAKmB,EAAQD,GAAb,CAQA,GAJAN,EAAW9P,EAAQnL,MAAO0b,EAAM5X,GAI3BmX,IAAaN,EAASR,UAC1B,KAAM,IAAIyB,WAAW,2BAOtBvB,GAAOY,IAKgB,gBAAbA,IACY,kBAAbA,KACRA,EAASZ,KAGLlc,EAAOgD,WAAYkZ,GAGlBoB,EACJpB,EAAK/c,KACJ2d,EACAjB,EAASuB,EAAUZ,EAAUhB,EAAU8B,GACvCzB,EAASuB,EAAUZ,EAAUd,EAAS4B,KAOvCF,IAEAlB,EAAK/c,KACJ2d,EACAjB,EAASuB,EAAUZ,EAAUhB,EAAU8B,GACvCzB,EAASuB,EAAUZ,EAAUd,EAAS4B,GACtCzB,EAASuB,EAAUZ,EAAUhB,EAC5BgB,EAASkB,eASP1Q,IAAYwO,IAChB+B,EAAOpa,OACPwC,GAASmX,KAKRQ,GAAWd,EAASmB,aAAeJ,EAAM5X,MAK7CiY,EAAUN,EACTE,EACA,WACC,IACCA,IACC,MAAQ3S,GAEJ7K,EAAOmc,SAAS0B,eACpB7d,EAAOmc,SAAS0B,cAAehT,EAC9B+S,EAAQE,YAMLT,EAAQ,GAAKD,IAIZpQ,IAAY0O,IAChB6B,EAAOpa,OACPwC,GAASkF,IAGV2R,EAASuB,WAAYR,EAAM5X,KAS3B0X,GACJO,KAKK5d,EAAOmc,SAAS6B,eACpBJ,EAAQE,WAAa9d,EAAOmc,SAAS6B,gBAEtC9f,EAAO+f,WAAYL,KAKtB,MAAO5d,GAAOmc,SAAU,SAAUS,GAGjCP,EAAQ,GAAK,GAAIzC,IAChBiC,EACC,EACAe,EACA5c,EAAOgD,WAAYma,GAClBA,EACA3B,EACDoB,EAASc,aAKXrB,EAAQ,GAAK,GAAIzC,IAChBiC,EACC,EACAe,EACA5c,EAAOgD,WAAYia,GAClBA,EACAzB,IAKHa,EAAQ,GAAK,GAAIzC,IAChBiC,EACC,EACAe,EACA5c,EAAOgD,WAAYka,GAClBA,EACAxB,MAGAM,WAKLA,QAAS,SAAUpY,GAClB,MAAc,OAAPA,EAAc5D,EAAOuC,OAAQqB,EAAKoY,GAAYA,IAGvDQ,IA2DD,OAxDAxc,GAAOwB,KAAM6a,EAAQ,SAAUza,EAAGib,GACjC,GAAIhV,GAAOgV,EAAO,GACjBqB,EAAcrB,EAAO,EAKtBb,GAASa,EAAO,IAAQhV,EAAK+R,IAGxBsE,GACJrW,EAAK+R,IACJ,WAIC0C,EAAQ4B,GAKT7B,EAAQ,EAAIza,GAAK,GAAIyZ,QAGrBgB,EAAQ,GAAK,GAAIf,MAOnBzT,EAAK+R,IAAKiD,EAAO,GAAI5B,MAKrBuB,EAAUK,EAAO,IAAQ,WAExB,MADAL,GAAUK,EAAO,GAAM,QAAU1e,OAASqe,EAAWrZ,OAAYhF,KAAM2D,WAChE3D,MAMRqe,EAAUK,EAAO,GAAM,QAAWhV,EAAK0T,WAIxCS,EAAQA,QAASQ,GAGZJ,GACJA,EAAKjd,KAAMqd,EAAUA,GAIfA,GAIR2B,KAAM,SAAUC,GACf,GAGCC,GAAYvc,UAAUf,OAGtBa,EAAIyc,EAGJC,EAAkBxa,MAAOlC,GACzB2c,EAAgB9f,EAAMU,KAAM2C,WAG5B0c,EAASxe,EAAOmc,WAGhBsC,EAAa,SAAU7c,GACtB,MAAO,UAAU2D,GAChB+Y,EAAiB1c,GAAMzD,KACvBogB,EAAe3c,GAAME,UAAUf,OAAS,EAAItC,EAAMU,KAAM2C,WAAcyD,IAC5D8Y,GACTG,EAAOb,YAAaW,EAAiBC,IAMzC,IAAKF,GAAa,IACjBzC,EAAYwC,EAAaI,EAAOrX,KAAMsX,EAAY7c,IAAMia,QAAS2C,EAAO1C,QAGhD,YAAnB0C,EAAOlC,SACXtc,EAAOgD,WAAYub,EAAe3c,IAAO2c,EAAe3c,GAAIsa,OAE5D,MAAOsC,GAAOtC,MAKhB,OAAQta,IACPga,EAAY2C,EAAe3c,GAAK6c,EAAY7c,GAAK4c,EAAO1C,OAGzD,OAAO0C,GAAOxC,YAOhB,IAAI0C,GAAc,wDAElB1e,GAAOmc,SAAS0B,cAAgB,SAAUpa,EAAOkb,GAI3CzgB,EAAO0gB,SAAW1gB,EAAO0gB,QAAQC,MAAQpb,GAASib,EAAY7S,KAAMpI,EAAMhB,OAC9EvE,EAAO0gB,QAAQC,KAAM,8BAAgCpb,EAAMqb,QAASrb,EAAMkb,MAAOA,IAOnF3e,EAAO+e,eAAiB,SAAUtb,GACjCvF,EAAO+f,WAAY,WAClB,KAAMxa,KAQR,IAAIub,GAAYhf,EAAOmc,UAEvBnc,GAAOG,GAAG8Y,MAAQ,SAAU9Y,GAY3B,MAVA6e,GACE9C,KAAM/b,GADR6e,SAMS,SAAUvb,GACjBzD,EAAO+e,eAAgBtb,KAGlBtF,MAGR6B,EAAOuC,QAGNiB,SAAS,EAITyb,UAAW,EAGXC,UAAW,SAAUC,GACfA,EACJnf,EAAOif,YAEPjf,EAAOiZ,OAAO,IAKhBA,MAAO,SAAUmG,IAGXA,KAAS,IAASpf,EAAOif,UAAYjf,EAAOwD,WAKjDxD,EAAOwD,SAAU,EAGZ4b,KAAS,KAAUpf,EAAOif,UAAY,GAK3CD,EAAUrB,YAAa5f,GAAYiC,QAIrCA,EAAOiZ,MAAMiD,KAAO8C,EAAU9C,IAG9B,SAASmD,KACRthB,EAASuhB,oBAAqB,mBAAoBD;AAClDnhB,EAAOohB,oBAAqB,OAAQD,GACpCrf,EAAOiZ,QAOqB,aAAxBlb,EAASwhB,YACa,YAAxBxhB,EAASwhB,aAA6BxhB,EAAS+P,gBAAgB0R,SAGjEthB,EAAO+f,WAAYje,EAAOiZ,QAK1Blb,EAASqQ,iBAAkB,mBAAoBiR,GAG/CnhB,EAAOkQ,iBAAkB,OAAQiR,GAQlC,IAAII,GAAS,SAAUre,EAAOjB,EAAIqM,EAAKjH,EAAOma,EAAWC,EAAUC,GAClE,GAAIhe,GAAI,EACPM,EAAMd,EAAML,OACZ8e,EAAc,MAAPrT,CAGR,IAA4B,WAAvBxM,EAAO6D,KAAM2I,GAAqB,CACtCkT,GAAY,CACZ,KAAM9d,IAAK4K,GACViT,EAAQre,EAAOjB,EAAIyB,EAAG4K,EAAK5K,IAAK,EAAM+d,EAAUC,OAI3C,IAAezc,SAAVoC,IACXma,GAAY,EAEN1f,EAAOgD,WAAYuC,KACxBqa,GAAM,GAGFC,IAGCD,GACJzf,EAAGhB,KAAMiC,EAAOmE,GAChBpF,EAAK,OAIL0f,EAAO1f,EACPA,EAAK,SAAUwB,EAAM6K,EAAKjH,GACzB,MAAOsa,GAAK1gB,KAAMa,EAAQ2B,GAAQ4D,MAKhCpF,GACJ,KAAQyB,EAAIM,EAAKN,IAChBzB,EACCiB,EAAOQ,GAAK4K,EAAKoT,EACjBra,EACAA,EAAMpG,KAAMiC,EAAOQ,GAAKA,EAAGzB,EAAIiB,EAAOQ,GAAK4K,IAM/C,OAAKkT,GACGte,EAIHye,EACG1f,EAAGhB,KAAMiC,GAGVc,EAAM/B,EAAIiB,EAAO,GAAKoL,GAAQmT,GAElCG,EAAa,SAAUC,GAQ1B,MAA0B,KAAnBA,EAAMnV,UAAqC,IAAnBmV,EAAMnV,YAAsBmV,EAAMnV,SAMlE,SAASoV,KACR7hB,KAAKiF,QAAUpD,EAAOoD,QAAU4c,EAAKC,MAGtCD,EAAKC,IAAM,EAEXD,EAAKpf,WAEJ2L,MAAO,SAAUwT,GAGhB,GAAIxa,GAAQwa,EAAO5hB,KAAKiF,QA4BxB,OAzBMmC,KACLA,KAKKua,EAAYC,KAIXA,EAAMnV,SACVmV,EAAO5hB,KAAKiF,SAAYmC,EAMxBhH,OAAO2hB,eAAgBH,EAAO5hB,KAAKiF,SAClCmC,MAAOA,EACP4a,cAAc,MAMX5a,GAER6a,IAAK,SAAUL,EAAOM,EAAM9a,GAC3B,GAAI+a,GACH/T,EAAQpO,KAAKoO,MAAOwT,EAIrB,IAAqB,gBAATM,GACX9T,EAAOvM,EAAOuE,UAAW8b,IAAW9a,MAMpC,KAAM+a,IAAQD,GACb9T,EAAOvM,EAAOuE,UAAW+b,IAAWD,EAAMC,EAG5C,OAAO/T,IAERtL,IAAK,SAAU8e,EAAOvT,GACrB,MAAerJ,UAARqJ,EACNrO,KAAKoO,MAAOwT,GAGZA,EAAO5hB,KAAKiF,UAAa2c,EAAO5hB,KAAKiF,SAAWpD,EAAOuE,UAAWiI,KAEpEiT,OAAQ,SAAUM,EAAOvT,EAAKjH,GAa7B,MAAapC,UAARqJ,GACCA,GAAsB,gBAARA,IAAgCrJ,SAAVoC,EAElCpH,KAAK8C,IAAK8e,EAAOvT,IASzBrO,KAAKiiB,IAAKL,EAAOvT,EAAKjH,GAILpC,SAAVoC,EAAsBA,EAAQiH,IAEtC4O,OAAQ,SAAU2E,EAAOvT,GACxB,GAAI5K,GACH2K,EAAQwT,EAAO5hB,KAAKiF,QAErB,IAAeD,SAAVoJ,EAAL,CAIA,GAAapJ,SAARqJ,EAAoB,CAGnBxM,EAAOkD,QAASsJ,GAIpBA,EAAMA,EAAI9K,IAAK1B,EAAOuE,YAEtBiI,EAAMxM,EAAOuE,UAAWiI,GAIxBA,EAAMA,IAAOD,IACVC,GACAA,EAAItB,MAAOoP,QAGf1Y,EAAI4K,EAAIzL,MAER,OAAQa,UACA2K,GAAOC,EAAK5K,KAKRuB,SAARqJ,GAAqBxM,EAAOqE,cAAekI,MAM1CwT,EAAMnV,SACVmV,EAAO5hB,KAAKiF,SAAYD,aAEjB4c,GAAO5hB,KAAKiF,YAItBmd,QAAS,SAAUR,GAClB,GAAIxT,GAAQwT,EAAO5hB,KAAKiF,QACxB,OAAiBD,UAAVoJ,IAAwBvM,EAAOqE,cAAekI,IAGvD,IAAIiU,GAAW,GAAIR,GAEfS,EAAW,GAAIT,GAcfU,EAAS,gCACZC,EAAa,QAEd,SAASC,GAASP,GACjB,MAAc,SAATA,GAIS,UAATA,IAIS,SAATA,EACG,KAIHA,KAAUA,EAAO,IACbA,EAGJK,EAAO7U,KAAMwU,GACVQ,KAAKC,MAAOT,GAGbA,GAGR,QAASU,GAAUpf,EAAM6K,EAAK6T,GAC7B,GAAI5d,EAIJ,IAAcU,SAATkd,GAAwC,IAAlB1e,EAAKiJ,SAI/B,GAHAnI,EAAO,QAAU+J,EAAIjJ,QAASod,EAAY,OAAQjc,cAClD2b,EAAO1e,EAAKmK,aAAcrJ,GAEL,gBAAT4d,GAAoB,CAC/B,IACCA,EAAOO,EAASP,GACf,MAAQxV,IAGV4V,EAASL,IAAKze,EAAM6K,EAAK6T,OAEzBA,GAAOld,MAGT,OAAOkd,GAGRrgB,EAAOuC,QACNge,QAAS,SAAU5e,GAClB,MAAO8e,GAASF,QAAS5e,IAAU6e,EAASD,QAAS5e,IAGtD0e,KAAM,SAAU1e,EAAMc,EAAM4d,GAC3B,MAAOI,GAAShB,OAAQ9d,EAAMc,EAAM4d,IAGrCW,WAAY,SAAUrf,EAAMc,GAC3Bge,EAASrF,OAAQzZ,EAAMc,IAKxBwe,MAAO,SAAUtf,EAAMc,EAAM4d,GAC5B,MAAOG,GAASf,OAAQ9d,EAAMc,EAAM4d,IAGrCa,YAAa,SAAUvf,EAAMc,GAC5B+d,EAASpF,OAAQzZ,EAAMc,MAIzBzC,EAAOG,GAAGoC,QACT8d,KAAM,SAAU7T,EAAKjH,GACpB,GAAI3D,GAAGa,EAAM4d,EACZ1e,EAAOxD,KAAM,GACb4O,EAAQpL,GAAQA,EAAKsG,UAGtB,IAAa9E,SAARqJ,EAAoB,CACxB,GAAKrO,KAAK4C,SACTsf,EAAOI,EAASxf,IAAKU,GAEE,IAAlBA,EAAKiJ,WAAmB4V,EAASvf,IAAKU,EAAM,iBAAmB,CACnEC,EAAImL,EAAMhM,MACV,OAAQa,IAIFmL,EAAOnL,KACXa,EAAOsK,EAAOnL,GAAIa,KACe,IAA5BA,EAAK7D,QAAS,WAClB6D,EAAOzC,EAAOuE,UAAW9B,EAAKhE,MAAO,IACrCsiB,EAAUpf,EAAMc,EAAM4d,EAAM5d,KAI/B+d,GAASJ,IAAKze,EAAM,gBAAgB,GAItC,MAAO0e,GAIR,MAAoB,gBAAR7T,GACJrO,KAAKqD,KAAM,WACjBif,EAASL,IAAKjiB,KAAMqO,KAIfiT,EAAQthB,KAAM,SAAUoH,GAC9B,GAAI8a,EAOJ,IAAK1e,GAAkBwB,SAAVoC,EAAb,CAKC,GADA8a,EAAOI,EAASxf,IAAKU,EAAM6K,GACbrJ,SAATkd,EACJ,MAAOA,EAMR,IADAA,EAAOU,EAAUpf,EAAM6K,GACTrJ,SAATkd,EACJ,MAAOA,OAQTliB,MAAKqD,KAAM,WAGVif,EAASL,IAAKjiB,KAAMqO,EAAKjH,MAExB,KAAMA,EAAOzD,UAAUf,OAAS,EAAG,MAAM,IAG7CigB,WAAY,SAAUxU,GACrB,MAAOrO,MAAKqD,KAAM,WACjBif,EAASrF,OAAQjd,KAAMqO,QAM1BxM,EAAOuC,QACNwY,MAAO,SAAUpZ,EAAMkC,EAAMwc,GAC5B,GAAItF,EAEJ,IAAKpZ,EAYJ,MAXAkC,IAASA,GAAQ,MAAS,QAC1BkX,EAAQyF,EAASvf,IAAKU,EAAMkC,GAGvBwc,KACEtF,GAAS/a,EAAOkD,QAASmd,GAC9BtF,EAAQyF,EAASf,OAAQ9d,EAAMkC,EAAM7D,EAAO6E,UAAWwb,IAEvDtF,EAAMpc,KAAM0hB,IAGPtF,OAIToG,QAAS,SAAUxf,EAAMkC,GACxBA,EAAOA,GAAQ,IAEf,IAAIkX,GAAQ/a,EAAO+a,MAAOpZ,EAAMkC,GAC/Bud,EAAcrG,EAAMha,OACpBZ,EAAK4a,EAAMrO,QACX2U,EAAQrhB,EAAOshB,YAAa3f,EAAMkC,GAClC6G,EAAO,WACN1K,EAAOmhB,QAASxf,EAAMkC,GAIZ,gBAAP1D,IACJA,EAAK4a,EAAMrO,QACX0U,KAGIjhB,IAIU,OAAT0D,GACJkX,EAAMhL,QAAS,oBAITsR,GAAME,KACbphB,EAAGhB,KAAMwC,EAAM+I,EAAM2W,KAGhBD,GAAeC,GACpBA,EAAMpN,MAAMgH,QAKdqG,YAAa,SAAU3f,EAAMkC,GAC5B,GAAI2I,GAAM3I,EAAO,YACjB,OAAO2c,GAASvf,IAAKU,EAAM6K,IAASgU,EAASf,OAAQ9d,EAAM6K,GAC1DyH,MAAOjU,EAAO0a,UAAW,eAAgBd,IAAK,WAC7C4G,EAASpF,OAAQzZ,GAAQkC,EAAO,QAAS2I,WAM7CxM,EAAOG,GAAGoC,QACTwY,MAAO,SAAUlX,EAAMwc,GACtB,GAAImB,GAAS,CAQb,OANqB,gBAAT3d,KACXwc,EAAOxc,EACPA,EAAO,KACP2d,KAGI1f,UAAUf,OAASygB,EAChBxhB,EAAO+a,MAAO5c,KAAM,GAAK0F,GAGjBV,SAATkd,EACNliB,KACAA,KAAKqD,KAAM,WACV,GAAIuZ,GAAQ/a,EAAO+a,MAAO5c,KAAM0F,EAAMwc,EAGtCrgB,GAAOshB,YAAanjB,KAAM0F,GAEZ,OAATA,GAAgC,eAAfkX,EAAO,IAC5B/a,EAAOmhB,QAAShjB,KAAM0F,MAI1Bsd,QAAS,SAAUtd,GAClB,MAAO1F,MAAKqD,KAAM,WACjBxB,EAAOmhB,QAAShjB,KAAM0F,MAGxB4d,WAAY,SAAU5d,GACrB,MAAO1F,MAAK4c,MAAOlX,GAAQ,UAK5BmY,QAAS,SAAUnY,EAAMD,GACxB,GAAI8B,GACHgc,EAAQ,EACRC,EAAQ3hB,EAAOmc,WACflM,EAAW9R,KACXyD,EAAIzD,KAAK4C,OACT8a,EAAU,aACC6F,GACTC,EAAMhE,YAAa1N,GAAYA,IAIb,iBAATpM,KACXD,EAAMC,EACNA,EAAOV,QAERU,EAAOA,GAAQ,IAEf,OAAQjC,IACP8D,EAAM8a,EAASvf,IAAKgP,EAAUrO,GAAKiC,EAAO,cACrC6B,GAAOA,EAAIuO,QACfyN,IACAhc,EAAIuO,MAAM2F,IAAKiC,GAIjB,OADAA,KACO8F,EAAM3F,QAASpY,KAGxB,IAAIge,GAAO,sCAA0CC,OAEjDC,GAAU,GAAI1Z,QAAQ,iBAAmBwZ,EAAO,cAAe,KAG/DG,IAAc,MAAO,QAAS,SAAU,QAExCC,GAAqB,SAAUrgB,EAAMkL,GAOvC,MAHAlL,GAAOkL,GAAMlL,EAGiB,SAAvBA,EAAKsgB,MAAMC,SACM,KAAvBvgB,EAAKsgB,MAAMC,SAMXliB,EAAOgH,SAAUrF,EAAK2J,cAAe3J,IAEH,SAAlC3B,EAAOmiB,IAAKxgB,EAAM,YAGjBygB,GAAO,SAAUzgB,EAAMa,EAASf,EAAUkE,GAC7C,GAAItE,GAAKoB,EACR4f,IAGD,KAAM5f,IAAQD,GACb6f,EAAK5f,GAASd,EAAKsgB,MAAOxf,GAC1Bd,EAAKsgB,MAAOxf,GAASD,EAASC,EAG/BpB,GAAMI,EAASI,MAAOF,EAAMgE,MAG5B,KAAMlD,IAAQD,GACbb,EAAKsgB,MAAOxf,GAAS4f,EAAK5f,EAG3B,OAAOpB,GAMR,SAASihB,IAAW3gB,EAAM2e,EAAMiC,EAAYC,GAC3C,GAAIC,GACHC,EAAQ,EACRC,EAAgB,GAChBC,EAAeJ,EACd,WACC,MAAOA,GAAMrV,OAEd,WACC,MAAOnN,GAAOmiB,IAAKxgB,EAAM2e,EAAM,KAEjCuC,EAAUD,IACVE,EAAOP,GAAcA,EAAY,KAASviB,EAAO+iB,UAAWzC,GAAS,GAAK,MAG1E0C,GAAkBhjB,EAAO+iB,UAAWzC,IAAmB,OAATwC,IAAkBD,IAC/Df,GAAQvW,KAAMvL,EAAOmiB,IAAKxgB,EAAM2e,GAElC,IAAK0C,GAAiBA,EAAe,KAAQF,EAAO,CAGnDA,EAAOA,GAAQE,EAAe,GAG9BT,EAAaA,MAGbS,GAAiBH,GAAW,CAE5B,GAICH,GAAQA,GAAS,KAGjBM,GAAgCN,EAChC1iB,EAAOiiB,MAAOtgB,EAAM2e,EAAM0C,EAAgBF,SAK1CJ,KAAYA,EAAQE,IAAiBC,IAAuB,IAAVH,KAAiBC,GAiBrE,MAbKJ,KACJS,GAAiBA,IAAkBH,GAAW,EAG9CJ,EAAWF,EAAY,GACtBS,GAAkBT,EAAY,GAAM,GAAMA,EAAY,IACrDA,EAAY,GACTC,IACJA,EAAMM,KAAOA,EACbN,EAAMnQ,MAAQ2Q,EACdR,EAAMpgB,IAAMqgB,IAGPA,EAIR,GAAIQ,MAEJ,SAASC,IAAmBvhB,GAC3B,GAAI4U,GACHhX,EAAMoC,EAAK2J,cACX7G,EAAW9C,EAAK8C,SAChByd,EAAUe,GAAmBxe,EAE9B,OAAKyd,GACGA,GAGR3L,EAAOhX,EAAI4jB,KAAKvjB,YAAaL,EAAIE,cAAegF,IAChDyd,EAAUliB,EAAOmiB,IAAK5L,EAAM,WAE5BA,EAAK1W,WAAWC,YAAayW,GAEZ,SAAZ2L,IACJA,EAAU,SAEXe,GAAmBxe,GAAayd,EAEzBA,GAGR,QAASkB,IAAUnT,EAAUoT,GAO5B,IANA,GAAInB,GAASvgB,EACZ2hB,KACA5J,EAAQ,EACR3Y,EAASkP,EAASlP,OAGX2Y,EAAQ3Y,EAAQ2Y,IACvB/X,EAAOsO,EAAUyJ,GACX/X,EAAKsgB,QAIXC,EAAUvgB,EAAKsgB,MAAMC,QAChBmB,GAKa,SAAZnB,IACJoB,EAAQ5J,GAAU8G,EAASvf,IAAKU,EAAM,YAAe,KAC/C2hB,EAAQ5J,KACb/X,EAAKsgB,MAAMC,QAAU,KAGK,KAAvBvgB,EAAKsgB,MAAMC,SAAkBF,GAAoBrgB,KACrD2hB,EAAQ5J,GAAUwJ,GAAmBvhB,KAGrB,SAAZugB,IACJoB,EAAQ5J,GAAU,OAGlB8G,EAASJ,IAAKze,EAAM,UAAWugB,IAMlC,KAAMxI,EAAQ,EAAGA,EAAQ3Y,EAAQ2Y,IACR,MAAnB4J,EAAQ5J,KACZzJ,EAAUyJ,GAAQuI,MAAMC,QAAUoB,EAAQ5J,GAI5C,OAAOzJ,GAGRjQ,EAAOG,GAAGoC,QACT8gB,KAAM,WACL,MAAOD,IAAUjlB,MAAM,IAExBolB,KAAM,WACL,MAAOH,IAAUjlB,OAElBqlB,OAAQ,SAAUlH,GACjB,MAAsB,iBAAVA,GACJA,EAAQne,KAAKklB,OAASllB,KAAKolB,OAG5BplB,KAAKqD,KAAM,WACZwgB,GAAoB7jB,MACxB6B,EAAQ7B,MAAOklB,OAEfrjB,EAAQ7B,MAAOolB,WAKnB,IAAIE,IAAiB,wBAEjBC,GAAW,iCAEXC,GAAc,4BAKdC,IAGHC,QAAU,EAAG,+BAAgC,aAK7CC,OAAS,EAAG,UAAW,YACvBC,KAAO,EAAG,oBAAqB,uBAC/BC,IAAM,EAAG,iBAAkB,oBAC3BC,IAAM,EAAG,qBAAsB,yBAE/BC,UAAY,EAAG,GAAI,IAIpBN,IAAQO,SAAWP,GAAQC,OAE3BD,GAAQQ,MAAQR,GAAQS,MAAQT,GAAQU,SAAWV,GAAQW,QAAUX,GAAQE,MAC7EF,GAAQY,GAAKZ,GAAQK,EAGrB,SAASQ,IAAQvkB,EAAS4O,GAIzB,GAAIzN,EAYJ,OATCA,GAD4C,mBAAjCnB,GAAQwL,qBACbxL,EAAQwL,qBAAsBoD,GAAO,KAEI,mBAA7B5O,GAAQiM,iBACpBjM,EAAQiM,iBAAkB2C,GAAO,QAM3B3L,SAAR2L,GAAqBA,GAAO9O,EAAOyE,SAAUvE,EAAS4O,GACnD9O,EAAOsB,OAASpB,GAAWmB,GAG5BA,EAKR,QAASqjB,IAAetjB,EAAOujB,GAI9B,IAHA,GAAI/iB,GAAI,EACP4X,EAAIpY,EAAML,OAEHa,EAAI4X,EAAG5X,IACd4e,EAASJ,IACRhf,EAAOQ,GACP,cACC+iB,GAAenE,EAASvf,IAAK0jB,EAAa/iB,GAAK,eAMnD,GAAIgjB,IAAQ,WAEZ,SAASC,IAAezjB,EAAOlB,EAAS4kB,EAASC,EAAWC,GAO3D,IANA,GAAIrjB,GAAM+D,EAAKoJ,EAAKmW,EAAMje,EAAU7E,EACnC+iB,EAAWhlB,EAAQilB,yBACnBC,KACAxjB,EAAI,EACJ4X,EAAIpY,EAAML,OAEHa,EAAI4X,EAAG5X,IAGd,GAFAD,EAAOP,EAAOQ,GAETD,GAAiB,IAATA,EAGZ,GAA6B,WAAxB3B,EAAO6D,KAAMlC,GAIjB3B,EAAOsB,MAAO8jB,EAAOzjB,EAAKiJ,UAAajJ,GAASA,OAG1C,IAAMijB,GAAM/Y,KAAMlK,GAIlB,CACN+D,EAAMA,GAAOwf,EAAStlB,YAAaM,EAAQT,cAAe,QAG1DqP,GAAQ4U,GAASnY,KAAM5J,KAAY,GAAI,KAAQ,GAAI+C,cACnDugB,EAAOrB,GAAS9U,IAAS8U,GAAQM,SACjCxe,EAAIqJ,UAAYkW,EAAM,GAAMjlB,EAAOqlB,cAAe1jB,GAASsjB,EAAM,GAGjE9iB,EAAI8iB,EAAM,EACV,OAAQ9iB,IACPuD,EAAMA,EAAI8M,SAKXxS,GAAOsB,MAAO8jB,EAAO1f,EAAIiF,YAGzBjF,EAAMwf,EAAStU,WAGflL,EAAIiL,YAAc,OAzBlByU,GAAMzmB,KAAMuB,EAAQolB,eAAgB3jB,GA+BvCujB,GAASvU,YAAc,GAEvB/O,EAAI,CACJ,OAAUD,EAAOyjB,EAAOxjB,KAGvB,GAAKmjB,GAAa/kB,EAAO+E,QAASpD,EAAMojB,MAClCC,GACJA,EAAQrmB,KAAMgD,OAgBhB,IAXAqF,EAAWhH,EAAOgH,SAAUrF,EAAK2J,cAAe3J,GAGhD+D,EAAM+e,GAAQS,EAAStlB,YAAa+B,GAAQ,UAGvCqF,GACJ0d,GAAehf,GAIXof,EAAU,CACd3iB,EAAI,CACJ,OAAUR,EAAO+D,EAAKvD,KAChBwhB,GAAY9X,KAAMlK,EAAKkC,MAAQ,KACnCihB,EAAQnmB,KAAMgD,GAMlB,MAAOujB,IAIR,WACC,GAAIA,GAAWnnB,EAASonB,yBACvBI,EAAML,EAAStlB,YAAa7B,EAAS0B,cAAe,QACpDuP,EAAQjR,EAAS0B,cAAe,QAMjCuP,GAAMjD,aAAc,OAAQ,SAC5BiD,EAAMjD,aAAc,UAAW,WAC/BiD,EAAMjD,aAAc,OAAQ,KAE5BwZ,EAAI3lB,YAAaoP,GAIjB5P,EAAQomB,WAAaD,EAAIE,WAAW,GAAOA,WAAW,GAAOjT,UAAUsB,QAIvEyR,EAAIxW,UAAY,yBAChB3P,EAAQsmB,iBAAmBH,EAAIE,WAAW,GAAOjT,UAAUwF,eAE5D,IAAIlK,IAAkB/P,EAAS+P,gBAK9B6X,GAAY,OACZC,GAAc,iDACdC,GAAiB,qBAElB,SAASC,MACR,OAAO,EAGR,QAASC,MACR,OAAO,EAKR,QAASC,MACR,IACC,MAAOjoB,GAAS0V,cACf,MAAQwS,KAGX,QAASC,IAAIvkB,EAAMwkB,EAAOlmB,EAAUogB,EAAMlgB,EAAIimB,GAC7C,GAAIC,GAAQxiB,CAGZ,IAAsB,gBAAVsiB,GAAqB,CAGP,gBAAblmB,KAGXogB,EAAOA,GAAQpgB,EACfA,EAAWkD,OAEZ,KAAMU,IAAQsiB,GACbD,GAAIvkB,EAAMkC,EAAM5D,EAAUogB,EAAM8F,EAAOtiB,GAAQuiB,EAEhD,OAAOzkB,GAsBR,GAnBa,MAAR0e,GAAsB,MAANlgB,GAGpBA,EAAKF,EACLogB,EAAOpgB,EAAWkD,QACD,MAANhD,IACc,gBAAbF,IAGXE,EAAKkgB,EACLA,EAAOld,SAIPhD,EAAKkgB,EACLA,EAAOpgB,EACPA,EAAWkD,SAGRhD,KAAO,EACXA,EAAK4lB,OACC,KAAM5lB,EACZ,MAAOwB,EAeR,OAZa,KAARykB,IACJC,EAASlmB,EACTA,EAAK,SAAUmmB,GAId,MADAtmB,KAASumB,IAAKD,GACPD,EAAOxkB,MAAO1D,KAAM2D,YAI5B3B,EAAGqF,KAAO6gB,EAAO7gB,OAAU6gB,EAAO7gB,KAAOxF,EAAOwF,SAE1C7D,EAAKH,KAAM,WACjBxB,EAAOsmB,MAAM1M,IAAKzb,KAAMgoB,EAAOhmB,EAAIkgB,EAAMpgB,KAQ3CD,EAAOsmB,OAEN3oB,UAEAic,IAAK,SAAUjY,EAAMwkB,EAAOnZ,EAASqT,EAAMpgB,GAE1C,GAAIumB,GAAaC,EAAa/gB,EAC7BghB,EAAQC,EAAGC,EACXtJ,EAASuJ,EAAUhjB,EAAMijB,EAAYC,EACrCC,EAAWxG,EAASvf,IAAKU,EAG1B,IAAMqlB,EAAN,CAKKha,EAAQA,UACZwZ,EAAcxZ,EACdA,EAAUwZ,EAAYxZ,QACtB/M,EAAWumB,EAAYvmB,UAKnBA,GACJD,EAAO4O,KAAKK,gBAAiBnB,GAAiB7N,GAIzC+M,EAAQxH,OACbwH,EAAQxH,KAAOxF,EAAOwF,SAIfkhB,EAASM,EAASN,UACzBA,EAASM,EAASN,YAEXD,EAAcO,EAASC,UAC9BR,EAAcO,EAASC,OAAS,SAAUpc,GAIzC,MAAyB,mBAAX7K,IAA0BA,EAAOsmB,MAAMY,YAAcrc,EAAEhH,KACpE7D,EAAOsmB,MAAMa,SAAStlB,MAAOF,EAAMG,WAAcqB,SAKpDgjB,GAAUA,GAAS,IAAKjb,MAAOoP,KAAqB,IACpDqM,EAAIR,EAAMplB,MACV,OAAQ4lB,IACPjhB,EAAMmgB,GAAeta,KAAM4a,EAAOQ,QAClC9iB,EAAOkjB,EAAWrhB,EAAK,GACvBohB,GAAephB,EAAK,IAAO,IAAKM,MAAO,KAAM3D,OAGvCwB,IAKNyZ,EAAUtd,EAAOsmB,MAAMhJ,QAASzZ,OAGhCA,GAAS5D,EAAWqd,EAAQ8J,aAAe9J,EAAQ+J,WAAcxjB,EAGjEyZ,EAAUtd,EAAOsmB,MAAMhJ,QAASzZ,OAGhC+iB,EAAY5mB,EAAOuC,QAClBsB,KAAMA,EACNkjB,SAAUA,EACV1G,KAAMA,EACNrT,QAASA,EACTxH,KAAMwH,EAAQxH,KACdvF,SAAUA,EACViJ,aAAcjJ,GAAYD,EAAOgQ,KAAK9E,MAAMhC,aAAa2C,KAAM5L,GAC/DqnB,UAAWR,EAAW7a,KAAM,MAC1Bua,IAGKK,EAAWH,EAAQ7iB,MAC1BgjB,EAAWH,EAAQ7iB,MACnBgjB,EAASU,cAAgB,EAGnBjK,EAAQkK,OACblK,EAAQkK,MAAMroB,KAAMwC,EAAM0e,EAAMyG,EAAYL,MAAkB,GAEzD9kB,EAAKyM,kBACTzM,EAAKyM,iBAAkBvK,EAAM4iB,IAK3BnJ,EAAQ1D,MACZ0D,EAAQ1D,IAAIza,KAAMwC,EAAMilB,GAElBA,EAAU5Z,QAAQxH,OACvBohB,EAAU5Z,QAAQxH,KAAOwH,EAAQxH,OAK9BvF,EACJ4mB,EAASvkB,OAAQukB,EAASU,gBAAiB,EAAGX,GAE9CC,EAASloB,KAAMioB,GAIhB5mB,EAAOsmB,MAAM3oB,OAAQkG,IAAS,KAMhCuX,OAAQ,SAAUzZ,EAAMwkB,EAAOnZ,EAAS/M,EAAUwnB,GAEjD,GAAItlB,GAAGulB,EAAWhiB,EACjBghB,EAAQC,EAAGC,EACXtJ,EAASuJ,EAAUhjB,EAAMijB,EAAYC,EACrCC,EAAWxG,EAASD,QAAS5e,IAAU6e,EAASvf,IAAKU,EAEtD,IAAMqlB,IAAeN,EAASM,EAASN,QAAvC,CAKAP,GAAUA,GAAS,IAAKjb,MAAOoP,KAAqB,IACpDqM,EAAIR,EAAMplB,MACV,OAAQ4lB,IAMP,GALAjhB,EAAMmgB,GAAeta,KAAM4a,EAAOQ,QAClC9iB,EAAOkjB,EAAWrhB,EAAK,GACvBohB,GAAephB,EAAK,IAAO,IAAKM,MAAO,KAAM3D,OAGvCwB,EAAN,CAOAyZ,EAAUtd,EAAOsmB,MAAMhJ,QAASzZ,OAChCA,GAAS5D,EAAWqd,EAAQ8J,aAAe9J,EAAQ+J,WAAcxjB,EACjEgjB,EAAWH,EAAQ7iB,OACnB6B,EAAMA,EAAK,IACV,GAAI0C,QAAQ,UAAY0e,EAAW7a,KAAM,iBAAoB,WAG9Dyb,EAAYvlB,EAAI0kB,EAAS9lB,MACzB,OAAQoB,IACPykB,EAAYC,EAAU1kB,IAEfslB,GAAeV,IAAaH,EAAUG,UACzC/Z,GAAWA,EAAQxH,OAASohB,EAAUphB,MACtCE,IAAOA,EAAImG,KAAM+a,EAAUU,YAC3BrnB,GAAYA,IAAa2mB,EAAU3mB,WACxB,OAAbA,IAAqB2mB,EAAU3mB,YAChC4mB,EAASvkB,OAAQH,EAAG,GAEfykB,EAAU3mB,UACd4mB,EAASU,gBAELjK,EAAQlC,QACZkC,EAAQlC,OAAOjc,KAAMwC,EAAMilB,GAOzBc,KAAcb,EAAS9lB,SACrBuc,EAAQqK,UACbrK,EAAQqK,SAASxoB,KAAMwC,EAAMmlB,EAAYE,EAASC,WAAa,GAE/DjnB,EAAO4nB,YAAajmB,EAAMkC,EAAMmjB,EAASC,cAGnCP,GAAQ7iB,QA1Cf,KAAMA,IAAQ6iB,GACb1mB,EAAOsmB,MAAMlL,OAAQzZ,EAAMkC,EAAOsiB,EAAOQ,GAAK3Z,EAAS/M,GAAU,EA8C/DD,GAAOqE,cAAeqiB,IAC1BlG,EAASpF,OAAQzZ,EAAM,mBAIzBwlB,SAAU,SAAUU,GAGnB,GAAIvB,GAAQtmB,EAAOsmB,MAAMwB,IAAKD,GAE1BjmB,EAAGO,EAAGd,EAAKwR,EAAS+T,EAAWmB,EAClCpiB,EAAO,GAAI7B,OAAOhC,UAAUf,QAC5B8lB,GAAarG,EAASvf,IAAK9C,KAAM,eAAoBmoB,EAAMziB,UAC3DyZ,EAAUtd,EAAOsmB,MAAMhJ,QAASgJ,EAAMziB,SAKvC,KAFA8B,EAAM,GAAM2gB,EAEN1kB,EAAI,EAAGA,EAAIE,UAAUf,OAAQa,IAClC+D,EAAM/D,GAAME,UAAWF,EAMxB,IAHA0kB,EAAM0B,eAAiB7pB,MAGlBmf,EAAQ2K,aAAe3K,EAAQ2K,YAAY9oB,KAAMhB,KAAMmoB,MAAY,EAAxE,CAKAyB,EAAe/nB,EAAOsmB,MAAMO,SAAS1nB,KAAMhB,KAAMmoB,EAAOO,GAGxDjlB,EAAI,CACJ,QAAUiR,EAAUkV,EAAcnmB,QAAY0kB,EAAM4B,uBAAyB,CAC5E5B,EAAM6B,cAAgBtV,EAAQlR,KAE9BQ,EAAI,CACJ,QAAUykB,EAAY/T,EAAQgU,SAAU1kB,QACtCmkB,EAAM8B,gCAID9B,EAAM+B,aAAc/B,EAAM+B,WAAWxc,KAAM+a,EAAUU,aAE1DhB,EAAMM,UAAYA,EAClBN,EAAMjG,KAAOuG,EAAUvG,KAEvBhf,IAAUrB,EAAOsmB,MAAMhJ,QAASsJ,EAAUG,eAAmBE,QAC5DL,EAAU5Z,SAAUnL,MAAOgR,EAAQlR,KAAMgE,GAE7BxC,SAAR9B,IACGilB,EAAM1U,OAASvQ,MAAU,IAC/BilB,EAAMgC,iBACNhC,EAAMiC,oBAYX,MAJKjL,GAAQkL,cACZlL,EAAQkL,aAAarpB,KAAMhB,KAAMmoB,GAG3BA,EAAM1U,SAGdiV,SAAU,SAAUP,EAAOO,GAC1B,GAAIjlB,GAAGglB,EAAWtW,EAAKmY,EAAiBC,EACvCX,KACAR,EAAgBV,EAASU,cACzBpa,EAAMmZ,EAAMxjB,MAGb,IAAKykB,GAIJpa,EAAIvC,YAOc,UAAf0b,EAAMziB,MAAoByiB,EAAMnS,QAAU,GAE7C,KAAQhH,IAAQhP,KAAMgP,EAAMA,EAAItN,YAAc1B,KAI7C,GAAsB,IAAjBgP,EAAIvC,WAAoC,UAAf0b,EAAMziB,MAAoBsJ,EAAI3C,YAAa,GAAS,CAGjF,IAFAie,KACAC,KACM9mB,EAAI,EAAGA,EAAI2lB,EAAe3lB,IAC/BglB,EAAYC,EAAUjlB,GAGtB0O,EAAMsW,EAAU3mB,SAAW,IAEMkD,SAA5BulB,EAAkBpY,KACtBoY,EAAkBpY,GAAQsW,EAAU1d,aACnClJ,EAAQsQ,EAAKnS,MAAOub,MAAOvM,MAC3BnN,EAAO4O,KAAM0B,EAAKnS,KAAM,MAAQgP,IAAQpM,QAErC2nB,EAAkBpY,IACtBmY,EAAgB9pB,KAAMioB,EAGnB6B,GAAgB1nB,QACpBgnB,EAAappB,MAAQgD,KAAMwL,EAAK0Z,SAAU4B,IAY9C,MALAtb,GAAMhP,KACDopB,EAAgBV,EAAS9lB,QAC7BgnB,EAAappB,MAAQgD,KAAMwL,EAAK0Z,SAAUA,EAASpoB,MAAO8oB,KAGpDQ,GAGRY,QAAS,SAAUlmB,EAAMmmB,GACxBrqB,OAAO2hB,eAAgBlgB,EAAO6oB,MAAMjoB,UAAW6B,GAC9CqmB,YAAY,EACZ3I,cAAc,EAEdlf,IAAKjB,EAAOgD,WAAY4lB,GACvB,WACC,GAAKzqB,KAAK4qB,cACR,MAAOH,GAAMzqB,KAAK4qB,gBAGrB,WACC,GAAK5qB,KAAK4qB,cACR,MAAO5qB,MAAK4qB,cAAetmB,IAI/B2d,IAAK,SAAU7a,GACdhH,OAAO2hB,eAAgB/hB,KAAMsE,GAC5BqmB,YAAY,EACZ3I,cAAc,EACd6I,UAAU,EACVzjB,MAAOA,QAMXuiB,IAAK,SAAUiB,GACd,MAAOA,GAAe/oB,EAAOoD,SAC5B2lB,EACA,GAAI/oB,GAAO6oB,MAAOE,IAGpBzL,SACC2L,MAGCC,UAAU,GAEX1V,OAGC2V,QAAS,WACR,GAAKhrB,OAAS6nB,MAAuB7nB,KAAKqV,MAEzC,MADArV,MAAKqV,SACE,GAGT4T,aAAc,WAEfgC,MACCD,QAAS,WACR,GAAKhrB,OAAS6nB,MAAuB7nB,KAAKirB,KAEzC,MADAjrB,MAAKirB,QACE,GAGThC,aAAc,YAEfiC,OAGCF,QAAS,WACR,GAAmB,aAAdhrB,KAAK0F,MAAuB1F,KAAKkrB,OAASrpB,EAAOyE,SAAUtG,KAAM,SAErE,MADAA,MAAKkrB,SACE,GAKTnF,SAAU,SAAUoC,GACnB,MAAOtmB,GAAOyE,SAAU6hB,EAAMxjB,OAAQ,OAIxCwmB,cACCd,aAAc,SAAUlC,GAIDnjB,SAAjBmjB,EAAM1U,QAAwB0U,EAAMyC,gBACxCzC,EAAMyC,cAAcQ,YAAcjD,EAAM1U,YAO7C5R,EAAO4nB,YAAc,SAAUjmB,EAAMkC,EAAMojB,GAGrCtlB,EAAK2d,qBACT3d,EAAK2d,oBAAqBzb,EAAMojB,IAIlCjnB,EAAO6oB,MAAQ,SAAUnmB,EAAK8mB,GAG7B,MAAQrrB,gBAAgB6B,GAAO6oB,OAK1BnmB,GAAOA,EAAImB,MACf1F,KAAK4qB,cAAgBrmB,EACrBvE,KAAK0F,KAAOnB,EAAImB,KAIhB1F,KAAKsrB,mBAAqB/mB,EAAIgnB,kBACHvmB,SAAzBT,EAAIgnB,kBAGJhnB,EAAI6mB,eAAgB,EACrBzD,GACAC,GAKD5nB,KAAK2E,OAAWJ,EAAII,QAAkC,IAAxBJ,EAAII,OAAO8H,SACxClI,EAAII,OAAOjD,WACX6C,EAAII,OAEL3E,KAAKgqB,cAAgBzlB,EAAIylB,cACzBhqB,KAAKwrB,cAAgBjnB,EAAIinB,eAIzBxrB,KAAK0F,KAAOnB,EAIR8mB,GACJxpB,EAAOuC,OAAQpE,KAAMqrB,GAItBrrB,KAAKyrB,UAAYlnB,GAAOA,EAAIknB,WAAa5pB,EAAO4F,WAGhDzH,KAAM6B,EAAOoD,UAAY,IA1CjB,GAAIpD,GAAO6oB,MAAOnmB,EAAK8mB,IA+ChCxpB,EAAO6oB,MAAMjoB,WACZE,YAAad,EAAO6oB,MACpBY,mBAAoB1D,GACpBmC,qBAAsBnC,GACtBqC,8BAA+BrC,GAC/B8D,aAAa,EAEbvB,eAAgB,WACf,GAAIzd,GAAI1M,KAAK4qB,aAEb5qB,MAAKsrB,mBAAqB3D,GAErBjb,IAAM1M,KAAK0rB,aACfhf,EAAEyd,kBAGJC,gBAAiB,WAChB,GAAI1d,GAAI1M,KAAK4qB,aAEb5qB,MAAK+pB,qBAAuBpC,GAEvBjb,IAAM1M,KAAK0rB,aACfhf,EAAE0d,mBAGJuB,yBAA0B,WACzB,GAAIjf,GAAI1M,KAAK4qB,aAEb5qB,MAAKiqB,8BAAgCtC,GAEhCjb,IAAM1M,KAAK0rB,aACfhf,EAAEif,2BAGH3rB,KAAKoqB,oBAKPvoB,EAAOwB,MACNuoB,QAAQ,EACRC,SAAS,EACTC,YAAY,EACZC,gBAAgB,EAChBC,SAAS,EACTC,QAAQ,EACRC,YAAY,EACZC,SAAS,EACTC,OAAO,EACPC,OAAO,EACPC,UAAU,EACVC,MAAM,EACNC,QAAQ,EACRC,UAAU,EACVpe,KAAK,EACLqe,SAAS,EACT1W,QAAQ,EACR2W,SAAS,EACTC,SAAS,EACTC,SAAS,EACTC,SAAS,EACTC,SAAS,EACTC,WAAW,EACXC,aAAa,EACbC,SAAS,EACTC,SAAS,EACTC,eAAe,EACfC,WAAW,EACXC,SAAS,EAETC,MAAO,SAAUpF,GAChB,GAAInS,GAASmS,EAAMnS,MAGnB,OAAoB,OAAfmS,EAAMoF,OAAiB/F,GAAU9Z,KAAMya,EAAMziB,MACxB,MAAlByiB,EAAMsE,SAAmBtE,EAAMsE,SAAWtE,EAAMuE,SAIlDvE,EAAMoF,OAAoBvoB,SAAXgR,GAAwByR,GAAY/Z,KAAMya,EAAMziB,MACtD,EAATsQ,EACG,EAGM,EAATA,EACG,EAGM,EAATA,EACG,EAGD,EAGDmS,EAAMoF,QAEZ1rB,EAAOsmB,MAAMqC,SAUhB3oB,EAAOwB,MACNmqB,WAAY,YACZC,WAAY,WACZC,aAAc,cACdC,aAAc,cACZ,SAAUC,EAAMjE,GAClB9nB,EAAOsmB,MAAMhJ,QAASyO,IACrB3E,aAAcU,EACdT,SAAUS,EAEVb,OAAQ,SAAUX,GACjB,GAAIjlB,GACHyB,EAAS3E,KACT6tB,EAAU1F,EAAMqD,cAChB/C,EAAYN,EAAMM,SASnB,OALMoF,KAAaA,IAAYlpB,GAAW9C,EAAOgH,SAAUlE,EAAQkpB,MAClE1F,EAAMziB,KAAO+iB,EAAUG,SACvB1lB,EAAMulB,EAAU5Z,QAAQnL,MAAO1D,KAAM2D,WACrCwkB,EAAMziB,KAAOikB,GAEPzmB,MAKVrB,EAAOG,GAAGoC,QAET2jB,GAAI,SAAUC,EAAOlmB,EAAUogB,EAAMlgB,GACpC,MAAO+lB,IAAI/nB,KAAMgoB,EAAOlmB,EAAUogB,EAAMlgB,IAEzCimB,IAAK,SAAUD,EAAOlmB,EAAUogB,EAAMlgB,GACrC,MAAO+lB,IAAI/nB,KAAMgoB,EAAOlmB,EAAUogB,EAAMlgB,EAAI,IAE7ComB,IAAK,SAAUJ,EAAOlmB,EAAUE,GAC/B,GAAIymB,GAAW/iB,CACf,IAAKsiB,GAASA,EAAMmC,gBAAkBnC,EAAMS,UAW3C,MARAA,GAAYT,EAAMS,UAClB5mB,EAAQmmB,EAAM6B,gBAAiBzB,IAC9BK,EAAUU,UACTV,EAAUG,SAAW,IAAMH,EAAUU,UACrCV,EAAUG,SACXH,EAAU3mB,SACV2mB,EAAU5Z,SAEJ7O,IAER,IAAsB,gBAAVgoB,GAAqB,CAGhC,IAAMtiB,IAAQsiB,GACbhoB,KAAKooB,IAAK1iB,EAAM5D,EAAUkmB,EAAOtiB,GAElC,OAAO1F,MAWR,MATK8B,MAAa,GAA6B,kBAAbA,KAGjCE,EAAKF,EACLA,EAAWkD,QAEPhD,KAAO,IACXA,EAAK4lB,IAEC5nB,KAAKqD,KAAM,WACjBxB,EAAOsmB,MAAMlL,OAAQjd,KAAMgoB,EAAOhmB,EAAIF,OAMzC,IAKCgsB,IAAY,8FAOZC,GAAe,wBAGfC,GAAW,oCACXC,GAAoB,cACpBC,GAAe,0CAEhB,SAASC,IAAoB3qB,EAAM4qB,GAClC,MAAKvsB,GAAOyE,SAAU9C,EAAM,UAC3B3B,EAAOyE,SAA+B,KAArB8nB,EAAQ3hB,SAAkB2hB,EAAUA,EAAQ3b,WAAY,MAElEjP,EAAK+J,qBAAsB,SAAW,IAAO/J,EAG9CA,EAIR,QAAS6qB,IAAe7qB,GAEvB,MADAA,GAAKkC,MAAyC,OAAhClC,EAAKmK,aAAc,SAAsB,IAAMnK,EAAKkC,KAC3DlC,EAER,QAAS8qB,IAAe9qB,GACvB,GAAIuJ,GAAQkhB,GAAkB7gB,KAAM5J,EAAKkC,KAQzC,OANKqH,GACJvJ,EAAKkC,KAAOqH,EAAO,GAEnBvJ,EAAK0K,gBAAiB,QAGhB1K,EAGR,QAAS+qB,IAAgBhqB,EAAKiqB,GAC7B,GAAI/qB,GAAG4X,EAAG3V,EAAM+oB,EAAUC,EAAUC,EAAUC,EAAUrG,CAExD,IAAuB,IAAlBiG,EAAK/hB,SAAV,CAKA,GAAK4V,EAASD,QAAS7d,KACtBkqB,EAAWpM,EAASf,OAAQ/c,GAC5BmqB,EAAWrM,EAASJ,IAAKuM,EAAMC,GAC/BlG,EAASkG,EAASlG,QAEJ,OACNmG,GAAS5F,OAChB4F,EAASnG,SAET,KAAM7iB,IAAQ6iB,GACb,IAAM9kB,EAAI,EAAG4X,EAAIkN,EAAQ7iB,GAAO9C,OAAQa,EAAI4X,EAAG5X,IAC9C5B,EAAOsmB,MAAM1M,IAAK+S,EAAM9oB,EAAM6iB,EAAQ7iB,GAAQjC,IAO7C6e,EAASF,QAAS7d,KACtBoqB,EAAWrM,EAAShB,OAAQ/c,GAC5BqqB,EAAW/sB,EAAOuC,UAAYuqB,GAE9BrM,EAASL,IAAKuM,EAAMI,KAKtB,QAASC,IAAUtqB,EAAKiqB,GACvB,GAAIloB,GAAWkoB,EAAKloB,SAASC,aAGX,WAAbD,GAAwBgf,GAAe5X,KAAMnJ,EAAImB,MACrD8oB,EAAK7Y,QAAUpR,EAAIoR,QAGK,UAAbrP,GAAqC,aAAbA,IACnCkoB,EAAK3U,aAAetV,EAAIsV,cAI1B,QAASiV,IAAUC,EAAYvnB,EAAMlE,EAAUujB,GAG9Crf,EAAOjH,EAAOmD,SAAW8D,EAEzB,IAAIuf,GAAUnjB,EAAO+iB,EAASqI,EAAYpf,EAAMxO,EAC/CqC,EAAI,EACJ4X,EAAI0T,EAAWnsB,OACfqsB,EAAW5T,EAAI,EACfjU,EAAQI,EAAM,GACd3C,EAAahD,EAAOgD,WAAYuC,EAGjC,IAAKvC,GACDwW,EAAI,GAAsB,gBAAVjU,KAChBnG,EAAQomB,YAAc2G,GAAStgB,KAAMtG,GACxC,MAAO2nB,GAAW1rB,KAAM,SAAUkY,GACjC,GAAIZ,GAAOoU,EAAWlrB,GAAI0X,EACrB1W,KACJ2C,EAAM,GAAMJ,EAAMpG,KAAMhB,KAAMub,EAAOZ,EAAKuU,SAE3CJ,GAAUnU,EAAMnT,EAAMlE,EAAUujB,IAIlC,IAAKxL,IACJ0L,EAAWL,GAAelf,EAAMunB,EAAY,GAAI5hB,eAAe,EAAO4hB,EAAYlI,GAClFjjB,EAAQmjB,EAAStU,WAEmB,IAA/BsU,EAASva,WAAW5J,SACxBmkB,EAAWnjB,GAIPA,GAASijB,GAAU,CAOvB,IANAF,EAAU9kB,EAAO0B,IAAK+iB,GAAQS,EAAU,UAAYsH,IACpDW,EAAarI,EAAQ/jB,OAKba,EAAI4X,EAAG5X,IACdmM,EAAOmX,EAEFtjB,IAAMwrB,IACVrf,EAAO/N,EAAO6C,MAAOkL,GAAM,GAAM,GAG5Bof,GAIJntB,EAAOsB,MAAOwjB,EAASL,GAAQ1W,EAAM,YAIvCtM,EAAStC,KAAM+tB,EAAYtrB,GAAKmM,EAAMnM,EAGvC,IAAKurB,EAOJ,IANA5tB,EAAMulB,EAASA,EAAQ/jB,OAAS,GAAIuK,cAGpCtL,EAAO0B,IAAKojB,EAAS2H,IAGf7qB,EAAI,EAAGA,EAAIurB,EAAYvrB,IAC5BmM,EAAO+W,EAASljB,GACX+hB,GAAY9X,KAAMkC,EAAKlK,MAAQ,MAClC2c,EAASf,OAAQ1R,EAAM,eACxB/N,EAAOgH,SAAUzH,EAAKwO,KAEjBA,EAAKrL,IAGJ1C,EAAOstB,UACXttB,EAAOstB,SAAUvf,EAAKrL,KAGvBrD,EAAS0O,EAAK4C,YAAYpN,QAAS8oB,GAAc,IAAM9sB,IAQ7D,MAAO2tB,GAGR,QAAS9R,IAAQzZ,EAAM1B,EAAUstB,GAKhC,IAJA,GAAIxf,GACHqX,EAAQnlB,EAAWD,EAAO0O,OAAQzO,EAAU0B,GAASA,EACrDC,EAAI,EAE4B,OAAvBmM,EAAOqX,EAAOxjB,IAAeA,IAChC2rB,GAA8B,IAAlBxf,EAAKnD,UACtB5K,EAAOwtB,UAAW/I,GAAQ1W,IAGtBA,EAAKlO,aACJ0tB,GAAYvtB,EAAOgH,SAAU+G,EAAKzC,cAAeyC,IACrD2W,GAAeD,GAAQ1W,EAAM,WAE9BA,EAAKlO,WAAWC,YAAaiO,GAI/B,OAAOpM,GAGR3B,EAAOuC,QACN8iB,cAAe,SAAUgI,GACxB,MAAOA,GAAK9pB,QAAS0oB,GAAW,cAGjCppB,MAAO,SAAUlB,EAAM8rB,EAAeC,GACrC,GAAI9rB,GAAG4X,EAAGmU,EAAaC,EACtB/qB,EAAQlB,EAAK8jB,WAAW,GACxBoI,EAAS7tB,EAAOgH,SAAUrF,EAAK2J,cAAe3J,EAG/C,MAAMvC,EAAQsmB,gBAAsC,IAAlB/jB,EAAKiJ,UAAoC,KAAlBjJ,EAAKiJ,UAC3D5K,EAAOkY,SAAUvW,IAMnB,IAHAisB,EAAenJ,GAAQ5hB,GACvB8qB,EAAclJ,GAAQ9iB,GAEhBC,EAAI,EAAG4X,EAAImU,EAAY5sB,OAAQa,EAAI4X,EAAG5X,IAC3CorB,GAAUW,EAAa/rB,GAAKgsB,EAAchsB,GAK5C,IAAK6rB,EACJ,GAAKC,EAIJ,IAHAC,EAAcA,GAAelJ,GAAQ9iB,GACrCisB,EAAeA,GAAgBnJ,GAAQ5hB,GAEjCjB,EAAI,EAAG4X,EAAImU,EAAY5sB,OAAQa,EAAI4X,EAAG5X,IAC3C8qB,GAAgBiB,EAAa/rB,GAAKgsB,EAAchsB,QAGjD8qB,IAAgB/qB,EAAMkB,EAWxB,OANA+qB,GAAenJ,GAAQ5hB,EAAO,UACzB+qB,EAAa7sB,OAAS,GAC1B2jB,GAAekJ,GAAeC,GAAUpJ,GAAQ9iB,EAAM,WAIhDkB,GAGR2qB,UAAW,SAAUpsB,GAKpB,IAJA,GAAIif,GAAM1e,EAAMkC,EACfyZ,EAAUtd,EAAOsmB,MAAMhJ,QACvB1b,EAAI,EAE6BuB,UAAxBxB,EAAOP,EAAOQ,IAAqBA,IAC5C,GAAKke,EAAYne,GAAS,CACzB,GAAO0e,EAAO1e,EAAM6e,EAASpd,SAAc,CAC1C,GAAKid,EAAKqG,OACT,IAAM7iB,IAAQwc,GAAKqG,OACbpJ,EAASzZ,GACb7D,EAAOsmB,MAAMlL,OAAQzZ,EAAMkC,GAI3B7D,EAAO4nB,YAAajmB,EAAMkC,EAAMwc,EAAK4G,OAOxCtlB,GAAM6e,EAASpd,SAAYD,OAEvBxB,EAAM8e,EAASrd,WAInBzB,EAAM8e,EAASrd,SAAYD,YAOhCnD,EAAOG,GAAGoC,QACTurB,OAAQ,SAAU7tB,GACjB,MAAOmb,IAAQjd,KAAM8B,GAAU,IAGhCmb,OAAQ,SAAUnb,GACjB,MAAOmb,IAAQjd,KAAM8B,IAGtBP,KAAM,SAAU6F,GACf,MAAOka,GAAQthB,KAAM,SAAUoH,GAC9B,MAAiBpC,UAAVoC,EACNvF,EAAON,KAAMvB,MACbA,KAAK8V,QAAQzS,KAAM,WACK,IAAlBrD,KAAKyM,UAAoC,KAAlBzM,KAAKyM,UAAqC,IAAlBzM,KAAKyM,WACxDzM,KAAKwS,YAAcpL,MAGpB,KAAMA,EAAOzD,UAAUf,SAG3BgtB,OAAQ,WACP,MAAOd,IAAU9uB,KAAM2D,UAAW,SAAUH,GAC3C,GAAuB,IAAlBxD,KAAKyM,UAAoC,KAAlBzM,KAAKyM,UAAqC,IAAlBzM,KAAKyM,SAAiB,CACzE,GAAI9H,GAASwpB,GAAoBnuB,KAAMwD,EACvCmB,GAAOlD,YAAa+B,OAKvBqsB,QAAS,WACR,MAAOf,IAAU9uB,KAAM2D,UAAW,SAAUH,GAC3C,GAAuB,IAAlBxD,KAAKyM,UAAoC,KAAlBzM,KAAKyM,UAAqC,IAAlBzM,KAAKyM,SAAiB,CACzE,GAAI9H,GAASwpB,GAAoBnuB,KAAMwD,EACvCmB,GAAOmrB,aAActsB,EAAMmB,EAAO8N,gBAKrCsd,OAAQ,WACP,MAAOjB,IAAU9uB,KAAM2D,UAAW,SAAUH,GACtCxD,KAAK0B,YACT1B,KAAK0B,WAAWouB,aAActsB,EAAMxD,SAKvCgwB,MAAO,WACN,MAAOlB,IAAU9uB,KAAM2D,UAAW,SAAUH,GACtCxD,KAAK0B,YACT1B,KAAK0B,WAAWouB,aAActsB,EAAMxD,KAAKmP,gBAK5C2G,MAAO,WAIN,IAHA,GAAItS,GACHC,EAAI,EAE2B,OAAtBD,EAAOxD,KAAMyD,IAAeA,IACd,IAAlBD,EAAKiJ,WAGT5K,EAAOwtB,UAAW/I,GAAQ9iB,GAAM,IAGhCA,EAAKgP,YAAc,GAIrB,OAAOxS,OAGR0E,MAAO,SAAU4qB,EAAeC,GAI/B,MAHAD,GAAiC,MAAjBA,GAAgCA,EAChDC,EAAyC,MAArBA,EAA4BD,EAAgBC,EAEzDvvB,KAAKuD,IAAK,WAChB,MAAO1B,GAAO6C,MAAO1E,KAAMsvB,EAAeC,MAI5CL,KAAM,SAAU9nB,GACf,MAAOka,GAAQthB,KAAM,SAAUoH,GAC9B,GAAI5D,GAAOxD,KAAM,OAChByD,EAAI,EACJ4X,EAAIrb,KAAK4C,MAEV,IAAeoC,SAAVoC,GAAyC,IAAlB5D,EAAKiJ,SAChC,MAAOjJ,GAAKoN,SAIb,IAAsB,gBAAVxJ,KAAuB2mB,GAAargB,KAAMtG,KACpDqe,IAAWF,GAASnY,KAAMhG,KAAa,GAAI,KAAQ,GAAIb,eAAkB,CAE1Ea,EAAQvF,EAAOqlB,cAAe9f,EAE9B,KACC,KAAQ3D,EAAI4X,EAAG5X,IACdD,EAAOxD,KAAMyD,OAGU,IAAlBD,EAAKiJ,WACT5K,EAAOwtB,UAAW/I,GAAQ9iB,GAAM,IAChCA,EAAKoN,UAAYxJ,EAInB5D,GAAO,EAGN,MAAQkJ,KAGNlJ,GACJxD,KAAK8V,QAAQ8Z,OAAQxoB,IAEpB,KAAMA,EAAOzD,UAAUf,SAG3BqtB,YAAa,WACZ,GAAIpJ,KAGJ,OAAOiI,IAAU9uB,KAAM2D,UAAW,SAAUH,GAC3C,GAAI2Q,GAASnU,KAAK0B,UAEbG,GAAO+E,QAAS5G,KAAM6mB,GAAY,IACtChlB,EAAOwtB,UAAW/I,GAAQtmB,OACrBmU,GACJA,EAAO+b,aAAc1sB,EAAMxD,QAK3B6mB,MAILhlB,EAAOwB,MACN8sB,SAAU,SACVC,UAAW,UACXN,aAAc,SACdO,YAAa,QACbC,WAAY,eACV,SAAUhsB,EAAMisB,GAClB1uB,EAAOG,GAAIsC,GAAS,SAAUxC,GAO7B,IANA,GAAImB,GACHC,KACAstB,EAAS3uB,EAAQC,GACjBgC,EAAO0sB,EAAO5tB,OAAS,EACvBa,EAAI,EAEGA,GAAKK,EAAML,IAClBR,EAAQQ,IAAMK,EAAO9D,KAAOA,KAAK0E,OAAO,GACxC7C,EAAQ2uB,EAAQ/sB,IAAO8sB,GAAYttB,GAInCzC,EAAKkD,MAAOR,EAAKD,EAAMH,MAGxB,OAAO9C,MAAKgD,UAAWE,KAGzB,IAAIutB,IAAU,UAEVC,GAAY,GAAIzmB,QAAQ,KAAOwZ,EAAO,kBAAmB,KAEzDkN,GAAY,SAAUntB,GAKxB,GAAI+oB,GAAO/oB,EAAK2J,cAAc4C,WAM9B,OAJMwc,IAASA,EAAKqE,SACnBrE,EAAOxsB,GAGDwsB,EAAKsE,iBAAkBrtB,KAKhC,WAIC,QAASstB,KAGR,GAAM1J,EAAN,CAIAA,EAAItD,MAAMiN,QACT,4GAID3J,EAAIxW,UAAY,GAChBjB,GAAgBlO,YAAauvB,EAE7B,IAAIC,GAAWlxB,EAAO8wB,iBAAkBzJ,EACxC8J,GAAoC,OAAjBD,EAASjhB,IAG5BmhB,EAAgD,QAAxBF,EAASG,WACjCC,EAA0C,QAAnBJ,EAASK,MAIhClK,EAAItD,MAAMyN,YAAc,MACxBC,EAA+C,QAAzBP,EAASM,YAE/B5hB,GAAgBhO,YAAaqvB,GAI7B5J,EAAM,MAGP,GAAI8J,GAAkBG,EAAsBG,EAAqBL,EAChEH,EAAYpxB,EAAS0B,cAAe,OACpC8lB,EAAMxnB,EAAS0B,cAAe,MAGzB8lB,GAAItD,QAMVsD,EAAItD,MAAM2N,eAAiB,cAC3BrK,EAAIE,WAAW,GAAOxD,MAAM2N,eAAiB,GAC7CxwB,EAAQywB,gBAA+C,gBAA7BtK,EAAItD,MAAM2N,eAEpCT,EAAUlN,MAAMiN,QAAU,4FAE1BC,EAAUvvB,YAAa2lB,GAEvBvlB,EAAOuC,OAAQnD,GACd0wB,cAAe,WAEd,MADAb,KACOI,GAERU,kBAAmB,WAElB,MADAd,KACOO,GAERQ,iBAAkB,WAEjB,MADAf,KACOU,GAERM,mBAAoB,WAEnB,MADAhB,KACOK,QAMV,SAASY,IAAQvuB,EAAMc,EAAM0tB,GAC5B,GAAIV,GAAOW,EAAUC,EAAUhvB,EAC9B4gB,EAAQtgB,EAAKsgB,KAoCd,OAlCAkO,GAAWA,GAAYrB,GAAWntB,GAI7BwuB,IACJ9uB,EAAM8uB,EAASG,iBAAkB7tB,IAAU0tB,EAAU1tB,GAExC,KAARpB,GAAerB,EAAOgH,SAAUrF,EAAK2J,cAAe3J,KACxDN,EAAMrB,EAAOiiB,MAAOtgB,EAAMc,KAQrBrD,EAAQ4wB,oBAAsBnB,GAAUhjB,KAAMxK,IAASutB,GAAQ/iB,KAAMpJ,KAG1EgtB,EAAQxN,EAAMwN,MACdW,EAAWnO,EAAMmO,SACjBC,EAAWpO,EAAMoO,SAGjBpO,EAAMmO,SAAWnO,EAAMoO,SAAWpO,EAAMwN,MAAQpuB,EAChDA,EAAM8uB,EAASV,MAGfxN,EAAMwN,MAAQA,EACdxN,EAAMmO,SAAWA,EACjBnO,EAAMoO,SAAWA,IAIJltB,SAAR9B,EAINA,EAAM,GACNA,EAIF,QAASkvB,IAAcC,EAAaC,GAGnC,OACCxvB,IAAK,WACJ,MAAKuvB,gBAIGryB,MAAK8C,KAKJ9C,KAAK8C,IAAMwvB,GAAS5uB,MAAO1D,KAAM2D,aAM7C,GAKC4uB,IAAe,4BACfC,IAAYC,SAAU,WAAYC,WAAY,SAAU3O,QAAS,SACjE4O,IACCC,cAAe,IACfC,WAAY,OAGbC,IAAgB,SAAU,MAAO,MACjCC,GAAanzB,EAAS0B,cAAe,OAAQwiB,KAG9C,SAASkP,IAAgB1uB,GAGxB,GAAKA,IAAQyuB,IACZ,MAAOzuB,EAIR,IAAI2uB,GAAU3uB,EAAM,GAAI9B,cAAgB8B,EAAKhE,MAAO,GACnDmD,EAAIqvB,GAAYlwB,MAEjB,OAAQa,IAEP,GADAa,EAAOwuB,GAAarvB,GAAMwvB,EACrB3uB,IAAQyuB,IACZ,MAAOzuB,GAKV,QAAS4uB,IAAmB1vB,EAAM4D,EAAO+rB,GAIxC,GAAIlsB,GAAU0c,GAAQvW,KAAMhG,EAC5B,OAAOH,GAGN/B,KAAKkuB,IAAK,EAAGnsB,EAAS,IAAQksB,GAAY,KAAUlsB,EAAS,IAAO,MACpEG,EAGF,QAASisB,IAAsB7vB,EAAMc,EAAMgvB,EAAOC,EAAaC,GAC9D,GAAI/vB,GACHuO,EAAM,CAWP,KAPCvO,EADI6vB,KAAYC,EAAc,SAAW,WACrC,EAIS,UAATjvB,EAAmB,EAAI,EAGpBb,EAAI,EAAGA,GAAK,EAGJ,WAAV6vB,IACJthB,GAAOnQ,EAAOmiB,IAAKxgB,EAAM8vB,EAAQ1P,GAAWngB,IAAK,EAAM+vB,IAGnDD,GAGW,YAAVD,IACJthB,GAAOnQ,EAAOmiB,IAAKxgB,EAAM,UAAYogB,GAAWngB,IAAK,EAAM+vB,IAI7C,WAAVF,IACJthB,GAAOnQ,EAAOmiB,IAAKxgB,EAAM,SAAWogB,GAAWngB,GAAM,SAAS,EAAM+vB,MAKrExhB,GAAOnQ,EAAOmiB,IAAKxgB,EAAM,UAAYogB,GAAWngB,IAAK,EAAM+vB,GAG5C,YAAVF,IACJthB,GAAOnQ,EAAOmiB,IAAKxgB,EAAM,SAAWogB,GAAWngB,GAAM,SAAS,EAAM+vB,IAKvE,OAAOxhB,GAGR,QAASyhB,IAAkBjwB,EAAMc,EAAMgvB,GAGtC,GAAIthB,GACH0hB,GAAmB,EACnBF,EAAS7C,GAAWntB,GACpB+vB,EAAiE,eAAnD1xB,EAAOmiB,IAAKxgB,EAAM,aAAa,EAAOgwB,EAYrD,IAPKhwB,EAAKmwB,iBAAiB/wB,SAC1BoP,EAAMxO,EAAKowB,wBAAyBtvB,IAMhC0N,GAAO,GAAY,MAAPA,EAAc,CAS9B,GANAA,EAAM+f,GAAQvuB,EAAMc,EAAMkvB,IACrBxhB,EAAM,GAAY,MAAPA,KACfA,EAAMxO,EAAKsgB,MAAOxf,IAIdosB,GAAUhjB,KAAMsE,GACpB,MAAOA,EAKR0hB,GAAmBH,IAChBtyB,EAAQ2wB,qBAAuB5f,IAAQxO,EAAKsgB,MAAOxf,IAGtD0N,EAAMjM,WAAYiM,IAAS,EAI5B,MAASA,GACRqhB,GACC7vB,EACAc,EACAgvB,IAAWC,EAAc,SAAW,WACpCG,EACAF,GAEE,KAGL3xB,EAAOuC,QAINyvB,UACCC,SACChxB,IAAK,SAAUU,EAAMwuB,GACpB,GAAKA,EAAW,CAGf,GAAI9uB,GAAM6uB,GAAQvuB,EAAM,UACxB,OAAe,KAARN,EAAa,IAAMA,MAO9B0hB,WACCmP,yBAA2B,EAC3BC,aAAe,EACfC,aAAe,EACfC,UAAY,EACZC,YAAc,EACdtB,YAAc,EACduB,YAAc,EACdN,SAAW,EACXO,OAAS,EACTC,SAAW,EACXC,QAAU,EACVC,QAAU,EACVC,MAAQ,GAKTC,UACCC,QAAS,YAIV7Q,MAAO,SAAUtgB,EAAMc,EAAM8C,EAAOksB,GAGnC,GAAM9vB,GAA0B,IAAlBA,EAAKiJ,UAAoC,IAAlBjJ,EAAKiJ,UAAmBjJ,EAAKsgB,MAAlE,CAKA,GAAI5gB,GAAKwC,EAAMwd,EACd0R,EAAW/yB,EAAOuE,UAAW9B,GAC7Bwf,EAAQtgB,EAAKsgB,KASd,OAPAxf,GAAOzC,EAAO6yB,SAAUE,KACrB/yB,EAAO6yB,SAAUE,GAAa5B,GAAgB4B,IAAcA,GAG/D1R,EAAQrhB,EAAOgyB,SAAUvvB,IAAUzC,EAAOgyB,SAAUe,GAGrC5vB,SAAVoC,EAoCC8b,GAAS,OAASA,IACwBle,UAA5C9B,EAAMggB,EAAMpgB,IAAKU,GAAM,EAAO8vB,IAEzBpwB,EAID4gB,EAAOxf,IA1CdoB,QAAc0B,GAGA,WAAT1B,IAAuBxC,EAAMygB,GAAQvW,KAAMhG,KAAalE,EAAK,KACjEkE,EAAQ+c,GAAW3gB,EAAMc,EAAMpB,GAG/BwC,EAAO,UAIM,MAAT0B,GAAiBA,IAAUA,IAKlB,WAAT1B,IACJ0B,GAASlE,GAAOA,EAAK,KAASrB,EAAO+iB,UAAWgQ,GAAa,GAAK,OAI7D3zB,EAAQywB,iBAA6B,KAAVtqB,GAAiD,IAAjC9C,EAAK7D,QAAS,gBAC9DqjB,EAAOxf,GAAS,WAIX4e,GAAY,OAASA,IACsBle,UAA9CoC,EAAQ8b,EAAMjB,IAAKze,EAAM4D,EAAOksB,MAElCxP,EAAOxf,GAAS8C,IAlBjB,UAmCF4c,IAAK,SAAUxgB,EAAMc,EAAMgvB,EAAOE,GACjC,GAAIxhB,GAAKjP,EAAKmgB,EACb0R,EAAW/yB,EAAOuE,UAAW9B,EAyB9B,OAtBAA,GAAOzC,EAAO6yB,SAAUE,KACrB/yB,EAAO6yB,SAAUE,GAAa5B,GAAgB4B,IAAcA,GAG/D1R,EAAQrhB,EAAOgyB,SAAUvvB,IAAUzC,EAAOgyB,SAAUe,GAG/C1R,GAAS,OAASA,KACtBlR,EAAMkR,EAAMpgB,IAAKU,GAAM,EAAM8vB,IAIjBtuB,SAARgN,IACJA,EAAM+f,GAAQvuB,EAAMc,EAAMkvB,IAId,WAARxhB,GAAoB1N,IAAQquB,MAChC3gB,EAAM2gB,GAAoBruB,IAIZ,KAAVgvB,GAAgBA,GACpBvwB,EAAMgD,WAAYiM,GACXshB,KAAU,GAAQuB,SAAU9xB,GAAQA,GAAO,EAAIiP,GAEhDA,KAITnQ,EAAOwB,MAAQ,SAAU,SAAW,SAAUI,EAAGa,GAChDzC,EAAOgyB,SAAUvvB,IAChBxB,IAAK,SAAUU,EAAMwuB,EAAUsB,GAC9B,GAAKtB,EAIJ,OAAOO,GAAa7kB,KAAM7L,EAAOmiB,IAAKxgB,EAAM,aAQxCA,EAAKmwB,iBAAiB/wB,QAAWY,EAAKowB,wBAAwBtC,MAIhEmC,GAAkBjwB,EAAMc,EAAMgvB,GAH9BrP,GAAMzgB,EAAMgvB,GAAS,WACpB,MAAOiB,IAAkBjwB,EAAMc,EAAMgvB,MAM1CrR,IAAK,SAAUze,EAAM4D,EAAOksB,GAC3B,GAAIrsB,GACHusB,EAASF,GAAS3C,GAAWntB,GAC7B2vB,EAAWG,GAASD,GACnB7vB,EACAc,EACAgvB,EACmD,eAAnDzxB,EAAOmiB,IAAKxgB,EAAM,aAAa,EAAOgwB,GACtCA,EAWF,OAPKL,KAAclsB,EAAU0c,GAAQvW,KAAMhG,KACb,QAA3BH,EAAS,IAAO,QAElBzD,EAAKsgB,MAAOxf,GAAS8C,EACrBA,EAAQvF,EAAOmiB,IAAKxgB,EAAMc,IAGpB4uB,GAAmB1vB,EAAM4D,EAAO+rB,OAK1CtxB,EAAOgyB,SAASzC,WAAagB,GAAcnxB,EAAQ6wB,mBAClD,SAAUtuB,EAAMwuB,GACf,GAAKA,EACJ,OAASjsB,WAAYgsB,GAAQvuB,EAAM,gBAClCA,EAAKowB,wBAAwBkB,KAC5B7Q,GAAMzgB,GAAQ4tB,WAAY,GAAK,WAC9B,MAAO5tB,GAAKowB,wBAAwBkB,QAElC,OAMRjzB,EAAOwB,MACN0xB,OAAQ,GACRC,QAAS,GACTC,OAAQ,SACN,SAAUC,EAAQC,GACpBtzB,EAAOgyB,SAAUqB,EAASC,IACzBC,OAAQ,SAAUhuB,GAOjB,IANA,GAAI3D,GAAI,EACP4xB,KAGAC,EAAyB,gBAAVluB,GAAqBA,EAAMS,MAAO,MAAUT,GAEpD3D,EAAI,EAAGA,IACd4xB,EAAUH,EAAStR,GAAWngB,GAAM0xB,GACnCG,EAAO7xB,IAAO6xB,EAAO7xB,EAAI,IAAO6xB,EAAO,EAGzC,OAAOD,KAIH5E,GAAQ/iB,KAAMwnB,KACnBrzB,EAAOgyB,SAAUqB,EAASC,GAASlT,IAAMiR,MAI3CrxB,EAAOG,GAAGoC,QACT4f,IAAK,SAAU1f,EAAM8C,GACpB,MAAOka,GAAQthB,KAAM,SAAUwD,EAAMc,EAAM8C,GAC1C,GAAIosB,GAAQzvB,EACXR,KACAE,EAAI,CAEL,IAAK5B,EAAOkD,QAAST,GAAS,CAI7B,IAHAkvB,EAAS7C,GAAWntB,GACpBO,EAAMO,EAAK1B,OAEHa,EAAIM,EAAKN,IAChBF,EAAKe,EAAMb,IAAQ5B,EAAOmiB,IAAKxgB,EAAMc,EAAMb,IAAK,EAAO+vB,EAGxD,OAAOjwB,GAGR,MAAiByB,UAAVoC,EACNvF,EAAOiiB,MAAOtgB,EAAMc,EAAM8C,GAC1BvF,EAAOmiB,IAAKxgB,EAAMc,IACjBA,EAAM8C,EAAOzD,UAAUf,OAAS,KAKrC,SAAS2yB,IAAO/xB,EAAMa,EAAS8d,EAAMle,EAAKuxB,GACzC,MAAO,IAAID,IAAM9yB,UAAUR,KAAMuB,EAAMa,EAAS8d,EAAMle,EAAKuxB,GAE5D3zB,EAAO0zB,MAAQA,GAEfA,GAAM9yB,WACLE,YAAa4yB,GACbtzB,KAAM,SAAUuB,EAAMa,EAAS8d,EAAMle,EAAKuxB,EAAQ7Q,GACjD3kB,KAAKwD,KAAOA,EACZxD,KAAKmiB,KAAOA,EACZniB,KAAKw1B,OAASA,GAAU3zB,EAAO2zB,OAAOzP,SACtC/lB,KAAKqE,QAAUA,EACfrE,KAAKkU,MAAQlU,KAAKyH,IAAMzH,KAAKgP,MAC7BhP,KAAKiE,IAAMA,EACXjE,KAAK2kB,KAAOA,IAAU9iB,EAAO+iB,UAAWzC,GAAS,GAAK,OAEvDnT,IAAK,WACJ,GAAIkU,GAAQqS,GAAME,UAAWz1B,KAAKmiB,KAElC,OAAOe,IAASA,EAAMpgB,IACrBogB,EAAMpgB,IAAK9C,MACXu1B,GAAME,UAAU1P,SAASjjB,IAAK9C,OAEhC01B,IAAK,SAAUC,GACd,GAAIC,GACH1S,EAAQqS,GAAME,UAAWz1B,KAAKmiB,KAoB/B,OAlBKniB,MAAKqE,QAAQwxB,SACjB71B,KAAK81B,IAAMF,EAAQ/zB,EAAO2zB,OAAQx1B,KAAKw1B,QACtCG,EAAS31B,KAAKqE,QAAQwxB,SAAWF,EAAS,EAAG,EAAG31B,KAAKqE,QAAQwxB,UAG9D71B,KAAK81B,IAAMF,EAAQD,EAEpB31B,KAAKyH,KAAQzH,KAAKiE,IAAMjE,KAAKkU,OAAU0hB,EAAQ51B,KAAKkU,MAE/ClU,KAAKqE,QAAQ0xB,MACjB/1B,KAAKqE,QAAQ0xB,KAAK/0B,KAAMhB,KAAKwD,KAAMxD,KAAKyH,IAAKzH,MAGzCkjB,GAASA,EAAMjB,IACnBiB,EAAMjB,IAAKjiB,MAEXu1B,GAAME,UAAU1P,SAAS9D,IAAKjiB,MAExBA,OAITu1B,GAAM9yB,UAAUR,KAAKQ,UAAY8yB,GAAM9yB,UAEvC8yB,GAAME,WACL1P,UACCjjB,IAAK,SAAUuhB,GACd,GAAI5Q,EAIJ,OAA6B,KAAxB4Q,EAAM7gB,KAAKiJ,UACa,MAA5B4X,EAAM7gB,KAAM6gB,EAAMlC,OAAoD,MAAlCkC,EAAM7gB,KAAKsgB,MAAOO,EAAMlC,MACrDkC,EAAM7gB,KAAM6gB,EAAMlC,OAO1B1O,EAAS5R,EAAOmiB,IAAKK,EAAM7gB,KAAM6gB,EAAMlC,KAAM,IAGrC1O,GAAqB,SAAXA,EAAwBA,EAAJ,IAEvCwO,IAAK,SAAUoC,GAKTxiB,EAAOm0B,GAAGD,KAAM1R,EAAMlC,MAC1BtgB,EAAOm0B,GAAGD,KAAM1R,EAAMlC,MAAQkC,GACK,IAAxBA,EAAM7gB,KAAKiJ,UACiC,MAArD4X,EAAM7gB,KAAKsgB,MAAOjiB,EAAO6yB,SAAUrQ,EAAMlC,SAC1CtgB,EAAOgyB,SAAUxP,EAAMlC,MAGxBkC,EAAM7gB,KAAM6gB,EAAMlC,MAASkC,EAAM5c,IAFjC5F,EAAOiiB,MAAOO,EAAM7gB,KAAM6gB,EAAMlC,KAAMkC,EAAM5c,IAAM4c,EAAMM,SAU5D4Q,GAAME,UAAUQ,UAAYV,GAAME,UAAUS,YAC3CjU,IAAK,SAAUoC,GACTA,EAAM7gB,KAAKiJ,UAAY4X,EAAM7gB,KAAK9B,aACtC2iB,EAAM7gB,KAAM6gB,EAAMlC,MAASkC,EAAM5c,OAKpC5F,EAAO2zB,QACNW,OAAQ,SAAUC,GACjB,MAAOA,IAERC,MAAO,SAAUD,GAChB,MAAO,GAAMlxB,KAAKoxB,IAAKF,EAAIlxB,KAAKqxB,IAAO,GAExCxQ,SAAU,SAGXlkB,EAAOm0B,GAAKT,GAAM9yB,UAAUR,KAG5BJ,EAAOm0B,GAAGD,OAKV,IACCS,IAAOC,GACPC,GAAW,yBACXC,GAAO,aAER,SAASC,MACHH,KACJ12B,EAAO82B,sBAAuBD,IAC9B/0B,EAAOm0B,GAAGc,QAKZ,QAASC,MAIR,MAHAh3B,GAAO+f,WAAY,WAClB0W,GAAQxxB,SAEAwxB,GAAQ30B,EAAO4F,MAIzB,QAASuvB,IAAOtxB,EAAMuxB,GACrB,GAAI1J,GACH9pB,EAAI,EACJmL,GAAUsoB,OAAQxxB,EAKnB,KADAuxB,EAAeA,EAAe,EAAI,EAC1BxzB,EAAI,EAAGA,GAAK,EAAIwzB,EACvB1J,EAAQ3J,GAAWngB,GACnBmL,EAAO,SAAW2e,GAAU3e,EAAO,UAAY2e,GAAU7nB,CAO1D,OAJKuxB,KACJroB,EAAMklB,QAAUllB,EAAM0iB,MAAQ5rB,GAGxBkJ,EAGR,QAASuoB,IAAa/vB,EAAO+a,EAAMiV,GAKlC,IAJA,GAAI/S,GACH0K,GAAesI,GAAUC,SAAUnV,QAAe5hB,OAAQ82B,GAAUC,SAAU,MAC9E/b,EAAQ,EACR3Y,EAASmsB,EAAWnsB,OACb2Y,EAAQ3Y,EAAQ2Y,IACvB,GAAO8I,EAAQ0K,EAAYxT,GAAQva,KAAMo2B,EAAWjV,EAAM/a,GAGzD,MAAOid,GAKV,QAASkT,IAAkB/zB,EAAM6nB,EAAOmM,GACvC,GAAIrV,GAAM/a,EAAOie,EAAQnC,EAAOuU,EAASC,EAAWC,EAAgB5T,EACnE6T,EAAQ,SAAWvM,IAAS,UAAYA,GACxCwM,EAAO73B,KACP4tB,KACA9J,EAAQtgB,EAAKsgB,MACbgU,EAASt0B,EAAKiJ,UAAYoX,GAAoBrgB,GAC9Cu0B,EAAW1V,EAASvf,IAAKU,EAAM,SAG1Bg0B,GAAK5a,QACVsG,EAAQrhB,EAAOshB,YAAa3f,EAAM,MACX,MAAlB0f,EAAM8U,WACV9U,EAAM8U,SAAW,EACjBP,EAAUvU,EAAMpN,MAAMgH,KACtBoG,EAAMpN,MAAMgH,KAAO,WACZoG,EAAM8U,UACXP,MAIHvU,EAAM8U,WAENH,EAAKzZ,OAAQ,WAGZyZ,EAAKzZ,OAAQ,WACZ8E,EAAM8U,WACAn2B,EAAO+a,MAAOpZ,EAAM,MAAOZ,QAChCsgB,EAAMpN,MAAMgH,WAOhB,KAAMqF,IAAQkJ,GAEb,GADAjkB,EAAQikB,EAAOlJ,GACVuU,GAAShpB,KAAMtG,GAAU,CAG7B,SAFOikB,GAAOlJ,GACdkD,EAASA,GAAoB,WAAVje,EACdA,KAAY0wB,EAAS,OAAS,QAAW,CAI7C,GAAe,SAAV1wB,IAAoB2wB,GAAiC/yB,SAArB+yB,EAAU5V,GAK9C,QAJA2V,IAAS,EAOXlK,EAAMzL,GAAS4V,GAAYA,EAAU5V,IAAUtgB,EAAOiiB,MAAOtgB,EAAM2e,GAMrE,GADAuV,GAAa71B,EAAOqE,cAAemlB,GAC7BqM,IAAa71B,EAAOqE,cAAe0nB,GAAzC,CAKKgK,GAA2B,IAAlBp0B,EAAKiJ,WAKlB+qB,EAAKS,UAAanU,EAAMmU,SAAUnU,EAAMoU,UAAWpU,EAAMqU,WAGzDR,EAAiBI,GAAYA,EAAShU,QACf,MAAlB4T,IACJA,EAAiBtV,EAASvf,IAAKU,EAAM,YAEtCugB,EAAUliB,EAAOmiB,IAAKxgB,EAAM,WACX,SAAZugB,IACC4T,EACJ5T,EAAU4T,GAIV1S,IAAYzhB,IAAQ,GACpBm0B,EAAiBn0B,EAAKsgB,MAAMC,SAAW4T,EACvC5T,EAAUliB,EAAOmiB,IAAKxgB,EAAM,WAC5ByhB,IAAYzhB,OAKG,WAAZugB,GAAoC,iBAAZA,GAAgD,MAAlB4T,IACrB,SAAhC91B,EAAOmiB,IAAKxgB,EAAM,WAGhBk0B,IACLG,EAAK7uB,KAAM,WACV8a,EAAMC,QAAU4T,IAEM,MAAlBA,IACJ5T,EAAUD,EAAMC,QAChB4T,EAA6B,SAAZ5T,EAAqB,GAAKA,IAG7CD,EAAMC,QAAU,iBAKdyT,EAAKS,WACTnU,EAAMmU,SAAW,SACjBJ,EAAKzZ,OAAQ,WACZ0F,EAAMmU,SAAWT,EAAKS,SAAU,GAChCnU,EAAMoU,UAAYV,EAAKS,SAAU,GACjCnU,EAAMqU,UAAYX,EAAKS,SAAU,MAKnCP,GAAY,CACZ,KAAMvV,IAAQyL,GAGP8J,IACAK,EACC,UAAYA,KAChBD,EAASC,EAASD,QAGnBC,EAAW1V,EAASf,OAAQ9d,EAAM,UAAYugB,QAAS4T,IAInDtS,IACJ0S,EAASD,QAAUA,GAIfA,GACJ7S,IAAYzhB,IAAQ,GAKrBq0B,EAAK7uB,KAAM,WAKJ8uB,GACL7S,IAAYzhB,IAEb6e,EAASpF,OAAQzZ,EAAM,SACvB,KAAM2e,IAAQyL,GACb/rB,EAAOiiB,MAAOtgB,EAAM2e,EAAMyL,EAAMzL,OAMnCuV,EAAYP,GAAaW,EAASC,EAAU5V,GAAS,EAAGA,EAAM0V,GACtD1V,IAAQ4V,KACfA,EAAU5V,GAASuV,EAAUxjB,MACxB4jB,IACJJ,EAAUzzB,IAAMyzB,EAAUxjB,MAC1BwjB,EAAUxjB,MAAQ,KAMtB,QAASkkB,IAAY/M,EAAOgN,GAC3B,GAAI9c,GAAOjX,EAAMkxB,EAAQpuB,EAAO8b,CAGhC,KAAM3H,IAAS8P,GAed,GAdA/mB,EAAOzC,EAAOuE,UAAWmV,GACzBia,EAAS6C,EAAe/zB,GACxB8C,EAAQikB,EAAO9P,GACV1Z,EAAOkD,QAASqC,KACpBouB,EAASpuB,EAAO,GAChBA,EAAQikB,EAAO9P,GAAUnU,EAAO,IAG5BmU,IAAUjX,IACd+mB,EAAO/mB,GAAS8C,QACTikB,GAAO9P,IAGf2H,EAAQrhB,EAAOgyB,SAAUvvB,GACpB4e,GAAS,UAAYA,GAAQ,CACjC9b,EAAQ8b,EAAMkS,OAAQhuB,SACfikB,GAAO/mB,EAId,KAAMiX,IAASnU,GACNmU,IAAS8P,KAChBA,EAAO9P,GAAUnU,EAAOmU,GACxB8c,EAAe9c,GAAUia,OAI3B6C,GAAe/zB,GAASkxB,EAK3B,QAAS6B,IAAW7zB,EAAM80B,EAAYj0B,GACrC,GAAIoP,GACH8kB,EACAhd,EAAQ,EACR3Y,EAASy0B,GAAUmB,WAAW51B,OAC9Byb,EAAWxc,EAAOmc,WAAWI,OAAQ,iBAG7B0Y,GAAKtzB,OAEbszB,EAAO,WACN,GAAKyB,EACJ,OAAO,CAYR,KAVA,GAAIE,GAAcjC,IAASO,KAC1B7W,EAAYhb,KAAKkuB,IAAK,EAAGgE,EAAUsB,UAAYtB,EAAUvB,SAAW4C,GAIpErgB,EAAO8H,EAAYkX,EAAUvB,UAAY,EACzCF,EAAU,EAAIvd,EACdmD,EAAQ,EACR3Y,EAASw0B,EAAUuB,OAAO/1B,OAEnB2Y,EAAQ3Y,EAAQ2Y,IACvB6b,EAAUuB,OAAQpd,GAAQma,IAAKC,EAKhC,OAFAtX,GAASkB,WAAY/b,GAAQ4zB,EAAWzB,EAASzV,IAE5CyV,EAAU,GAAK/yB,EACZsd,GAEP7B,EAASmB,YAAahc,GAAQ4zB,KACvB,IAGTA,EAAY/Y,EAASR,SACpBra,KAAMA,EACN6nB,MAAOxpB,EAAOuC,UAAYk0B,GAC1Bd,KAAM31B,EAAOuC,QAAQ,GACpBi0B,iBACA7C,OAAQ3zB,EAAO2zB,OAAOzP,UACpB1hB,GACHu0B,mBAAoBN,EACpBO,gBAAiBx0B,EACjBq0B,UAAWlC,IAASO,KACpBlB,SAAUxxB,EAAQwxB,SAClB8C,UACAxB,YAAa,SAAUhV,EAAMle,GAC5B,GAAIogB,GAAQxiB,EAAO0zB,MAAO/xB,EAAM4zB,EAAUI,KAAMrV,EAAMle,EACpDmzB,EAAUI,KAAKa,cAAelW,IAAUiV,EAAUI,KAAKhC,OAEzD,OADA4B,GAAUuB,OAAOn4B,KAAM6jB,GAChBA,GAERjB,KAAM,SAAU0V,GACf,GAAIvd,GAAQ,EAIX3Y,EAASk2B,EAAU1B,EAAUuB,OAAO/1B,OAAS,CAC9C,IAAK21B,EACJ,MAAOv4B,KAGR,KADAu4B,GAAU,EACFhd,EAAQ3Y,EAAQ2Y,IACvB6b,EAAUuB,OAAQpd,GAAQma,IAAK,EAUhC,OANKoD,IACJza,EAASkB,WAAY/b,GAAQ4zB,EAAW,EAAG,IAC3C/Y,EAASmB,YAAahc,GAAQ4zB,EAAW0B,KAEzCza,EAASuB,WAAYpc,GAAQ4zB,EAAW0B,IAElC94B,QAGTqrB,EAAQ+L,EAAU/L,KAInB,KAFA+M,GAAY/M,EAAO+L,EAAUI,KAAKa,eAE1B9c,EAAQ3Y,EAAQ2Y,IAEvB,GADA9H,EAAS4jB,GAAUmB,WAAYjd,GAAQva,KAAMo2B,EAAW5zB,EAAM6nB,EAAO+L,EAAUI,MAM9E,MAJK31B,GAAOgD,WAAY4O,EAAO2P,QAC9BvhB,EAAOshB,YAAaiU,EAAU5zB,KAAM4zB,EAAUI,KAAK5a,OAAQwG,KAC1DvhB,EAAOyF,MAAOmM,EAAO2P,KAAM3P,IAEtBA,CAmBT,OAfA5R,GAAO0B,IAAK8nB,EAAO8L,GAAaC,GAE3Bv1B,EAAOgD,WAAYuyB,EAAUI,KAAKtjB,QACtCkjB,EAAUI,KAAKtjB,MAAMlT,KAAMwC,EAAM4zB,GAGlCv1B,EAAOm0B,GAAG+C,MACTl3B,EAAOuC,OAAQ0yB,GACdtzB,KAAMA,EACNq0B,KAAMT,EACNxa,MAAOwa,EAAUI,KAAK5a,SAKjBwa,EAAUxY,SAAUwY,EAAUI,KAAK5Y,UACxC5V,KAAMouB,EAAUI,KAAKxuB,KAAMouB,EAAUI,KAAKwB,UAC1Clb,KAAMsZ,EAAUI,KAAK1Z,MACrBM,OAAQgZ,EAAUI,KAAKpZ,QAG1Bvc,EAAOw1B,UAAYx1B,EAAOuC,OAAQizB,IAEjCC,UACC2B,KAAO,SAAU9W,EAAM/a,GACtB,GAAIid,GAAQrkB,KAAKm3B,YAAahV,EAAM/a,EAEpC,OADA+c,IAAWE,EAAM7gB,KAAM2e,EAAMwB,GAAQvW,KAAMhG,GAASid,GAC7CA,KAIT6U,QAAS,SAAU7N,EAAO/nB,GACpBzB,EAAOgD,WAAYwmB,IACvB/nB,EAAW+nB,EACXA,GAAU,MAEVA,EAAQA,EAAMte,MAAOoP,EAOtB,KAJA,GAAIgG,GACH5G,EAAQ,EACR3Y,EAASyoB,EAAMzoB,OAER2Y,EAAQ3Y,EAAQ2Y,IACvB4G,EAAOkJ,EAAO9P,GACd8b,GAAUC,SAAUnV,GAASkV,GAAUC,SAAUnV,OACjDkV,GAAUC,SAAUnV,GAAOvQ,QAAStO,IAItCk1B,YAAcjB,IAEd4B,UAAW,SAAU71B,EAAUusB,GACzBA,EACJwH,GAAUmB,WAAW5mB,QAAStO,GAE9B+zB,GAAUmB,WAAWh4B,KAAM8C,MAK9BzB,EAAOu3B,MAAQ,SAAUA,EAAO5D,EAAQxzB,GACvC,GAAIq3B,GAAMD,GAA0B,gBAAVA,GAAqBv3B,EAAOuC,UAAYg1B,IACjEJ,SAAUh3B,IAAOA,GAAMwzB,GACtB3zB,EAAOgD,WAAYu0B,IAAWA,EAC/BvD,SAAUuD,EACV5D,OAAQxzB,GAAMwzB,GAAUA,IAAW3zB,EAAOgD,WAAY2wB,IAAYA,EAoCnE,OAhCK3zB,GAAOm0B,GAAG5N,KAAOxoB,EAASk4B,OAC9BuB,EAAIxD,SAAW,EAGc,gBAAjBwD,GAAIxD,WACVwD,EAAIxD,WAAYh0B,GAAOm0B,GAAGsD,OAC9BD,EAAIxD,SAAWh0B,EAAOm0B,GAAGsD,OAAQD,EAAIxD,UAGrCwD,EAAIxD,SAAWh0B,EAAOm0B,GAAGsD,OAAOvT,UAMjB,MAAbsT,EAAIzc,OAAiByc,EAAIzc,SAAU,IACvCyc,EAAIzc,MAAQ,MAIbyc,EAAInV,IAAMmV,EAAIL,SAEdK,EAAIL,SAAW,WACTn3B,EAAOgD,WAAYw0B,EAAInV,MAC3BmV,EAAInV,IAAIljB,KAAMhB,MAGVq5B,EAAIzc,OACR/a,EAAOmhB,QAAShjB,KAAMq5B,EAAIzc,QAIrByc,GAGRx3B,EAAOG,GAAGoC,QACTm1B,OAAQ,SAAUH,EAAOI,EAAIhE,EAAQlyB,GAGpC,MAAOtD,MAAKuQ,OAAQsT,IAAqBG,IAAK,UAAW,GAAIkB,OAG3DjhB,MAAMw1B,SAAW3F,QAAS0F,GAAMJ,EAAO5D,EAAQlyB,IAElDm2B,QAAS,SAAUtX,EAAMiX,EAAO5D,EAAQlyB,GACvC,GAAIwS,GAAQjU,EAAOqE,cAAeic,GACjCuX,EAAS73B,EAAOu3B,MAAOA,EAAO5D,EAAQlyB,GACtCq2B,EAAc,WAGb,GAAI9B,GAAOR,GAAWr3B,KAAM6B,EAAOuC,UAAY+d,GAAQuX,IAGlD5jB,GAASuM,EAASvf,IAAK9C,KAAM,YACjC63B,EAAKzU,MAAM,GAKd,OAFCuW,GAAYC,OAASD,EAEf7jB,GAAS4jB,EAAO9c,SAAU,EAChC5c,KAAKqD,KAAMs2B,GACX35B,KAAK4c,MAAO8c,EAAO9c,MAAO+c,IAE5BvW,KAAM,SAAU1d,EAAM4d,EAAYwV,GACjC,GAAIe,GAAY,SAAU3W,GACzB,GAAIE,GAAOF,EAAME,WACVF,GAAME,KACbA,EAAM0V,GAYP,OATqB,gBAATpzB,KACXozB,EAAUxV,EACVA,EAAa5d,EACbA,EAAOV,QAEHse,GAAc5d,KAAS,GAC3B1F,KAAK4c,MAAOlX,GAAQ,SAGd1F,KAAKqD,KAAM,WACjB,GAAI2f,IAAU,EACbzH,EAAgB,MAAR7V,GAAgBA,EAAO,aAC/Bo0B,EAASj4B,EAAOi4B,OAChB5X,EAAOG,EAASvf,IAAK9C,KAEtB,IAAKub,EACC2G,EAAM3G,IAAW2G,EAAM3G,GAAQ6H,MACnCyW,EAAW3X,EAAM3G,QAGlB,KAAMA,IAAS2G,GACTA,EAAM3G,IAAW2G,EAAM3G,GAAQ6H,MAAQuT,GAAKjpB,KAAM6N,IACtDse,EAAW3X,EAAM3G,GAKpB,KAAMA,EAAQue,EAAOl3B,OAAQ2Y,KACvBue,EAAQve,GAAQ/X,OAASxD,MACnB,MAAR0F,GAAgBo0B,EAAQve,GAAQqB,QAAUlX,IAE5Co0B,EAAQve,GAAQsc,KAAKzU,KAAM0V,GAC3B9V,GAAU,EACV8W,EAAO31B,OAAQoX,EAAO,KAOnByH,GAAY8V,GAChBj3B,EAAOmhB,QAAShjB,KAAM0F,MAIzBk0B,OAAQ,SAAUl0B,GAIjB,MAHKA,MAAS,IACbA,EAAOA,GAAQ,MAET1F,KAAKqD,KAAM,WACjB,GAAIkY,GACH2G,EAAOG,EAASvf,IAAK9C,MACrB4c,EAAQsF,EAAMxc,EAAO,SACrBwd,EAAQhB,EAAMxc,EAAO,cACrBo0B,EAASj4B,EAAOi4B,OAChBl3B,EAASga,EAAQA,EAAMha,OAAS,CAajC,KAVAsf,EAAK0X,QAAS,EAGd/3B,EAAO+a,MAAO5c,KAAM0F,MAEfwd,GAASA,EAAME,MACnBF,EAAME,KAAKpiB,KAAMhB,MAAM,GAIlBub,EAAQue,EAAOl3B,OAAQ2Y,KACvBue,EAAQve,GAAQ/X,OAASxD,MAAQ85B,EAAQve,GAAQqB,QAAUlX,IAC/Do0B,EAAQve,GAAQsc,KAAKzU,MAAM,GAC3B0W,EAAO31B,OAAQoX,EAAO,GAKxB,KAAMA,EAAQ,EAAGA,EAAQ3Y,EAAQ2Y,IAC3BqB,EAAOrB,IAAWqB,EAAOrB,GAAQqe,QACrChd,EAAOrB,GAAQqe,OAAO54B,KAAMhB,YAKvBkiB,GAAK0X,YAKf/3B,EAAOwB,MAAQ,SAAU,OAAQ,QAAU,SAAUI,EAAGa,GACvD,GAAIy1B,GAAQl4B,EAAOG,GAAIsC,EACvBzC,GAAOG,GAAIsC,GAAS,SAAU80B,EAAO5D,EAAQlyB,GAC5C,MAAgB,OAAT81B,GAAkC,iBAAVA,GAC9BW,EAAMr2B,MAAO1D,KAAM2D,WACnB3D,KAAKy5B,QAASzC,GAAO1yB,GAAM,GAAQ80B,EAAO5D,EAAQlyB,MAKrDzB,EAAOwB,MACN22B,UAAWhD,GAAO,QAClBiD,QAASjD,GAAO,QAChBkD,YAAalD,GAAO,UACpBmD,QAAUrG,QAAS,QACnBsG,SAAWtG,QAAS,QACpBuG,YAAcvG,QAAS,WACrB,SAAUxvB,EAAM+mB,GAClBxpB,EAAOG,GAAIsC,GAAS,SAAU80B,EAAO5D,EAAQlyB,GAC5C,MAAOtD,MAAKy5B,QAASpO,EAAO+N,EAAO5D,EAAQlyB,MAI7CzB,EAAOi4B,UACPj4B,EAAOm0B,GAAGc,KAAO,WAChB,GAAIiC,GACHt1B,EAAI,EACJq2B,EAASj4B,EAAOi4B,MAIjB,KAFAtD,GAAQ30B,EAAO4F,MAEPhE,EAAIq2B,EAAOl3B,OAAQa,IAC1Bs1B,EAAQe,EAAQr2B,GAGVs1B,KAAWe,EAAQr2B,KAAQs1B,GAChCe,EAAO31B,OAAQV,IAAK,EAIhBq2B,GAAOl3B,QACZf,EAAOm0B,GAAG5S,OAEXoT,GAAQxxB,QAGTnD,EAAOm0B,GAAG+C,MAAQ,SAAUA,GAC3Bl3B,EAAOi4B,OAAOt5B,KAAMu4B,GACfA,IACJl3B,EAAOm0B,GAAG9hB,QAEVrS,EAAOi4B,OAAOtwB,OAIhB3H,EAAOm0B,GAAGsE,SAAW,GACrBz4B,EAAOm0B,GAAG9hB,MAAQ,WACXuiB,KACLA,GAAU12B,EAAO82B,sBAChB92B,EAAO82B,sBAAuBD,IAC9B72B,EAAOw6B,YAAa14B,EAAOm0B,GAAGc,KAAMj1B,EAAOm0B,GAAGsE,YAIjDz4B,EAAOm0B,GAAG5S,KAAO,WACXrjB,EAAOy6B,qBACXz6B,EAAOy6B,qBAAsB/D,IAE7B12B,EAAO06B,cAAehE,IAGvBA,GAAU,MAGX50B,EAAOm0B,GAAGsD,QACToB,KAAM,IACNC,KAAM,IAGN5U,SAAU,KAMXlkB,EAAOG,GAAG44B,MAAQ,SAAUC,EAAMn1B,GAIjC,MAHAm1B,GAAOh5B,EAAOm0B,GAAKn0B,EAAOm0B,GAAGsD,OAAQuB,IAAUA,EAAOA,EACtDn1B,EAAOA,GAAQ,KAER1F,KAAK4c,MAAOlX,EAAM,SAAU6G,EAAM2W,GACxC,GAAI4X,GAAU/6B,EAAO+f,WAAYvT,EAAMsuB,EACvC3X,GAAME,KAAO,WACZrjB,EAAOg7B,aAAcD,OAMxB,WACC,GAAIjqB,GAAQjR,EAAS0B,cAAe,SACnC8G,EAASxI,EAAS0B,cAAe,UACjC+3B,EAAMjxB,EAAO3G,YAAa7B,EAAS0B,cAAe,UAEnDuP,GAAMnL,KAAO,WAIbzE,EAAQ+5B,QAA0B,KAAhBnqB,EAAMzJ,MAIxBnG,EAAQg6B,YAAc5B,EAAIzjB,SAI1B/E,EAAQjR,EAAS0B,cAAe,SAChCuP,EAAMzJ,MAAQ,IACdyJ,EAAMnL,KAAO,QACbzE,EAAQi6B,WAA6B,MAAhBrqB,EAAMzJ,QAI5B,IAAI+zB,IACHrsB,GAAajN,EAAOgQ,KAAK/C,UAE1BjN,GAAOG,GAAGoC,QACT2N,KAAM,SAAUzN,EAAM8C,GACrB,MAAOka,GAAQthB,KAAM6B,EAAOkQ,KAAMzN,EAAM8C,EAAOzD,UAAUf,OAAS,IAGnEw4B,WAAY,SAAU92B,GACrB,MAAOtE,MAAKqD,KAAM,WACjBxB,EAAOu5B,WAAYp7B,KAAMsE,QAK5BzC,EAAOuC,QACN2N,KAAM,SAAUvO,EAAMc,EAAM8C,GAC3B,GAAIlE,GAAKggB,EACRmY,EAAQ73B,EAAKiJ,QAGd,IAAe,IAAV4uB,GAAyB,IAAVA,GAAyB,IAAVA,EAKnC,MAAkC,mBAAtB73B,GAAKmK,aACT9L,EAAOsgB,KAAM3e,EAAMc,EAAM8C,IAKlB,IAAVi0B,GAAgBx5B,EAAOkY,SAAUvW,KACrC0f,EAAQrhB,EAAOy5B,UAAWh3B,EAAKiC,iBAC5B1E,EAAOgQ,KAAK9E,MAAMjC,KAAK4C,KAAMpJ,GAAS62B,GAAWn2B;AAGtCA,SAAVoC,EACW,OAAVA,MACJvF,GAAOu5B,WAAY53B,EAAMc,GAIrB4e,GAAS,OAASA,IACuBle,UAA3C9B,EAAMggB,EAAMjB,IAAKze,EAAM4D,EAAO9C,IACzBpB,GAGRM,EAAKoK,aAActJ,EAAM8C,EAAQ,IAC1BA,GAGH8b,GAAS,OAASA,IAA+C,QAApChgB,EAAMggB,EAAMpgB,IAAKU,EAAMc,IACjDpB,GAGRA,EAAMrB,EAAO4O,KAAKsB,KAAMvO,EAAMc,GAGhB,MAAPpB,EAAc8B,OAAY9B,KAGlCo4B,WACC51B,MACCuc,IAAK,SAAUze,EAAM4D,GACpB,IAAMnG,EAAQi6B,YAAwB,UAAV9zB,GAC3BvF,EAAOyE,SAAU9C,EAAM,SAAY,CACnC,GAAIwO,GAAMxO,EAAK4D,KAKf,OAJA5D,GAAKoK,aAAc,OAAQxG,GACtB4K,IACJxO,EAAK4D,MAAQ4K,GAEP5K,MAMXg0B,WAAY,SAAU53B,EAAM4D,GAC3B,GAAI9C,GACHb,EAAI,EAIJ83B,EAAYn0B,GAASA,EAAM2F,MAAOoP,EAEnC,IAAKof,GAA+B,IAAlB/3B,EAAKiJ,SACtB,MAAUnI,EAAOi3B,EAAW93B,KAC3BD,EAAK0K,gBAAiB5J,MAO1B62B,IACClZ,IAAK,SAAUze,EAAM4D,EAAO9C,GAQ3B,MAPK8C,MAAU,EAGdvF,EAAOu5B,WAAY53B,EAAMc,GAEzBd,EAAKoK,aAActJ,EAAMA,GAEnBA,IAITzC,EAAOwB,KAAMxB,EAAOgQ,KAAK9E,MAAMjC,KAAK4Y,OAAO3W,MAAO,QAAU,SAAUtJ,EAAGa,GACxE,GAAIk3B,GAAS1sB,GAAYxK,IAAUzC,EAAO4O,KAAKsB,IAE/CjD,IAAYxK,GAAS,SAAUd,EAAMc,EAAM2D,GAC1C,GAAI/E,GAAK4lB,EACR2S,EAAgBn3B,EAAKiC,aAYtB,OAVM0B,KAGL6gB,EAASha,GAAY2sB,GACrB3sB,GAAY2sB,GAAkBv4B,EAC9BA,EAAqC,MAA/Bs4B,EAAQh4B,EAAMc,EAAM2D,GACzBwzB,EACA,KACD3sB,GAAY2sB,GAAkB3S,GAExB5lB,IAOT,IAAIw4B,IAAa,sCAChBC,GAAa,eAEd95B,GAAOG,GAAGoC,QACT+d,KAAM,SAAU7d,EAAM8C,GACrB,MAAOka,GAAQthB,KAAM6B,EAAOsgB,KAAM7d,EAAM8C,EAAOzD,UAAUf,OAAS,IAGnEg5B,WAAY,SAAUt3B,GACrB,MAAOtE,MAAKqD,KAAM,iBACVrD,MAAM6B,EAAOg6B,QAASv3B,IAAUA,QAK1CzC,EAAOuC,QACN+d,KAAM,SAAU3e,EAAMc,EAAM8C,GAC3B,GAAIlE,GAAKggB,EACRmY,EAAQ73B,EAAKiJ,QAGd,IAAe,IAAV4uB,GAAyB,IAAVA,GAAyB,IAAVA,EAWnC,MAPe,KAAVA,GAAgBx5B,EAAOkY,SAAUvW,KAGrCc,EAAOzC,EAAOg6B,QAASv3B,IAAUA,EACjC4e,EAAQrhB,EAAO4zB,UAAWnxB,IAGZU,SAAVoC,EACC8b,GAAS,OAASA,IACuBle,UAA3C9B,EAAMggB,EAAMjB,IAAKze,EAAM4D,EAAO9C,IACzBpB,EAGCM,EAAMc,GAAS8C,EAGpB8b,GAAS,OAASA,IAA+C,QAApChgB,EAAMggB,EAAMpgB,IAAKU,EAAMc,IACjDpB,EAGDM,EAAMc,IAGdmxB,WACChgB,UACC3S,IAAK,SAAUU,GAOd,GAAIs4B,GAAWj6B,EAAO4O,KAAKsB,KAAMvO,EAAM,WAEvC,OAAKs4B,GACGC,SAAUD,EAAU,IAI3BJ,GAAWhuB,KAAMlK,EAAK8C,WACtBq1B,GAAWjuB,KAAMlK,EAAK8C,WACtB9C,EAAKgS,KAEE,QAQXqmB,SACCG,MAAO,UACPC,QAAS,eAYLh7B,EAAQg6B,cACbp5B,EAAO4zB,UAAU7f,UAChB9S,IAAK,SAAUU,GAId,GAAI2Q,GAAS3Q,EAAK9B,UAIlB,OAHKyS,IAAUA,EAAOzS,YACrByS,EAAOzS,WAAWmU,cAEZ,MAERoM,IAAK,SAAUze,GAId,GAAI2Q,GAAS3Q,EAAK9B,UACbyS,KACJA,EAAO0B,cAEF1B,EAAOzS,YACXyS,EAAOzS,WAAWmU,kBAOvBhU,EAAOwB,MACN,WACA,WACA,YACA,cACA,cACA,UACA,UACA,SACA,cACA,mBACE,WACFxB,EAAOg6B,QAAS77B,KAAKuG,eAAkBvG,MAQvC,SAASk8B,IAAkB90B,GAC1B,GAAI0P,GAAS1P,EAAM2F,MAAOoP,MAC1B,OAAOrF,GAAOhJ,KAAM,KAItB,QAASquB,IAAU34B,GAClB,MAAOA,GAAKmK,cAAgBnK,EAAKmK,aAAc,UAAa,GAG7D9L,EAAOG,GAAGoC,QACTg4B,SAAU,SAAUh1B,GACnB,GAAIi1B,GAAS74B,EAAMwL,EAAKstB,EAAUC,EAAOv4B,EAAGw4B,EAC3C/4B,EAAI,CAEL,IAAK5B,EAAOgD,WAAYuC,GACvB,MAAOpH,MAAKqD,KAAM,SAAUW,GAC3BnC,EAAQ7B,MAAOo8B,SAAUh1B,EAAMpG,KAAMhB,KAAMgE,EAAGm4B,GAAUn8B,SAI1D,IAAsB,gBAAVoH,IAAsBA,EAAQ,CACzCi1B,EAAUj1B,EAAM2F,MAAOoP,MAEvB,OAAU3Y,EAAOxD,KAAMyD,KAItB,GAHA64B,EAAWH,GAAU34B,GACrBwL,EAAwB,IAAlBxL,EAAKiJ,UAAoB,IAAMyvB,GAAkBI,GAAa,IAEzD,CACVt4B,EAAI,CACJ,OAAUu4B,EAAQF,EAASr4B,KACrBgL,EAAIvO,QAAS,IAAM87B,EAAQ,KAAQ,IACvCvtB,GAAOutB,EAAQ,IAKjBC,GAAaN,GAAkBltB,GAC1BstB,IAAaE,GACjBh5B,EAAKoK,aAAc,QAAS4uB,IAMhC,MAAOx8B,OAGRy8B,YAAa,SAAUr1B,GACtB,GAAIi1B,GAAS74B,EAAMwL,EAAKstB,EAAUC,EAAOv4B,EAAGw4B,EAC3C/4B,EAAI,CAEL,IAAK5B,EAAOgD,WAAYuC,GACvB,MAAOpH,MAAKqD,KAAM,SAAUW,GAC3BnC,EAAQ7B,MAAOy8B,YAAar1B,EAAMpG,KAAMhB,KAAMgE,EAAGm4B,GAAUn8B,SAI7D,KAAM2D,UAAUf,OACf,MAAO5C,MAAK+R,KAAM,QAAS,GAG5B,IAAsB,gBAAV3K,IAAsBA,EAAQ,CACzCi1B,EAAUj1B,EAAM2F,MAAOoP,MAEvB,OAAU3Y,EAAOxD,KAAMyD,KAMtB,GALA64B,EAAWH,GAAU34B,GAGrBwL,EAAwB,IAAlBxL,EAAKiJ,UAAoB,IAAMyvB,GAAkBI,GAAa,IAEzD,CACVt4B,EAAI,CACJ,OAAUu4B,EAAQF,EAASr4B,KAG1B,MAAQgL,EAAIvO,QAAS,IAAM87B,EAAQ,QAClCvtB,EAAMA,EAAI5J,QAAS,IAAMm3B,EAAQ,IAAK,IAKxCC,GAAaN,GAAkBltB,GAC1BstB,IAAaE,GACjBh5B,EAAKoK,aAAc,QAAS4uB,IAMhC,MAAOx8B,OAGR08B,YAAa,SAAUt1B,EAAOu1B,GAC7B,GAAIj3B,SAAc0B,EAElB,OAAyB,iBAAbu1B,IAAmC,WAATj3B,EAC9Bi3B,EAAW38B,KAAKo8B,SAAUh1B,GAAUpH,KAAKy8B,YAAar1B,GAGzDvF,EAAOgD,WAAYuC,GAChBpH,KAAKqD,KAAM,SAAUI,GAC3B5B,EAAQ7B,MAAO08B,YACdt1B,EAAMpG,KAAMhB,KAAMyD,EAAG04B,GAAUn8B,MAAQ28B,GACvCA,KAKI38B,KAAKqD,KAAM,WACjB,GAAI8M,GAAW1M,EAAGkX,EAAMiiB,CAExB,IAAc,WAATl3B,EAAoB,CAGxBjC,EAAI,EACJkX,EAAO9Y,EAAQ7B,MACf48B,EAAax1B,EAAM2F,MAAOoP,MAE1B,OAAUhM,EAAYysB,EAAYn5B,KAG5BkX,EAAKkiB,SAAU1sB,GACnBwK,EAAK8hB,YAAatsB,GAElBwK,EAAKyhB,SAAUjsB,OAKInL,UAAVoC,GAAgC,YAAT1B,IAClCyK,EAAYgsB,GAAUn8B,MACjBmQ,GAGJkS,EAASJ,IAAKjiB,KAAM,gBAAiBmQ,GAOjCnQ,KAAK4N,cACT5N,KAAK4N,aAAc,QAClBuC,GAAa/I,KAAU,EACvB,GACAib,EAASvf,IAAK9C,KAAM,kBAAqB,QAO9C68B,SAAU,SAAU/6B,GACnB,GAAIqO,GAAW3M,EACdC,EAAI,CAEL0M,GAAY,IAAMrO,EAAW,GAC7B,OAAU0B,EAAOxD,KAAMyD,KACtB,GAAuB,IAAlBD,EAAKiJ,WACP,IAAMyvB,GAAkBC,GAAU34B,IAAW,KAAM/C,QAAS0P,MAC7D,OAAO,CAIV,QAAO,IAOT,IAAI2sB,IAAU,KAEdj7B,GAAOG,GAAGoC,QACT4N,IAAK,SAAU5K,GACd,GAAI8b,GAAOhgB,EAAK2B,EACfrB,EAAOxD,KAAM,EAEd,EAAA,GAAM2D,UAAUf,OA4BhB,MAFAiC,GAAahD,EAAOgD,WAAYuC,GAEzBpH,KAAKqD,KAAM,SAAUI,GAC3B,GAAIuO,EAEmB,KAAlBhS,KAAKyM,WAKTuF,EADInN,EACEuC,EAAMpG,KAAMhB,KAAMyD,EAAG5B,EAAQ7B,MAAOgS,OAEpC5K,EAIK,MAAP4K,EACJA,EAAM,GAEoB,gBAARA,GAClBA,GAAO,GAEInQ,EAAOkD,QAASiN,KAC3BA,EAAMnQ,EAAO0B,IAAKyO,EAAK,SAAU5K,GAChC,MAAgB,OAATA,EAAgB,GAAKA,EAAQ,MAItC8b,EAAQrhB,EAAOk7B,SAAU/8B,KAAK0F,OAAU7D,EAAOk7B,SAAU/8B,KAAKsG,SAASC,eAGjE2c,GAAY,OAASA,IAA+Cle,SAApCke,EAAMjB,IAAKjiB,KAAMgS,EAAK,WAC3DhS,KAAKoH,MAAQ4K,KAzDd,IAAKxO,EAIJ,MAHA0f,GAAQrhB,EAAOk7B,SAAUv5B,EAAKkC,OAC7B7D,EAAOk7B,SAAUv5B,EAAK8C,SAASC,eAE3B2c,GACJ,OAASA,IACgCle,UAAvC9B,EAAMggB,EAAMpgB,IAAKU,EAAM,UAElBN,GAGRA,EAAMM,EAAK4D,MAGS,gBAARlE,GACJA,EAAIkC,QAAS03B,GAAS,IAIhB,MAAP55B,EAAc,GAAKA,OA4C9BrB,EAAOuC,QACN24B,UACCrX,QACC5iB,IAAK,SAAUU,GAEd,GAAIwO,GAAMnQ,EAAO4O,KAAKsB,KAAMvO,EAAM,QAClC,OAAc,OAAPwO,EACNA,EAMAkqB,GAAkBr6B,EAAON,KAAMiC,MAGlC4E,QACCtF,IAAK,SAAUU,GACd,GAAI4D,GAAOse,EAAQjiB,EAClBY,EAAUb,EAAKa,QACfkX,EAAQ/X,EAAKqS,cACboS,EAAoB,eAAdzkB,EAAKkC,KACXyf,EAAS8C,EAAM,QACfmL,EAAMnL,EAAM1M,EAAQ,EAAIlX,EAAQzB,MAUjC,KAPCa,EADI8X,EAAQ,EACR6X,EAGAnL,EAAM1M,EAAQ,EAIX9X,EAAI2vB,EAAK3vB,IAKhB,GAJAiiB,EAASrhB,EAASZ,IAIXiiB,EAAO9P,UAAYnS,IAAM8X,KAG7BmK,EAAOrZ,YACLqZ,EAAOhkB,WAAW2K,WACnBxK,EAAOyE,SAAUof,EAAOhkB,WAAY,aAAiB,CAMxD,GAHA0F,EAAQvF,EAAQ6jB,GAAS1T,MAGpBiW,EACJ,MAAO7gB,EAIR+d,GAAO3kB,KAAM4G,GAIf,MAAO+d,IAGRlD,IAAK,SAAUze,EAAM4D,GACpB,GAAI41B,GAAWtX,EACdrhB,EAAUb,EAAKa,QACf8gB,EAAStjB,EAAO6E,UAAWU,GAC3B3D,EAAIY,EAAQzB,MAEb,OAAQa,IACPiiB,EAASrhB,EAASZ,IAIbiiB,EAAO9P,SACX/T,EAAO+E,QAAS/E,EAAOk7B,SAASrX,OAAO5iB,IAAK4iB,GAAUP,SAEtD6X,GAAY,EAUd,OAHMA,KACLx5B,EAAKqS,kBAECsP,OAOXtjB,EAAOwB,MAAQ,QAAS,YAAc,WACrCxB,EAAOk7B,SAAU/8B,OAChBiiB,IAAK,SAAUze,EAAM4D,GACpB,GAAKvF,EAAOkD,QAASqC,GACpB,MAAS5D,GAAKmS,QAAU9T,EAAO+E,QAAS/E,EAAQ2B,GAAOwO,MAAO5K,QAI3DnG,EAAQ+5B,UACbn5B,EAAOk7B,SAAU/8B,MAAO8C,IAAM,SAAUU,GACvC,MAAwC,QAAjCA,EAAKmK,aAAc,SAAqB,KAAOnK,EAAK4D,SAW9D,IAAI61B,IAAc,iCAElBp7B,GAAOuC,OAAQvC,EAAOsmB,OAErB6C,QAAS,SAAU7C,EAAOjG,EAAM1e,EAAM05B,GAErC,GAAIz5B,GAAGuL,EAAKzH,EAAK41B,EAAYC,EAAQtU,EAAQ3J,EAC5Cke,GAAc75B,GAAQ5D,GACtB8F,EAAO9E,EAAOI,KAAMmnB,EAAO,QAAWA,EAAMziB,KAAOyiB,EACnDQ,EAAa/nB,EAAOI,KAAMmnB,EAAO,aAAgBA,EAAMgB,UAAUthB,MAAO,OAKzE,IAHAmH,EAAMzH,EAAM/D,EAAOA,GAAQ5D,EAGJ,IAAlB4D,EAAKiJ,UAAoC,IAAlBjJ,EAAKiJ,WAK5BwwB,GAAYvvB,KAAMhI,EAAO7D,EAAOsmB,MAAMY,aAItCrjB,EAAKjF,QAAS,UAGlBkoB,EAAajjB,EAAKmC,MAAO,KACzBnC,EAAOijB,EAAWpa,QAClBoa,EAAWzkB,QAEZk5B,EAAS13B,EAAKjF,QAAS,KAAQ,GAAK,KAAOiF,EAG3CyiB,EAAQA,EAAOtmB,EAAOoD,SACrBkjB,EACA,GAAItmB,GAAO6oB,MAAOhlB,EAAuB,gBAAVyiB,IAAsBA,GAGtDA,EAAMmV,UAAYJ,EAAe,EAAI,EACrC/U,EAAMgB,UAAYR,EAAW7a,KAAM,KACnCqa,EAAM+B,WAAa/B,EAAMgB,UACxB,GAAIlf,QAAQ,UAAY0e,EAAW7a,KAAM,iBAAoB,WAC7D,KAGDqa,EAAM1U,OAASzO,OACTmjB,EAAMxjB,SACXwjB,EAAMxjB,OAASnB,GAIhB0e,EAAe,MAARA,GACJiG,GACFtmB,EAAO6E,UAAWwb,GAAQiG,IAG3BhJ,EAAUtd,EAAOsmB,MAAMhJ,QAASzZ,OAC1Bw3B,IAAgB/d,EAAQ6L,SAAW7L,EAAQ6L,QAAQtnB,MAAOF,EAAM0e,MAAW,GAAjF,CAMA,IAAMgb,IAAiB/d,EAAQ4L,WAAalpB,EAAO+D,SAAUpC,GAAS,CAMrE,IAJA25B,EAAahe,EAAQ8J,cAAgBvjB,EAC/Bu3B,GAAYvvB,KAAMyvB,EAAaz3B,KACpCsJ,EAAMA,EAAItN,YAEHsN,EAAKA,EAAMA,EAAItN,WACtB27B,EAAU78B,KAAMwO,GAChBzH,EAAMyH,CAIFzH,MAAU/D,EAAK2J,eAAiBvN,IACpCy9B,EAAU78B,KAAM+G,EAAIwI,aAAexI,EAAIg2B,cAAgBx9B,GAKzD0D,EAAI,CACJ,QAAUuL,EAAMquB,EAAW55B,QAAY0kB,EAAM4B,uBAE5C5B,EAAMziB,KAAOjC,EAAI,EAChB05B,EACAhe,EAAQ+J,UAAYxjB,EAGrBojB,GAAWzG,EAASvf,IAAKkM,EAAK,eAAoBmZ,EAAMziB,OACvD2c,EAASvf,IAAKkM,EAAK,UACf8Z,GACJA,EAAOplB,MAAOsL,EAAKkT,GAIpB4G,EAASsU,GAAUpuB,EAAKouB,GACnBtU,GAAUA,EAAOplB,OAASie,EAAY3S,KAC1CmZ,EAAM1U,OAASqV,EAAOplB,MAAOsL,EAAKkT,GAC7BiG,EAAM1U,UAAW,GACrB0U,EAAMgC,iBAoCT,OAhCAhC,GAAMziB,KAAOA,EAGPw3B,GAAiB/U,EAAMmD,sBAEpBnM,EAAQ4G,UACf5G,EAAQ4G,SAASriB,MAAO25B,EAAU7zB,MAAO0Y,MAAW,IACpDP,EAAYne,IAIP45B,GAAUv7B,EAAOgD,WAAYrB,EAAMkC,MAAa7D,EAAO+D,SAAUpC,KAGrE+D,EAAM/D,EAAM45B,GAEP71B,IACJ/D,EAAM45B,GAAW,MAIlBv7B,EAAOsmB,MAAMY,UAAYrjB,EACzBlC,EAAMkC,KACN7D,EAAOsmB,MAAMY,UAAY/jB,OAEpBuC,IACJ/D,EAAM45B,GAAW71B,IAMd4gB,EAAM1U,SAKd+pB,SAAU,SAAU93B,EAAMlC,EAAM2kB,GAC/B,GAAIzb,GAAI7K,EAAOuC,OACd,GAAIvC,GAAO6oB,MACXvC,GAECziB,KAAMA,EACNgmB,aAAa,GAIf7pB,GAAOsmB,MAAM6C,QAASte,EAAG,KAAMlJ,MAKjC3B,EAAOG,GAAGoC,QAET4mB,QAAS,SAAUtlB,EAAMwc,GACxB,MAAOliB,MAAKqD,KAAM,WACjBxB,EAAOsmB,MAAM6C,QAAStlB,EAAMwc,EAAMliB,SAGpCy9B,eAAgB,SAAU/3B,EAAMwc,GAC/B,GAAI1e,GAAOxD,KAAM,EACjB,IAAKwD,EACJ,MAAO3B,GAAOsmB,MAAM6C,QAAStlB,EAAMwc,EAAM1e,GAAM,MAMlD3B,EAAOwB,KAAM,wLAEgDwE,MAAO,KACnE,SAAUpE,EAAGa,GAGbzC,EAAOG,GAAIsC,GAAS,SAAU4d,EAAMlgB,GACnC,MAAO2B,WAAUf,OAAS,EACzB5C,KAAK+nB,GAAIzjB,EAAM,KAAM4d,EAAMlgB,GAC3BhC,KAAKgrB,QAAS1mB,MAIjBzC,EAAOG,GAAGoC,QACTs5B,MAAO,SAAUC,EAAQC,GACxB,MAAO59B,MAAKwtB,WAAYmQ,GAASlQ,WAAYmQ,GAASD,MAOxD18B,EAAQ48B,QAAU,aAAe99B,GAW3BkB,EAAQ48B,SACbh8B,EAAOwB,MAAQgS,MAAO,UAAW4V,KAAM,YAAc,SAAU2C,EAAMjE,GAGpE,GAAI9a,GAAU,SAAUsZ,GACvBtmB,EAAOsmB,MAAMqV,SAAU7T,EAAKxB,EAAMxjB,OAAQ9C,EAAOsmB,MAAMwB,IAAKxB,IAG7DtmB,GAAOsmB,MAAMhJ,QAASwK,IACrBN,MAAO,WACN,GAAIjoB,GAAMpB,KAAKmN,eAAiBnN,KAC/B89B,EAAWzb,EAASf,OAAQlgB,EAAKuoB,EAE5BmU,IACL18B,EAAI6O,iBAAkB2d,EAAM/e,GAAS,GAEtCwT,EAASf,OAAQlgB,EAAKuoB,GAAOmU,GAAY,GAAM,IAEhDtU,SAAU,WACT,GAAIpoB,GAAMpB,KAAKmN,eAAiBnN,KAC/B89B,EAAWzb,EAASf,OAAQlgB,EAAKuoB,GAAQ,CAEpCmU,GAKLzb,EAASf,OAAQlgB,EAAKuoB,EAAKmU,IAJ3B18B,EAAI+f,oBAAqByM,EAAM/e,GAAS,GACxCwT,EAASpF,OAAQ7b,EAAKuoB,OAS3B,IAAIxU,IAAWpV,EAAOoV,SAElB4oB,GAAQl8B,EAAO4F,MAEfu2B,GAAS,IAKbn8B,GAAOo8B,SAAW,SAAU/b,GAC3B,GAAIpO,EACJ,KAAMoO,GAAwB,gBAATA,GACpB,MAAO,KAKR,KACCpO,GAAM,GAAM/T,GAAOm+B,WAAcC,gBAAiBjc,EAAM,YACvD,MAAQxV,GACToH,EAAM9O,OAMP,MAHM8O,KAAOA,EAAIvG,qBAAsB,eAAgB3K,QACtDf,EAAOyD,MAAO,gBAAkB4c,GAE1BpO,EAIR,IACCsqB,IAAW,QACXC,GAAQ,SACRC,GAAkB,wCAClBC,GAAe,oCAEhB,SAASC,IAAatJ,EAAQzvB,EAAKg5B,EAAahjB,GAC/C,GAAInX,EAEJ,IAAKzC,EAAOkD,QAASU,GAGpB5D,EAAOwB,KAAMoC,EAAK,SAAUhC,EAAG6Z,GACzBmhB,GAAeL,GAAS1wB,KAAMwnB,GAGlCzZ,EAAKyZ,EAAQ5X,GAKbkhB,GACCtJ,EAAS,KAAqB,gBAAN5X,IAAuB,MAALA,EAAY7Z,EAAI,IAAO,IACjE6Z,EACAmhB,EACAhjB,SAKG,IAAMgjB,GAAsC,WAAvB58B,EAAO6D,KAAMD,GAUxCgW,EAAKyZ,EAAQzvB,OAPb,KAAMnB,IAAQmB,GACb+4B,GAAatJ,EAAS,IAAM5wB,EAAO,IAAKmB,EAAKnB,GAAQm6B,EAAahjB,GAYrE5Z,EAAO68B,MAAQ,SAAUp1B,EAAGm1B,GAC3B,GAAIvJ,GACHyJ,KACAljB,EAAM,SAAUpN,EAAKuwB,GAGpB,GAAIx3B,GAAQvF,EAAOgD,WAAY+5B,GAC9BA,IACAA,CAEDD,GAAGA,EAAE/7B,QAAWi8B,mBAAoBxwB,GAAQ,IAC3CwwB,mBAA6B,MAATz3B,EAAgB,GAAKA,GAI5C,IAAKvF,EAAOkD,QAASuE,IAASA,EAAE5G,SAAWb,EAAOiD,cAAewE,GAGhEzH,EAAOwB,KAAMiG,EAAG,WACfmS,EAAKzb,KAAKsE,KAAMtE,KAAKoH,aAOtB,KAAM8tB,IAAU5rB,GACfk1B,GAAatJ,EAAQ5rB,EAAG4rB,GAAUuJ,EAAahjB,EAKjD,OAAOkjB,GAAE7wB,KAAM,MAGhBjM,EAAOG,GAAGoC,QACT06B,UAAW,WACV,MAAOj9B,GAAO68B,MAAO1+B,KAAK++B,mBAE3BA,eAAgB,WACf,MAAO/+B,MAAKuD,IAAK,WAGhB,GAAIuO,GAAWjQ,EAAOsgB,KAAMniB,KAAM,WAClC,OAAO8R,GAAWjQ,EAAO6E,UAAWoL,GAAa9R,OAEjDuQ,OAAQ,WACR,GAAI7K,GAAO1F,KAAK0F,IAGhB,OAAO1F,MAAKsE,OAASzC,EAAQ7B,MAAOma,GAAI,cACvCokB,GAAa7wB,KAAM1N,KAAKsG,YAAeg4B,GAAgB5wB,KAAMhI,KAC3D1F,KAAK2V,UAAY2P,GAAe5X,KAAMhI,MAEzCnC,IAAK,SAAUE,EAAGD,GAClB,GAAIwO,GAAMnQ,EAAQ7B,MAAOgS,KAEzB,OAAY,OAAPA,EACG,KAGHnQ,EAAOkD,QAASiN,GACbnQ,EAAO0B,IAAKyO,EAAK,SAAUA,GACjC,OAAS1N,KAAMd,EAAKc,KAAM8C,MAAO4K,EAAI5M,QAASi5B,GAAO,YAI9C/5B,KAAMd,EAAKc,KAAM8C,MAAO4K,EAAI5M,QAASi5B,GAAO,WAClDv7B,QAKN,IACCk8B,IAAM,OACNC,GAAQ,OACRC,GAAa,gBACbC,GAAW,6BAGXC,GAAiB,4DACjBC,GAAa,iBACbC,GAAY,QAWZ9G,MAOA+G,MAGAC,GAAW,KAAKj/B,OAAQ,KAGxBk/B,GAAe7/B,EAAS0B,cAAe,IACvCm+B,IAAajqB,KAAOL,GAASK,IAG9B,SAASkqB,IAA6BC,GAGrC,MAAO,UAAUC,EAAoB3hB,GAED,gBAAvB2hB,KACX3hB,EAAO2hB,EACPA,EAAqB,IAGtB,IAAIC,GACHp8B,EAAI,EACJq8B,EAAYF,EAAmBr5B,cAAcwG,MAAOoP,MAErD,IAAKta,EAAOgD,WAAYoZ,GAGvB,MAAU4hB,EAAWC,EAAWr8B,KAGR,MAAlBo8B,EAAU,IACdA,EAAWA,EAASv/B,MAAO,IAAO,KAChCq/B,EAAWE,GAAaF,EAAWE,QAAmBjuB,QAASqM,KAI/D0hB,EAAWE,GAAaF,EAAWE,QAAmBr/B,KAAMyd,IAQnE,QAAS8hB,IAA+BJ,EAAWt7B,EAASw0B,EAAiBmH,GAE5E,GAAIC,MACHC,EAAqBP,IAAcJ,EAEpC,SAASY,GAASN,GACjB,GAAIjqB,EAcJ,OAbAqqB,GAAWJ,IAAa,EACxBh+B,EAAOwB,KAAMs8B,EAAWE,OAAkB,SAAUt0B,EAAG60B,GACtD,GAAIC,GAAsBD,EAAoB/7B,EAASw0B,EAAiBmH,EACxE,OAAoC,gBAAxBK,IACVH,GAAqBD,EAAWI,GAKtBH,IACDtqB,EAAWyqB,GADf,QAHNh8B,EAAQy7B,UAAUluB,QAASyuB,GAC3BF,EAASE,IACF,KAKFzqB,EAGR,MAAOuqB,GAAS97B,EAAQy7B,UAAW,MAAUG,EAAW,MAASE,EAAS,KAM3E,QAASG,IAAY37B,EAAQJ,GAC5B,GAAI8J,GAAKzJ,EACR27B,EAAc1+B,EAAO2+B,aAAaD,eAEnC,KAAMlyB,IAAO9J,GACQS,SAAfT,EAAK8J,MACPkyB,EAAalyB,GAAQ1J,EAAWC,IAAUA,OAAiByJ,GAAQ9J,EAAK8J,GAO5E,OAJKzJ,IACJ/C,EAAOuC,QAAQ,EAAMO,EAAQC,GAGvBD,EAOR,QAAS87B,IAAqB9B,EAAGqB,EAAOU,GAEvC,GAAIC,GAAIj7B,EAAMk7B,EAAeC,EAC5B3lB,EAAWyjB,EAAEzjB,SACb4kB,EAAYnB,EAAEmB,SAGf,OAA2B,MAAnBA,EAAW,GAClBA,EAAUvxB,QACEvJ,SAAP27B,IACJA,EAAKhC,EAAEmC,UAAYd,EAAMe,kBAAmB,gBAK9C,IAAKJ,EACJ,IAAMj7B,IAAQwV,GACb,GAAKA,EAAUxV,IAAUwV,EAAUxV,GAAOgI,KAAMizB,GAAO,CACtDb,EAAUluB,QAASlM,EACnB,OAMH,GAAKo6B,EAAW,IAAOY,GACtBE,EAAgBd,EAAW,OACrB,CAGN,IAAMp6B,IAAQg7B,GAAY,CACzB,IAAMZ,EAAW,IAAOnB,EAAEqC,WAAYt7B,EAAO,IAAMo6B,EAAW,IAAQ,CACrEc,EAAgBl7B,CAChB,OAEKm7B,IACLA,EAAgBn7B,GAKlBk7B,EAAgBA,GAAiBC,EAMlC,GAAKD,EAIJ,MAHKA,KAAkBd,EAAW,IACjCA,EAAUluB,QAASgvB,GAEbF,EAAWE,GAOpB,QAASK,IAAatC,EAAGuC,EAAUlB,EAAOmB,GACzC,GAAIC,GAAOC,EAASC,EAAM/5B,EAAK4T,EAC9B6lB,KAGAlB,EAAYnB,EAAEmB,UAAUx/B,OAGzB,IAAKw/B,EAAW,GACf,IAAMwB,IAAQ3C,GAAEqC,WACfA,EAAYM,EAAK/6B,eAAkBo4B,EAAEqC,WAAYM,EAInDD,GAAUvB,EAAUvxB,OAGpB,OAAQ8yB,EAcP,GAZK1C,EAAE4C,eAAgBF,KACtBrB,EAAOrB,EAAE4C,eAAgBF,IAAcH,IAIlC/lB,GAAQgmB,GAAaxC,EAAE6C,aAC5BN,EAAWvC,EAAE6C,WAAYN,EAAUvC,EAAEkB,WAGtC1kB,EAAOkmB,EACPA,EAAUvB,EAAUvxB,QAKnB,GAAiB,MAAZ8yB,EAEJA,EAAUlmB,MAGJ,IAAc,MAATA,GAAgBA,IAASkmB,EAAU,CAM9C,GAHAC,EAAON,EAAY7lB,EAAO,IAAMkmB,IAAaL,EAAY,KAAOK,IAG1DC,EACL,IAAMF,IAASJ,GAId,GADAz5B,EAAM65B,EAAMv5B,MAAO,KACdN,EAAK,KAAQ85B,IAGjBC,EAAON,EAAY7lB,EAAO,IAAM5T,EAAK,KACpCy5B,EAAY,KAAOz5B,EAAK,KACb,CAGN+5B,KAAS,EACbA,EAAON,EAAYI,GAGRJ,EAAYI,MAAY,IACnCC,EAAU95B,EAAK,GACfu4B,EAAUluB,QAASrK,EAAK,IAEzB,OAOJ,GAAK+5B,KAAS,EAGb,GAAKA,GAAQ3C,EAAAA,UACZuC,EAAWI,EAAMJ,OAEjB,KACCA,EAAWI,EAAMJ,GAChB,MAAQx0B,GACT,OACCyR,MAAO,cACP7Y,MAAOg8B,EAAO50B,EAAI,sBAAwByO,EAAO,OAASkmB,IASjE,OAASljB,MAAO,UAAW+D,KAAMgf,GAGlCr/B,EAAOuC,QAGNq9B,OAAQ,EAGRC,gBACAC,QAEAnB,cACCoB,IAAKzsB,GAASK,KACd9P,KAAM,MACNm8B,QAASzC,GAAe1xB,KAAMyH,GAAS2sB,UACvCtiC,QAAQ,EACRuiC,aAAa,EACbC,OAAO,EACPC,YAAa,mDAcbC,SACCjJ,IAAKuG,GACLj+B,KAAM,aACN2tB,KAAM,YACNpb,IAAK,4BACLquB,KAAM,qCAGPjnB,UACCpH,IAAK,UACLob,KAAM,SACNiT,KAAM,YAGPZ,gBACCztB,IAAK,cACLvS,KAAM,eACN4gC,KAAM,gBAKPnB,YAGCoB,SAAUz2B,OAGV02B,aAAa,EAGbC,YAAa5f,KAAKC,MAGlB4f,WAAY1gC,EAAOo8B,UAOpBsC,aACCqB,KAAK,EACL7/B,SAAS,IAOXygC,UAAW,SAAU79B,EAAQ89B,GAC5B,MAAOA,GAGNnC,GAAYA,GAAY37B,EAAQ9C,EAAO2+B,cAAgBiC,GAGvDnC,GAAYz+B,EAAO2+B,aAAc77B,IAGnC+9B,cAAehD,GAA6BlH,IAC5CmK,cAAejD,GAA6BH,IAG5CqD,KAAM,SAAUhB,EAAKv9B,GAGA,gBAARu9B,KACXv9B,EAAUu9B,EACVA,EAAM58B,QAIPX,EAAUA,KAEV,IAAIw+B,GAGHC,EAGAC,EACAC,EAGAC,EAGAC,EAGAhiB,EAGAiiB,EAGA1/B,EAGA2/B,EAGAzE,EAAI98B,EAAO2gC,aAAen+B,GAG1Bg/B,EAAkB1E,EAAE58B,SAAW48B,EAG/B2E,EAAqB3E,EAAE58B,UACpBshC,EAAgB52B,UAAY42B,EAAgB3gC,QAC7Cb,EAAQwhC,GACRxhC,EAAOsmB,MAGT9J,EAAWxc,EAAOmc,WAClBulB,EAAmB1hC,EAAO0a,UAAW,eAGrCinB,EAAa7E,EAAE6E,eAGfC,KACAC,KAGAC,EAAW,WAGX3D,GACC5e,WAAY,EAGZ2f,kBAAmB,SAAU1yB,GAC5B,GAAItB,EACJ,IAAKmU,EAAY,CAChB,IAAM8hB,EAAkB,CACvBA,IACA,OAAUj2B,EAAQoyB,GAAS/xB,KAAM21B,GAChCC,EAAiBj2B,EAAO,GAAIxG,eAAkBwG,EAAO,GAGvDA,EAAQi2B,EAAiB30B,EAAI9H,eAE9B,MAAgB,OAATwG,EAAgB,KAAOA,GAI/B62B,sBAAuB,WACtB,MAAO1iB,GAAY6hB,EAAwB,MAI5Cc,iBAAkB,SAAUv/B,EAAM8C,GAMjC,MALkB,OAAb8Z,IACJ5c,EAAOo/B,EAAqBp/B,EAAKiC,eAChCm9B,EAAqBp/B,EAAKiC,gBAAmBjC,EAC9Cm/B,EAAgBn/B,GAAS8C,GAEnBpH,MAIR8jC,iBAAkB,SAAUp+B,GAI3B,MAHkB,OAAbwb,IACJyd,EAAEmC,SAAWp7B,GAEP1F,MAIRwjC,WAAY,SAAUjgC,GACrB,GAAIpC,EACJ,IAAKoC,EACJ,GAAK2d,EAGJ8e,EAAM5hB,OAAQ7a,EAAKy8B,EAAM+D,aAIzB,KAAM5iC,IAAQoC,GACbigC,EAAYriC,IAAWqiC,EAAYriC,GAAQoC,EAAKpC,GAInD,OAAOnB,OAIRgkC,MAAO,SAAUC,GAChB,GAAIC,GAAYD,GAAcN,CAK9B,OAJKd,IACJA,EAAUmB,MAAOE,GAElBl7B,EAAM,EAAGk7B,GACFlkC,MAoBV,IAfAqe,EAASR,QAASmiB,GAKlBrB,EAAEiD,MAAUA,GAAOjD,EAAEiD,KAAOzsB,GAASK,MAAS,IAC5CpQ,QAASk6B,GAAWnqB,GAAS2sB,SAAW,MAG1CnD,EAAEj5B,KAAOrB,EAAQuZ,QAAUvZ,EAAQqB,MAAQi5B,EAAE/gB,QAAU+gB,EAAEj5B,KAGzDi5B,EAAEmB,WAAcnB,EAAEkB,UAAY,KAAMt5B,cAAcwG,MAAOoP,KAAqB,IAGxD,MAAjBwiB,EAAEwF,YAAsB,CAC5BjB,EAAYtjC,EAAS0B,cAAe,IAKpC,KACC4hC,EAAU1tB,KAAOmpB,EAAEiD,IAInBsB,EAAU1tB,KAAO0tB,EAAU1tB,KAC3BmpB,EAAEwF,YAAc1E,GAAaqC,SAAW,KAAOrC,GAAa2E,MAC3DlB,EAAUpB,SAAW,KAAOoB,EAAUkB,KACtC,MAAQ13B,GAITiyB,EAAEwF,aAAc,GAalB,GARKxF,EAAEzc,MAAQyc,EAAEoD,aAAiC,gBAAXpD,GAAEzc,OACxCyc,EAAEzc,KAAOrgB,EAAO68B,MAAOC,EAAEzc,KAAMyc,EAAEF,cAIlCsB,GAA+BvH,GAAYmG,EAAGt6B,EAAS27B,GAGlD9e,EACJ,MAAO8e,EAKRmD,GAActhC,EAAOsmB,OAASwW,EAAEn/B,OAG3B2jC,GAAmC,IAApBthC,EAAO4/B,UAC1B5/B,EAAOsmB,MAAM6C,QAAS,aAIvB2T,EAAEj5B,KAAOi5B,EAAEj5B,KAAKlD,cAGhBm8B,EAAE0F,YAAchF,GAAW3xB,KAAMixB,EAAEj5B,MAKnCo9B,EAAWnE,EAAEiD,IAAIx8B,QAAS65B,GAAO,IAG3BN,EAAE0F,WAuBI1F,EAAEzc,MAAQyc,EAAEoD,aACoD,KAAzEpD,EAAEsD,aAAe,IAAKxhC,QAAS,uCACjCk+B,EAAEzc,KAAOyc,EAAEzc,KAAK9c,QAAS45B,GAAK,OAtB9BoE,EAAWzE,EAAEiD,IAAIthC,MAAOwiC,EAASlgC,QAG5B+7B,EAAEzc,OACN4gB,IAAc9E,GAAOtwB,KAAMo1B,GAAa,IAAM,KAAQnE,EAAEzc,WAGjDyc,GAAEzc,MAILyc,EAAEvwB,SAAU,IAChB00B,EAAWA,EAAS19B,QAAS85B,GAAY,MACzCkE,GAAapF,GAAOtwB,KAAMo1B,GAAa,IAAM,KAAQ,KAAS/E,MAAYqF,GAI3EzE,EAAEiD,IAAMkB,EAAWM,GASfzE,EAAE2F,aACDziC,EAAO6/B,aAAcoB,IACzB9C,EAAM6D,iBAAkB,oBAAqBhiC,EAAO6/B,aAAcoB,IAE9DjhC,EAAO8/B,KAAMmB,IACjB9C,EAAM6D,iBAAkB,gBAAiBhiC,EAAO8/B,KAAMmB,MAKnDnE,EAAEzc,MAAQyc,EAAE0F,YAAc1F,EAAEsD,eAAgB,GAAS59B,EAAQ49B,cACjEjC,EAAM6D,iBAAkB,eAAgBlF,EAAEsD,aAI3CjC,EAAM6D,iBACL,SACAlF,EAAEmB,UAAW,IAAOnB,EAAEuD,QAASvD,EAAEmB,UAAW,IAC3CnB,EAAEuD,QAASvD,EAAEmB,UAAW,KACA,MAArBnB,EAAEmB,UAAW,GAAc,KAAON,GAAW,WAAa,IAC7Db,EAAEuD,QAAS,KAIb,KAAMz+B,IAAKk7B,GAAE4F,QACZvE,EAAM6D,iBAAkBpgC,EAAGk7B,EAAE4F,QAAS9gC,GAIvC,IAAKk7B,EAAE6F,aACJ7F,EAAE6F,WAAWxjC,KAAMqiC,EAAiBrD,EAAOrB,MAAQ,GAASzd,GAG9D,MAAO8e,GAAMgE,OAed,IAXAL,EAAW,QAGXJ,EAAiB9nB,IAAKkjB,EAAE3F,UACxBgH,EAAMh3B,KAAM21B,EAAE8F,SACdzE,EAAMliB,KAAM6gB,EAAEr5B,OAGdu9B,EAAY9C,GAA+BR,GAAYZ,EAAGt6B,EAAS27B,GAK5D,CASN,GARAA,EAAM5e,WAAa,EAGd+hB,GACJG,EAAmBtY,QAAS,YAAcgV,EAAOrB,IAI7Czd,EACJ,MAAO8e,EAIHrB,GAAEqD,OAASrD,EAAE7D,QAAU,IAC3BmI,EAAeljC,EAAO+f,WAAY,WACjCkgB,EAAMgE,MAAO,YACXrF,EAAE7D,SAGN,KACC5Z,GAAY,EACZ2hB,EAAU6B,KAAMjB,EAAgBz6B,GAC/B,MAAQ0D,GAGT,GAAKwU,EACJ,KAAMxU,EAIP1D,MAAU0D,QAhCX1D,MAAU,eAqCX,SAASA,GAAM+6B,EAAQY,EAAkBjE,EAAW6D,GACnD,GAAIpD,GAAWsD,EAASn/B,EAAO47B,EAAU0D,EACxCX,EAAaU,CAGTzjB,KAILA,GAAY,EAGP+hB,GACJljC,EAAOg7B,aAAckI,GAKtBJ,EAAY79B,OAGZ+9B,EAAwBwB,GAAW,GAGnCvE,EAAM5e,WAAa2iB,EAAS,EAAI,EAAI,EAGpC5C,EAAY4C,GAAU,KAAOA,EAAS,KAAkB,MAAXA,EAGxCrD,IACJQ,EAAWT,GAAqB9B,EAAGqB,EAAOU,IAI3CQ,EAAWD,GAAatC,EAAGuC,EAAUlB,EAAOmB,GAGvCA,GAGCxC,EAAE2F,aACNM,EAAW5E,EAAMe,kBAAmB,iBAC/B6D,IACJ/iC,EAAO6/B,aAAcoB,GAAa8B,GAEnCA,EAAW5E,EAAMe,kBAAmB,QAC/B6D,IACJ/iC,EAAO8/B,KAAMmB,GAAa8B,IAKZ,MAAXb,GAA6B,SAAXpF,EAAEj5B,KACxBu+B,EAAa,YAGS,MAAXF,EACXE,EAAa,eAIbA,EAAa/C,EAAS/iB,MACtBsmB,EAAUvD,EAAShf,KACnB5c,EAAQ47B,EAAS57B,MACjB67B,GAAa77B,KAKdA,EAAQ2+B,GACHF,GAAWE,IACfA,EAAa,QACRF,EAAS,IACbA,EAAS,KAMZ/D,EAAM+D,OAASA,EACf/D,EAAMiE,YAAeU,GAAoBV,GAAe,GAGnD9C,EACJ9iB,EAASmB,YAAa6jB,GAAmBoB,EAASR,EAAYjE,IAE9D3hB,EAASuB,WAAYyjB,GAAmBrD,EAAOiE,EAAY3+B,IAI5D06B,EAAMwD,WAAYA,GAClBA,EAAax+B,OAERm+B,GACJG,EAAmBtY,QAASmW,EAAY,cAAgB,aACrDnB,EAAOrB,EAAGwC,EAAYsD,EAAUn/B,IAIpCi+B,EAAiBnmB,SAAUimB,GAAmBrD,EAAOiE,IAEhDd,IACJG,EAAmBtY,QAAS,gBAAkBgV,EAAOrB,MAG3C98B,EAAO4/B,QAChB5/B,EAAOsmB,MAAM6C,QAAS,cAKzB,MAAOgV,IAGR6E,QAAS,SAAUjD,EAAK1f,EAAM5e,GAC7B,MAAOzB,GAAOiB,IAAK8+B,EAAK1f,EAAM5e,EAAU,SAGzCwhC,UAAW,SAAUlD,EAAKt+B,GACzB,MAAOzB,GAAOiB,IAAK8+B,EAAK58B,OAAW1B,EAAU,aAI/CzB,EAAOwB,MAAQ,MAAO,QAAU,SAAUI,EAAGma,GAC5C/b,EAAQ+b,GAAW,SAAUgkB,EAAK1f,EAAM5e,EAAUoC,GAUjD,MAPK7D,GAAOgD,WAAYqd,KACvBxc,EAAOA,GAAQpC,EACfA,EAAW4e,EACXA,EAAOld,QAIDnD,EAAO+gC,KAAM/gC,EAAOuC,QAC1Bw9B,IAAKA,EACLl8B,KAAMkY,EACNiiB,SAAUn6B,EACVwc,KAAMA,EACNuiB,QAASnhC,GACPzB,EAAOiD,cAAe88B,IAASA,OAKpC//B,EAAOstB,SAAW,SAAUyS,GAC3B,MAAO//B,GAAO+gC,MACbhB,IAAKA,EAGLl8B,KAAM,MACNm6B,SAAU,SACVzxB,OAAO,EACP4zB,OAAO,EACPxiC,QAAQ,EACRulC,UAAU,KAKZljC,EAAOG,GAAGoC,QACT4gC,QAAS,SAAU9V,GAClB,GAAIpI,EAyBJ,OAvBK9mB,MAAM,KACL6B,EAAOgD,WAAYqqB,KACvBA,EAAOA,EAAKluB,KAAMhB,KAAM,KAIzB8mB,EAAOjlB,EAAQqtB,EAAMlvB,KAAM,GAAImN,eAAgBtJ,GAAI,GAAIa,OAAO,GAEzD1E,KAAM,GAAI0B,YACdolB,EAAKgJ,aAAc9vB,KAAM,IAG1B8mB,EAAKvjB,IAAK,WACT,GAAIC,GAAOxD,IAEX,OAAQwD,EAAKyhC,kBACZzhC,EAAOA,EAAKyhC,iBAGb,OAAOzhC,KACJosB,OAAQ5vB,OAGNA,MAGRklC,UAAW,SAAUhW,GACpB,MAAKrtB,GAAOgD,WAAYqqB,GAChBlvB,KAAKqD,KAAM,SAAUI,GAC3B5B,EAAQ7B,MAAOklC,UAAWhW,EAAKluB,KAAMhB,KAAMyD,MAItCzD,KAAKqD,KAAM,WACjB,GAAIsX,GAAO9Y,EAAQ7B,MAClBkb,EAAWP,EAAKO,UAEZA,GAAStY,OACbsY,EAAS8pB,QAAS9V,GAGlBvU,EAAKiV,OAAQV,MAKhBpI,KAAM,SAAUoI,GACf,GAAIrqB,GAAahD,EAAOgD,WAAYqqB,EAEpC,OAAOlvB,MAAKqD,KAAM,SAAUI,GAC3B5B,EAAQ7B,MAAOglC,QAASngC,EAAaqqB,EAAKluB,KAAMhB,KAAMyD,GAAMyrB,MAI9DiW,OAAQ,SAAUrjC,GAIjB,MAHA9B,MAAKmU,OAAQrS,GAAW6S,IAAK,QAAStR,KAAM,WAC3CxB,EAAQ7B,MAAOiwB,YAAajwB,KAAKwM,cAE3BxM,QAKT6B,EAAOgQ,KAAK9H,QAAQ+tB,OAAS,SAAUt0B,GACtC,OAAQ3B,EAAOgQ,KAAK9H,QAAQq7B,QAAS5hC,IAEtC3B,EAAOgQ,KAAK9H,QAAQq7B,QAAU,SAAU5hC,GACvC,SAAWA,EAAK6hC,aAAe7hC,EAAK8hC,cAAgB9hC,EAAKmwB,iBAAiB/wB,SAM3Ef,EAAO2+B,aAAa+E,IAAM,WACzB,IACC,MAAO,IAAIxlC,GAAOylC,eACjB,MAAQ94B,KAGX,IAAI+4B,KAGFC,EAAG,IAIHC,KAAM,KAEPC,GAAe/jC,EAAO2+B,aAAa+E,KAEpCtkC,GAAQ4kC,OAASD,IAAkB,mBAAqBA,IACxD3kC,EAAQ2hC,KAAOgD,KAAiBA,GAEhC/jC,EAAO8gC,cAAe,SAAUt+B,GAC/B,GAAIf,GAAUwiC,CAGd,IAAK7kC,EAAQ4kC,MAAQD,KAAiBvhC,EAAQ8/B,YAC7C,OACCO,KAAM,SAAUH,EAASvL,GACxB,GAAIv1B,GACH8hC,EAAMlhC,EAAQkhC,KAWf,IATAA,EAAIQ,KACH1hC,EAAQqB,KACRrB,EAAQu9B,IACRv9B,EAAQ29B,MACR39B,EAAQ2hC,SACR3hC,EAAQmS,UAIJnS,EAAQ4hC,UACZ,IAAMxiC,IAAKY,GAAQ4hC,UAClBV,EAAK9hC,GAAMY,EAAQ4hC,UAAWxiC,EAK3BY,GAAQy8B,UAAYyE,EAAIzB,kBAC5ByB,EAAIzB,iBAAkBz/B,EAAQy8B,UAQzBz8B,EAAQ8/B,aAAgBI,EAAS,sBACtCA,EAAS,oBAAuB,iBAIjC,KAAM9gC,IAAK8gC,GACVgB,EAAI1B,iBAAkBpgC,EAAG8gC,EAAS9gC,GAInCH,GAAW,SAAUoC,GACpB,MAAO,YACDpC,IACJA,EAAWwiC,EAAgBP,EAAIW,OAC9BX,EAAIY,QAAUZ,EAAIa,QAAUb,EAAIc,mBAAqB,KAExC,UAAT3gC,EACJ6/B,EAAIvB,QACgB,UAATt+B,EAKgB,gBAAf6/B,GAAIxB,OACf/K,EAAU,EAAG,SAEbA,EAGCuM,EAAIxB,OACJwB,EAAItB,YAINjL,EACCyM,GAAkBF,EAAIxB,SAAYwB,EAAIxB,OACtCwB,EAAItB,WAK+B,UAAjCsB,EAAIe,cAAgB,SACM,gBAArBf,GAAIgB,cACRC,OAAQjB,EAAIrE,WACZ3/B,KAAMgkC,EAAIgB,cACbhB,EAAI3B,4BAQT2B,EAAIW,OAAS5iC,IACbwiC,EAAgBP,EAAIY,QAAU7iC,EAAU,SAKnB0B,SAAhBugC,EAAIa,QACRb,EAAIa,QAAUN,EAEdP,EAAIc,mBAAqB,WAGA,IAAnBd,EAAInkB,YAMRrhB,EAAO+f,WAAY,WACbxc,GACJwiC,OAQLxiC,EAAWA,EAAU,QAErB,KAGCiiC,EAAIb,KAAMrgC,EAAQggC,YAAchgC,EAAQ6d,MAAQ,MAC/C,MAAQxV,GAGT,GAAKpJ,EACJ,KAAMoJ,KAKTs3B,MAAO,WACD1gC,GACJA,QAWLzB,EAAO6gC,cAAe,SAAU/D,GAC1BA,EAAEwF,cACNxF,EAAEzjB,SAAS7Z,QAAS,KAKtBQ,EAAO2gC,WACNN,SACC7gC,OAAQ,6FAGT6Z,UACC7Z,OAAQ,2BAET2/B,YACCyF,cAAe,SAAUllC,GAExB,MADAM,GAAOsE,WAAY5E,GACZA,MAMVM,EAAO6gC,cAAe,SAAU,SAAU/D,GACxB35B,SAAZ25B,EAAEvwB,QACNuwB,EAAEvwB,OAAQ,GAENuwB,EAAEwF,cACNxF,EAAEj5B,KAAO,SAKX7D,EAAO8gC,cAAe,SAAU,SAAUhE,GAGzC,GAAKA,EAAEwF,YAAc,CACpB,GAAI9iC,GAAQiC,CACZ,QACCohC,KAAM,SAAUn5B,EAAGytB,GAClB33B,EAASQ,EAAQ,YAAasgB,MAC7BukB,QAAS/H,EAAEgI,cACXpiC,IAAKo6B,EAAEiD,MACJ7Z,GACH,aACAzkB,EAAW,SAAUsjC,GACpBvlC,EAAO4b,SACP3Z,EAAW,KACNsjC,GACJ5N,EAAuB,UAAb4N,EAAIlhC,KAAmB,IAAM,IAAKkhC,EAAIlhC,QAMnD9F,EAAS4B,KAAKC,YAAaJ,EAAQ,KAEpC2iC,MAAO,WACD1gC,GACJA,QAUL,IAAIujC,OACHC,GAAS,mBAGVjlC,GAAO2gC,WACNuE,MAAO,WACPC,cAAe,WACd,GAAI1jC,GAAWujC,GAAar9B,OAAW3H,EAAOoD,QAAU,IAAQ84B,IAEhE,OADA/9B,MAAMsD,IAAa,EACZA,KAKTzB,EAAO6gC,cAAe,aAAc,SAAU/D,EAAGsI,EAAkBjH,GAElE,GAAIkH,GAAcC,EAAaC,EAC9BC,EAAW1I,EAAEoI,SAAU,IAAWD,GAAOp5B,KAAMixB,EAAEiD,KAChD,MACkB,gBAAXjD,GAAEzc,MAE6C,KADnDyc,EAAEsD,aAAe,IACjBxhC,QAAS,sCACXqmC,GAAOp5B,KAAMixB,EAAEzc,OAAU,OAI5B,IAAKmlB,GAAiC,UAArB1I,EAAEmB,UAAW,GA8D7B,MA3DAoH,GAAevI,EAAEqI,cAAgBnlC,EAAOgD,WAAY85B,EAAEqI,eACrDrI,EAAEqI,gBACFrI,EAAEqI,cAGEK,EACJ1I,EAAG0I,GAAa1I,EAAG0I,GAAWjiC,QAAS0hC,GAAQ,KAAOI,GAC3CvI,EAAEoI,SAAU,IACvBpI,EAAEiD,MAAS5D,GAAOtwB,KAAMixB,EAAEiD,KAAQ,IAAM,KAAQjD,EAAEoI,MAAQ,IAAMG,GAIjEvI,EAAEqC,WAAY,eAAkB,WAI/B,MAHMoG,IACLvlC,EAAOyD,MAAO4hC,EAAe,mBAEvBE,EAAmB,IAI3BzI,EAAEmB,UAAW,GAAM,OAGnBqH,EAAcpnC,EAAQmnC,GACtBnnC,EAAQmnC,GAAiB,WACxBE,EAAoBzjC,WAIrBq8B,EAAM5hB,OAAQ,WAGQpZ,SAAhBmiC,EACJtlC,EAAQ9B,GAAS67B,WAAYsL,GAI7BnnC,EAAQmnC,GAAiBC,EAIrBxI,EAAGuI,KAGPvI,EAAEqI,cAAgBC,EAAiBD,cAGnCH,GAAarmC,KAAM0mC,IAIfE,GAAqBvlC,EAAOgD,WAAYsiC,IAC5CA,EAAaC,EAAmB,IAGjCA,EAAoBD,EAAcniC,SAI5B,WAYT/D,EAAQqmC,mBAAqB,WAC5B,GAAItiB,GAAOplB,EAAS2nC,eAAeD,mBAAoB,IAAKtiB,IAE5D,OADAA,GAAKpU,UAAY,6BACiB,IAA3BoU,EAAKxY,WAAW5J,UAQxBf,EAAOgZ,UAAY,SAAUqH,EAAMngB,EAASylC,GAC3C,GAAqB,gBAATtlB,GACX,QAEuB,kBAAZngB,KACXylC,EAAczlC,EACdA,GAAU,EAGX,IAAIoV,GAAMswB,EAAQ9gB,CAwBlB,OAtBM5kB,KAIAd,EAAQqmC,oBACZvlC,EAAUnC,EAAS2nC,eAAeD,mBAAoB,IAKtDnwB,EAAOpV,EAAQT,cAAe,QAC9B6V,EAAK3B,KAAO5V,EAASuV,SAASK,KAC9BzT,EAAQP,KAAKC,YAAa0V,IAE1BpV,EAAUnC,GAIZ6nC,EAASltB,EAAWnN,KAAM8U,GAC1ByE,GAAW6gB,MAGNC,GACK1lC,EAAQT,cAAemmC,EAAQ,MAGzCA,EAAS/gB,IAAiBxE,GAAQngB,EAAS4kB,GAEtCA,GAAWA,EAAQ/jB,QACvBf,EAAQ8kB,GAAU1J,SAGZpb,EAAOsB,SAAWskC,EAAOj7B,cAOjC3K,EAAOG,GAAG8oB,KAAO,SAAU8W,EAAK8F,EAAQpkC,GACvC,GAAIxB,GAAU4D,EAAMw7B,EACnBvmB,EAAO3a,KACPooB,EAAMwZ,EAAInhC,QAAS,IAsDpB,OApDK2nB,QACJtmB,EAAWo6B,GAAkB0F,EAAIthC,MAAO8nB,IACxCwZ,EAAMA,EAAIthC,MAAO,EAAG8nB,IAIhBvmB,EAAOgD,WAAY6iC,IAGvBpkC,EAAWokC,EACXA,EAAS1iC,QAGE0iC,GAA4B,gBAAXA,KAC5BhiC,EAAO,QAIHiV,EAAK/X,OAAS,GAClBf,EAAO+gC,MACNhB,IAAKA,EAKLl8B,KAAMA,GAAQ,MACdm6B,SAAU,OACV3d,KAAMwlB,IACH1+B,KAAM,SAAUu9B,GAGnBrF,EAAWv9B,UAEXgX,EAAKuU,KAAMptB,EAIVD,EAAQ,SAAU+tB,OAAQ/tB,EAAOgZ,UAAW0rB,IAAiB91B,KAAM3O,GAGnEykC,KAKEnoB,OAAQ9a,GAAY,SAAU08B,EAAO+D,GACxCppB,EAAKtX,KAAM,WACVC,EAASI,MAAO1D,KAAMkhC,IAAclB,EAAMuG,aAAcxC,EAAQ/D,QAK5DhgC,MAOR6B,EAAOwB,MACN,YACA,WACA,eACA,YACA,cACA,YACE,SAAUI,EAAGiC,GACf7D,EAAOG,GAAI0D,GAAS,SAAU1D,GAC7B,MAAOhC,MAAK+nB,GAAIriB,EAAM1D,MAOxBH,EAAOgQ,KAAK9H,QAAQ49B,SAAW,SAAUnkC,GACxC,MAAO3B,GAAOiF,KAAMjF,EAAOi4B,OAAQ,SAAU93B,GAC5C,MAAOwB,KAASxB,EAAGwB,OAChBZ,OASL,SAASglC,IAAWpkC,GACnB,MAAO3B,GAAO+D,SAAUpC,GAASA,EAAyB,IAAlBA,EAAKiJ,UAAkBjJ,EAAKuM,YAGrElO,EAAOgmC,QACNC,UAAW,SAAUtkC,EAAMa,EAASZ,GACnC,GAAIskC,GAAaC,EAASC,EAAWC,EAAQC,EAAWC,EAAYC,EACnE5V,EAAW5wB,EAAOmiB,IAAKxgB,EAAM,YAC7B8kC,EAAUzmC,EAAQ2B,GAClB6nB,IAGiB,YAAboH,IACJjvB,EAAKsgB,MAAM2O,SAAW,YAGvB0V,EAAYG,EAAQT,SACpBI,EAAYpmC,EAAOmiB,IAAKxgB,EAAM,OAC9B4kC,EAAavmC,EAAOmiB,IAAKxgB,EAAM,QAC/B6kC,GAAmC,aAAb5V,GAAwC,UAAbA,KAC9CwV,EAAYG,GAAa3nC,QAAS,WAIhC4nC,GACJN,EAAcO,EAAQ7V,WACtByV,EAASH,EAAY/3B,IACrBg4B,EAAUD,EAAYjT,OAGtBoT,EAASniC,WAAYkiC,IAAe,EACpCD,EAAUjiC,WAAYqiC,IAAgB,GAGlCvmC,EAAOgD,WAAYR,KAGvBA,EAAUA,EAAQrD,KAAMwC,EAAMC,EAAG5B,EAAOuC,UAAY+jC,KAGjC,MAAf9jC,EAAQ2L,MACZqb,EAAMrb,IAAQ3L,EAAQ2L,IAAMm4B,EAAUn4B,IAAQk4B,GAE1B,MAAhB7jC,EAAQywB,OACZzJ,EAAMyJ,KAASzwB,EAAQywB,KAAOqT,EAAUrT,KAASkT,GAG7C,SAAW3jC,GACfA,EAAQkkC,MAAMvnC,KAAMwC,EAAM6nB,GAG1Bid,EAAQtkB,IAAKqH,KAKhBxpB,EAAOG,GAAGoC,QACTyjC,OAAQ,SAAUxjC,GAGjB,GAAKV,UAAUf,OACd,MAAmBoC,UAAZX,EACNrE,KACAA,KAAKqD,KAAM,SAAUI,GACpB5B,EAAOgmC,OAAOC,UAAW9nC,KAAMqE,EAASZ,IAI3C,IAAIgF,GAAS+/B,EAAKC,EAAMrnC,EACvBoC,EAAOxD,KAAM,EAEd,IAAMwD,EAON,MAAMA,GAAKmwB,iBAAiB/wB,QAI5B6lC,EAAOjlC,EAAKowB,wBAGP6U,EAAKnX,OAASmX,EAAKvR,QACvB91B,EAAMoC,EAAK2J,cACXq7B,EAAMZ,GAAWxmC,GACjBqH,EAAUrH,EAAIuO,iBAGbK,IAAKy4B,EAAKz4B,IAAMw4B,EAAIE,YAAcjgC,EAAQkgC,UAC1C7T,KAAM2T,EAAK3T,KAAO0T,EAAII,YAAcngC,EAAQogC,aAKvCJ,IAlBGz4B,IAAK,EAAG8kB,KAAM,IAqBzBrC,SAAU,WACT,GAAMzyB,KAAM,GAAZ,CAIA,GAAI8oC,GAAcjB,EACjBrkC,EAAOxD,KAAM,GACb+oC,GAAiB/4B,IAAK,EAAG8kB,KAAM,EA4BhC,OAxBwC,UAAnCjzB,EAAOmiB,IAAKxgB,EAAM,YAGtBqkC,EAASrkC,EAAKowB,yBAKdkV,EAAe9oC,KAAK8oC,eAGpBjB,EAAS7nC,KAAK6nC,SACRhmC,EAAOyE,SAAUwiC,EAAc,GAAK,UACzCC,EAAeD,EAAajB,UAI7BkB,GACC/4B,IAAK+4B,EAAa/4B,IAAMnO,EAAOmiB,IAAK8kB,EAAc,GAAK,kBAAkB,GACzEhU,KAAMiU,EAAajU,KAAOjzB,EAAOmiB,IAAK8kB,EAAc,GAAK,mBAAmB,MAM7E94B,IAAK63B,EAAO73B,IAAM+4B,EAAa/4B,IAAMnO,EAAOmiB,IAAKxgB,EAAM,aAAa,GACpEsxB,KAAM+S,EAAO/S,KAAOiU,EAAajU,KAAOjzB,EAAOmiB,IAAKxgB,EAAM,cAAc,MAc1EslC,aAAc,WACb,MAAO9oC,MAAKuD,IAAK,WAChB,GAAIulC,GAAe9oC,KAAK8oC,YAExB,OAAQA,GAA2D,WAA3CjnC,EAAOmiB,IAAK8kB,EAAc,YACjDA,EAAeA,EAAaA,YAG7B,OAAOA,IAAgBn5B,QAM1B9N,EAAOwB,MAAQ6yB,WAAY,cAAeD,UAAW,eAAiB,SAAUrY,EAAQuE,GACvF,GAAInS,GAAM,gBAAkBmS,CAE5BtgB,GAAOG,GAAI4b,GAAW,SAAU5L,GAC/B,MAAOsP,GAAQthB,KAAM,SAAUwD,EAAMoa,EAAQ5L,GAC5C,GAAIw2B,GAAMZ,GAAWpkC,EAErB,OAAawB,UAARgN,EACGw2B,EAAMA,EAAKrmB,GAAS3e,EAAMoa,QAG7B4qB,EACJA,EAAIQ,SACFh5B,EAAYw4B,EAAII,YAAV52B,EACPhC,EAAMgC,EAAMw2B,EAAIE,aAIjBllC,EAAMoa,GAAW5L,IAEhB4L,EAAQ5L,EAAKrO,UAAUf,WAU5Bf,EAAOwB,MAAQ,MAAO,QAAU,SAAUI,EAAG0e,GAC5CtgB,EAAOgyB,SAAU1R,GAASiQ,GAAcnxB,EAAQ0wB,cAC/C,SAAUnuB,EAAMwuB,GACf,GAAKA,EAIJ,MAHAA,GAAWD,GAAQvuB,EAAM2e,GAGlBuO,GAAUhjB,KAAMskB,GACtBnwB,EAAQ2B,GAAOivB,WAAYtQ,GAAS,KACpC6P,MAQLnwB,EAAOwB,MAAQ4lC,OAAQ,SAAUC,MAAO,SAAW,SAAU5kC,EAAMoB,GAClE7D,EAAOwB,MAAQ2xB,QAAS,QAAU1wB,EAAM8pB,QAAS1oB,EAAMyjC,GAAI,QAAU7kC,GACpE,SAAU8kC,EAAcC,GAGxBxnC,EAAOG,GAAIqnC,GAAa,SAAUtU,EAAQ3tB,GACzC,GAAIma,GAAY5d,UAAUf,SAAYwmC,GAAkC,iBAAXrU,IAC5DzB,EAAQ8V,IAAkBrU,KAAW,GAAQ3tB,KAAU,EAAO,SAAW,SAE1E,OAAOka,GAAQthB,KAAM,SAAUwD,EAAMkC,EAAM0B,GAC1C,GAAIhG,EAEJ,OAAKS,GAAO+D,SAAUpC,GAGkB,IAAhC6lC,EAAS5oC,QAAS,SACxB+C,EAAM,QAAUc,GAChBd,EAAK5D,SAAS+P,gBAAiB,SAAWrL,GAIrB,IAAlBd,EAAKiJ,UACTrL,EAAMoC,EAAKmM,gBAIJzK,KAAKkuB,IACX5vB,EAAKwhB,KAAM,SAAW1gB,GAAQlD,EAAK,SAAWkD,GAC9Cd,EAAKwhB,KAAM,SAAW1gB,GAAQlD,EAAK,SAAWkD,GAC9ClD,EAAK,SAAWkD,KAIDU,SAAVoC,EAGNvF,EAAOmiB,IAAKxgB,EAAMkC,EAAM4tB,GAGxBzxB,EAAOiiB,MAAOtgB,EAAMkC,EAAM0B,EAAOksB,IAChC5tB,EAAM6b,EAAYwT,EAAS/vB,OAAWuc,QAM5C1f,EAAOG,GAAGoC,QAETklC,KAAM,SAAUthB,EAAO9F,EAAMlgB,GAC5B,MAAOhC,MAAK+nB,GAAIC,EAAO,KAAM9F,EAAMlgB,IAEpCunC,OAAQ,SAAUvhB,EAAOhmB,GACxB,MAAOhC,MAAKooB,IAAKJ,EAAO,KAAMhmB,IAG/BwnC,SAAU,SAAU1nC,EAAUkmB,EAAO9F,EAAMlgB,GAC1C,MAAOhC,MAAK+nB,GAAIC,EAAOlmB,EAAUogB,EAAMlgB,IAExCynC,WAAY,SAAU3nC,EAAUkmB,EAAOhmB,GAGtC,MAA4B,KAArB2B,UAAUf,OAChB5C,KAAKooB,IAAKtmB,EAAU,MACpB9B,KAAKooB,IAAKJ,EAAOlmB,GAAY,KAAME,MAItCH,EAAO6nC,UAAYhnB,KAAKC,MAkBD,kBAAXgnB,SAAyBA,OAAOC,KAC3CD,OAAQ,YAAc,WACrB,MAAO9nC,IAOT,IAGCgoC,IAAU9pC,EAAO8B,OAGjBioC,GAAK/pC,EAAOgqC,CAyBb,OAvBAloC,GAAOmoC,WAAa,SAAUplC,GAS7B,MARK7E,GAAOgqC,IAAMloC,IACjB9B,EAAOgqC,EAAID,IAGPllC,GAAQ7E,EAAO8B,SAAWA,IAC9B9B,EAAO8B,OAASgoC,IAGVhoC,GAMF5B,IACLF,EAAO8B,OAAS9B,EAAOgqC,EAAIloC,GAOrBA","file":"jquery.min.js"}{
    "name": "jquery",
    "description": "JavaScript library for DOM operations",
    "version": "3.1.1",
    "homepage": "http://jquery.com",
    "author": {
        "name": "jQuery Foundation and other contributors",
        "url": "https://github.com/jquery/jquery/blob/master/AUTHORS.txt"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/jquery/jquery.git"
    },
    "keywords": [
        "jquery",
        "javascript",
        "browser",
        "library"
    ],
    "bugs": {
        "url": "http://bugs.jquery.com"
    },
    "licenses": [
        {
            "type": "MIT",
            "url": "https://github.com/jquery/jquery/blob/master/LICENSE.txt"
        }
    ],
    "spm": {
        "main": "jquery.js"
    }
}

jQuery Component
================

Shim [repository](https://github.com/components/jquery) for the [jQuery](http://jquery.com).

If you're looking for jquery-migrate: It got it's [own repository](https://github.com/components/jquery-migrate) since jQuery v3.0.0.

Package Managers
----------------

* [Bower](http://bower.io/): `jquery`
* [Component](https://github.com/component/component): `components/jquery`
* [Composer](http://packagist.org/packages/components/jquery): `components/jquery`
* [spm](http://spmjs.io/package/jquery): `jquery`
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
);
<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
    'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
    '383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
    '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
    'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php',
    '3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php',
    '93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php',
    '2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php',
    'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php',
    'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php',
    '8a9dc1de0ca7e01f3e08231539562f61' => $vendorDir . '/aws/aws-sdk-php/src/functions.php',
    'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php',
    'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
);
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Dropbox' => array($vendorDir . '/dropbox/dropbox-sdk/lib'),
);
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'splitbrain\\PHPArchive\\' => array($vendorDir . '/splitbrain/php-archive/src'),
    'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
    'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'),
    'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'),
    'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'),
    'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'),
    'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'),
    'Sabre\\DAV\\' => array($vendorDir . '/sabre/dav/lib/DAV'),
    'Sabre\\DAVACL\\' => array($vendorDir . '/sabre/dav/lib/DAVACL'),
    'Sabre\\CardDAV\\' => array($vendorDir . '/sabre/dav/lib/CardDAV'),
    'Sabre\\CalDAV\\' => array($vendorDir . '/sabre/dav/lib/CalDAV'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
    'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
    'MicrosoftAzure\\Storage\\' => array($vendorDir . '/microsoft/azure-storage/src'),
    'Mhetreramesh\\Flysystem\\' => array($vendorDir . '/mhetreramesh/flysystem-backblaze/src'),
    'League\\Flysystem\\WebDAV\\' => array($vendorDir . '/league/flysystem-webdav/src'),
    'League\\Flysystem\\Sftp\\' => array($vendorDir . '/league/flysystem-sftp/src'),
    'League\\Flysystem\\Dropbox\\' => array($vendorDir . '/league/flysystem-dropbox/src'),
    'League\\Flysystem\\Azure\\' => array($vendorDir . '/league/flysystem-azure/src'),
    'League\\Flysystem\\AwsS3v3\\' => array($vendorDir . '/league/flysystem-aws-s3-v3/src'),
    'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
    'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'),
    'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
    'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
    'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
    'ChrisWhite\\B2\\' => array($vendorDir . '/cwhite92/b2-sdk-php/src'),
    'Aws\\' => array($vendorDir . '/aws/aws-sdk-php/src'),
);
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit571f9d19802717f7be61d57b40d60b28
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInit571f9d19802717f7be61d57b40d60b28', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit571f9d19802717f7be61d57b40d60b28', 'loadClassLoader'));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit571f9d19802717f7be61d57b40d60b28::getInitializer($loader));
        } else {
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }

            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        $loader->register(true);

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInit571f9d19802717f7be61d57b40d60b28::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire571f9d19802717f7be61d57b40d60b28($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequire571f9d19802717f7be61d57b40d60b28($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit571f9d19802717f7be61d57b40d60b28
{
    public static $files = array (
        'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
        'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
        '383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php',
        '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
        'b067bc7112e384b61c701452d53a14a8' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/JmesPath.php',
        '3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php',
        '93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php',
        '2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php',
        'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php',
        'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php',
        '8a9dc1de0ca7e01f3e08231539562f61' => __DIR__ . '/..' . '/aws/aws-sdk-php/src/functions.php',
        'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php',
        'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
    );

    public static $prefixLengthsPsr4 = array (
        's' => 
        array (
            'splitbrain\\PHPArchive\\' => 22,
        ),
        'p' => 
        array (
            'phpseclib\\' => 10,
        ),
        'S' => 
        array (
            'Sabre\\Xml\\' => 10,
            'Sabre\\VObject\\' => 14,
            'Sabre\\Uri\\' => 10,
            'Sabre\\HTTP\\' => 11,
            'Sabre\\Event\\' => 12,
            'Sabre\\DAV\\' => 10,
            'Sabre\\DAVACL\\' => 13,
            'Sabre\\CardDAV\\' => 14,
            'Sabre\\CalDAV\\' => 13,
        ),
        'P' => 
        array (
            'Psr\\Log\\' => 8,
            'Psr\\Http\\Message\\' => 17,
        ),
        'M' => 
        array (
            'Monolog\\' => 8,
            'MicrosoftAzure\\Storage\\' => 23,
            'Mhetreramesh\\Flysystem\\' => 23,
        ),
        'L' => 
        array (
            'League\\Flysystem\\WebDAV\\' => 24,
            'League\\Flysystem\\Sftp\\' => 22,
            'League\\Flysystem\\Dropbox\\' => 25,
            'League\\Flysystem\\Azure\\' => 23,
            'League\\Flysystem\\AwsS3v3\\' => 25,
            'League\\Flysystem\\' => 17,
        ),
        'J' => 
        array (
            'JmesPath\\' => 9,
        ),
        'G' => 
        array (
            'GuzzleHttp\\Psr7\\' => 16,
            'GuzzleHttp\\Promise\\' => 19,
            'GuzzleHttp\\' => 11,
        ),
        'C' => 
        array (
            'ChrisWhite\\B2\\' => 14,
        ),
        'A' => 
        array (
            'Aws\\' => 4,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'splitbrain\\PHPArchive\\' => 
        array (
            0 => __DIR__ . '/..' . '/splitbrain/php-archive/src',
        ),
        'phpseclib\\' => 
        array (
            0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
        ),
        'Sabre\\Xml\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/xml/lib',
        ),
        'Sabre\\VObject\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/vobject/lib',
        ),
        'Sabre\\Uri\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/uri/lib',
        ),
        'Sabre\\HTTP\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/http/lib',
        ),
        'Sabre\\Event\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/event/lib',
        ),
        'Sabre\\DAV\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/dav/lib/DAV',
        ),
        'Sabre\\DAVACL\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/dav/lib/DAVACL',
        ),
        'Sabre\\CardDAV\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/dav/lib/CardDAV',
        ),
        'Sabre\\CalDAV\\' => 
        array (
            0 => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV',
        ),
        'Psr\\Log\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
        ),
        'Psr\\Http\\Message\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/http-message/src',
        ),
        'Monolog\\' => 
        array (
            0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
        ),
        'MicrosoftAzure\\Storage\\' => 
        array (
            0 => __DIR__ . '/..' . '/microsoft/azure-storage/src',
        ),
        'Mhetreramesh\\Flysystem\\' => 
        array (
            0 => __DIR__ . '/..' . '/mhetreramesh/flysystem-backblaze/src',
        ),
        'League\\Flysystem\\WebDAV\\' => 
        array (
            0 => __DIR__ . '/..' . '/league/flysystem-webdav/src',
        ),
        'League\\Flysystem\\Sftp\\' => 
        array (
            0 => __DIR__ . '/..' . '/league/flysystem-sftp/src',
        ),
        'League\\Flysystem\\Dropbox\\' => 
        array (
            0 => __DIR__ . '/..' . '/league/flysystem-dropbox/src',
        ),
        'League\\Flysystem\\Azure\\' => 
        array (
            0 => __DIR__ . '/..' . '/league/flysystem-azure/src',
        ),
        'League\\Flysystem\\AwsS3v3\\' => 
        array (
            0 => __DIR__ . '/..' . '/league/flysystem-aws-s3-v3/src',
        ),
        'League\\Flysystem\\' => 
        array (
            0 => __DIR__ . '/..' . '/league/flysystem/src',
        ),
        'JmesPath\\' => 
        array (
            0 => __DIR__ . '/..' . '/mtdowling/jmespath.php/src',
        ),
        'GuzzleHttp\\Psr7\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
        ),
        'GuzzleHttp\\Promise\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
        ),
        'GuzzleHttp\\' => 
        array (
            0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
        ),
        'ChrisWhite\\B2\\' => 
        array (
            0 => __DIR__ . '/..' . '/cwhite92/b2-sdk-php/src',
        ),
        'Aws\\' => 
        array (
            0 => __DIR__ . '/..' . '/aws/aws-sdk-php/src',
        ),
    );

    public static $prefixesPsr0 = array (
        'D' => 
        array (
            'Dropbox' => 
            array (
                0 => __DIR__ . '/..' . '/dropbox/dropbox-sdk/lib',
            ),
        ),
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit571f9d19802717f7be61d57b40d60b28::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit571f9d19802717f7be61d57b40d60b28::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInit571f9d19802717f7be61d57b40d60b28::$prefixesPsr0;

        }, null, ClassLoader::class);
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    http://www.php-fig.org/psr/psr-0/
 * @see    http://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    // PSR-4
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();
    private $apcuPrefix;

    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', $this->prefixesPsr0);
        }

        return array();
    }

    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array $classMap Class to filename map
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string       $prefix  The prefix
     * @param array|string $paths   The PSR-0 root directories
     * @param bool         $prepend Whether to prepend the directories
     */
    public function add($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    (array) $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string       $prefix  The prefix/namespace, with trailing '\\'
     * @param array|string $paths   The PSR-4 base directories
     * @param bool         $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    (array) $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                (array) $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string       $prefix The prefix
     * @param array|string $paths  The PSR-0 base directories
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string       $prefix The prefix/namespace, with trailing '\\'
     * @param array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
     * Unregisters this instance as an autoloader.
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return bool|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
[
    {
        "name": "splitbrain/php-archive",
        "version": "1.0.7",
        "version_normalized": "1.0.7.0",
        "source": {
            "type": "git",
            "url": "https://github.com/splitbrain/php-archive.git",
            "reference": "c075304b44c4aadff0718af445e86bf730f331ff"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/splitbrain/php-archive/zipball/c075304b44c4aadff0718af445e86bf730f331ff",
            "reference": "c075304b44c4aadff0718af445e86bf730f331ff",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "require-dev": {
            "phpunit/phpunit": "4.5.*"
        },
        "time": "2015-08-12T13:24:34+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "splitbrain\\PHPArchive\\": "src"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Andreas Gohr",
                "email": "andi@splitbrain.org"
            }
        ],
        "description": "Pure-PHP implementation to read and write TAR and ZIP archives",
        "keywords": [
            "archive",
            "extract",
            "tar",
            "unpack",
            "unzip",
            "zip"
        ]
    },
    {
        "name": "components/jquery",
        "version": "3.1.1",
        "version_normalized": "3.1.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/components/jquery.git",
            "reference": "09a1658378bc1f818856086396ebeab7d0ec2276"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/components/jquery/zipball/09a1658378bc1f818856086396ebeab7d0ec2276",
            "reference": "09a1658378bc1f818856086396ebeab7d0ec2276",
            "shasum": ""
        },
        "time": "2016-09-23T06:06:29+00:00",
        "type": "component",
        "extra": {
            "component": {
                "scripts": [
                    "jquery.js"
                ],
                "files": [
                    "jquery.min.js",
                    "jquery.min.map"
                ]
            }
        },
        "installation-source": "dist",
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "John Resig",
                "email": "jeresig@gmail.com"
            }
        ],
        "description": "jQuery JavaScript Library",
        "homepage": "http://jquery.com"
    },
    {
        "name": "vakata/jstree",
        "version": "3.3.3",
        "version_normalized": "3.3.3.0",
        "source": {
            "type": "git",
            "url": "https://github.com/vakata/jstree.git",
            "reference": "9770c6711f0a155ec494f635aaf0add9b3cfb45c"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/vakata/jstree/zipball/9770c6711f0a155ec494f635aaf0add9b3cfb45c",
            "reference": "9770c6711f0a155ec494f635aaf0add9b3cfb45c",
            "shasum": ""
        },
        "require": {
            "components/jquery": ">=1.9.1"
        },
        "suggest": {
            "robloach/component-installer": "Allows installation of Components via Composer"
        },
        "time": "2016-10-31T09:49:21+00:00",
        "type": "component",
        "extra": {
            "component": {
                "scripts": [
                    "dist/jstree.js"
                ],
                "styles": [
                    "dist/themes/default/style.css"
                ],
                "images": [
                    "dist/themes/default/32px.png",
                    "dist/themes/default/40px.png",
                    "dist/themes/default/throbber.gif"
                ],
                "files": [
                    "dist/jstree.min.js",
                    "dist/themes/default/style.min.css",
                    "dist/themes/default/32px.png",
                    "dist/themes/default/40px.png",
                    "dist/themes/default/throbber.gif"
                ]
            }
        },
        "installation-source": "dist",
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Ivan Bozhanov",
                "email": "jstree@jstree.com"
            }
        ],
        "description": "jsTree is jquery plugin, that provides interactive trees.",
        "homepage": "http://jstree.com"
    },
    {
        "name": "league/flysystem",
        "version": "1.0.32",
        "version_normalized": "1.0.32.0",
        "source": {
            "type": "git",
            "url": "https://github.com/thephpleague/flysystem.git",
            "reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1b5c4a0031697f46e779a9d1b309c2e1b24daeab",
            "reference": "1b5c4a0031697f46e779a9d1b309c2e1b24daeab",
            "shasum": ""
        },
        "require": {
            "php": ">=5.5.9"
        },
        "conflict": {
            "league/flysystem-sftp": "<1.0.6"
        },
        "require-dev": {
            "ext-fileinfo": "*",
            "mockery/mockery": "~0.9",
            "phpspec/phpspec": "^2.2",
            "phpunit/phpunit": "~4.8"
        },
        "suggest": {
            "ext-fileinfo": "Required for MimeType",
            "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
            "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
            "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
            "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
            "league/flysystem-copy": "Allows you to use Copy.com storage",
            "league/flysystem-dropbox": "Allows you to use Dropbox storage",
            "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
            "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
            "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
            "league/flysystem-webdav": "Allows you to use WebDAV storage",
            "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
        },
        "time": "2016-10-19T20:38:46+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.1-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "League\\Flysystem\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Frank de Jonge",
                "email": "info@frenky.net"
            }
        ],
        "description": "Filesystem abstraction: Many filesystems, one API.",
        "keywords": [
            "Cloud Files",
            "WebDAV",
            "abstraction",
            "aws",
            "cloud",
            "copy.com",
            "dropbox",
            "file systems",
            "files",
            "filesystem",
            "filesystems",
            "ftp",
            "rackspace",
            "remote",
            "s3",
            "sftp",
            "storage"
        ]
    },
    {
        "name": "psr/log",
        "version": "1.0.2",
        "version_normalized": "1.0.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/php-fig/log.git",
            "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
            "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "time": "2016-10-10T12:19:37+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Psr\\Log\\": "Psr/Log/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "PHP-FIG",
                "homepage": "http://www.php-fig.org/"
            }
        ],
        "description": "Common interface for logging libraries",
        "homepage": "https://github.com/php-fig/log",
        "keywords": [
            "log",
            "psr",
            "psr-3"
        ]
    },
    {
        "name": "monolog/monolog",
        "version": "1.22.0",
        "version_normalized": "1.22.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/Seldaek/monolog.git",
            "reference": "bad29cb8d18ab0315e6c477751418a82c850d558"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558",
            "reference": "bad29cb8d18ab0315e6c477751418a82c850d558",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0",
            "psr/log": "~1.0"
        },
        "provide": {
            "psr/log-implementation": "1.0.0"
        },
        "require-dev": {
            "aws/aws-sdk-php": "^2.4.9 || ^3.0",
            "doctrine/couchdb": "~1.0@dev",
            "graylog2/gelf-php": "~1.0",
            "jakub-onderka/php-parallel-lint": "0.9",
            "php-amqplib/php-amqplib": "~2.4",
            "php-console/php-console": "^3.1.3",
            "phpunit/phpunit": "~4.5",
            "phpunit/phpunit-mock-objects": "2.3.0",
            "ruflin/elastica": ">=0.90 <3.0",
            "sentry/sentry": "^0.13",
            "swiftmailer/swiftmailer": "~5.3"
        },
        "suggest": {
            "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
            "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
            "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
            "ext-mongo": "Allow sending log messages to a MongoDB server",
            "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
            "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
            "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
            "php-console/php-console": "Allow sending log messages to Google Chrome",
            "rollbar/rollbar": "Allow sending log messages to Rollbar",
            "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
            "sentry/sentry": "Allow sending log messages to a Sentry server"
        },
        "time": "2016-11-26T00:15:39+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Monolog\\": "src/Monolog"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Jordi Boggiano",
                "email": "j.boggiano@seld.be",
                "homepage": "http://seld.be"
            }
        ],
        "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
        "homepage": "http://github.com/Seldaek/monolog",
        "keywords": [
            "log",
            "logging",
            "psr-3"
        ]
    },
    {
        "name": "phpseclib/phpseclib",
        "version": "2.0.4",
        "version_normalized": "2.0.4.0",
        "source": {
            "type": "git",
            "url": "https://github.com/phpseclib/phpseclib.git",
            "reference": "ab8028c93c03cc8d9c824efa75dc94f1db2369bf"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/ab8028c93c03cc8d9c824efa75dc94f1db2369bf",
            "reference": "ab8028c93c03cc8d9c824efa75dc94f1db2369bf",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "require-dev": {
            "phing/phing": "~2.7",
            "phpunit/phpunit": "~4.0",
            "sami/sami": "~2.0",
            "squizlabs/php_codesniffer": "~2.0"
        },
        "suggest": {
            "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
            "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
            "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
            "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
        },
        "time": "2016-10-04T00:57:04+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "files": [
                "phpseclib/bootstrap.php"
            ],
            "psr-4": {
                "phpseclib\\": "phpseclib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Jim Wigginton",
                "email": "terrafrost@php.net",
                "role": "Lead Developer"
            },
            {
                "name": "Patrick Monnerat",
                "email": "pm@datasphere.ch",
                "role": "Developer"
            },
            {
                "name": "Andreas Fischer",
                "email": "bantu@phpbb.com",
                "role": "Developer"
            },
            {
                "name": "Hans-Jürgen Petrich",
                "email": "petrich@tronic-media.com",
                "role": "Developer"
            },
            {
                "name": "Graham Campbell",
                "email": "graham@alt-three.com",
                "role": "Developer"
            }
        ],
        "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
        "homepage": "http://phpseclib.sourceforge.net",
        "keywords": [
            "BigInteger",
            "aes",
            "asn.1",
            "asn1",
            "blowfish",
            "crypto",
            "cryptography",
            "encryption",
            "rsa",
            "security",
            "sftp",
            "signature",
            "signing",
            "ssh",
            "twofish",
            "x.509",
            "x509"
        ]
    },
    {
        "name": "league/flysystem-sftp",
        "version": "1.0.13",
        "version_normalized": "1.0.13.0",
        "source": {
            "type": "git",
            "url": "https://github.com/thephpleague/flysystem-sftp.git",
            "reference": "ae7fb1f30a2db3fa5ca3e7db4b42124d7c3a053a"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/thephpleague/flysystem-sftp/zipball/ae7fb1f30a2db3fa5ca3e7db4b42124d7c3a053a",
            "reference": "ae7fb1f30a2db3fa5ca3e7db4b42124d7c3a053a",
            "shasum": ""
        },
        "require": {
            "league/flysystem": "~1.0",
            "php": ">=5.4.0",
            "phpseclib/phpseclib": "~2.0"
        },
        "require-dev": {
            "mockery/mockery": "0.9.*",
            "phpunit/phpunit": "~4.0"
        },
        "time": "2016-12-08T21:28:01+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "League\\Flysystem\\Sftp\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Frank de Jonge",
                "email": "info@frenky.net"
            }
        ],
        "description": "Flysystem adapter for SFTP"
    },
    {
        "name": "dropbox/dropbox-sdk",
        "version": "v1.1.7",
        "version_normalized": "1.1.7.0",
        "source": {
            "type": "git",
            "url": "https://github.com/dropbox/dropbox-sdk-php.git",
            "reference": "0f1a178ca9c0271bca6426dde8f5a2241578deae"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/dropbox/dropbox-sdk-php/zipball/0f1a178ca9c0271bca6426dde8f5a2241578deae",
            "reference": "0f1a178ca9c0271bca6426dde8f5a2241578deae",
            "shasum": ""
        },
        "require": {
            "ext-curl": "*",
            "php": ">= 5.3"
        },
        "require-dev": {
            "apigen/apigen": "4.1.2",
            "phpunit/phpunit": "~4.0",
            "squizlabs/php_codesniffer": "2.0.0RC3"
        },
        "time": "2016-08-08T23:48:49+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Dropbox": "lib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "description": "Dropbox SDK for PHP",
        "keywords": [
            "dropbox"
        ]
    },
    {
        "name": "league/flysystem-dropbox",
        "version": "1.0.4",
        "version_normalized": "1.0.4.0",
        "source": {
            "type": "git",
            "url": "https://github.com/thephpleague/flysystem-dropbox.git",
            "reference": "939f91ca00d0255d9b3aa313e191480d00f09382"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/thephpleague/flysystem-dropbox/zipball/939f91ca00d0255d9b3aa313e191480d00f09382",
            "reference": "939f91ca00d0255d9b3aa313e191480d00f09382",
            "shasum": ""
        },
        "require": {
            "dropbox/dropbox-sdk": "~1.1",
            "league/flysystem": "~1.0",
            "php": ">=5.4.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.8"
        },
        "time": "2016-04-25T18:51:39+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "League\\Flysystem\\Dropbox\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Frank de Jonge",
                "email": "info@frenky.net"
            }
        ],
        "description": "Flysystem adapter for Dropbox"
    },
    {
        "name": "guzzlehttp/promises",
        "version": "v1.3.1",
        "version_normalized": "1.3.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/guzzle/promises.git",
            "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
            "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
            "shasum": ""
        },
        "require": {
            "php": ">=5.5.0"
        },
        "require-dev": {
            "phpunit/phpunit": "^4.0"
        },
        "time": "2016-12-20T10:07:11+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.4-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "GuzzleHttp\\Promise\\": "src/"
            },
            "files": [
                "src/functions_include.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Michael Dowling",
                "email": "mtdowling@gmail.com",
                "homepage": "https://github.com/mtdowling"
            }
        ],
        "description": "Guzzle promises library",
        "keywords": [
            "promise"
        ]
    },
    {
        "name": "psr/http-message",
        "version": "1.0.1",
        "version_normalized": "1.0.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/php-fig/http-message.git",
            "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
            "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "time": "2016-08-06T14:39:51+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Psr\\Http\\Message\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "PHP-FIG",
                "homepage": "http://www.php-fig.org/"
            }
        ],
        "description": "Common interface for HTTP messages",
        "homepage": "https://github.com/php-fig/http-message",
        "keywords": [
            "http",
            "http-message",
            "psr",
            "psr-7",
            "request",
            "response"
        ]
    },
    {
        "name": "guzzlehttp/psr7",
        "version": "1.3.1",
        "version_normalized": "1.3.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/guzzle/psr7.git",
            "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
            "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b",
            "shasum": ""
        },
        "require": {
            "php": ">=5.4.0",
            "psr/http-message": "~1.0"
        },
        "provide": {
            "psr/http-message-implementation": "1.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.0"
        },
        "time": "2016-06-24T23:00:38+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.4-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "GuzzleHttp\\Psr7\\": "src/"
            },
            "files": [
                "src/functions_include.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Michael Dowling",
                "email": "mtdowling@gmail.com",
                "homepage": "https://github.com/mtdowling"
            }
        ],
        "description": "PSR-7 message implementation",
        "keywords": [
            "http",
            "message",
            "stream",
            "uri"
        ]
    },
    {
        "name": "guzzlehttp/guzzle",
        "version": "6.2.2",
        "version_normalized": "6.2.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/guzzle/guzzle.git",
            "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ebf29dee597f02f09f4d5bbecc68230ea9b08f60",
            "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60",
            "shasum": ""
        },
        "require": {
            "guzzlehttp/promises": "^1.0",
            "guzzlehttp/psr7": "^1.3.1",
            "php": ">=5.5"
        },
        "require-dev": {
            "ext-curl": "*",
            "phpunit/phpunit": "^4.0",
            "psr/log": "^1.0"
        },
        "time": "2016-10-08T15:01:37+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "6.2-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "files": [
                "src/functions_include.php"
            ],
            "psr-4": {
                "GuzzleHttp\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Michael Dowling",
                "email": "mtdowling@gmail.com",
                "homepage": "https://github.com/mtdowling"
            }
        ],
        "description": "Guzzle is a PHP HTTP client library",
        "homepage": "http://guzzlephp.org/",
        "keywords": [
            "client",
            "curl",
            "framework",
            "http",
            "http client",
            "rest",
            "web service"
        ]
    },
    {
        "name": "microsoft/azure-storage",
        "version": "v0.10.2",
        "version_normalized": "0.10.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/Azure/azure-storage-php.git",
            "reference": "27de05b00c1858d6e1c6adbc489efa90e0342f82"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/Azure/azure-storage-php/zipball/27de05b00c1858d6e1c6adbc489efa90e0342f82",
            "reference": "27de05b00c1858d6e1c6adbc489efa90e0342f82",
            "shasum": ""
        },
        "require": {
            "guzzlehttp/guzzle": "~6.0",
            "php": ">=5.5.0"
        },
        "require-dev": {
            "mikey179/vfsstream": "~1.6",
            "pdepend/pdepend": "~2.2",
            "phploc/phploc": "~2.1",
            "phpmd/phpmd": "@stable",
            "phpunit/phpunit": "~4.0",
            "sebastian/phpcpd": "~2.0",
            "squizlabs/php_codesniffer": "2.*",
            "theseer/phpdox": "~0.8"
        },
        "time": "2016-08-19T08:32:57+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "0.10.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "MicrosoftAzure\\Storage\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Azure Storage PHP SDK",
                "email": "dmsh@microsoft.com"
            }
        ],
        "description": "This project provides a set of PHP client libraries that make it easy to access Microsoft Azure storage APIs.",
        "keywords": [
            "azure",
            "php",
            "sdk",
            "storage"
        ]
    },
    {
        "name": "league/flysystem-azure",
        "version": "1.0.4",
        "version_normalized": "1.0.4.0",
        "source": {
            "type": "git",
            "url": "https://github.com/thephpleague/flysystem-azure.git",
            "reference": "0b9838c4f75ee41bc390357b0350e9a62e3b3a1f"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/thephpleague/flysystem-azure/zipball/0b9838c4f75ee41bc390357b0350e9a62e3b3a1f",
            "reference": "0b9838c4f75ee41bc390357b0350e9a62e3b3a1f",
            "shasum": ""
        },
        "require": {
            "league/flysystem": "~1.0",
            "microsoft/azure-storage": "~0.10.1",
            "php": ">=5.5.0"
        },
        "require-dev": {
            "mockery/mockery": "~0.9",
            "phpunit/phpunit": "~4.0"
        },
        "time": "2016-07-10T19:08:39+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "League\\Flysystem\\Azure\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Frank de Jonge",
                "email": "info@frenky.net"
            }
        ],
        "description": "Flysystem adapter for Windows Azure"
    },
    {
        "name": "mtdowling/jmespath.php",
        "version": "2.4.0",
        "version_normalized": "2.4.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/jmespath/jmespath.php.git",
            "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac",
            "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac",
            "shasum": ""
        },
        "require": {
            "php": ">=5.4.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.0"
        },
        "time": "2016-12-03T22:08:25+00:00",
        "bin": [
            "bin/jp.php"
        ],
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "JmesPath\\": "src/"
            },
            "files": [
                "src/JmesPath.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Michael Dowling",
                "email": "mtdowling@gmail.com",
                "homepage": "https://github.com/mtdowling"
            }
        ],
        "description": "Declaratively specify how to extract elements from a JSON document",
        "keywords": [
            "json",
            "jsonpath"
        ]
    },
    {
        "name": "aws/aws-sdk-php",
        "version": "3.22.5",
        "version_normalized": "3.22.5.0",
        "source": {
            "type": "git",
            "url": "https://github.com/aws/aws-sdk-php.git",
            "reference": "081d565ba5c20af78fcde6dcf7fdbfc48739f39f"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/081d565ba5c20af78fcde6dcf7fdbfc48739f39f",
            "reference": "081d565ba5c20af78fcde6dcf7fdbfc48739f39f",
            "shasum": ""
        },
        "require": {
            "guzzlehttp/guzzle": "^5.3.1|^6.2.1",
            "guzzlehttp/promises": "~1.0",
            "guzzlehttp/psr7": "~1.3.1",
            "mtdowling/jmespath.php": "~2.2",
            "php": ">=5.5"
        },
        "require-dev": {
            "andrewsville/php-token-reflection": "^1.4",
            "aws/aws-php-sns-message-validator": "~1.0",
            "behat/behat": "~3.0",
            "doctrine/cache": "~1.4",
            "ext-dom": "*",
            "ext-json": "*",
            "ext-openssl": "*",
            "ext-pcre": "*",
            "ext-simplexml": "*",
            "ext-spl": "*",
            "nette/neon": "^2.3",
            "phpunit/phpunit": "~4.0|~5.0",
            "psr/cache": "^1.0"
        },
        "suggest": {
            "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
            "doctrine/cache": "To use the DoctrineCacheAdapter",
            "ext-curl": "To send requests using cURL",
            "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages"
        },
        "time": "2017-02-15T22:39:19+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Aws\\": "src/"
            },
            "files": [
                "src/functions.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "Apache-2.0"
        ],
        "authors": [
            {
                "name": "Amazon Web Services",
                "homepage": "http://aws.amazon.com"
            }
        ],
        "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
        "homepage": "http://aws.amazon.com/sdkforphp",
        "keywords": [
            "amazon",
            "aws",
            "cloud",
            "dynamodb",
            "ec2",
            "glacier",
            "s3",
            "sdk"
        ]
    },
    {
        "name": "league/flysystem-aws-s3-v3",
        "version": "1.0.13",
        "version_normalized": "1.0.13.0",
        "source": {
            "type": "git",
            "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
            "reference": "dc56a8faf3aff0841f9eae04b6af94a50657896c"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/dc56a8faf3aff0841f9eae04b6af94a50657896c",
            "reference": "dc56a8faf3aff0841f9eae04b6af94a50657896c",
            "shasum": ""
        },
        "require": {
            "aws/aws-sdk-php": "^3.0.0",
            "league/flysystem": "~1.0",
            "php": ">=5.5.0"
        },
        "require-dev": {
            "henrikbjorn/phpspec-code-coverage": "~1.0.1",
            "phpspec/phpspec": "^2.0.0"
        },
        "time": "2016-06-21T21:34:35+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "League\\Flysystem\\AwsS3v3\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Frank de Jonge",
                "email": "info@frenky.net"
            }
        ],
        "description": "Flysystem adapter for the AWS S3 SDK v3.x"
    },
    {
        "name": "cwhite92/b2-sdk-php",
        "version": "v1.2.1",
        "version_normalized": "1.2.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/cwhite92/b2-sdk-php.git",
            "reference": "3a83aedbb5d9c85b33524defef5f1ec7895ed9ac"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/cwhite92/b2-sdk-php/zipball/3a83aedbb5d9c85b33524defef5f1ec7895ed9ac",
            "reference": "3a83aedbb5d9c85b33524defef5f1ec7895ed9ac",
            "shasum": ""
        },
        "require": {
            "guzzlehttp/guzzle": "^6.1",
            "php": ">=5.5.0"
        },
        "require-dev": {
            "phpunit/phpunit": "4.8.*"
        },
        "time": "2016-07-21T02:38:03+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "ChrisWhite\\B2\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Chris White",
                "email": "chris@cwhite.me",
                "homepage": "https://cwhite.me"
            }
        ],
        "description": "A SDK for working with B2 cloud storage.",
        "homepage": "https://github.com/cwhite92/b2-sdk-php",
        "keywords": [
            "b2",
            "backblaze",
            "backup",
            "cloud",
            "filesystem",
            "storage"
        ]
    },
    {
        "name": "mhetreramesh/flysystem-backblaze",
        "version": "1.0.5",
        "version_normalized": "1.0.5.0",
        "source": {
            "type": "git",
            "url": "https://github.com/mhetreramesh/flysystem-backblaze.git",
            "reference": "a5579267ea8fe602008329b8697a2cda822735a1"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/mhetreramesh/flysystem-backblaze/zipball/a5579267ea8fe602008329b8697a2cda822735a1",
            "reference": "a5579267ea8fe602008329b8697a2cda822735a1",
            "shasum": ""
        },
        "require": {
            "cwhite92/b2-sdk-php": "^1.2",
            "league/flysystem": "~1.0",
            "php": ">=5.5.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.0||~5.0",
            "scrutinizer/ocular": "~1.1",
            "squizlabs/php_codesniffer": "~2.3"
        },
        "time": "2016-08-27T07:24:32+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Mhetreramesh\\Flysystem\\": "src"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Ramesh Mhetre",
                "email": "mhetreramesh@gmail.com",
                "homepage": "https://about.me/rameshmhetre",
                "role": "Developer"
            }
        ],
        "description": "Backblaze adapter for the flysystem filesystem abstraction library",
        "homepage": "https://github.com/mhetreramesh/flysystem-backblaze",
        "keywords": [
            "Flysystem",
            "api",
            "backblaze",
            "client",
            "filesystem"
        ]
    },
    {
        "name": "sabre/uri",
        "version": "1.2.1",
        "version_normalized": "1.2.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/fruux/sabre-uri.git",
            "reference": "ada354d83579565949d80b2e15593c2371225e61"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/fruux/sabre-uri/zipball/ada354d83579565949d80b2e15593c2371225e61",
            "reference": "ada354d83579565949d80b2e15593c2371225e61",
            "shasum": ""
        },
        "require": {
            "php": ">=5.4.7"
        },
        "require-dev": {
            "phpunit/phpunit": ">=4.0,<6.0",
            "sabre/cs": "~1.0.0"
        },
        "time": "2017-02-20T19:59:28+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "files": [
                "lib/functions.php"
            ],
            "psr-4": {
                "Sabre\\Uri\\": "lib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Evert Pot",
                "email": "me@evertpot.com",
                "homepage": "http://evertpot.com/",
                "role": "Developer"
            }
        ],
        "description": "Functions for making sense out of URIs.",
        "homepage": "http://sabre.io/uri/",
        "keywords": [
            "rfc3986",
            "uri",
            "url"
        ]
    },
    {
        "name": "sabre/xml",
        "version": "1.5.0",
        "version_normalized": "1.5.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/fruux/sabre-xml.git",
            "reference": "59b20e5bbace9912607481634f97d05a776ffca7"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/fruux/sabre-xml/zipball/59b20e5bbace9912607481634f97d05a776ffca7",
            "reference": "59b20e5bbace9912607481634f97d05a776ffca7",
            "shasum": ""
        },
        "require": {
            "ext-dom": "*",
            "ext-xmlreader": "*",
            "ext-xmlwriter": "*",
            "lib-libxml": ">=2.6.20",
            "php": ">=5.5.5",
            "sabre/uri": ">=1.0,<3.0.0"
        },
        "require-dev": {
            "phpunit/phpunit": "*",
            "sabre/cs": "~1.0.0"
        },
        "time": "2016-10-09T22:57:52+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Sabre\\Xml\\": "lib/"
            },
            "files": [
                "lib/Deserializer/functions.php",
                "lib/Serializer/functions.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Evert Pot",
                "email": "me@evertpot.com",
                "homepage": "http://evertpot.com/",
                "role": "Developer"
            },
            {
                "name": "Markus Staab",
                "email": "markus.staab@redaxo.de",
                "role": "Developer"
            }
        ],
        "description": "sabre/xml is an XML library that you may not hate.",
        "homepage": "https://sabre.io/xml/",
        "keywords": [
            "XMLReader",
            "XMLWriter",
            "dom",
            "xml"
        ]
    },
    {
        "name": "sabre/vobject",
        "version": "4.1.2",
        "version_normalized": "4.1.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/fruux/sabre-vobject.git",
            "reference": "d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c",
            "reference": "d0fde2fafa2a3dad1f559c2d1c2591d4fd75ae3c",
            "shasum": ""
        },
        "require": {
            "ext-mbstring": "*",
            "php": ">=5.5",
            "sabre/xml": ">=1.5 <3.0"
        },
        "require-dev": {
            "phpunit/phpunit": "*",
            "sabre/cs": "^1.0.0"
        },
        "suggest": {
            "hoa/bench": "If you would like to run the benchmark scripts"
        },
        "time": "2016-12-06T04:14:09+00:00",
        "bin": [
            "bin/vobject",
            "bin/generate_vcards"
        ],
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "4.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Sabre\\VObject\\": "lib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Evert Pot",
                "email": "me@evertpot.com",
                "homepage": "http://evertpot.com/",
                "role": "Developer"
            },
            {
                "name": "Dominik Tobschall",
                "email": "dominik@fruux.com",
                "homepage": "http://tobschall.de/",
                "role": "Developer"
            },
            {
                "name": "Ivan Enderlin",
                "email": "ivan.enderlin@hoa-project.net",
                "homepage": "http://mnt.io/",
                "role": "Developer"
            }
        ],
        "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
        "homepage": "http://sabre.io/vobject/",
        "keywords": [
            "availability",
            "freebusy",
            "iCalendar",
            "ical",
            "ics",
            "jCal",
            "jCard",
            "recurrence",
            "rfc2425",
            "rfc2426",
            "rfc2739",
            "rfc4770",
            "rfc5545",
            "rfc5546",
            "rfc6321",
            "rfc6350",
            "rfc6351",
            "rfc6474",
            "rfc6638",
            "rfc6715",
            "rfc6868",
            "vCalendar",
            "vCard",
            "vcf",
            "xCal",
            "xCard"
        ]
    },
    {
        "name": "sabre/event",
        "version": "3.0.0",
        "version_normalized": "3.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/fruux/sabre-event.git",
            "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/fruux/sabre-event/zipball/831d586f5a442dceacdcf5e9c4c36a4db99a3534",
            "reference": "831d586f5a442dceacdcf5e9c4c36a4db99a3534",
            "shasum": ""
        },
        "require": {
            "php": ">=5.5"
        },
        "require-dev": {
            "phpunit/phpunit": "*",
            "sabre/cs": "~0.0.4"
        },
        "time": "2015-11-05T20:14:39+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Sabre\\Event\\": "lib/"
            },
            "files": [
                "lib/coroutine.php",
                "lib/Loop/functions.php",
                "lib/Promise/functions.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Evert Pot",
                "email": "me@evertpot.com",
                "homepage": "http://evertpot.com/",
                "role": "Developer"
            }
        ],
        "description": "sabre/event is a library for lightweight event-based programming",
        "homepage": "http://sabre.io/event/",
        "keywords": [
            "EventEmitter",
            "async",
            "events",
            "hooks",
            "plugin",
            "promise",
            "signal"
        ]
    },
    {
        "name": "sabre/http",
        "version": "4.2.2",
        "version_normalized": "4.2.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/fruux/sabre-http.git",
            "reference": "dd50e7260356f4599d40270826f9548b23efa204"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/fruux/sabre-http/zipball/dd50e7260356f4599d40270826f9548b23efa204",
            "reference": "dd50e7260356f4599d40270826f9548b23efa204",
            "shasum": ""
        },
        "require": {
            "ext-ctype": "*",
            "ext-mbstring": "*",
            "php": ">=5.4",
            "sabre/event": ">=1.0.0,<4.0.0",
            "sabre/uri": "~1.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.3",
            "sabre/cs": "~0.0.1"
        },
        "suggest": {
            "ext-curl": " to make http requests with the Client class"
        },
        "time": "2017-01-02T19:38:42+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "files": [
                "lib/functions.php"
            ],
            "psr-4": {
                "Sabre\\HTTP\\": "lib/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Evert Pot",
                "email": "me@evertpot.com",
                "homepage": "http://evertpot.com/",
                "role": "Developer"
            }
        ],
        "description": "The sabre/http library provides utilities for dealing with http requests and responses. ",
        "homepage": "https://github.com/fruux/sabre-http",
        "keywords": [
            "http"
        ]
    },
    {
        "name": "sabre/dav",
        "version": "3.2.2",
        "version_normalized": "3.2.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/fruux/sabre-dav.git",
            "reference": "e987775e619728f12205606c9cc3ee565ffb1516"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/fruux/sabre-dav/zipball/e987775e619728f12205606c9cc3ee565ffb1516",
            "reference": "e987775e619728f12205606c9cc3ee565ffb1516",
            "shasum": ""
        },
        "require": {
            "ext-ctype": "*",
            "ext-date": "*",
            "ext-dom": "*",
            "ext-iconv": "*",
            "ext-mbstring": "*",
            "ext-pcre": "*",
            "ext-simplexml": "*",
            "ext-spl": "*",
            "lib-libxml": ">=2.7.0",
            "php": ">=5.5.0",
            "psr/log": "^1.0",
            "sabre/event": ">=2.0.0, <4.0.0",
            "sabre/http": "^4.2.1",
            "sabre/uri": "^1.0.1",
            "sabre/vobject": "^4.1.0",
            "sabre/xml": "^1.4.0"
        },
        "require-dev": {
            "evert/phpdoc-md": "~0.1.0",
            "monolog/monolog": "^1.18",
            "phpunit/phpunit": "> 4.8, <6.0.0",
            "sabre/cs": "^1.0.0"
        },
        "suggest": {
            "ext-curl": "*",
            "ext-pdo": "*"
        },
        "time": "2017-02-15T03:06:08+00:00",
        "bin": [
            "bin/sabredav",
            "bin/naturalselection"
        ],
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "3.1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Sabre\\DAV\\": "lib/DAV/",
                "Sabre\\DAVACL\\": "lib/DAVACL/",
                "Sabre\\CalDAV\\": "lib/CalDAV/",
                "Sabre\\CardDAV\\": "lib/CardDAV/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Evert Pot",
                "email": "me@evertpot.com",
                "homepage": "http://evertpot.com/",
                "role": "Developer"
            }
        ],
        "description": "WebDAV Framework for PHP",
        "homepage": "http://sabre.io/",
        "keywords": [
            "CalDAV",
            "CardDAV",
            "WebDAV",
            "framework",
            "iCalendar"
        ]
    },
    {
        "name": "league/flysystem-webdav",
        "version": "1.0.5",
        "version_normalized": "1.0.5.0",
        "source": {
            "type": "git",
            "url": "https://github.com/thephpleague/flysystem-webdav.git",
            "reference": "5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/thephpleague/flysystem-webdav/zipball/5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7",
            "reference": "5fb6f5a45e5f2a5519a26032d98ad7d7c65f81d7",
            "shasum": ""
        },
        "require": {
            "league/flysystem": "~1.0",
            "php": ">=5.5.0",
            "sabre/dav": "~3.1"
        },
        "require-dev": {
            "mockery/mockery": "~0.9",
            "phpunit/phpunit": "~4.0"
        },
        "time": "2016-12-14T11:28:55+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "League\\Flysystem\\WebDAV\\": "src"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Frank de Jonge",
                "email": "info@frenky.net"
            }
        ],
        "description": "Flysystem adapter for WebDAV"
    }
]

Copyright (c) 2016 Nils Adermann, Jordi Boggiano

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.

/vendor
composer.lock
language: php

php:
  - '5.5'
  - '5.6'
  - '7.0'
  - hhvm
  - nightly

before_script: composer install
{
    "name": "cwhite92/b2-sdk-php",
    "description": "A SDK for working with B2 cloud storage.",
    "keywords": ["b2", "storage", "backblaze", "cloud", "filesystem", "backup"],
    "homepage": "https://github.com/cwhite92/b2-sdk-php",
    "license": "MIT",
    "type": "library",
    "authors": [
        {
            "name": "Chris White",
            "email": "chris@cwhite.me",
            "homepage": "https://cwhite.me"
        }
    ],
    "require": {
        "php": ">=5.5.0",
        "guzzlehttp/guzzle": "^6.1"
    },
    "require-dev": {
        "phpunit/phpunit": "4.8.*"
    },
    "autoload": {
        "psr-4": {
            "ChrisWhite\\B2\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "ChrisWhite\\B2\\Tests\\": "tests/"
        }
    }
}
The MIT License (MIT)

Copyright (c) 2016 Chris White

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.
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="./vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false"
>
    <testsuites>
        <testsuite name="b2-sdk-php tests">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>
</phpunit>
## Backblaze B2 SDK for PHP
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Latest Version](https://img.shields.io/github/release/cwhite92/b2-sdk-php.svg?style=flat-square)](https://github.com/cwhite92/b2-sdk-php/releases)
[![SensioLabs Rating](https://img.shields.io/sensiolabs/i/d5e44d75-84d2-40c7-b0d4-7f628429e139.svg?style=flat-square)](https://insight.sensiolabs.com/projects/d5e44d75-84d2-40c7-b0d4-7f628429e139)
[![Build Status](https://img.shields.io/travis/cwhite92/b2-sdk-php.svg?style=flat-square)](https://travis-ci.org/cwhite92/b2-sdk-php)

`b2-sdk-php` is a client library for working with Backblaze's B2 storage service. It aims to make using the service as
easy as possible by exposing a clear API and taking influence from other SDKs that you may be familiar with.

## Example

This is just a short example, full examples to come on the wiki.

```php
use ChrisWhite\B2\Client;
use ChrisWhite\B2\Bucket;

$client = new Client('accountId', 'applicationKey');

// Returns a Bucket object.
$bucket = $client->createBucket([
    'BucketName' => 'my-special-bucket',
    'BucketType' => Bucket::TYPE_PRIVATE // or TYPE_PUBLIC
]);

// Change the bucket to private. Also returns a Bucket object.
$updatedBucket = $client->updateBucket([
    'BucketId' => $bucket->getId(),
    'BucketType' => Bucket::TYPE_PUBLIC
]);

// Retrieve an array of Bucket objects on your account.
$buckets = $client->listBuckets();

// Delete a bucket.
$client->deleteBucket([
    'BucketId' => '4c2b957661da9c825f465e1b'
]);

// Upload a file to a bucket. Returns a File object.
$file = $client->upload([
    'BucketName' => 'my-special-bucket',
    'FileName' => 'path/to/upload/to',
    'Body' => 'I am the file content'

    // The file content can also be provided via a resource.
    // 'Body' => fopen('/path/to/input', 'r')
]);

// Download a file from a bucket. Returns the file content.
$fileContent = $client->download([
    'FileId' => $file->getId()

    // Can also identify the file via bucket and path:
    // 'BucketName' => 'my-special-bucket',
    // 'FileName' => 'path/to/file'

    // Can also save directly to a location on disk. This will cause download() to not return file content.
    // 'SaveAs' => '/path/to/save/location'
]);
```

## Installation

Installation is via Composer:

```bash
$ composer require cwhite92/b2-sdk-php
```

## Tests

Tests are run with PHPUnit. After installing PHPUnit via Composer:

```bash
$ vendor/bin/phpunit
```

## Contributors

Feel free to contribute in any way you can whether that be reporting issues, making suggestions or sending PRs. :)

## License

MIT.
<?php

namespace ChrisWhite\B2;

class Bucket
{
    const TYPE_PUBLIC = 'allPublic';
    const TYPE_PRIVATE = 'allPrivate';

    protected $id;
    protected $name;
    protected $type;

    /**
     * Bucket constructor.
     *
     * @param $id
     * @param $name
     * @param $type
     */
    public function __construct($id, $name, $type)
    {
        $this->id = $id;
        $this->name = $name;
        $this->type = $type;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getType()
    {
        return $this->type;
    }
}
<?php

namespace ChrisWhite\B2;

use ChrisWhite\B2\Exceptions\NotFoundException;
use ChrisWhite\B2\Exceptions\ValidationException;
use ChrisWhite\B2\Http\Client as HttpClient;

class Client
{
    protected $accountId;
    protected $applicationKey;

    protected $authToken;
    protected $apiUrl;
    protected $downloadUrl;

    protected $client;

    /**
     * Client constructor. Accepts the account ID, application key and an optional array of options.
     *
     * @param $accountId
     * @param $applicationKey
     * @param array $options
     */
    public function __construct($accountId, $applicationKey, array $options = [])
    {
        $this->accountId = $accountId;
        $this->applicationKey = $applicationKey;

        if (isset($options['client'])) {
            $this->client = $options['client'];
        } else {
            $this->client = new HttpClient(['exceptions' => false]);
        }

        $this->authorizeAccount();
    }

    /**
     * Create a bucket with the given name and type.
     *
     * @param array $options
     * @return Bucket
     * @throws ValidationException
     */
    public function createBucket(array $options)
    {
        if (!in_array($options['BucketType'], [Bucket::TYPE_PUBLIC, Bucket::TYPE_PRIVATE])) {
            throw new ValidationException(
                sprintf('Bucket type must be %s or %s', Bucket::TYPE_PRIVATE, Bucket::TYPE_PUBLIC)
            );
        }

        $response = $this->client->request('POST', $this->apiUrl.'/b2_create_bucket', [
            'headers' => [
                'Authorization' => $this->authToken,
            ],
            'json' => [
                'accountId' => $this->accountId,
                'bucketName' => $options['BucketName'],
                'bucketType' => $options['BucketType']
            ]
        ]);

        return new Bucket($response['bucketId'], $response['bucketName'], $response['bucketType']);
    }

    /**
     * Updates the type attribute of a bucket by the given ID.
     *
     * @param array $options
     * @return Bucket
     * @throws ValidationException
     */
    public function updateBucket(array $options)
    {
        if (!in_array($options['BucketType'], [Bucket::TYPE_PUBLIC, Bucket::TYPE_PRIVATE])) {
            throw new ValidationException(
                sprintf('Bucket type must be %s or %s', Bucket::TYPE_PRIVATE, Bucket::TYPE_PUBLIC)
            );
        }

        if (!isset($options['BucketId']) && isset($options['BucketName'])) {
            $options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
        }

        $response = $this->client->request('POST', $this->apiUrl.'/b2_update_bucket', [
            'headers' => [
                'Authorization' => $this->authToken,
            ],
            'json' => [
                'accountId' => $this->accountId,
                'bucketId' => $options['BucketId'],
                'bucketType' => $options['BucketType']
            ]
        ]);

        return new Bucket($response['bucketId'], $response['bucketName'], $response['bucketType']);
    }

    /**
     * Returns a list of bucket objects representing the buckets on the account.
     *
     * @return array
     */
    public function listBuckets()
    {
        $buckets = [];

        $response = $this->client->request('POST', $this->apiUrl.'/b2_list_buckets', [
            'headers' => [
                'Authorization' => $this->authToken,
            ],
            'json' => [
                'accountId' => $this->accountId
            ]
        ]);

        foreach ($response['buckets'] as $bucket) {
            $buckets[] = new Bucket($bucket['bucketId'], $bucket['bucketName'], $bucket['bucketType']);
        }

        return $buckets;
    }

    /**
     * Deletes the bucket identified by its ID.
     *
     * @param array $options
     * @return bool
     */
    public function deleteBucket(array $options)
    {
        if (!isset($options['BucketId']) && isset($options['BucketName'])) {
            $options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
        }

        $this->client->request('POST', $this->apiUrl.'/b2_delete_bucket', [
            'headers' => [
                'Authorization' => $this->authToken
            ],
            'json' => [
                'accountId' => $this->accountId,
                'bucketId' => $options['BucketId']
            ]
        ]);

        return true;
    }

    /**
     * Uploads a file to a bucket and returns a File object.
     *
     * @param array $options
     * @return File
     */
    public function upload(array $options)
    {
        // Clean the path if it starts with /.
        if (substr($options['FileName'], 0, 1) === '/') {
            $options['FileName'] = ltrim($options['FileName'], '/');
        }

        if (!isset($options['BucketId']) && isset($options['BucketName'])) {
            $options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
        }

        // Retrieve the URL that we should be uploading to.
        $response = $this->client->request('POST', $this->apiUrl.'/b2_get_upload_url', [
            'headers' => [
                'Authorization' => $this->authToken
            ],
            'json' => [
                'bucketId' => $options['BucketId']
            ]
        ]);

        $uploadEndpoint = $response['uploadUrl'];
        $uploadAuthToken = $response['authorizationToken'];

        if (is_resource($options['Body'])) {
            // We need to calculate the file's hash incrementally from the stream.
            $context = hash_init('sha1');
            hash_update_stream($context, $options['Body']);
            $hash = hash_final($context);

            // Similarly, we have to use fstat to get the size of the stream.
            $size = fstat($options['Body'])['size'];
            
            // Rewind the stream before passing it to the HTTP client.
            rewind($options['Body']);
        } else {
            // We've been given a simple string body, it's super simple to calculate the hash and size.
            $hash = sha1($options['Body']);
            $size = mb_strlen($options['Body']);
        }

        if (!isset($options['FileLastModified'])) {
            $options['FileLastModified'] = round(microtime(true) * 1000);
        }

        if (!isset($options['FileContentType'])) {
            $options['FileContentType'] = 'b2/x-auto';
        }

        $response = $this->client->request('POST', $uploadEndpoint, [
            'headers' => [
                'Authorization' => $uploadAuthToken,
                'Content-Type' => $options['FileContentType'],
                'Content-Length' => $size,
                'X-Bz-File-Name' => $options['FileName'],
                'X-Bz-Content-Sha1' => $hash,
                'X-Bz-Info-src_last_modified_millis' => $options['FileLastModified']
            ],
            'body' => $options['Body']
        ]);

        return new File(
            $response['fileId'],
            $response['fileName'],
            $response['contentSha1'],
            $response['contentLength'],
            $response['contentType'],
            $response['fileInfo']
        );
    }

    /**
     * Download a file from a B2 bucket.
     *
     * @param array $options
     * @return bool|mixed|string
     */
    public function download(array $options)
    {
        $requestUrl = null;
        $requestOptions = [
            'headers' => [
                'Authorization' => $this->authToken
            ],
            'sink' => isset($options['SaveAs']) ? $options['SaveAs'] : null
        ];

        if (isset($options['FileId'])) {
            $requestOptions['query'] = ['fileId' => $options['FileId']];
            $requestUrl = $this->downloadUrl.'/b2api/v1/b2_download_file_by_id';
        } else {
            if (!isset($options['BucketName']) && isset($options['BucketId'])) {
                $options['BucketName'] = $this->getBucketNameFromId($options['BucketId']);
            }

            $requestUrl = sprintf('%s/file/%s/%s', $this->downloadUrl, $options['BucketName'], $options['FileName']);
        }

        $response = $this->client->request('GET', $requestUrl, $requestOptions, false);

        return isset($options['SaveAs']) ? true : $response;
    }

    /**
     * Retrieve a collection of File objects representing the files stored inside a bucket.
     *
     * @param array $options
     * @return array
     */
    public function listFiles(array $options)
    {
        // if FileName is set, we only attempt to retrieve information about that single file.
        $fileName = !empty($options['FileName']) ? $options['FileName'] : null;

        $nextFileName = null;
        $maxFileCount = 1000;
        $files = [];

        if (!isset($options['BucketId']) && isset($options['BucketName'])) {
            $options['BucketId'] = $this->getBucketIdFromName($options['BucketName']);
        }

        if ($fileName) {
            $nextFileName = $fileName;
            $maxFileCount = 1;
        }

        // B2 returns, at most, 1000 files per "page". Loop through the pages and compile an array of File objects.
        while (true) {
            $response = $this->client->request('POST', $this->apiUrl.'/b2_list_file_names', [
                'headers' => [
                    'Authorization' => $this->authToken
                ],
                'json' => [
                    'bucketId' => $options['BucketId'],
                    'startFileName' => $nextFileName,
                    'maxFileCount' => $maxFileCount,
                ]
            ]);

            foreach ($response['files'] as $file) {
                // if we have a file name set, only retrieve information if the file name matches
                if (!$fileName || ($fileName === $file['fileName'])) {
                    $files[] = new File($file['fileId'], $file['fileName'], null, $file['size']);
                }
            }

            if ($fileName || $response['nextFileName'] === null) {
                // We've got all the files - break out of loop.
                break;
            }

            $nextFileName = $response['nextFileName'];
        }

        return $files;
    }

    /**
     * Test whether a file exists in B2 for the given bucket.
     *
     * @param array $options
     * @return boolean
     */
    public function fileExists(array $options)
    {
        $files = $this->listFiles($options);

        return !empty($files);
    }


    /**
     * Returns a single File object representing a file stored on B2.
     *
     * @param array $options
     * @throws NotFoundException If no file id was provided and BucketName + FileName does not resolve to a file, a NotFoundException is thrown.
     * @return File
     */
    public function getFile(array $options)
    {
        if (!isset($options['FileId']) && isset($options['BucketName']) && isset($options['FileName'])) {
            $options['FileId'] = $this->getFileIdFromBucketAndFileName($options['BucketName'], $options['FileName']);

            if (!$options['FileId']) {
                throw new NotFoundException();
            }
        }

        $response = $this->client->request('POST', $this->apiUrl.'/b2_get_file_info', [
            'headers' => [
                'Authorization' => $this->authToken
            ],
            'json' => [
                'fileId' => $options['FileId']
            ]
        ]);

        return new File(
            $response['fileId'],
            $response['fileName'],
            $response['contentSha1'],
            $response['contentLength'],
            $response['contentType'],
            $response['fileInfo'],
            $response['bucketId'],
            $response['action'],
            $response['uploadTimestamp']
        );
    }

    /**
     * Deletes the file identified by ID from Backblaze B2.
     *
     * @param array $options
     * @return bool
     */
    public function deleteFile(array $options)
    {
        if (!isset($options['FileName'])) {
            $file = $this->getFile($options);

            $options['FileName'] = $file->getName();
        }

        if (!isset($options['FileId']) && isset($options['BucketName']) && isset($options['FileName'])) {
            $file = $this->getFile($options);

            $options['FileId'] = $file->getId();
        }

        $this->client->request('POST', $this->apiUrl.'/b2_delete_file_version', [
            'headers' => [
                'Authorization' => $this->authToken
            ],
            'json' => [
                'fileName' => $options['FileName'],
                'fileId' => $options['FileId']
            ]
        ]);

        return true;
    }

    /**
     * Authorize the B2 account in order to get an auth token and API/download URLs.
     *
     * @throws \Exception
     */
    protected function authorizeAccount()
    {
        $response = $this->client->request('GET', 'https://api.backblaze.com/b2api/v1/b2_authorize_account', [
            'auth' => [$this->accountId, $this->applicationKey]
        ]);

        $this->authToken = $response['authorizationToken'];
        $this->apiUrl = $response['apiUrl'].'/b2api/v1';
        $this->downloadUrl = $response['downloadUrl'];
    }

    /**
     * Maps the provided bucket name to the appropriate bucket ID.
     *
     * @param $name
     * @return null
     */
    protected function getBucketIdFromName($name)
    {
        $buckets = $this->listBuckets();

        foreach ($buckets as $bucket) {
            if ($bucket->getName() === $name) {
                return $bucket->getId();
            }
        }

        return null;
    }

    /**
     * Maps the provided bucket ID to the appropriate bucket name.
     *
     * @param $id
     * @return null
     */
    protected function getBucketNameFromId($id)
    {
        $buckets = $this->listBuckets();

        foreach ($buckets as $bucket) {
            if ($bucket->getId() === $id) {
                return $bucket->getName();
            }
        }

        return null;
    }

    protected function getFileIdFromBucketAndFileName($bucketName, $fileName)
    {
        $files = $this->listFiles([
            'BucketName' => $bucketName,
            'FileName' => $fileName,
        ]);

        foreach ($files as $file) {
            if ($file->getName() === $fileName) {
                return $file->getId();
            }
        }

        return null;
    }
}
<?php

namespace ChrisWhite\B2;

use ChrisWhite\B2\Exceptions\B2Exception;
use ChrisWhite\B2\Exceptions\BadJsonException;
use ChrisWhite\B2\Exceptions\BadValueException;
use ChrisWhite\B2\Exceptions\BucketAlreadyExistsException;
use ChrisWhite\B2\Exceptions\NotFoundException;
use ChrisWhite\B2\Exceptions\FileNotPresentException;
use ChrisWhite\B2\Exceptions\BucketNotEmptyException;
use GuzzleHttp\Psr7\Response;

class ErrorHandler
{
    protected static $mappings = [
        'bad_json' => BadJsonException::class,
        'bad_value' => BadValueException::class,
        'duplicate_bucket_name' => BucketAlreadyExistsException::class,
        'not_found' => NotFoundException::class,
        'file_not_present' => FileNotPresentException::class,
        'cannot_delete_non_empty_bucket' => BucketNotEmptyException::class
    ];

    public static function handleErrorResponse(Response $response)
    {
        $responseJson = json_decode($response->getBody(), true);

        if (isset(self::$mappings[$responseJson['code']])) {
            $exceptionClass = self::$mappings[$responseJson['code']];
        } else {
            // We don't have an exception mapped to this response error, throw generic exception
            $exceptionClass = B2Exception::class;
        }

        throw new $exceptionClass('Received error from B2: '.$responseJson['message']);
    }
}
<?php

namespace ChrisWhite\B2\Exceptions;

class B2Exception extends \Exception
{
}
<?php

namespace ChrisWhite\B2\Exceptions;

class BadJsonException extends \Exception
{
}
<?php

namespace ChrisWhite\B2\Exceptions;

class BadValueException extends \Exception
{
}
<?php

namespace ChrisWhite\B2\Exceptions;

class BucketAlreadyExistsException extends \Exception
{
}
<?php

namespace ChrisWhite\B2\Exceptions;

class BucketNotEmptyException extends \Exception
{
}
<?php

namespace ChrisWhite\B2\Exceptions;

class FileNotPresentException extends \Exception
{
}
<?php

namespace ChrisWhite\B2\Exceptions;

class NotFoundException extends \Exception
{
}
<?php

namespace ChrisWhite\B2\Exceptions;

class ValidationException extends \Exception
{
}
<?php

namespace ChrisWhite\B2;

class File
{
    protected $id;
    protected $name;
    protected $hash;
    protected $size;
    protected $type;
    protected $info;
    protected $bucketId;
    protected $action;
    protected $uploadTimestamp;

    /**
     * File constructor.
     *
     * @param $id
     * @param $name
     * @param $hash
     * @param $size
     * @param $type
     * @param $info
     * @param $bucketId
     * @param $action
     * @param $uploadTimestamp
     */
    public function __construct($id, $name, $hash = null, $size = null, $type = null, $info = null, $bucketId = null, $action = null, $uploadTimestamp = null)
    {
        $this->id = $id;
        $this->name = $name;
        $this->hash = $hash;
        $this->size = $size;
        $this->type = $type;
        $this->info = $info;
        $this->bucketId = $bucketId;
        $this->action = $action;
        $this->uploadTimestamp = $uploadTimestamp;
    }

    /**
     * @return string
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return string
     */
    public function getHash()
    {
        return $this->hash;
    }

    /**
     * @return int
     */
    public function getSize()
    {
        return $this->size;
    }

    /**
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @return array
     */
    public function getInfo()
    {
        return $this->info;
    }

    /**
     * @return string
     */
    public function getBucketId()
    {
        return $this->bucketId;
    }

    /**
     * @return string
     */
    public function getAction()
    {
        return $this->action;
    }

    /**
     * @return string
     */
    public function getUploadTimestamp()
    {
        return $this->uploadTimestamp;
    }
}
<?php

namespace ChrisWhite\B2\Http;

use ChrisWhite\B2\ErrorHandler;
use GuzzleHttp\Client as GuzzleClient;

/**
 * Client wrapper around Guzzle.
 *
 * @package ChrisWhite\B2\Http
 */
class Client extends GuzzleClient
{
    /**
     * Sends a response to the B2 API, automatically handling decoding JSON and errors.
     *
     * @param string $method
     * @param null $uri
     * @param array $options
     * @param bool $asJson
     * @return mixed|string
     */
    public function request($method, $uri = null, array $options = [], $asJson = true)
    {
        $response = parent::request($method, $uri, $options);

        if ($response->getStatusCode() !== 200) {
            ErrorHandler::handleErrorResponse($response);
        }

        if ($asJson) {
            return json_decode($response->getBody(), true);
        }

        return $response->getBody()->getContents();
    }
}
<?php

namespace ChrisWhite\B2\Tests;

use ChrisWhite\B2\Client;
use ChrisWhite\B2\Bucket;
use ChrisWhite\B2\Exceptions\BadValueException;
use ChrisWhite\B2\Exceptions\BucketNotEmptyException;
use ChrisWhite\B2\Exceptions\NotFoundException;
use ChrisWhite\B2\File;
use ChrisWhite\B2\Exceptions\BucketAlreadyExistsException;
use ChrisWhite\B2\Exceptions\BadJsonException;
use ChrisWhite\B2\Exceptions\ValidationException;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Stream;

class ClientTest extends \PHPUnit_Framework_TestCase
{
    use TestHelper;

    public function testCreatePublicBucket()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'create_bucket_public.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        // Test that we get a public bucket back after creation
        $bucket = $client->createBucket([
            'BucketName' => 'Test bucket',
            'BucketType' => Bucket::TYPE_PUBLIC
        ]);
        $this->assertInstanceOf(Bucket::class, $bucket);
        $this->assertEquals('Test bucket', $bucket->getName());
        $this->assertEquals(Bucket::TYPE_PUBLIC, $bucket->getType());
    }

    public function testCreatePrivateBucket()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'create_bucket_private.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        // Test that we get a private bucket back after creation
        $bucket = $client->createBucket([
            'BucketName' => 'Test bucket',
            'BucketType' => Bucket::TYPE_PRIVATE
        ]);
        $this->assertInstanceOf(Bucket::class, $bucket);
        $this->assertEquals('Test bucket', $bucket->getName());
        $this->assertEquals(Bucket::TYPE_PRIVATE, $bucket->getType());
    }

    public function testBucketAlreadyExistsExceptionThrown()
    {
        $this->setExpectedException(BucketAlreadyExistsException::class);

        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(400, [], 'create_bucket_exists.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);
        $client->createBucket([
            'BucketName' => 'I already exist',
            'BucketType' => Bucket::TYPE_PRIVATE
        ]);
    }

    public function testInvalidBucketTypeThrowsException()
    {
        $this->setExpectedException(ValidationException::class);

        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);
        $client->createBucket([
            'BucketName' => 'Test bucket',
            'BucketType' => 'i am not valid'
        ]);
    }

    public function testUpdateBucketToPrivate()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'update_bucket_to_private.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $bucket = $client->updateBucket([
            'BucketId' => 'bucketId',
            'BucketType' => Bucket::TYPE_PRIVATE
        ]);

        $this->assertInstanceOf(Bucket::class, $bucket);
        $this->assertEquals('bucketId', $bucket->getId());
        $this->assertEquals(Bucket::TYPE_PRIVATE, $bucket->getType());
    }

    public function testUpdateBucketToPublic()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'update_bucket_to_public.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $bucket = $client->updateBucket([
            'BucketId' => 'bucketId',
            'BucketType' => Bucket::TYPE_PUBLIC
        ]);

        $this->assertInstanceOf(Bucket::class, $bucket);
        $this->assertEquals('bucketId', $bucket->getId());
        $this->assertEquals(Bucket::TYPE_PUBLIC, $bucket->getType());
    }

    public function testList3Buckets()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'list_buckets_3.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $buckets = $client->listBuckets();
        $this->assertInternalType('array', $buckets);
        $this->assertCount(3, $buckets);
        $this->assertInstanceOf(Bucket::class, $buckets[0]);
    }

    public function testEmptyArrayWithNoBuckets()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'list_buckets_0.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $buckets = $client->listBuckets();
        $this->assertInternalType('array', $buckets);
        $this->assertCount(0, $buckets);
    }

    public function testDeleteBucket()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'delete_bucket.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $this->assertTrue($client->deleteBucket([
            'BucketId' => 'bucketId'
        ]));
    }

    public function testBadJsonThrownDeletingNonExistentBucket()
    {
        $this->setExpectedException(BadJsonException::class);

        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(400, [], 'delete_bucket_non_existent.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $client->deleteBucket([
            'BucketId' => 'bucketId'
        ]);
    }

    public function testBucketNotEmptyThrownDeletingNonEmptyBucket()
    {
        $this->setExpectedException(BucketNotEmptyException::class);

        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(400, [], 'bucket_not_empty.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $client->deleteBucket([
            'BucketId' => 'bucketId'
        ]);
    }

    public function testUploadingResource()
    {
        $container = [];
        $history = Middleware::history($container);
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'get_upload_url.json'),
            $this->buildResponseFromStub(200, [], 'upload.json')
        ], $history);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        // Set up the resource being uploaded.
        $content = 'The quick brown box jumps over the lazy dog';
        $resource = fopen('php://memory', 'r+');
        fwrite($resource, $content);
        rewind($resource);

        $file = $client->upload([
            'BucketId' => 'bucketId',
            'FileName' => 'test.txt',
            'Body' => $resource
        ]);

        $this->assertInstanceOf(File::class, $file);

        // We'll also check the Guzzle history to make sure the upload request got created correctly.
        $uploadRequest = $container[2]['request'];
        $this->assertEquals('uploadUrl', $uploadRequest->getRequestTarget());
        $this->assertEquals('authToken', $uploadRequest->getHeader('Authorization')[0]);
        $this->assertEquals(strlen($content), $uploadRequest->getHeader('Content-Length')[0]);
        $this->assertEquals('test.txt', $uploadRequest->getHeader('X-Bz-File-Name')[0]);
        $this->assertEquals(sha1($content), $uploadRequest->getHeader('X-Bz-Content-Sha1')[0]);
        $this->assertEquals(round(microtime(true) * 1000), $uploadRequest->getHeader('X-Bz-Info-src_last_modified_millis')[0], '', 100);
        $this->assertInstanceOf(Stream::class, $uploadRequest->getBody());
    }

    public function testUploadingString()
    {
        $container = [];
        $history = Middleware::history($container);
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'get_upload_url.json'),
            $this->buildResponseFromStub(200, [], 'upload.json')
        ], $history);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $content = 'The quick brown box jumps over the lazy dog';

        $file = $client->upload([
            'BucketId' => 'bucketId',
            'FileName' => 'test.txt',
            'Body' => $content
        ]);

        $this->assertInstanceOf(File::class, $file);

        // We'll also check the Guzzle history to make sure the upload request got created correctly.
        $uploadRequest = $container[2]['request'];
        $this->assertEquals('uploadUrl', $uploadRequest->getRequestTarget());
        $this->assertEquals('authToken', $uploadRequest->getHeader('Authorization')[0]);
        $this->assertEquals(strlen($content), $uploadRequest->getHeader('Content-Length')[0]);
        $this->assertEquals('test.txt', $uploadRequest->getHeader('X-Bz-File-Name')[0]);
        $this->assertEquals(sha1($content), $uploadRequest->getHeader('X-Bz-Content-Sha1')[0]);
        $this->assertEquals(round(microtime(true) * 1000), $uploadRequest->getHeader('X-Bz-Info-src_last_modified_millis')[0], '', 100);
        $this->assertInstanceOf(Stream::class, $uploadRequest->getBody());
    }

    public function testUploadingWithCustomContentTypeAndLastModified()
    {
        $container = [];
        $history = Middleware::history($container);
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'get_upload_url.json'),
            $this->buildResponseFromStub(200, [], 'upload.json')
        ], $history);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        // My birthday :)
        $lastModified =  701568000000;
        $contentType = 'text/plain';

        $file = $client->upload([
            'BucketId' => 'bucketId',
            'FileName' => 'test.txt',
            'Body' => 'Test file content',
            'FileContentType' => $contentType,
            'FileLastModified' => $lastModified
        ]);

        $this->assertInstanceOf(File::class, $file);

        // We'll also check the Guzzle history to make sure the upload request got created correctly.
        $uploadRequest = $container[2]['request'];
        $this->assertEquals($lastModified, $uploadRequest->getHeader('X-Bz-Info-src_last_modified_millis')[0]);
        $this->assertEquals($contentType, $uploadRequest->getHeader('Content-Type')[0]);
        $this->assertInstanceOf(Stream::class, $uploadRequest->getBody());
    }

    public function testDownloadByIdWithoutSavePath()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'download_content')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $fileContent = $client->download([
            'FileId' => 'fileId'
        ]);

        $this->assertEquals($fileContent, 'The quick brown fox jumps over the lazy dog');
    }

    public function testDownloadByIdWithSavePath()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'download_content')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $client->download([
            'FileId' => 'fileId',
            'SaveAs' => __DIR__.'/test.txt'
        ]);

        $this->assertFileExists(__DIR__.'/test.txt');
        $this->assertEquals('The quick brown fox jumps over the lazy dog', file_get_contents(__DIR__.'/test.txt'));

        unlink(__DIR__.'/test.txt');
    }

    public function testDownloadingByIncorrectIdThrowsException()
    {
        $this->setExpectedException(BadValueException::class);

        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(400, [], 'download_by_incorrect_id.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $client->download([
            'FileId' => 'incorrect'
        ]);
    }

    public function testDownloadByPathWithoutSavePath()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'download_content')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $fileContent = $client->download([
            'BucketName' => 'test-bucket',
            'FileName' => 'test.txt'
        ]);

        $this->assertEquals($fileContent, 'The quick brown fox jumps over the lazy dog');
    }

    public function testDownloadByPathWithSavePath()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'download_content')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $client->download([
            'BucketName' => 'test-bucket',
            'FileName' => 'test.txt',
            'SaveAs' => __DIR__.'/test.txt'
        ]);

        $this->assertFileExists(__DIR__.'/test.txt');
        $this->assertEquals('The quick brown fox jumps over the lazy dog', file_get_contents(__DIR__.'/test.txt'));

        unlink(__DIR__.'/test.txt');
    }

    public function testDownloadingByIncorrectPathThrowsException()
    {
        $this->setExpectedException(NotFoundException::class);

        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(400, [], 'download_by_incorrect_path.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $client->download([
            'BucketName' => 'test-bucket',
            'FileName' => 'path/to/incorrect/file.txt'
        ]);
    }

    public function testListFilesHandlesMultiplePages()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'list_files_page1.json'),
            $this->buildResponseFromStub(200, [], 'list_files_page2.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $files = $client->listFiles([
            'BucketId' => 'bucketId'
        ]);

        $this->assertInternalType('array', $files);
        $this->assertInstanceOf(File::class, $files[0]);
        $this->assertCount(1500, $files);
    }

    public function testListFilesReturnsEmptyArrayWithNoFiles()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'list_files_empty.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $files = $client->listFiles([
            'BucketId' => 'bucketId'
        ]);

        $this->assertInternalType('array', $files);
        $this->assertCount(0, $files);
    }

    public function testGetFile()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'get_file.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $file = $client->getFile([
            'FileId' => 'fileId'
        ]);

        $this->assertInstanceOf(File::class, $file);
    }

    public function testGettingNonExistentFileThrowsException()
    {
        $this->setExpectedException(BadJsonException::class);

        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(400, [], 'get_file_non_existent.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $client->getFile([
            'FileId' => 'fileId'
        ]);
    }

    public function testDeleteFile()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'get_file.json'),
            $this->buildResponseFromStub(200, [], 'delete_file.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $this->assertTrue($client->deleteFile([
            'FileId' => 'fileId'
        ]));
    }

    public function testDeleteFileRetrievesFileNameWhenNotProvided()
    {
        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(200, [], 'get_file.json'),
            $this->buildResponseFromStub(200, [], 'delete_file.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $this->assertTrue($client->deleteFile([
            'FileId' => 'fileId'
        ]));
    }

    public function testDeletingNonExistentFileThrowsException()
    {
        $this->setExpectedException(BadJsonException::class);

        $guzzle = $this->buildGuzzleFromResponses([
            $this->buildResponseFromStub(200, [], 'authorize_account.json'),
            $this->buildResponseFromStub(400, [], 'delete_file_non_existent.json')
        ]);

        $client = new Client('testId', 'testKey', ['client' => $guzzle]);

        $this->assertTrue($client->deleteFile([
            'FileId' => 'fileId',
            'FileName' => 'fileName'
        ]));
    }
}
{
  "accountId": "testId",
  "apiUrl": "https://api900.backblaze.com",
  "authorizationToken": "testAuthToken",
  "downloadUrl": "https://f900.backblaze.com"
}{
  "code": "cannot_delete_non_empty_bucket",
  "message": "Cannot delete non-empty bucket",
  "status": 400
}{
  "code": "duplicate_bucket_name",
  "message": "Bucket name is already in use.",
  "status": 400
}{
  "bucketId" : "4a48fe8875c6214145260818",
  "accountId" : "010203040506",
  "bucketName" : "Test bucket",
  "bucketType" : "allPrivate"
}{
  "bucketId" : "4a48fe8875c6214145260818",
  "accountId" : "010203040506",
  "bucketName" : "Test bucket",
  "bucketType" : "allPublic"
}{
  "bucketId" : "testId",
  "accountId" : "010203040506",
  "bucketName" : "Test bucket",
  "bucketType" : "allPrivate"
}{
  "code": "bad_json",
  "message": "bucketId not valid for account",
  "status": 400
}{
  "fileId": "1_z3c5bd336015abcc25f260e1b_g1120714ffb1e68f8_d20160207_g146154_c001_f0341018_t0032",
  "fileName": "ripple.png"
}{
  "code": "bad_json",
  "message": "Bad fileId: 3_z3c5bd556012abdc25f260e1b_f1320712ffb1e68f8_d20154207_m145454_c;101_v0001038_t0032sfd",
  "status": 400
}{
  "code": "bad_value",
  "message": "bad fileId: 4_z4c2b957661da9c825f260e1b_f119f1fae240dae93_d20160131_m162947_c001_v0001015_t0020123123",
  "status": 400
}{
  "code": "not_found",
  "message": "bucket testytest does not have file: sdjfbnsdf",
  "status": 404
}The quick brown fox jumps over the lazy dog{
  "accountId": "accountId",
  "bucketId": "bucketId",
  "contentLength": 20,
  "contentSha1": "bc77b0349d325be71ed2ca26d5e68173210e9e18",
  "contentType": "application/octet-stream",
  "fileId": "4_z4c2b953461da9c825f260e1b_f1114dbf5bg9707e8_d20160206_m012226_c001_v1111017_t0010",
  "fileInfo": {
    "src_last_modified_millis": "1454721688784"
  },
  "action": "upload",
  "uploadTimestamp": "1465983870000",
  "fileName": "Test file.bin"
}
{
  "code": "bad_json",
  "message": "Bad fileId: nonExistantFileId",
  "status": 400
}{
  "authorizationToken": "authToken",
  "bucketId": "bucketId",
  "uploadUrl": "uploadUrl"
}{
  "buckets": []
}{
  "buckets": [
    {
      "bucketId": "4a48fe8875c6214145260818",
      "accountId": "30f20426f0b1",
      "bucketName" : "Kitten Videos",
      "bucketType": "allPrivate"
    },
    {
      "bucketId" : "5b232e8875c6214145260818",
      "accountId": "30f20426f0b1",
      "bucketName": "Puppy Videos",
      "bucketType": "allPublic"
    },
    {
      "bucketId": "87ba238875c6214145260818",
      "accountId": "30f20426f0b1",
      "bucketName": "Vacation Pictures",
      "bucketType" : "allPrivate"
    }
  ]
}{
  "files": [],
  "nextFileName": null
}
{
  "files": [
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    }
  ],
  "nextFileName": "Test document (1,007th copy) "
}
{
  "files": [
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },{
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    },
    {
      "action": "upload",
      "fileId": "4_z4c2b957661hy9c825f260e1b_f115af4dca081b246_d20160131_m160446_f001_v0011017_t0002",
      "fileName": "testfile.bin",
      "size": 140827,
      "uploadTimestamp": 1454256286000
    }
  ],
  "nextFileName": null
}
{
  "accountId": "accountId",
  "bucketId": "bucketId",
  "bucketName": "test-bucket",
  "bucketType": "allPrivate"
}{
  "accountId": "accountId",
  "bucketId": "bucketId",
  "bucketName": "test-bucket",
  "bucketType": "allPublic"
}{
  "accountId": "accountId",
  "bucketId": "bucketId",
  "contentLength": 555,
  "contentSha1": "0f4dd743cc9c501c70b6e3d2e033e179926ee768",
  "contentType": "application/octet-stream",
  "fileId": "fileId",
  "fileInfo": {
    "src_last_modified_millis": "1453326047773"
  },
  "fileName": "testfile.bin"
}<?php

namespace ChrisWhite\B2\Tests;

use ChrisWhite\B2\Http\Client as HttpClient;

trait TestHelper
{
    protected function buildGuzzleFromResponses(array $responses, $history = null)
    {
        $mock = new \GuzzleHttp\Handler\MockHandler($responses);
        $handler = new \GuzzleHttp\HandlerStack($mock);

        if ($history) {
            $handler->push($history);
        }

        return new HttpClient(['handler' => $handler]);
    }

    protected function buildResponseFromStub($statusCode, array $headers = [], $responseFile)
    {
        $response = file_get_contents(dirname(__FILE__).'/responses/'.$responseFile);

        return new \GuzzleHttp\Psr7\Response($statusCode, $headers, $response);
    }
}
---------------------------------------------
1.1.7 (2015-06-29)

- Util: Fix syntax to be compatible with PHP 5.3.
- Many minor fixes.

---------------------------------------------
1.1.6 (2015-06-29)

- WebAuth: Add support for "force_reapprove" parameter.
- Add exception class for HTTP 507 error (over quota).
- Put SDK version in HTTP request's "User-Agent" field.

---------------------------------------------
1.1.5 (2015-02-23)

- Add RootCertificates::useExternalPaths() so SDK can be used in PHARs.
- When reading AppInfo/AuthInfo files, strip out UTF-8 BOM if present.

---------------------------------------------
1.1.4 (2014-10-21)

- Use TLS instead of SSLv3 (SSLv3 has security issues).

---------------------------------------------
1.1.3 (2014-01-28)

- Fix for PHP installations that use NSS as the SSL backend.

---------------------------------------------
1.1.2 (2013-12-10)

- Bail out if 'mbstring.func_overload' is set.  We need the standard string
  functions to behave as originally defined.
- Tweak web-file-browser.php example to work in an existing web server setup.
- Fix bug with file uploads, triggered when a chunked upload request succeeds
  but the response doesn't get back to us.
- Add support for "path_prefix" parameter to /delta.
- Add support for API call that upgrades OAuth 1 tokens to OAuth 2 tokens.
- Stricter SSL: Use pinned root certificates and a fixed list of ciphersuites.
  Include "examples/test-ssl.php" to let people check their PHP installation.

---------------------------------------------
1.1.1 (2013-08-21)

- uploadFile no longer closes the caller's input stream.
	
---------------------------------------------
1.1.0 (2013-07-08)

- Switch to OAuth 2.  This is a backwards incompatible change to the auth API,
  but all regular API calls remain unchanged.

---------------------------------------------
1.0.2 (2013-06-17)

- Fixed performance problem with file uploads when reading from from a
  network stream.
- Treat HTTP 502 like HTTP 500 (yields better retry behavior)

---------------------------------------------
1.0.1 (2013-05-09)

- Fix Client.getMetadataWithChildrenIfChanged
- Add "--locale=..." option to examples.

---------------------------------------------
1.0.0 (2013-04-04)

- First versioned release.
{
    "name": "dropbox/dropbox-sdk",
    "description": "Dropbox SDK for PHP",
    "keywords" : [ "dropbox" ],

    "license": "MIT",

    "autoload": {
        "psr-0": { "Dropbox": "lib/" }
    },

    "require": {
        "php": ">= 5.3",
        "ext-curl": "*"
    },

    "require-dev": {
        "phpunit/phpunit": "~4.0",
        "apigen/apigen": "4.1.2",
        "squizlabs/php_codesniffer": "2.0.0RC3"
    }
}
{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
        "This file is @generated automatically"
    ],
    "hash": "b72dc8906a5dd6b2da2a295a76027aeb",
    "content-hash": "688e11d5d97a5921d435a4dd6949d7b7",
    "packages": [],
    "packages-dev": [
        {
            "name": "andrewsville/php-token-reflection",
            "version": "1.4.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Andrewsville/PHP-Token-Reflection.git",
                "reference": "e6d0ac2baf66cdf154be34c3d2a2aa1bd4b426ee"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Andrewsville/PHP-Token-Reflection/zipball/e6d0ac2baf66cdf154be34c3d2a2aa1bd4b426ee",
                "reference": "e6d0ac2baf66cdf154be34c3d2a2aa1bd4b426ee",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "php": ">=5.3.0"
            },
            "type": "library",
            "autoload": {
                "psr-0": {
                    "TokenReflection": "./"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3"
            ],
            "authors": [
                {
                    "name": "Ondřej Nešpor",
                    "homepage": "https://github.com/andrewsville"
                },
                {
                    "name": "Jaroslav Hanslík",
                    "homepage": "https://github.com/kukulich"
                }
            ],
            "description": "Library emulating the PHP internal reflection using just the tokenized source code.",
            "homepage": "http://andrewsville.github.com/PHP-Token-Reflection/",
            "keywords": [
                "library",
                "reflection",
                "tokenizer"
            ],
            "time": "2014-08-06 16:37:08"
        },
        {
            "name": "apigen/apigen",
            "version": "v4.1.2",
            "source": {
                "type": "git",
                "url": "https://github.com/ApiGen/ApiGen.git",
                "reference": "3365433ea3433b0e5c8f763608f8e63cbedb2a3a"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/ApiGen/ApiGen/zipball/3365433ea3433b0e5c8f763608f8e63cbedb2a3a",
                "reference": "3365433ea3433b0e5c8f763608f8e63cbedb2a3a",
                "shasum": ""
            },
            "require": {
                "andrewsville/php-token-reflection": "~1.4",
                "apigen/theme-bootstrap": "~1.1.2",
                "apigen/theme-default": "~1.0.1",
                "herrera-io/phar-update": "~2.0",
                "kdyby/events": "~2.0",
                "kukulich/fshl": "~2.1",
                "latte/latte": ">=2.2.0,<2.3.5",
                "michelf/php-markdown": "~1.4",
                "nette/application": "~2.2",
                "nette/bootstrap": "~2.2",
                "nette/di": "~2.2",
                "nette/mail": "~2.2",
                "nette/neon": "~2.2",
                "nette/robot-loader": "~2.2",
                "nette/safe-stream": "~2.2",
                "php": ">=5.4",
                "symfony/console": "~2.6",
                "symfony/options-resolver": "~2.6.1",
                "symfony/yaml": "~2.6",
                "tracy/tracy": "~2.2"
            },
            "require-dev": {
                "herrera-io/box": "~1.6",
                "mockery/mockery": "~0.9"
            },
            "bin": [
                "bin/apigen"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "4.1.0-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "ApiGen\\": "src"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "http://davidgrudl.com"
                },
                {
                    "name": "Ondřej Nešpor",
                    "homepage": "https://github.com/andrewsville"
                },
                {
                    "name": "Jaroslav Hanslík",
                    "homepage": "https://github.com/kukulich"
                },
                {
                    "name": "Tomáš Votruba",
                    "email": "tomas.vot@gmail.com"
                },
                {
                    "name": "Olivier Laviale",
                    "homepage": "https://github.com/olvlvl"
                }
            ],
            "description": "PHP source code API generator",
            "homepage": "http://apigen.org/",
            "keywords": [
                "api",
                "documentation",
                "generator",
                "phpdoc"
            ],
            "time": "2015-11-29 20:11:30"
        },
        {
            "name": "apigen/theme-bootstrap",
            "version": "v1.1.3",
            "source": {
                "type": "git",
                "url": "https://github.com/ApiGen/ThemeBootstrap.git",
                "reference": "55a35b4a3a9a5fcaa6a8fc43fb304983cab98c6c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/ApiGen/ThemeBootstrap/zipball/55a35b4a3a9a5fcaa6a8fc43fb304983cab98c6c",
                "reference": "55a35b4a3a9a5fcaa6a8fc43fb304983cab98c6c",
                "shasum": ""
            },
            "require": {
                "latte/latte": "~2.2"
            },
            "type": "library",
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Tomáš Votruba",
                    "email": "tomas.vot@gmail.com"
                },
                {
                    "name": "Olivier Laviale",
                    "homepage": "https://github.com/olvlvl"
                }
            ],
            "description": "Twitter Bootstrap theme for ApiGen",
            "homepage": "http://apigen.org/",
            "time": "2015-10-11 14:52:50"
        },
        {
            "name": "apigen/theme-default",
            "version": "v1.0.2",
            "source": {
                "type": "git",
                "url": "https://github.com/ApiGen/ThemeDefault.git",
                "reference": "51648cf83645d9ae6c655fe46bcd26a347d45336"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/ApiGen/ThemeDefault/zipball/51648cf83645d9ae6c655fe46bcd26a347d45336",
                "reference": "51648cf83645d9ae6c655fe46bcd26a347d45336",
                "shasum": ""
            },
            "require": {
                "latte/latte": "~2.2"
            },
            "type": "library",
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "http://davidgrudl.com"
                },
                {
                    "name": "Ondřej Nešpor",
                    "homepage": "https://github.com/Andrewsville"
                },
                {
                    "name": "Jaroslav Hanslík",
                    "homepage": "https://github.com/kukulich"
                },
                {
                    "name": "Tomáš Votruba",
                    "email": "tomas.vot@gmail.com"
                },
                {
                    "name": "Olivier Laviale",
                    "homepage": "https://github.com/olvlvl"
                }
            ],
            "description": "Default theme for ApiGen",
            "homepage": "http://apigen.org/",
            "time": "2015-10-11 14:55:30"
        },
        {
            "name": "doctrine/instantiator",
            "version": "1.0.5",
            "source": {
                "type": "git",
                "url": "https://github.com/doctrine/instantiator.git",
                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3,<8.0-DEV"
            },
            "require-dev": {
                "athletic/athletic": "~0.1.8",
                "ext-pdo": "*",
                "ext-phar": "*",
                "phpunit/phpunit": "~4.0",
                "squizlabs/php_codesniffer": "~2.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Marco Pivetta",
                    "email": "ocramius@gmail.com",
                    "homepage": "http://ocramius.github.com/"
                }
            ],
            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
            "homepage": "https://github.com/doctrine/instantiator",
            "keywords": [
                "constructor",
                "instantiate"
            ],
            "time": "2015-06-14 21:17:01"
        },
        {
            "name": "herrera-io/json",
            "version": "1.0.3",
            "source": {
                "type": "git",
                "url": "https://github.com/kherge-abandoned/php-json.git",
                "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/kherge-abandoned/php-json/zipball/60c696c9370a1e5136816ca557c17f82a6fa83f1",
                "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1",
                "shasum": ""
            },
            "require": {
                "ext-json": "*",
                "justinrainbow/json-schema": ">=1.0,<2.0-dev",
                "php": ">=5.3.3",
                "seld/jsonlint": ">=1.0,<2.0-dev"
            },
            "require-dev": {
                "herrera-io/phpunit-test-case": "1.*",
                "mikey179/vfsstream": "1.1.0",
                "phpunit/phpunit": "3.7.*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "files": [
                    "src/lib/json_version.php"
                ],
                "psr-0": {
                    "Herrera\\Json": "src/lib"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Kevin Herrera",
                    "email": "kevin@herrera.io",
                    "homepage": "http://kevin.herrera.io"
                }
            ],
            "description": "A library for simplifying JSON linting and validation.",
            "homepage": "http://herrera-io.github.com/php-json",
            "keywords": [
                "json",
                "lint",
                "schema",
                "validate"
            ],
            "time": "2013-10-30 16:51:34"
        },
        {
            "name": "herrera-io/phar-update",
            "version": "2.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/kherge-abandoned/php-phar-update.git",
                "reference": "15643c90d3d43620a4f45c910e6afb7a0ad4b488"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/kherge-abandoned/php-phar-update/zipball/15643c90d3d43620a4f45c910e6afb7a0ad4b488",
                "reference": "15643c90d3d43620a4f45c910e6afb7a0ad4b488",
                "shasum": ""
            },
            "require": {
                "herrera-io/json": "1.*",
                "herrera-io/version": "1.*",
                "php": ">=5.3.3"
            },
            "require-dev": {
                "herrera-io/phpunit-test-case": "1.*",
                "mikey179/vfsstream": "1.1.0",
                "phpunit/phpunit": "3.7.*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.0-dev"
                }
            },
            "autoload": {
                "files": [
                    "src/lib/constants.php"
                ],
                "psr-0": {
                    "Herrera\\Phar\\Update": "src/lib"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Kevin Herrera",
                    "email": "kevin@herrera.io",
                    "homepage": "http://kevin.herrera.io"
                }
            ],
            "description": "A library for self-updating Phars.",
            "homepage": "http://herrera-io.github.com/php-phar-update",
            "keywords": [
                "phar",
                "update"
            ],
            "time": "2013-11-09 17:13:13"
        },
        {
            "name": "herrera-io/version",
            "version": "1.1.1",
            "source": {
                "type": "git",
                "url": "https://github.com/kherge-abandoned/php-version.git",
                "reference": "d39d9642b92a04d8b8a28b871b797a35a2545e85"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/kherge-abandoned/php-version/zipball/d39d9642b92a04d8b8a28b871b797a35a2545e85",
                "reference": "d39d9642b92a04d8b8a28b871b797a35a2545e85",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "herrera-io/phpunit-test-case": "1.*",
                "phpunit/phpunit": "3.7.*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.1.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Herrera\\Version": "src/lib"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Kevin Herrera",
                    "email": "kevin@herrera.io",
                    "homepage": "http://kevin.herrera.io"
                }
            ],
            "description": "A library for creating, editing, and comparing semantic versioning numbers.",
            "homepage": "http://github.com/herrera-io/php-version",
            "keywords": [
                "semantic",
                "version"
            ],
            "time": "2014-05-27 05:29:25"
        },
        {
            "name": "justinrainbow/json-schema",
            "version": "1.6.1",
            "source": {
                "type": "git",
                "url": "https://github.com/justinrainbow/json-schema.git",
                "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/cc84765fb7317f6b07bd8ac78364747f95b86341",
                "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.29"
            },
            "require-dev": {
                "json-schema/json-schema-test-suite": "1.1.0",
                "phpdocumentor/phpdocumentor": "~2",
                "phpunit/phpunit": "~3.7"
            },
            "bin": [
                "bin/validate-json"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.6.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "JsonSchema\\": "src/JsonSchema/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Bruno Prieto Reis",
                    "email": "bruno.p.reis@gmail.com"
                },
                {
                    "name": "Justin Rainbow",
                    "email": "justin.rainbow@gmail.com"
                },
                {
                    "name": "Igor Wiedler",
                    "email": "igor@wiedler.ch"
                },
                {
                    "name": "Robert Schönthal",
                    "email": "seroscho@googlemail.com"
                }
            ],
            "description": "A library to validate a json schema.",
            "homepage": "https://github.com/justinrainbow/json-schema",
            "keywords": [
                "json",
                "schema"
            ],
            "time": "2016-01-25 15:43:01"
        },
        {
            "name": "kdyby/events",
            "version": "v2.4.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Kdyby/Events.git",
                "reference": "8049e0fc7abb48178b4a2a9af230eceebe1a83bc"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Kdyby/Events/zipball/8049e0fc7abb48178b4a2a9af230eceebe1a83bc",
                "reference": "8049e0fc7abb48178b4a2a9af230eceebe1a83bc",
                "shasum": ""
            },
            "require": {
                "nette/di": "~2.3@dev",
                "nette/utils": "~2.3@dev"
            },
            "require-dev": {
                "jakub-onderka/php-parallel-lint": "~0.7",
                "latte/latte": "~2.3@dev",
                "nette/application": "~2.3@dev",
                "nette/bootstrap": "~2.3@dev",
                "nette/caching": "~2.3@dev",
                "nette/component-model": "~2.2@dev",
                "nette/database": "~2.3@dev",
                "nette/deprecated": "~2.3@dev",
                "nette/di": "~2.3@dev",
                "nette/finder": "~2.3@dev",
                "nette/forms": "~2.3@dev",
                "nette/http": "~2.3@dev",
                "nette/mail": "~2.3@dev",
                "nette/neon": "~2.3@dev",
                "nette/nette": "~2.3@dev",
                "nette/php-generator": "~2.3@dev",
                "nette/reflection": "~2.3@dev",
                "nette/robot-loader": "~2.3@dev",
                "nette/safe-stream": "~2.3@dev",
                "nette/security": "~2.3@dev",
                "nette/tester": "~1.4@rc",
                "nette/tokenizer": "~2.2@dev",
                "nette/utils": "~2.3@dev",
                "symfony/event-dispatcher": "~2.5",
                "tracy/tracy": "~2.3@dev"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.4-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Kdyby\\Events\\": "src/"
                },
                "classmap": [
                    "src/Kdyby/Events/exceptions.php"
                ],
                "files": [
                    "src/Doctrine/compatibility.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "Filip Procházka",
                    "email": "filip@prochazka.su",
                    "homepage": "http://filip-prochazka.com"
                }
            ],
            "description": "Events for Nette Framework",
            "homepage": "http://kdyby.org",
            "keywords": [
                "kdyby",
                "nette"
            ],
            "time": "2015-04-04 16:29:31"
        },
        {
            "name": "kukulich/fshl",
            "version": "2.1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/kukulich/fshl.git",
                "reference": "974c294ade5d76c0c16b6fe3fd3a584ba999b24f"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/kukulich/fshl/zipball/974c294ade5d76c0c16b6fe3fd3a584ba999b24f",
                "reference": "2.1.0",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3"
            },
            "type": "library",
            "autoload": {
                "psr-0": {
                    "FSHL": "./"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "GPL-2.0+"
            ],
            "authors": [
                {
                    "name": "Jaroslav Hanslík",
                    "homepage": "https://github.com/kukulich"
                }
            ],
            "description": "FSHL is a free, open source, universal, fast syntax highlighter written in PHP.",
            "homepage": "http://fshl.kukulich.cz/",
            "keywords": [
                "highlight",
                "library",
                "syntax"
            ],
            "time": "2012-09-08 12:00:07"
        },
        {
            "name": "latte/latte",
            "version": "v2.3.4",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/latte.git",
                "reference": "5e891af999776d2204a9d06ad66ad8fa0bcd4f8b"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/latte/zipball/5e891af999776d2204a9d06ad66ad8fa0bcd4f8b",
                "reference": "5e891af999776d2204a9d06ad66ad8fa0bcd4f8b",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "php": ">=5.3.1"
            },
            "require-dev": {
                "nette/tester": "~1.3"
            },
            "suggest": {
                "ext-fileinfo": "to use filter |datastream",
                "ext-mbstring": "to use filters like lower, upper, capitalize, ..."
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "http://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "http://nette.org/contributors"
                }
            ],
            "description": "Latte: the amazing template engine for PHP",
            "homepage": "http://latte.nette.org",
            "keywords": [
                "templating",
                "twig"
            ],
            "time": "2015-08-23 12:36:55"
        },
        {
            "name": "michelf/php-markdown",
            "version": "1.6.0",
            "source": {
                "type": "git",
                "url": "https://github.com/michelf/php-markdown.git",
                "reference": "156e56ee036505ec637d761ee62dc425d807183c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/michelf/php-markdown/zipball/156e56ee036505ec637d761ee62dc425d807183c",
                "reference": "156e56ee036505ec637d761ee62dc425d807183c",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-lib": "1.4.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Michelf": ""
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Michel Fortin",
                    "email": "michel.fortin@michelf.ca",
                    "homepage": "https://michelf.ca/",
                    "role": "Developer"
                },
                {
                    "name": "John Gruber",
                    "homepage": "https://daringfireball.net/"
                }
            ],
            "description": "PHP Markdown",
            "homepage": "https://michelf.ca/projects/php-markdown/",
            "keywords": [
                "markdown"
            ],
            "time": "2015-12-24 01:37:31"
        },
        {
            "name": "nette/application",
            "version": "v2.3.9",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/application.git",
                "reference": "84d309df061306b7dc47c9d75ee9926b570b76a0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/application/zipball/84d309df061306b7dc47c9d75ee9926b570b76a0",
                "reference": "84d309df061306b7dc47c9d75ee9926b570b76a0",
                "shasum": ""
            },
            "require": {
                "nette/component-model": "~2.2",
                "nette/http": "~2.2",
                "nette/reflection": "~2.2",
                "nette/security": "~2.2",
                "nette/utils": "~2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "latte/latte": "~2.3.9",
                "nette/di": "~2.3",
                "nette/forms": "~2.2",
                "nette/robot-loader": "~2.2",
                "nette/tester": "~1.3"
            },
            "suggest": {
                "latte/latte": "Allows using Latte in templates",
                "nette/forms": "Allows to use Nette\\Application\\UI\\Form"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette Application MVC Component",
            "homepage": "https://nette.org",
            "time": "2016-01-20 03:00:00"
        },
        {
            "name": "nette/bootstrap",
            "version": "v2.3.4",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/bootstrap.git",
                "reference": "e32964df66f2c5a3a50b229204f583d20c1f6829"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/bootstrap/zipball/e32964df66f2c5a3a50b229204f583d20c1f6829",
                "reference": "e32964df66f2c5a3a50b229204f583d20c1f6829",
                "shasum": ""
            },
            "require": {
                "nette/di": "~2.3",
                "nette/utils": "~2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "latte/latte": "~2.2",
                "nette/application": "~2.3",
                "nette/caching": "~2.3",
                "nette/database": "~2.3",
                "nette/forms": "~2.3",
                "nette/http": "~2.3",
                "nette/mail": "~2.3",
                "nette/robot-loader": "~2.2",
                "nette/safe-stream": "~2.2",
                "nette/security": "~2.3",
                "nette/tester": "~1.3",
                "tracy/tracy": "~2.3"
            },
            "suggest": {
                "nette/robot-loader": "to use Configurator::createRobotLoader()",
                "tracy/tracy": "to use Configurator::enableDebugger()"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette Bootstrap",
            "homepage": "https://nette.org",
            "time": "2015-11-16 20:18:31"
        },
        {
            "name": "nette/caching",
            "version": "v2.4.3",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/caching.git",
                "reference": "f60c1767fe5a4b2b45edf2a98cf9dc70c15ea1b1"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/caching/zipball/f60c1767fe5a4b2b45edf2a98cf9dc70c15ea1b1",
                "reference": "f60c1767fe5a4b2b45edf2a98cf9dc70c15ea1b1",
                "shasum": ""
            },
            "require": {
                "nette/finder": "~2.2",
                "nette/utils": "~2.2",
                "php": ">=5.4.4"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "latte/latte": "~2.3.0",
                "nette/di": "~2.3",
                "nette/tester": "~1.6"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.4-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette Caching Component",
            "homepage": "https://nette.org",
            "time": "2015-11-29 21:35:25"
        },
        {
            "name": "nette/component-model",
            "version": "v2.2.4",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/component-model.git",
                "reference": "07bce436051fd92d084642ce7a47f00045e0d1e5"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/component-model/zipball/07bce436051fd92d084642ce7a47f00045e0d1e5",
                "reference": "07bce436051fd92d084642ce7a47f00045e0d1e5",
                "shasum": ""
            },
            "require": {
                "nette/utils": "^2.3.5",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/tester": "~1.3"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette Component Model",
            "homepage": "https://nette.org",
            "time": "2015-10-06 17:54:05"
        },
        {
            "name": "nette/di",
            "version": "v2.3.8",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/di.git",
                "reference": "d8778100be121937b6e9fba6eadff5691744d727"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/di/zipball/d8778100be121937b6e9fba6eadff5691744d727",
                "reference": "d8778100be121937b6e9fba6eadff5691744d727",
                "shasum": ""
            },
            "require": {
                "nette/neon": "^2.3.3",
                "nette/php-generator": "^2.3.3",
                "nette/utils": "^2.3.5",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/tester": "^1.6"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette Dependency Injection Component",
            "homepage": "https://nette.org",
            "time": "2016-01-20 02:58:28"
        },
        {
            "name": "nette/finder",
            "version": "v2.3.2",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/finder.git",
                "reference": "ea8e796b42d542bd90e76f5b2a41c2c86a008256"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/finder/zipball/ea8e796b42d542bd90e76f5b2a41c2c86a008256",
                "reference": "ea8e796b42d542bd90e76f5b2a41c2c86a008256",
                "shasum": ""
            },
            "require": {
                "nette/utils": "~2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/tester": "~1.4"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette Finder: Files Searching",
            "homepage": "https://nette.org",
            "time": "2015-10-20 17:15:41"
        },
        {
            "name": "nette/http",
            "version": "v2.3.4",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/http.git",
                "reference": "551f06cba60d3b8e08d0e7f6c19591b59f36f22d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/http/zipball/551f06cba60d3b8e08d0e7f6c19591b59f36f22d",
                "reference": "551f06cba60d3b8e08d0e7f6c19591b59f36f22d",
                "shasum": ""
            },
            "require": {
                "nette/utils": "~2.2, >=2.2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/di": "~2.3",
                "nette/tester": "~1.4"
            },
            "suggest": {
                "ext-fileinfo": "to detect type of uploaded files"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette HTTP Component",
            "homepage": "https://nette.org",
            "time": "2016-01-20 00:55:41"
        },
        {
            "name": "nette/mail",
            "version": "v2.3.4",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/mail.git",
                "reference": "3b8ba96407029d94d83177086bcb5e1fe0046929"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/mail/zipball/3b8ba96407029d94d83177086bcb5e1fe0046929",
                "reference": "3b8ba96407029d94d83177086bcb5e1fe0046929",
                "shasum": ""
            },
            "require": {
                "ext-iconv": "*",
                "nette/utils": "~2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/di": "~2.3",
                "nette/tester": "~1.3"
            },
            "suggest": {
                "ext-fileinfo": "to detect type of attached files"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette Mail: Sending E-mails",
            "homepage": "https://nette.org",
            "time": "2015-11-29 22:09:25"
        },
        {
            "name": "nette/neon",
            "version": "v2.3.4",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/neon.git",
                "reference": "0042f72a2c51b24f5de3ca5f365494cd9b0d45f0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/neon/zipball/0042f72a2c51b24f5de3ca5f365494cd9b0d45f0",
                "reference": "0042f72a2c51b24f5de3ca5f365494cd9b0d45f0",
                "shasum": ""
            },
            "require": {
                "ext-iconv": "*",
                "php": ">=5.3.1"
            },
            "require-dev": {
                "nette/tester": "~1.4"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette NEON: parser & generator for Nette Object Notation",
            "homepage": "http://ne-on.org",
            "time": "2016-01-13 14:20:27"
        },
        {
            "name": "nette/php-generator",
            "version": "v2.3.5",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/php-generator.git",
                "reference": "224730a45e0cd6e76e692997a3d981a592f80b5d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/php-generator/zipball/224730a45e0cd6e76e692997a3d981a592f80b5d",
                "reference": "224730a45e0cd6e76e692997a3d981a592f80b5d",
                "shasum": ""
            },
            "require": {
                "nette/utils": "~2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/tester": "~1.4"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette PHP Generator",
            "homepage": "https://nette.org",
            "time": "2015-11-29 22:14:37"
        },
        {
            "name": "nette/reflection",
            "version": "v2.3.1",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/reflection.git",
                "reference": "9c2ed2a29f1f58125a0f19ffc987812d6b17d3e6"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/reflection/zipball/9c2ed2a29f1f58125a0f19ffc987812d6b17d3e6",
                "reference": "9c2ed2a29f1f58125a0f19ffc987812d6b17d3e6",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "nette/caching": "~2.2",
                "nette/utils": "~2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/di": "~2.3",
                "nette/tester": "~1.4"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "http://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "http://nette.org/contributors"
                }
            ],
            "description": "Nette PHP Reflection Component",
            "homepage": "http://nette.org",
            "time": "2015-07-11 21:34:53"
        },
        {
            "name": "nette/robot-loader",
            "version": "v2.3.1",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/robot-loader.git",
                "reference": "69331d359bbc9e5f911c12b82187cac914d983fb"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/robot-loader/zipball/69331d359bbc9e5f911c12b82187cac914d983fb",
                "reference": "69331d359bbc9e5f911c12b82187cac914d983fb",
                "shasum": ""
            },
            "require": {
                "nette/caching": "~2.2",
                "nette/finder": "~2.3",
                "nette/utils": "~2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/tester": "~1.4"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "http://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "http://nette.org/contributors"
                }
            ],
            "description": "Nette RobotLoader: comfortable autoloading",
            "homepage": "http://nette.org",
            "time": "2015-07-11 21:20:57"
        },
        {
            "name": "nette/safe-stream",
            "version": "v2.3.1",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/safe-stream.git",
                "reference": "bf30db367b51a0932c44dcb9a378927644d48b2e"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/safe-stream/zipball/bf30db367b51a0932c44dcb9a378927644d48b2e",
                "reference": "bf30db367b51a0932c44dcb9a378927644d48b2e",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/tester": "~1.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.3-dev"
                }
            },
            "autoload": {
                "files": [
                    "src/loader.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "http://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "http://nette.org/contributors"
                }
            ],
            "description": "Nette SafeStream: Atomic Operations",
            "homepage": "http://nette.org",
            "time": "2015-07-11 20:59:15"
        },
        {
            "name": "nette/security",
            "version": "v2.3.1",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/security.git",
                "reference": "744264a42b506d63009d7e3853ed72b04c99e964"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/security/zipball/744264a42b506d63009d7e3853ed72b04c99e964",
                "reference": "744264a42b506d63009d7e3853ed72b04c99e964",
                "shasum": ""
            },
            "require": {
                "nette/utils": "~2.2",
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/di": "~2.3",
                "nette/http": "~2.3",
                "nette/tester": "~1.4"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "http://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "http://nette.org/contributors"
                }
            ],
            "description": "Nette Security: Access Control Component",
            "homepage": "http://nette.org",
            "time": "2015-07-11 21:22:53"
        },
        {
            "name": "nette/utils",
            "version": "v2.3.7",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/utils.git",
                "reference": "6f1ed73088c28a24acc9657ca14b3418a270e24b"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/utils/zipball/6f1ed73088c28a24acc9657ca14b3418a270e24b",
                "reference": "6f1ed73088c28a24acc9657ca14b3418a270e24b",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.1"
            },
            "conflict": {
                "nette/nette": "<2.2"
            },
            "require-dev": {
                "nette/tester": "~1.0"
            },
            "suggest": {
                "ext-gd": "to use Image",
                "ext-iconv": "to use Strings::webalize() and toAscii()",
                "ext-intl": "for script transliteration in Strings::webalize() and toAscii()",
                "ext-mbstring": "to use Strings::lower() etc..."
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Nette Utility Classes",
            "homepage": "https://nette.org",
            "time": "2015-11-30 00:11:35"
        },
        {
            "name": "phpdocumentor/reflection-docblock",
            "version": "2.0.4",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.0"
            },
            "suggest": {
                "dflydev/markdown": "~1.0",
                "erusev/parsedown": "~1.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.0.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "phpDocumentor": [
                        "src/"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Mike van Riel",
                    "email": "mike.vanriel@naenius.com"
                }
            ],
            "time": "2015-02-03 12:10:50"
        },
        {
            "name": "phpspec/prophecy",
            "version": "v1.5.0",
            "source": {
                "type": "git",
                "url": "https://github.com/phpspec/prophecy.git",
                "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
                "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
                "shasum": ""
            },
            "require": {
                "doctrine/instantiator": "^1.0.2",
                "phpdocumentor/reflection-docblock": "~2.0",
                "sebastian/comparator": "~1.1"
            },
            "require-dev": {
                "phpspec/phpspec": "~2.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Prophecy\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Konstantin Kudryashov",
                    "email": "ever.zet@gmail.com",
                    "homepage": "http://everzet.com"
                },
                {
                    "name": "Marcello Duarte",
                    "email": "marcello.duarte@gmail.com"
                }
            ],
            "description": "Highly opinionated mocking framework for PHP 5.3+",
            "homepage": "https://github.com/phpspec/prophecy",
            "keywords": [
                "Double",
                "Dummy",
                "fake",
                "mock",
                "spy",
                "stub"
            ],
            "time": "2015-08-13 10:07:40"
        },
        {
            "name": "phpunit/php-code-coverage",
            "version": "2.2.4",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "phpunit/php-file-iterator": "~1.3",
                "phpunit/php-text-template": "~1.2",
                "phpunit/php-token-stream": "~1.3",
                "sebastian/environment": "^1.3.2",
                "sebastian/version": "~1.0"
            },
            "require-dev": {
                "ext-xdebug": ">=2.1.4",
                "phpunit/phpunit": "~4"
            },
            "suggest": {
                "ext-dom": "*",
                "ext-xdebug": ">=2.2.1",
                "ext-xmlwriter": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.2.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
            "keywords": [
                "coverage",
                "testing",
                "xunit"
            ],
            "time": "2015-10-06 15:47:00"
        },
        {
            "name": "phpunit/php-file-iterator",
            "version": "1.4.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
            "keywords": [
                "filesystem",
                "iterator"
            ],
            "time": "2015-06-21 13:08:43"
        },
        {
            "name": "phpunit/php-text-template",
            "version": "1.2.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-text-template.git",
                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "Simple template engine.",
            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
            "keywords": [
                "template"
            ],
            "time": "2015-06-21 13:50:34"
        },
        {
            "name": "phpunit/php-timer",
            "version": "1.0.7",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-timer.git",
                "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
                "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Utility class for timing",
            "homepage": "https://github.com/sebastianbergmann/php-timer/",
            "keywords": [
                "timer"
            ],
            "time": "2015-06-21 08:01:12"
        },
        {
            "name": "phpunit/php-token-stream",
            "version": "1.4.8",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.2"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Wrapper around PHP's tokenizer extension.",
            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
            "keywords": [
                "tokenizer"
            ],
            "time": "2015-09-15 10:49:45"
        },
        {
            "name": "phpunit/phpunit",
            "version": "4.8.21",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/phpunit.git",
                "reference": "ea76b17bced0500a28098626b84eda12dbcf119c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c",
                "reference": "ea76b17bced0500a28098626b84eda12dbcf119c",
                "shasum": ""
            },
            "require": {
                "ext-dom": "*",
                "ext-json": "*",
                "ext-pcre": "*",
                "ext-reflection": "*",
                "ext-spl": "*",
                "php": ">=5.3.3",
                "phpspec/prophecy": "^1.3.1",
                "phpunit/php-code-coverage": "~2.1",
                "phpunit/php-file-iterator": "~1.4",
                "phpunit/php-text-template": "~1.2",
                "phpunit/php-timer": ">=1.0.6",
                "phpunit/phpunit-mock-objects": "~2.3",
                "sebastian/comparator": "~1.1",
                "sebastian/diff": "~1.2",
                "sebastian/environment": "~1.3",
                "sebastian/exporter": "~1.2",
                "sebastian/global-state": "~1.0",
                "sebastian/version": "~1.0",
                "symfony/yaml": "~2.1|~3.0"
            },
            "suggest": {
                "phpunit/php-invoker": "~1.1"
            },
            "bin": [
                "phpunit"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "4.8.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "The PHP Unit Testing framework.",
            "homepage": "https://phpunit.de/",
            "keywords": [
                "phpunit",
                "testing",
                "xunit"
            ],
            "time": "2015-12-12 07:45:58"
        },
        {
            "name": "phpunit/phpunit-mock-objects",
            "version": "2.3.8",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
                "shasum": ""
            },
            "require": {
                "doctrine/instantiator": "^1.0.2",
                "php": ">=5.3.3",
                "phpunit/php-text-template": "~1.2",
                "sebastian/exporter": "~1.2"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "suggest": {
                "ext-soap": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.3.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Mock Object library for PHPUnit",
            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
            "keywords": [
                "mock",
                "xunit"
            ],
            "time": "2015-10-02 06:51:40"
        },
        {
            "name": "sebastian/comparator",
            "version": "1.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/comparator.git",
                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "sebastian/diff": "~1.2",
                "sebastian/exporter": "~1.2"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.2.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Volker Dusch",
                    "email": "github@wallbash.com"
                },
                {
                    "name": "Bernhard Schussek",
                    "email": "bschussek@2bepublished.at"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Provides the functionality to compare PHP values for equality",
            "homepage": "http://www.github.com/sebastianbergmann/comparator",
            "keywords": [
                "comparator",
                "compare",
                "equality"
            ],
            "time": "2015-07-26 15:48:44"
        },
        {
            "name": "sebastian/diff",
            "version": "1.4.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/diff.git",
                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.8"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Kore Nordmann",
                    "email": "mail@kore-nordmann.de"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Diff implementation",
            "homepage": "https://github.com/sebastianbergmann/diff",
            "keywords": [
                "diff"
            ],
            "time": "2015-12-08 07:14:41"
        },
        {
            "name": "sebastian/environment",
            "version": "1.3.3",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/environment.git",
                "reference": "6e7133793a8e5a5714a551a8324337374be209df"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
                "reference": "6e7133793a8e5a5714a551a8324337374be209df",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Provides functionality to handle HHVM/PHP environments",
            "homepage": "http://www.github.com/sebastianbergmann/environment",
            "keywords": [
                "Xdebug",
                "environment",
                "hhvm"
            ],
            "time": "2015-12-02 08:37:27"
        },
        {
            "name": "sebastian/exporter",
            "version": "1.2.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/exporter.git",
                "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
                "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "sebastian/recursion-context": "~1.0"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.2.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Volker Dusch",
                    "email": "github@wallbash.com"
                },
                {
                    "name": "Bernhard Schussek",
                    "email": "bschussek@2bepublished.at"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                },
                {
                    "name": "Adam Harvey",
                    "email": "aharvey@php.net"
                }
            ],
            "description": "Provides the functionality to export PHP variables for visualization",
            "homepage": "http://www.github.com/sebastianbergmann/exporter",
            "keywords": [
                "export",
                "exporter"
            ],
            "time": "2015-06-21 07:55:53"
        },
        {
            "name": "sebastian/global-state",
            "version": "1.1.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/global-state.git",
                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.2"
            },
            "suggest": {
                "ext-uopz": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Snapshotting of global state",
            "homepage": "http://www.github.com/sebastianbergmann/global-state",
            "keywords": [
                "global state"
            ],
            "time": "2015-10-12 03:26:01"
        },
        {
            "name": "sebastian/recursion-context",
            "version": "1.0.2",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/recursion-context.git",
                "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
                "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                },
                {
                    "name": "Adam Harvey",
                    "email": "aharvey@php.net"
                }
            ],
            "description": "Provides functionality to recursively process PHP variables",
            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
            "time": "2015-11-11 19:50:13"
        },
        {
            "name": "sebastian/version",
            "version": "1.0.6",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/version.git",
                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
                "shasum": ""
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
            "homepage": "https://github.com/sebastianbergmann/version",
            "time": "2015-06-21 13:59:46"
        },
        {
            "name": "seld/jsonlint",
            "version": "1.4.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Seldaek/jsonlint.git",
                "reference": "66834d3e3566bb5798db7294619388786ae99394"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/66834d3e3566bb5798db7294619388786ae99394",
                "reference": "66834d3e3566bb5798db7294619388786ae99394",
                "shasum": ""
            },
            "require": {
                "php": "^5.3 || ^7.0"
            },
            "bin": [
                "bin/jsonlint"
            ],
            "type": "library",
            "autoload": {
                "psr-4": {
                    "Seld\\JsonLint\\": "src/Seld/JsonLint/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Jordi Boggiano",
                    "email": "j.boggiano@seld.be",
                    "homepage": "http://seld.be"
                }
            ],
            "description": "JSON Linter",
            "keywords": [
                "json",
                "linter",
                "parser",
                "validator"
            ],
            "time": "2015-11-21 02:21:41"
        },
        {
            "name": "squizlabs/php_codesniffer",
            "version": "2.0.0RC3",
            "source": {
                "type": "git",
                "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
                "reference": "995d6e97f79b5f15631e2082ee6eb80279d114f2"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/995d6e97f79b5f15631e2082ee6eb80279d114f2",
                "reference": "995d6e97f79b5f15631e2082ee6eb80279d114f2",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "php": ">=5.1.2"
            },
            "bin": [
                "scripts/phpcs",
                "scripts/phpcbf"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-phpcs-fixer": "2.0.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "CodeSniffer.php",
                    "CodeSniffer/CLI.php",
                    "CodeSniffer/Exception.php",
                    "CodeSniffer/File.php",
                    "CodeSniffer/Fixer.php",
                    "CodeSniffer/Report.php",
                    "CodeSniffer/Reporting.php",
                    "CodeSniffer/Sniff.php",
                    "CodeSniffer/Tokens.php",
                    "CodeSniffer/Reports/",
                    "CodeSniffer/Tokenizers/",
                    "CodeSniffer/DocGenerators/",
                    "CodeSniffer/Standards/AbstractPatternSniff.php",
                    "CodeSniffer/Standards/AbstractScopeSniff.php",
                    "CodeSniffer/Standards/AbstractVariableSniff.php",
                    "CodeSniffer/Standards/IncorrectPatternException.php",
                    "CodeSniffer/Standards/Generic/Sniffs/",
                    "CodeSniffer/Standards/MySource/Sniffs/",
                    "CodeSniffer/Standards/PEAR/Sniffs/",
                    "CodeSniffer/Standards/PSR1/Sniffs/",
                    "CodeSniffer/Standards/PSR2/Sniffs/",
                    "CodeSniffer/Standards/Squiz/Sniffs/",
                    "CodeSniffer/Standards/Zend/Sniffs/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Greg Sherwood",
                    "role": "lead"
                }
            ],
            "description": "PHP_CodeSniffer tokenises PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
            "homepage": "http://www.squizlabs.com/php-codesniffer",
            "keywords": [
                "phpcs",
                "standards"
            ],
            "time": "2014-10-15 23:54:48"
        },
        {
            "name": "symfony/console",
            "version": "v2.8.2",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/console.git",
                "reference": "d0239fb42f98dd02e7d342f793c5d2cdee0c478d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/console/zipball/d0239fb42f98dd02e7d342f793c5d2cdee0c478d",
                "reference": "d0239fb42f98dd02e7d342f793c5d2cdee0c478d",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9",
                "symfony/polyfill-mbstring": "~1.0"
            },
            "require-dev": {
                "psr/log": "~1.0",
                "symfony/event-dispatcher": "~2.1|~3.0.0",
                "symfony/process": "~2.1|~3.0.0"
            },
            "suggest": {
                "psr/log": "For using the console logger",
                "symfony/event-dispatcher": "",
                "symfony/process": ""
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Console\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Console Component",
            "homepage": "https://symfony.com",
            "time": "2016-01-14 08:33:16"
        },
        {
            "name": "symfony/options-resolver",
            "version": "v2.6.13",
            "target-dir": "Symfony/Component/OptionsResolver",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/options-resolver.git",
                "reference": "31e56594cee489e9a235b852228b0598b52101c1"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/options-resolver/zipball/31e56594cee489e9a235b852228b0598b52101c1",
                "reference": "31e56594cee489e9a235b852228b0598b52101c1",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "symfony/phpunit-bridge": "~2.7"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.6-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Symfony\\Component\\OptionsResolver\\": ""
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony OptionsResolver Component",
            "homepage": "https://symfony.com",
            "keywords": [
                "config",
                "configuration",
                "options"
            ],
            "time": "2015-05-13 11:33:56"
        },
        {
            "name": "symfony/polyfill-mbstring",
            "version": "v1.1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/polyfill-mbstring.git",
                "reference": "1289d16209491b584839022f29257ad859b8532d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
                "reference": "1289d16209491b584839022f29257ad859b8532d",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "suggest": {
                "ext-mbstring": "For best performance"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.1-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Polyfill\\Mbstring\\": ""
                },
                "files": [
                    "bootstrap.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Nicolas Grekas",
                    "email": "p@tchwork.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony polyfill for the Mbstring extension",
            "homepage": "https://symfony.com",
            "keywords": [
                "compatibility",
                "mbstring",
                "polyfill",
                "portable",
                "shim"
            ],
            "time": "2016-01-20 09:13:37"
        },
        {
            "name": "symfony/yaml",
            "version": "v2.8.2",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/yaml.git",
                "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/yaml/zipball/34c8a4b51e751e7ea869b8262f883d008a2b81b8",
                "reference": "34c8a4b51e751e7ea869b8262f883d008a2b81b8",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Yaml\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Yaml Component",
            "homepage": "https://symfony.com",
            "time": "2016-01-13 10:28:07"
        },
        {
            "name": "tracy/tracy",
            "version": "v2.3.8",
            "source": {
                "type": "git",
                "url": "https://github.com/nette/tracy.git",
                "reference": "9aa0ade4444def3178aa808255ea326b9e3da9f2"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nette/tracy/zipball/9aa0ade4444def3178aa808255ea326b9e3da9f2",
                "reference": "9aa0ade4444def3178aa808255ea326b9e3da9f2",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.1"
            },
            "require-dev": {
                "nette/di": "~2.3",
                "nette/tester": "~1.3"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src"
                ],
                "files": [
                    "src/shortcuts.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause",
                "GPL-2.0",
                "GPL-3.0"
            ],
            "authors": [
                {
                    "name": "David Grudl",
                    "homepage": "https://davidgrudl.com"
                },
                {
                    "name": "Nette Community",
                    "homepage": "https://nette.org/contributors"
                }
            ],
            "description": "Tracy: useful PHP debugger",
            "homepage": "https://tracy.nette.org",
            "keywords": [
                "debug",
                "debugger",
                "nette"
            ],
            "time": "2016-01-20 03:14:47"
        }
    ],
    "aliases": [],
    "minimum-stability": "stable",
    "stability-flags": {
        "squizlabs/php_codesniffer": 5
    },
    "prefer-stable": false,
    "prefer-lowest": false,
    "platform": {
        "php": ">= 5.3",
        "ext-curl": "*"
    },
    "platform-dev": []
}
#!/usr/bin/env php
<?php

require_once __DIR__.'/helper.php';

list($client) = parseArgs("account-info", $argv);

$accountInfo = $client->getAccountInfo();

print_r($accountInfo);
#!/usr/bin/env php
<?php

require_once __DIR__."/../lib/Dropbox/strict.php";

if (PHP_SAPI !== "cli") {
    throw new \Exception("This program was meant to be run from the command-line and not as a web app.  Bad value for PHP_SAPI.  Expected \"cli\", given \"".PHP_SAPI."\".");
}

// NOTE: You should be using Composer's global autoloader.  But just so these examples
// work for people who don't have Composer, we'll use the library's "autoload.php".
require_once __DIR__.'/../lib/Dropbox/autoload.php';

use \Dropbox as dbx;

if ($argc === 1) {
    echoHelp($argv[0]);
    die;
}
if ($argc !== 3) {
    fwrite(STDERR, "Expecting exactly 2 arguments, got ".($argc - 1)."\n");
    fwrite(STDERR, "Run with no arguments for help\n");
    die;
}

$argAppInfoFile = $argv[1];
$argAuthFileOutput = $argv[2];

try {
    list($appInfoJson, $appInfo) = dbx\AppInfo::loadFromJsonFileWithRaw($argAppInfoFile);
}
catch (dbx\AppInfoLoadException $ex) {
    fwrite(STDERR, "Error loading <app-info-file>: ".$ex->getMessage()."\n");
    die;
}

// This is a command-line tool (as opposed to a web app), so we can't supply a redirect URI.
$webAuth = new dbx\WebAuthNoRedirect($appInfo, "examples-authorize", "en");
$authorizeUrl = $webAuth->start();

echo "1. Go to: $authorizeUrl\n";
echo "2. Click \"Allow\" (you might have to log in first).\n";
echo "3. Copy the authorization code.\n";
echo "Enter the authorization code here: ";
$authCode = \trim(\fgets(STDIN));

list($accessToken, $userId) = $webAuth->finish($authCode);

echo "Authorization complete.\n";
echo "- User ID: $userId\n";
echo "- Access Token: $accessToken\n";

$authArr = array(
    "access_token" => $accessToken,
);

if (array_key_exists('auth_host', $appInfoJson)) {
    $authArr['auth_host'] = $appInfoJson['auth_host'];
}
if (array_key_exists('host_suffix', $appInfoJson)) {
    $authArr['host_suffix'] = $appInfoJson['host_suffix'];
}

$json_options = 0;
if (defined('JSON_PRETTY_PRINT')) {
    $json_options |= JSON_PRETTY_PRINT;  // Supported in PHP 5.4+
}
$json = json_encode($authArr, $json_options);

if (file_put_contents($argAuthFileOutput, $json) !== false) {
    echo "Saved authorization information to \"$argAuthFileOutput\".\n";
}
else {
    fwrite(STDERR, "Error saving to \"$argAuthFileOutput\".\n");
    fwrite(STDERR, "Dumping to stderr instead:\n");
    fwrite(STDERR, $json);
    fwrite(STDERR, "\n");
    die;
}

function echoHelp($command) {
    echo "\n";
    echo "Usage: $command <app-info-file> <auth-file-output>\n";
    echo "\n";
    echo "<app-info-file>: A JSON file with information about your API app.  Example:\n";
    echo "\n";
    echo "  {\n";
    echo "    \"key\": \"Your Dropbox API app key\",\n";
    echo "    \"secret\": \"Your Dropbox API app secret\"\n";
    echo "  }\n";
    echo "\n";
    echo "  Get an API app key by registering with Dropbox:\n";
    echo "    https://dropbox.com/developers/apps\n";
    echo "\n";
    echo "<auth-file-output>: If authorization is successful, the resulting API\n";
    echo "  access token will be saved to this file, which can then be used with\n";
    echo "  other example programs, such as \"examples/account-info.php\".\n";
    echo "\n";
}
#!/usr/bin/env php
<?php

require_once __DIR__.'/helper.php';
use \Dropbox as dbx;

list($client, $dropboxPath, $localPath) = parseArgs("get-shareable-link", $argv,
    // Required parameters
    array(
        array("dropbox-path", "The path of the file (on Dropbox) to get a shareable link for."),
    ));

$pathError = dbx\Path::findErrorNonRoot($dropboxPath);
if ($pathError !== null) {
    fwrite(STDERR, "Invalid <dropbox-path>: $pathError\n");
    die;
}

$url = $client->createShareableLink($dropboxPath);
if ($url === null) {
    fwrite(STDERR, "File not found on Dropbox.\n");
    die;
}

print "$url\n";
#!/usr/bin/env php
<?php

require_once __DIR__.'/helper.php';
use \Dropbox as dbx;

/* @var dbx\Client $client */
/* @var string $cursor */
list($client, $cursor) = parseArgs("delta", $argv,
    // Required parameters
    array(),
    // Optional parameters
    array(
        array("cursor", "The cursor returned by the previous delta call (optional)."),
    ));

$deltaPage = $client->getDelta($cursor);

$numAdds = 0;
$numRemoves = 0;
foreach ($deltaPage["entries"] as $entry) {
    list($lcPath, $metadata) = $entry;
    if ($metadata === null) {
        echo "- $lcPath\n";
        $numRemoves++;
    } else {
        echo "+ $lcPath\n";
        $numAdds++;
    }
}
echo "Num Adds: $numAdds\n";
echo "Num Removes: $numRemoves\n";
echo "Has More: ".$deltaPage["has_more"]."\n";
echo "Cursor: ".$deltaPage["cursor"]."\n";
#!/usr/bin/env php
<?php

require_once __DIR__.'/helper.php';
use \Dropbox as dbx;

/* @var dbx\Client $client */
/* @var string $dropboxPath */
list($client, $dropboxPath) = parseArgs("direct-link", $argv, array(
        array("dropbox-path", "The path (on Dropbox) to create a temporary direct link for."),
    ));

$pathError = dbx\Path::findError($dropboxPath);
if ($pathError !== null) {
    fwrite(STDERR, "Invalid <dropbox-path>: $pathError\n");
    die;
}

$link = $client->createTemporaryDirectLink($dropboxPath);

print_r($link);
#!/usr/bin/env php
<?php

require_once __DIR__.'/helper.php';
use \Dropbox as dbx;

/* @var dbx\Client $client */
/* @var string $dropboxPath */
/* @var string $localPath */
list($client, $dropboxPath, $localPath) = parseArgs("download-file", $argv,
    // Required parameters
    array(
        array("dropbox-path", "The path of the file (on Dropbox) to download."),
        array("local-path", "The local path to save the downloaded file contents to."),
    ));

$pathError = dbx\Path::findErrorNonRoot($dropboxPath);
if ($pathError !== null) {
    fwrite(STDERR, "Invalid <dropbox-path>: $pathError\n");
    die;
}

$metadata = $client->getFile($dropboxPath, fopen($localPath, "wb"));
if ($metadata === null) {
    fwrite(STDERR, "File not found on Dropbox.\n");
    die;
}

print_r($metadata);
echo "File contents written to \"$localPath\"\n";
#!/usr/bin/env php
<?php

require_once __DIR__.'/helper.php';
use \Dropbox as dbx;

/* @var dbx\Client $client */
/* @var string $dropboxPath */
list($client, $dropboxPath) = parseArgs("get-metadata", $argv, array(
        array("dropbox-path", "The path (on Dropbox) that you want metadata for."),
    ));

$pathError = dbx\Path::findError($dropboxPath);
if ($pathError !== null) {
    fwrite(STDERR, "Invalid <dropbox-path>: $pathError\n");
    die;
}

$metadata = $client->getMetadataWithChildren($dropboxPath);

if ($metadata === null) {
    fwrite(STDERR, "No file or folder at that path.\n");
    die;
}

// If it's a folder, remove the 'contents' list from $metadata; print that stuff out after.
$children = null;
if ($metadata['is_dir']) {
    $children = $metadata['contents'];
    unset($metadata['contents']);
}

print_r($metadata);

if ($children !== null && count($children) > 0) {
    print "Children:\n";
    foreach ($children as $child) {
        $name = dbx\Path::getName($child['path']);
        if ($child['is_dir']) $name = "$name/";  // Put a "/" after folder names.
        print "- $name\n";
    }
}
<?php

require_once __DIR__."/../lib/Dropbox/strict.php";

if (PHP_SAPI !== "cli") {
    throw new \Exception("This program was meant to be run from the command-line and not as a web app.  Bad value for PHP_SAPI.  Expected \"cli\", given \"".PHP_SAPI."\".");
}

// NOTE: You should be using Composer's global autoloader.  But just so these examples
// work for people who don't have Composer, we'll use the library's "autoload.php".
require_once __DIR__.'/../lib/Dropbox/autoload.php';
use \Dropbox as dbx;

/**
 * A helper function that checks for the correct number of command line arguments,
 * loads the auth-file, and creates a \Dropbox\Client object.
 *
 * It returns an array where the first element is the \Dropbox\Client object and the
 * rest of the elements are the arguments you wanted.
 */
function parseArgs($exampleName, $argv, $requiredParams = null, $optionalParams = null)
{
    if ($requiredParams === null) $requiredParams = array();
    if ($optionalParams === null) $optionalParams = array();

    $minArgs = 1 + count($requiredParams);
    $maxArgs = $minArgs + count($optionalParams);

    $programName = $argv[0];
    $args = \array_slice($argv, 1);

    // If no args.  Print help message.
    if (count($args) === 0) {
        // Construct the param list for the "Usage" line.
        $paramSpec = "";
        foreach ($requiredParams as $p) {
            $paramSpec .= " ".$p[0];
        }
        if (count($optionalParams) > 0) {
            $paramSpec .= " [";
            foreach ($optionalParams as $p) {
                $paramSpec .= " ".$p[0];
            }
            $paramSpec .= " ]";
        }

        echo "\n";
        echo "Usage: $programName auth-file$paramSpec\n";
        echo "\n";

        // Print out help for each param.
        printParamHelp("auth-file",
            "A file with authorization information.  You can use the \"examples/authorize.php\" ".
            "program to generate this file.");
        foreach (array_merge($requiredParams, $optionalParams) as $param) {
            list($paramName, $paramDesc) = $param;
            printParamHelp($paramName, $paramDesc);
        }

        echo "\n";
        echo "OPTIONS:\n";
        echo "\n";
        echo "--locale=...   (example: --locale=fr)\n";
        echo "     The locale you want the Dropbox API to use for localized strings.\n";
        echo "\n";
        exit(0);
    }

    $locale = null;

    // Parse out the option args.
    $nonOptionArgs = array();
    for ($i = 0; $i < count($args); $i++) {
        $arg = $args[$i];
        if ($arg === "-" || $arg === "--") {
            // No more options.  Put the rest on the list of non-option args.
            for ($i++; $i < count($args); $i++) {
                \array_push($nonOptionArgs, $args[$i]);
            }
            break;
        }
        if (startsWith($arg, "-")) {
            if (startsWith($arg, "--")) {
                $option = substr($arg, 2);
            } else {
                $option = substr($arg, 1);
            }
            $equalPos = \strpos($option, "=");
            if ($equalPos === false) {
                $optionName = $option;
                $optionArg = null;
            } else {
                $optionName = \substr($option, 0, $equalPos);
                $optionArg = \substr($option, $equalPos+1);
            }

            if ($optionName === 'locale') {
                if ($optionArg === null) {
                    fwrite(STDERR, "\"locale\" option requires an argument.\n");
                    fwrite(STDERR, "Run with no arguments for help.\n");
                    die;
                }
                $locale = $optionArg;
                if (count($locale) === 0) {
                    fwrite(STDERR, "\"locale\" must not be empty.\n");
                    fwrite(STDERR, "Run with no arguments for help.\n");
                    die;
                }
            }
            else {
                fwrite(STDERR, "Unknown option: \"$optionName\".\n");
                fwrite(STDERR, "Run with no arguments for help.\n");
                die;
            }
        }
        else {
            \array_push($nonOptionArgs, $arg);
        }
    }

    $givenArgs = count($nonOptionArgs);

    // Make sure the argument count is compatible with the parameter count.
    if ($minArgs === $maxArgs) {
        if (count($nonOptionArgs) !== $minArgs) {
            fwrite(STDERR, "Expecting exactly $minArgs non-option arguments, got $givenArgs.\n");
            fwrite(STDERR, "Run with no arguments for help.\n");
            die;
        }
    }
    else {
        if ($givenArgs < $minArgs) {
            fwrite(STDERR, "Expecting at least $minArgs non-option arguments, got $givenArgs.\n");
            fwrite(STDERR, "Run with no arguments for help.\n");
            die;
        }
        else if ($givenArgs > $maxArgs) {
            fwrite(STDERR, "Expecting at most $maxArgs non-option arguments, got $givenArgs.\n");
            fwrite(STDERR, "Run with no arguments for help.\n");
            die;
        }
    }

    try {
        list($accessToken, $host) = dbx\AuthInfo::loadFromJsonFile($nonOptionArgs[0]);
    }
    catch (dbx\AuthInfoLoadException $ex) {
        fwrite(STDERR, "Error loading <auth-file>: ".$ex->getMessage()."\n");
        die;
    }

    $client = new dbx\Client($accessToken, "examples-$exampleName", $locale, $host);

    // Fill in the extra/optional arg slots with nulls.
    $ret = array_slice($nonOptionArgs, 1);
    while (count($ret) < $maxArgs) {
        array_push($ret, null);
    }

    // Return the args they need, plus the $client object in front.
    array_unshift($ret, $client);
    return $ret;
}

function startsWith($s, $prefix)
{
    return (\substr_compare($s, $prefix, 0, count($prefix)) === 0);
}

function printParamHelp($paramName, $paramDesc)
{
    $wordWrapWidth = 70;
    $lines = wordwrap("$paramName: $paramDesc", $wordWrapWidth, "\n  ");
    echo "$lines\n\n";
}
#!/usr/bin/env php
<?php

require_once __DIR__.'/helper.php';
use \Dropbox as dbx;

/* @var dbx\Client $client */
/* @var string $dropboxPath */
list($client, $dropboxPath) = parseArgs("link", $argv, array(
        array("dropbox-path", "The path (on Dropbox) to create a link for."),
    ));

$pathError = dbx\Path::findError($dropboxPath);
if ($pathError !== null) {
    fwrite(STDERR, "Invalid <dropbox-path>: $pathError\n");
    die;
}

$link = $client->createShareableLink($dropboxPath);

print($link."\n");
#! /usr/bin/env php
<?php
/*
Test to see if the PHP installation performs basic SSL security checks.
-----------------------------------------------------------------------------
If any of the tests fail, it means your PHP installation uses an insecure SSL
implementation.  An attacker can view and manipulate your communication with
the Dropbox API servers.  If this is the case, try upgrading your version of
PHP.
-----------------------------------------------------------------------------
You can run this script in one of three ways:
1. Run on the command-line: php test-ssl.php
2. Put the SDK folder in your web server's folder and view this page.
3. Add the following code to a test page on your site:
    <?php
        require_once "path-to-dropbox-sdk/lib/Dropbox/autoload.php"
        echo "<pre>\n"
        Dropbox\SSLTester::test();
        echo "</pre>\n"
    ?>
*/

require_once __DIR__."/../lib/Dropbox/strict.php";

// NOTE: You should be using Composer's global autoloader.  But just so these examples
// work for people who don't have Composer, we'll use the library's "autoload.php".
require_once __DIR__.'/../lib/Dropbox/autoload.php';

use \Dropbox as dbx;

if (PHP_SAPI === "cli") {
    // Command-line test.
    $passed = dbx\SSLTester::test();
    if (!$passed) exit(1);
}
else {
    // Web test.
    echo "<pre>\n";
    dbx\SSLTester::test();
    echo "</pre>\n";
}
#!/usr/bin/env php
<?php

require_once __DIR__."/../lib/Dropbox/strict.php";

if (PHP_SAPI !== "cli") {
    throw new \Exception("This program was meant to be run from the command-line and not as a web app.  Bad value for PHP_SAPI.  Expected \"cli\", given \"".PHP_SAPI."\".");
}

// NOTE: You should be using Composer's global autoloader.  But just so these examples
// work for people who don't have Composer, we'll use the library's "autoload.php".
require_once __DIR__.'/../lib/Dropbox/autoload.php';

use \Dropbox as dbx;

if ($argc === 1) {
    echoHelp($argv[0]);
    die;
}

$remainingArgs = array();
$optionsAllowed = true;
$disable = false;
for ($i = 1; $i < $argc; $i++) {
    $arg = $argv[$i];
    if ($optionsAllowed && strpos($arg, "-") === 0) {
        if ($arg === "--") {
            $optionsAllowed = false;
            continue;
        }
        if ($arg === "--disable") {
            if ($disable) {
                fwrite(STDERR, "Option \"--disable\" used more than once.\n");
                die;
            }
            $disable = true;
        }
        else {
            fwrite(STDERR, "Unrecognized option \"$arg\".\n");
            fwrite(STDERR, "Run with no arguments for help\n");
        }
    }
    else {
        array_push($remainingArgs, $arg);
    }
}

if (count($remainingArgs) !== 3) {
    fwrite(STDERR, "Expecting exactly 3 non-option arguments, got ".count($remainingArgs)."\n");
    fwrite(STDERR, "Run with no arguments for help\n");
    die;
}

$appInfoFile = $remainingArgs[0];
$oauth1AccessToken = new dbx\OAuth1AccessToken($remainingArgs[1], $remainingArgs[2]);

try {
    list($appInfoJson, $appInfo) = dbx\AppInfo::loadFromJsonFileWithRaw($appInfoFile);
}
catch (dbx\AppInfoLoadException $ex) {
    fwrite(STDERR, "Error loading <app-info-file>: ".$ex->getMessage()."\n");
    die;
}

// Get an OAuth 2 access token.

$upgrader = new dbx\OAuth1Upgrader($appInfo, "examples-authorize", "en");
$oauth2AccessToken = $upgrader->createOAuth2AccessToken($oauth1AccessToken);
echo "OAuth 2 access token obtained.\n";

// Write out auth JSON.

$authArr = array(
    "access_token" => $oauth2AccessToken,
);

if (array_key_exists('host', $appInfoJson)) {
    $authArr['host'] = $appInfoJson['host'];
}

$json_options = 0;
if (defined('JSON_PRETTY_PRINT')) {
    $json_options |= JSON_PRETTY_PRINT;  // Supported in PHP 5.4+
}
echo json_encode($authArr, $json_options)."\n";

// Disable the OAuth 1 access token.

if ($disable) {
    $upgrader->disableOAuth1AccessToken($oauth1AccessToken);
    echo "OAuth 1 access token disabled.\n";
}

function echoHelp($command) {
    echo "\n";
    echo "Usage: $command <app-info-file> <oa1-access-token-key> <oa1-access-token-secret>\n";
    echo "\n";
    echo "<app-info-file>: A JSON file with information about your API app.  Example:\n";
    echo "\n";
    echo "  {\n";
    echo "    \"key\": \"Your Dropbox API app key\",\n";
    echo "    \"secret\": \"Your Dropbox API app secret\",\n";
    echo "  }\n";
    echo "\n";
    echo "  Get an API app key by registering with Dropbox:\n";
    echo "    https://dropbox.com/developers/apps\n";
    echo "\n";
    echo "<oa1-access-token-key>: The OAuth 1 access token key.\n";
    echo "\n";
    echo "<oa1-access-token-secret>: The OAuth 1 access token secret.\n";
    echo "\n";
    echo "Options:\n";
    echo "\n";
    echo "  --disable: After we get an OAuth 2 access token, tell the server to\n";
    echo "      disable the OAuth 1 access token.\n";
    echo "\n";
}
#!/usr/bin/env php
<?php

require_once __DIR__.'/helper.php';
use \Dropbox as dbx;

/* @var dbx\Client $client */
/* @var string $sourcePath */
/* @var string $dropboxPath */
list($client, $sourcePath, $dropboxPath) = parseArgs("upload-file", $argv, array(
        array("source-path", "A path to a local file or a URL of a resource."),
        array("dropbox-path", "The path (on Dropbox) to save the file to."),
    ));

$pathError = dbx\Path::findErrorNonRoot($dropboxPath);
if ($pathError !== null) {
    fwrite(STDERR, "Invalid <dropbox-path>: $pathError\n");
    die;
}

$size = null;
if (\stream_is_local($sourcePath)) {
    $size = \filesize($sourcePath);
}

$fp = fopen($sourcePath, "rb");
$metadata = $client->uploadFile($dropboxPath, dbx\WriteMode::add(), $fp, $size);
fclose($fp);

print_r($metadata);
<?php

require_once __DIR__.'/../lib/Dropbox/strict.php';

$appInfoFile = __DIR__."/web-file-browser.app";

// NOTE: You should be using Composer's global autoloader.  But just so these examples
// work for people who don't have Composer, we'll use the library's "autoload.php".
require_once __DIR__.'/../lib/Dropbox/autoload.php';

use \Dropbox as dbx;

$requestPath = init();

session_start();

if ($requestPath === "/dropbox-auth-start") {
    $authorizeUrl = getWebAuth()->start();
    header("Location: $authorizeUrl");
}
else if ($requestPath === "/dropbox-auth-finish") {
    try {
        list($accessToken, $userId, $urlState) = getWebAuth()->finish($_GET);
        // We didn't pass in $urlState to finish, and we're assuming the session can't be
        // tampered with, so this should be null.
        assert($urlState === null);
    }
    catch (dbx\WebAuthException_BadRequest $ex) {
        respondWithError(400, "Bad Request");
        // Write full details to server error log.
        // IMPORTANT: Never show the $ex->getMessage() string to the user -- it could contain
        // sensitive information.
        error_log("/dropbox-auth-finish: bad request: " . $ex->getMessage());
        exit;
    }
    catch (dbx\WebAuthException_BadState $ex) {
        // Auth session expired.  Restart the auth process.
        header("Location: ".getPath("dropbox-auth-start"));
        exit;
    }
    catch (dbx\WebAuthException_Csrf $ex) {
        respondWithError(403, "Unauthorized", "CSRF mismatch");
        // Write full details to server error log.
        // IMPORTANT: Never show the $ex->getMessage() string to the user -- it contains
        // sensitive information that could be used to bypass the CSRF check.
        error_log("/dropbox-auth-finish: CSRF mismatch: " . $ex->getMessage());
        exit;
    }
    catch (dbx\WebAuthException_NotApproved $ex) {
        echo renderHtmlPage("Not Authorized?", "Why not?");
        exit;
    }
    catch (dbx\WebAuthException_Provider $ex) {
        error_log("/dropbox-auth-finish: unknown error: " . $ex->getMessage());
        respondWithError(500, "Internal Server Error");
        exit;
    }
    catch (dbx\Exception $ex) {
        error_log("/dropbox-auth-finish: error communicating with Dropbox API: " . $ex->getMessage());
        respondWithError(500, "Internal Server Error");
        exit;
    }

    // NOTE: A real web app would store the access token in a database.
    $_SESSION['access-token'] = $accessToken;

    echo renderHtmlPage("Authorized!",
        "Auth complete, <a href='".htmlspecialchars(getPath(""))."'>click here</a> to browse.");
}
else if ($requestPath === "/dropbox-auth-unlink") {
    // "Forget" the access token.
    unset($_SESSION['access-token']);
    echo renderHtmlPage("Unlinked.",
        "Go back <a href='".htmlspecialchars(getPath(""))."'>home</a>.");
}
else if ($requestPath === "/") {
    $dbxClient = getClient();

    if ($dbxClient === false) {
        header("Location: ".getPath("dropbox-auth-start"));
        exit;
    }

    $path = "/";
    if (isset($_GET['path'])) $path = $_GET['path'];

    $entry = $dbxClient->getMetadataWithChildren($path);
    if ($entry['is_dir']) {
        echo renderFolder($entry);
    }
    else {
        echo renderFile($entry);
    }
}
else if ($requestPath == "/download") {
    $dbxClient = getClient();

    if ($dbxClient === false) {
        header("Location: ".getPath("dropbox-auth-start"));
        exit;
    }

    if (!isset($_GET['path'])) {
        header("Location: ".getPath(""));
        exit;
    }
    $path = $_GET['path'];

    $fd = tmpfile();
    $metadata = $dbxClient->getFile($path, $fd);

    header("Content-Type: $metadata[mime_type]");
    fseek($fd, 0);
    fpassthru($fd);
    fclose($fd);
}
else if ($requestPath === "/upload") {
    if (empty($_FILES['file']['name'])) {
        echo renderHtmlPage("Error", "Please choose a file to upload");
        exit;
    }

    if (!empty($_FILES['file']['error'])) {
        echo renderHtmlPage("Error", "Error ".$_FILES['file']['error']." uploading file.  See <a href='http://php.net/manual/en/features.file-upload.errors.php'>the docs</a> for details");
        exit;
    }

    $dbxClient = getClient();

    $remoteDir = "/";
    if (isset($_POST['folder'])) $remoteDir = $_POST['folder'];

    $remotePath = rtrim($remoteDir, "/")."/".$_FILES['file']['name'];

    $fp = fopen($_FILES['file']['tmp_name'], "rb");
    $result = $dbxClient->uploadFile($remotePath, dbx\WriteMode::add(), $fp);
    fclose($fp);
    $str = print_r($result, true);
    echo renderHtmlPage("Uploading File", "Result: <pre>$str</pre>");
}
else {
    echo renderHtmlPage("Bad URL", "No handler for $requestPath");
    exit;
}

function renderFolder($entry)
{
    // TODO: Add a token to counter CSRF attacks.
    $upload_path = htmlspecialchars(getPath('upload'));
    $path = htmlspecialchars($entry['path']);
    $form = <<<HTML
        <form action='$upload_path' method='post' enctype='multipart/form-data'>
        <label for='file'>Upload file:</label> <input name='file' type='file'/>
        <input type='submit' value='Upload'/>
        <input name='folder' type='hidden' value='$path'/>
        </form>
HTML;

    $listing = '';
    foreach ($entry['contents'] as $child) {
        $cp = $child['path'];
        $cn = basename($cp);
        if ($child['is_dir']) $cn .= '/';

        $cp = htmlspecialchars($cp);
        $link = getPath("?path=".htmlspecialchars($cp));
        $listing .= "<div><a style='text-decoration: none' href='$link'>$cn</a></div>";
    }

    return renderHtmlPage("Folder: $entry[path]", $form.$listing);
}

function getAppConfig()
{
    global $appInfoFile;

    try {
        $appInfo = dbx\AppInfo::loadFromJsonFile($appInfoFile);
    }
    catch (dbx\AppInfoLoadException $ex) {
        throw new Exception("Unable to load \"$appInfoFile\": " . $ex->getMessage());
    }

    $clientIdentifier = "examples-web-file-browser";
    $userLocale = null;

    return array($appInfo, $clientIdentifier, $userLocale);
}

function getClient()
{
    if (!isset($_SESSION['access-token'])) {
        return false;
    }

    list($appInfo, $clientIdentifier, $userLocale) = getAppConfig();
    $accessToken = $_SESSION['access-token'];
    return new dbx\Client($accessToken, $clientIdentifier, $userLocale, $appInfo->getHost());
}

function getWebAuth()
{
    list($appInfo, $clientIdentifier, $userLocale) = getAppConfig();
    $redirectUri = getUrl("dropbox-auth-finish");
    $csrfTokenStore = new dbx\ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token');
    return new dbx\WebAuth($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, $userLocale);
}

function renderFile($entry)
{
    $metadataStr = htmlspecialchars(print_r($entry, true));
    $downloadPath = getPath("download?path=".htmlspecialchars($entry['path']));
    $body = <<<HTML
        <pre>$metadataStr</pre>
        <a href="$downloadPath">Download this file</a>
HTML;

    return renderHtmlPage("File: ".$entry['path'], $body);
}

function renderHtmlPage($title, $body)
{
    return <<<HTML
    <html>
        <head>
            <title>$title</title>
        </head>
        <body>
            <h1>$title</h1>
            $body
        </body>
    </html>
HTML;
}

function respondWithError($code, $title, $body = "")
{
    $proto = $_SERVER['SERVER_PROTOCOL'];
    header("$proto $code $title", true, $code);
    echo renderHtmlPage($title, $body);
}

function getUrl($relative_path)
{
    if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
        $scheme = "https";
    } else {
        $scheme = "http";
    }
    $host = $_SERVER['HTTP_HOST'];
    $path = getPath($relative_path);
    return $scheme."://".$host.$path;
}

function getPath($relative_path)
{
    if (PHP_SAPI === 'cli-server') {
        return "/".$relative_path;
    } else {
        return $_SERVER["SCRIPT_NAME"]."/".$relative_path;
    }
}

function init()
{
    global $argv;

    // If we were run as a command-line script, launch the PHP built-in web server.
    if (PHP_SAPI === 'cli') {
        launchBuiltInWebServer($argv);
        assert(false);
    }

    if (PHP_SAPI === 'cli-server') {
        // For when we're running under PHP's built-in web server, do the routing here.
        return $_SERVER['SCRIPT_NAME'];
    }
    else {
        // For when we're running under CGI or mod_php.
        if (isset($_SERVER['PATH_INFO'])) {
            return $_SERVER['PATH_INFO'];
        } else {
            return "/";
        }
    }
}

function launchBuiltInWebServer($argv)
{
    // The built-in web server is only available in PHP 5.4+.
    if (version_compare(PHP_VERSION, '5.4.0', '<')) {
        fprintf(STDERR,
            "Unable to run example.  The version of PHP you used to run this script (".PHP_VERSION.")\n".
            "doesn't have a built-in web server.  You need PHP 5.4 or newer.\n".
            "\n".
            "You can still run this example if you have a web server that supports PHP 5.3.\n".
            "Copy the Dropbox PHP SDK into your web server's document path and access it there.\n");
        exit(2);
    }

    $php_file = $argv[0];
    if (count($argv) === 1) {
        $port = 5000;
    } else if (count($argv) === 2) {
        $port = intval($argv[1]);
    } else {
        fprintf(STDERR,
            "Too many arguments.\n".
            "Usage: php $argv[0] [server-port]\n");
        exit(1);
    }

    $host = "localhost:$port";
    $cmd = escapeshellarg(PHP_BINARY)." -S ".$host." ".escapeshellarg($php_file);
    $descriptors = array(
        0 => array("pipe", "r"),  // Process' stdin.  We'll just close this right away.
        1 => STDOUT,              // Relay process' stdout to ours.
        2 => STDERR,              // Relay process' stderr to ours.
    );
    $proc = proc_open($cmd, $descriptors, $pipes);
    if ($proc === false) {
        fprintf(STDERR,
            "Unable to launch PHP's built-in web server.  Used command:\n".
            "   $cmd\n");
        exit(2);
    }
    fclose($pipes[0]);  // Close the process' stdin.
    $exitCode = proc_close($proc);  // Wait for process to exit.
    exit($exitCode);
}
<?php
namespace Dropbox;

/**
 * Your app's API key and secret.
 */
final class AppInfo
{
    /**
     * Your Dropbox <em>app key</em> (OAuth calls this the <em>consumer key</em>).  You can
     * create an app key and secret on the <a href="http://dropbox.com/developers/apps">Dropbox developer website</a>.
     *
     * @return string
     */
    function getKey() { return $this->key; }

    /** @var string */
    private $key;

    /**
     * Your Dropbox <em>app secret</em> (OAuth calls this the <em>consumer secret</em>).  You can
     * create an app key and secret on the <a href="http://dropbox.com/developers/apps">Dropbox developer website</a>.
     *
     * Make sure that this is kept a secret.  Someone with your app secret can impesonate your
     * application.  People sometimes ask for help on the Dropbox API forums and
     * copy/paste code that includes their app secret.  Do not do that.
     *
     * @return string
     */
    function getSecret() { return $this->secret; }

    /** @var string */
    private $secret;

    /**
     * The set of servers your app will use.  This defaults to the standard Dropbox servers
     * {@link Host::getDefault}.
     *
     * @return Host
     *
     * @internal
     */
    function getHost() { return $this->host; }

    /** @var Host */
    private $host;

    /**
     * Constructor.
     *
     * @param string $key
     *    See {@link getKey()}
     * @param string $secret
     *    See {@link getSecret()}
     */
    function __construct($key, $secret)
    {
        self::checkKeyArg($key);
        self::checkSecretArg($secret);

        $this->key = $key;
        $this->secret = $secret;

        // The $host parameter is sort of internal.  We don't include it in the param list because
        // we don't want it to be included in the documentation.  Use PHP arg list hacks to get at
        // it.
        $host = null;
        if (\func_num_args() == 3) {
            $host = \func_get_arg(2);
            Host::checkArgOrNull("host", $host);
        }
        if ($host === null) {
            $host = Host::getDefault();
        }
        $this->host = $host;
    }

    /**
     * Loads a JSON file containing information about your app. At a minimum, the file must include
     * the "key" and "secret" fields.  Run 'php authorize.php' in the examples directory
     * for details about what this file should look like.
     *
     * @param string $path
     *    Path to a JSON file
     *
     * @return AppInfo
     *
     * @throws AppInfoLoadException
     */
    static function loadFromJsonFile($path)
    {
        list($rawJson, $appInfo) = self::loadFromJsonFileWithRaw($path);
        return $appInfo;
    }

    /**
     * Loads a JSON file containing information about your app. At a minimum, the file must include
     * the "key" and "secret" fields.  Run 'php authorize.php' in the examples directory
     * for details about what this file should look like.
     *
     * @param string $path
     *    Path to a JSON file
     *
     * @return array
     *    A list of two items.  The first is a PHP array representation of the raw JSON, the second
     *    is an AppInfo object that is the parsed version of the JSON.
     *
     * @throws AppInfoLoadException
     *
     * @internal
     */
    static function loadFromJsonFileWithRaw($path)
    {
        if (!file_exists($path)) {
            throw new AppInfoLoadException("File doesn't exist: \"$path\"");
        }

        $str = Util::stripUtf8Bom(file_get_contents($path));
        $jsonArr = json_decode($str, true, 10);

        if (is_null($jsonArr)) {
            throw new AppInfoLoadException("JSON parse error: \"$path\"");
        }

        $appInfo = self::loadFromJson($jsonArr);

        return array($jsonArr, $appInfo);
    }

    /**
     * Parses a JSON object to build an AppInfo object.  If you would like to load this from a file,
     * use the loadFromJsonFile() method.
     *
     * @param array $jsonArr Output from json_decode($str, true)
     *
     * @return AppInfo
     *
     * @throws AppInfoLoadException
     */
    static function loadFromJson($jsonArr)
    {
        if (!is_array($jsonArr)) {
            throw new AppInfoLoadException("Expecting JSON object, got something else");
        }

        $requiredKeys = array("key", "secret");
        foreach ($requiredKeys as $key) {
            if (!array_key_exists($key, $jsonArr)) {
                throw new AppInfoLoadException("Missing field \"$key\"");
            }

            if (!is_string($jsonArr[$key])) {
                throw new AppInfoLoadException("Expecting field \"$key\" to be a string");
            }
        }

        // Check app_key and app_secret
        $appKey = $jsonArr["key"];
        $appSecret = $jsonArr["secret"];

        $tokenErr = self::getTokenPartError($appKey);
        if (!is_null($tokenErr)) {
            throw new AppInfoLoadException("Field \"key\" doesn't look like a valid app key: $tokenErr");
        }

        $tokenErr = self::getTokenPartError($appSecret);
        if (!is_null($tokenErr)) {
            throw new AppInfoLoadException("Field \"secret\" doesn't look like a valid app secret: $tokenErr");
        }

        try {
            $host = Host::loadFromJson($jsonArr);
        }
        catch (HostLoadException $ex) {
            throw new AppInfoLoadException($ex->getMessage());
        }

        return new AppInfo($appKey, $appSecret, $host);
    }

    /**
     * Use this to check that a function argument is of type `AppInfo`
     *
     * @internal
     */
    static function checkArg($argName, $argValue)
    {
        if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
    }

    /**
     * Use this to check that a function argument is either `null` or of type
     * `AppInfo`.
     *
     * @internal
     */
    static function checkArgOrNull($argName, $argValue)
    {
        if ($argValue === null) return;
        if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
    }

    /** @internal */
    static function getTokenPartError($s)
    {
        if ($s === null) return "can't be null";
        if (strlen($s) === 0) return "can't be empty";
        if (strstr($s, ' ')) return "can't contain a space";
        return null;  // 'null' means "no error"
    }

    /** @internal */
    static function checkKeyArg($key)
    {
        $error = self::getTokenPartError($key);
        if ($error === null) return;
        throw new \InvalidArgumentException("Bad 'key': \"$key\": $error.");
    }

    /** @internal */
    static function checkSecretArg($secret)
    {
        $error = self::getTokenPartError($secret);
        if ($error === null) return;
        throw new \InvalidArgumentException("Bad 'secret': \"$secret\": $error.");
    }

}
<?php
namespace Dropbox;

/**
 * Thrown by the `AppInfo::loadXXX` methods if something goes wrong.
 */
final class AppInfoLoadException extends \Exception
{
    /**
     * @param string $message
     *
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * A class that gives get/put/clear access to a single entry in an array.
 */
class ArrayEntryStore implements ValueStore
{
    /** @var array */
    private $array;

    /** @var mixed */
    private $key;

    /**
     * Constructor.
     *
     * @param array $array
     *    The array that we'll be accessing.
     *
     * @param mixed $key
     *    The key for the array element we'll be accessing.
     */
    function __construct(&$array, $key)
    {
        $this->array = &$array;
        $this->key = $key;
    }

    /**
     * Returns the entry's current value or `null` if nothing is set.
     *
     * @return object
     */
    function get()
    {
        if (isset($this->array[$this->key])) {
            return $this->array[$this->key];
        } else {
            return null;
        }
    }

    /**
     * Set the array entry to the given value.
     *
     * @param object $value
     */
    function set($value)
    {
        $this->array[$this->key] = $value;
    }

    /**
     * Clear the entry.
     */
    function clear()
    {
        unset($this->array[$this->key]);
    }
}
<?php
namespace Dropbox;

/**
 * Base class for API authorization-related classes.
 */
class AuthBase
{
    /**
     * Whatever AppInfo was passed into the constructor.
     *
     * @return AppInfo
     */
    function getAppInfo() { return $this->appInfo; }

    /** @var AppInfo */
    protected $appInfo;

    /**
     * An identifier for the API client, typically of the form "Name/Version".
     * This is used to set the HTTP `User-Agent` header when making API requests.
     * Example: `"PhotoEditServer/1.3"`
     *
     * If you're the author a higher-level library on top of the basic SDK, and the
     * "Photo Edit" app's server code is using your library to access Dropbox, you should append
     * your library's name and version to form the full identifier.  For example,
     * if your library is called "File Picker", you might set this field to:
     * `"PhotoEditServer/1.3 FilePicker/0.1-beta"`
     *
     * The exact format of the `User-Agent` header is described in
     * <a href="http://tools.ietf.org/html/rfc2616#section-3.8">section 3.8 of the HTTP specification</a>.
     *
     * Note that underlying HTTP client may append other things to the `User-Agent`, such as
     * the name of the library being used to actually make the HTTP request (such as cURL).
     *
     * @return string
     */
    function getClientIdentifier() { return $this->clientIdentifier; }

    /** @var string */
    protected $clientIdentifier;

    /**
     * The locale of the user of your application.  Some API calls return localized
     * data and error messages; this "user locale" setting determines which locale
     * the server should use to localize those strings.
     *
     * @return null|string
     */
    function getUserLocale() { return $this->userLocale; }

    /** @var string */
    protected $userLocale;

    /**
     * Constructor.
     *
     * @param AppInfo $appInfo
     *     See {@link getAppInfo()}
     * @param string $clientIdentifier
     *     See {@link getClientIdentifier()}
     * @param null|string $userLocale
     *     See {@link getUserLocale()}
     */
    function __construct($appInfo, $clientIdentifier, $userLocale = null)
    {
        AppInfo::checkArg("appInfo", $appInfo);
        Client::checkClientIdentifierArg("clientIdentifier", $clientIdentifier);
        Checker::argStringNonEmptyOrNull("userLocale", $userLocale);

        $this->appInfo = $appInfo;
        $this->clientIdentifier = $clientIdentifier;
        $this->userLocale = $userLocale;
    }
}
<?php
namespace Dropbox;

/**
 * This class contains methods to load an AppInfo and AccessToken from a JSON file.
 * This can help simplify simple scripts (such as the example programs that come with the
 * SDK) but is probably not useful in typical Dropbox API apps.
 *
 */
final class AuthInfo
{
    /**
     * Loads a JSON file containing authorization information for your app. 'php authorize.php'
     * in the examples directory for details about what this file should look like.
     *
     * @param string $path
     *    Path to a JSON file
     * @return array
     *    A `list(string $accessToken, Host $host)`.
     *
     * @throws AuthInfoLoadException
     */
    static function loadFromJsonFile($path)
    {
        if (!file_exists($path)) {
            throw new AuthInfoLoadException("File doesn't exist: \"$path\"");
        }

        $str = Util::stripUtf8Bom(file_get_contents($path));
        $jsonArr = json_decode($str, true, 10);

        if (is_null($jsonArr)) {
            throw new AuthInfoLoadException("JSON parse error: \"$path\"");
        }

        return self::loadFromJson($jsonArr);
    }

    /**
     * Parses a JSON object to build an AuthInfo object.  If you would like to load this from a file,
     * please use the @see loadFromJsonFile method.
     *
     * @param array $jsonArr
     *    A parsed JSON object, typcally the result of json_decode(..., true).
     * @return array
     *    A `list(string $accessToken, Host $host)`.
     *
     * @throws AuthInfoLoadException
     */
    private static function loadFromJson($jsonArr)
    {
        if (!is_array($jsonArr)) {
            throw new AuthInfoLoadException("Expecting JSON object, found something else");
        }

        // Check access_token
        if (!array_key_exists('access_token', $jsonArr)) {
            throw new AuthInfoLoadException("Missing field \"access_token\"");
        }

        $accessToken = $jsonArr['access_token'];
        if (!is_string($accessToken)) {
            throw new AuthInfoLoadException("Expecting field \"access_token\" to be a string");
        }

        try {
            $host = Host::loadFromJson($jsonArr);
        }
        catch (HostLoadException $ex) {
            throw new AuthInfoLoadException($ex->getMessage());
        }

        return array($accessToken, $host);
    }
}
<?php
namespace Dropbox;

/**
 * Thrown by the `AuthInfo::loadXXX` methods if something goes wrong.
 */
final class AuthInfoLoadException extends \Exception
{
    /**
     * @param string $message
     *
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

// The Dropbox SDK autoloader.  You probably shouldn't be using this.  Instead,
// use a global autoloader, like the Composer autoloader.
//
// But if you really don't want to use a global autoloader, do this:
//
//     require_once "<path-to-here>/Dropbox/autoload.php"

/**
 * @internal
 */
function autoload($name)
{
    // If the name doesn't start with "Dropbox\", then its not once of our classes.
    if (\substr_compare($name, "Dropbox\\", 0, 8) !== 0) return;

    // Take the "Dropbox\" prefix off.
    $stem = \substr($name, 8);

    // Convert "\" and "_" to path separators.
    $pathified_stem = \str_replace(array("\\", "_"), '/', $stem);

    $path = __DIR__ . "/" . $pathified_stem . ".php";
    if (\is_file($path)) {
        require_once $path;
    }
}

\spl_autoload_register('Dropbox\autoload');
# DigiCert Assured ID Root CA.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             0c:e7:e0:e5:17:d8:46:fe:8f:e5:60:fc:1b:f0:30:39
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA
#         Validity
#             Not Before: Nov 10 00:00:00 2006 GMT
#             Not After : Nov 10 00:00:00 2031 GMT
#         Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:ad:0e:15:ce:e4:43:80:5c:b1:87:f3:b7:60:f9:
#                     71:12:a5:ae:dc:26:94:88:aa:f4:ce:f5:20:39:28:
#                     58:60:0c:f8:80:da:a9:15:95:32:61:3c:b5:b1:28:
#                     84:8a:8a:dc:9f:0a:0c:83:17:7a:8f:90:ac:8a:e7:
#                     79:53:5c:31:84:2a:f6:0f:98:32:36:76:cc:de:dd:
#                     3c:a8:a2:ef:6a:fb:21:f2:52:61:df:9f:20:d7:1f:
#                     e2:b1:d9:fe:18:64:d2:12:5b:5f:f9:58:18:35:bc:
#                     47:cd:a1:36:f9:6b:7f:d4:b0:38:3e:c1:1b:c3:8c:
#                     33:d9:d8:2f:18:fe:28:0f:b3:a7:83:d6:c3:6e:44:
#                     c0:61:35:96:16:fe:59:9c:8b:76:6d:d7:f1:a2:4b:
#                     0d:2b:ff:0b:72:da:9e:60:d0:8e:90:35:c6:78:55:
#                     87:20:a1:cf:e5:6d:0a:c8:49:7c:31:98:33:6c:22:
#                     e9:87:d0:32:5a:a2:ba:13:82:11:ed:39:17:9d:99:
#                     3a:72:a1:e6:fa:a4:d9:d5:17:31:75:ae:85:7d:22:
#                     ae:3f:01:46:86:f6:28:79:c8:b1:da:e4:57:17:c4:
#                     7e:1c:0e:b0:b4:92:a6:56:b3:bd:b2:97:ed:aa:a7:
#                     f0:b7:c5:a8:3f:95:16:d0:ff:a1:96:eb:08:5f:18:
#                     77:4f
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Digital Signature, Certificate Sign, CRL Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier:
#                 45:EB:A2:AF:F4:92:CB:82:31:2D:51:8B:A7:A7:21:9D:F3:6D:C8:0F
#             X509v3 Authority Key Identifier:
#                 keyid:45:EB:A2:AF:F4:92:CB:82:31:2D:51:8B:A7:A7:21:9D:F3:6D:C8:0F
#
#     Signature Algorithm: sha1WithRSAEncryption
#          a2:0e:bc:df:e2:ed:f0:e3:72:73:7a:64:94:bf:f7:72:66:d8:
#          32:e4:42:75:62:ae:87:eb:f2:d5:d9:de:56:b3:9f:cc:ce:14:
#          28:b9:0d:97:60:5c:12:4c:58:e4:d3:3d:83:49:45:58:97:35:
#          69:1a:a8:47:ea:56:c6:79:ab:12:d8:67:81:84:df:7f:09:3c:
#          94:e6:b8:26:2c:20:bd:3d:b3:28:89:f7:5f:ff:22:e2:97:84:
#          1f:e9:65:ef:87:e0:df:c1:67:49:b3:5d:eb:b2:09:2a:eb:26:
#          ed:78:be:7d:3f:2b:f3:b7:26:35:6d:5f:89:01:b6:49:5b:9f:
#          01:05:9b:ab:3d:25:c1:cc:b6:7f:c2:f1:6f:86:c6:fa:64:68:
#          eb:81:2d:94:eb:42:b7:fa:8c:1e:dd:62:f1:be:50:67:b7:6c:
#          bd:f3:f1:1f:6b:0c:36:07:16:7f:37:7c:a9:5b:6d:7a:f1:12:
#          46:60:83:d7:27:04:be:4b:ce:97:be:c3:67:2a:68:11:df:80:
#          e7:0c:33:66:bf:13:0d:14:6e:f3:7f:1f:63:10:1e:fa:8d:1b:
#          25:6d:6c:8f:a5:b7:61:01:b1:d2:a3:26:a1:10:71:9d:ad:e2:
#          c3:f9:c3:99:51:b7:2b:07:08:ce:2e:e6:50:b2:a7:fa:0a:45:
#          2f:a2:f0:f2
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----
# DigiCert Global Root CA.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             08:3b:e0:56:90:42:46:b1:a1:75:6a:c9:59:91:c7:4a
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
#         Validity
#             Not Before: Nov 10 00:00:00 2006 GMT
#             Not After : Nov 10 00:00:00 2031 GMT
#         Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:e2:3b:e1:11:72:de:a8:a4:d3:a3:57:aa:50:a2:
#                     8f:0b:77:90:c9:a2:a5:ee:12:ce:96:5b:01:09:20:
#                     cc:01:93:a7:4e:30:b7:53:f7:43:c4:69:00:57:9d:
#                     e2:8d:22:dd:87:06:40:00:81:09:ce:ce:1b:83:bf:
#                     df:cd:3b:71:46:e2:d6:66:c7:05:b3:76:27:16:8f:
#                     7b:9e:1e:95:7d:ee:b7:48:a3:08:da:d6:af:7a:0c:
#                     39:06:65:7f:4a:5d:1f:bc:17:f8:ab:be:ee:28:d7:
#                     74:7f:7a:78:99:59:85:68:6e:5c:23:32:4b:bf:4e:
#                     c0:e8:5a:6d:e3:70:bf:77:10:bf:fc:01:f6:85:d9:
#                     a8:44:10:58:32:a9:75:18:d5:d1:a2:be:47:e2:27:
#                     6a:f4:9a:33:f8:49:08:60:8b:d4:5f:b4:3a:84:bf:
#                     a1:aa:4a:4c:7d:3e:cf:4f:5f:6c:76:5e:a0:4b:37:
#                     91:9e:dc:22:e6:6d:ce:14:1a:8e:6a:cb:fe:cd:b3:
#                     14:64:17:c7:5b:29:9e:32:bf:f2:ee:fa:d3:0b:42:
#                     d4:ab:b7:41:32:da:0c:d4:ef:f8:81:d5:bb:8d:58:
#                     3f:b5:1b:e8:49:28:a2:70:da:31:04:dd:f7:b2:16:
#                     f2:4c:0a:4e:07:a8:ed:4a:3d:5e:b5:7f:a3:90:c3:
#                     af:27
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Digital Signature, Certificate Sign, CRL Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier:
#                 03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
#             X509v3 Authority Key Identifier:
#                 keyid:03:DE:50:35:56:D1:4C:BB:66:F0:A3:E2:1B:1B:C3:97:B2:3D:D1:55
#
#     Signature Algorithm: sha1WithRSAEncryption
#          cb:9c:37:aa:48:13:12:0a:fa:dd:44:9c:4f:52:b0:f4:df:ae:
#          04:f5:79:79:08:a3:24:18:fc:4b:2b:84:c0:2d:b9:d5:c7:fe:
#          f4:c1:1f:58:cb:b8:6d:9c:7a:74:e7:98:29:ab:11:b5:e3:70:
#          a0:a1:cd:4c:88:99:93:8c:91:70:e2:ab:0f:1c:be:93:a9:ff:
#          63:d5:e4:07:60:d3:a3:bf:9d:5b:09:f1:d5:8e:e3:53:f4:8e:
#          63:fa:3f:a7:db:b4:66:df:62:66:d6:d1:6e:41:8d:f2:2d:b5:
#          ea:77:4a:9f:9d:58:e2:2b:59:c0:40:23:ed:2d:28:82:45:3e:
#          79:54:92:26:98:e0:80:48:a8:37:ef:f0:d6:79:60:16:de:ac:
#          e8:0e:cd:6e:ac:44:17:38:2f:49:da:e1:45:3e:2a:b9:36:53:
#          cf:3a:50:06:f7:2e:e8:c4:57:49:6c:61:21:18:d5:04:ad:78:
#          3c:2c:3a:80:6b:a7:eb:af:15:14:e9:d8:89:c1:b9:38:6c:e2:
#          91:6c:8a:ff:64:b9:77:25:57:30:c0:1b:24:a3:e1:dc:e9:df:
#          47:7c:b5:b4:24:08:05:30:ec:2d:bd:0b:bf:45:bf:50:b9:a9:
#          f3:eb:98:01:12:ad:c8:88:c6:98:34:5f:8d:0a:3c:c6:e9:d5:
#          95:95:6d:de
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
# DigiCert High Assurance EV Root CA.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             02:ac:5c:26:6a:0b:40:9b:8f:0b:79:f2:ae:46:25:77
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
#         Validity
#             Not Before: Nov 10 00:00:00 2006 GMT
#             Not After : Nov 10 00:00:00 2031 GMT
#         Subject: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:c6:cc:e5:73:e6:fb:d4:bb:e5:2d:2d:32:a6:df:
#                     e5:81:3f:c9:cd:25:49:b6:71:2a:c3:d5:94:34:67:
#                     a2:0a:1c:b0:5f:69:a6:40:b1:c4:b7:b2:8f:d0:98:
#                     a4:a9:41:59:3a:d3:dc:94:d6:3c:db:74:38:a4:4a:
#                     cc:4d:25:82:f7:4a:a5:53:12:38:ee:f3:49:6d:71:
#                     91:7e:63:b6:ab:a6:5f:c3:a4:84:f8:4f:62:51:be:
#                     f8:c5:ec:db:38:92:e3:06:e5:08:91:0c:c4:28:41:
#                     55:fb:cb:5a:89:15:7e:71:e8:35:bf:4d:72:09:3d:
#                     be:3a:38:50:5b:77:31:1b:8d:b3:c7:24:45:9a:a7:
#                     ac:6d:00:14:5a:04:b7:ba:13:eb:51:0a:98:41:41:
#                     22:4e:65:61:87:81:41:50:a6:79:5c:89:de:19:4a:
#                     57:d5:2e:e6:5d:1c:53:2c:7e:98:cd:1a:06:16:a4:
#                     68:73:d0:34:04:13:5c:a1:71:d3:5a:7c:55:db:5e:
#                     64:e1:37:87:30:56:04:e5:11:b4:29:80:12:f1:79:
#                     39:88:a2:02:11:7c:27:66:b7:88:b7:78:f2:ca:0a:
#                     a8:38:ab:0a:64:c2:bf:66:5d:95:84:c1:a1:25:1e:
#                     87:5d:1a:50:0b:20:12:cc:41:bb:6e:0b:51:38:b8:
#                     4b:cb
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Digital Signature, Certificate Sign, CRL Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier:
#                 B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
#             X509v3 Authority Key Identifier:
#                 keyid:B1:3E:C3:69:03:F8:BF:47:01:D4:98:26:1A:08:02:EF:63:64:2B:C3
#
#     Signature Algorithm: sha1WithRSAEncryption
#          1c:1a:06:97:dc:d7:9c:9f:3c:88:66:06:08:57:21:db:21:47:
#          f8:2a:67:aa:bf:18:32:76:40:10:57:c1:8a:f3:7a:d9:11:65:
#          8e:35:fa:9e:fc:45:b5:9e:d9:4c:31:4b:b8:91:e8:43:2c:8e:
#          b3:78:ce:db:e3:53:79:71:d6:e5:21:94:01:da:55:87:9a:24:
#          64:f6:8a:66:cc:de:9c:37:cd:a8:34:b1:69:9b:23:c8:9e:78:
#          22:2b:70:43:e3:55:47:31:61:19:ef:58:c5:85:2f:4e:30:f6:
#          a0:31:16:23:c8:e7:e2:65:16:33:cb:bf:1a:1b:a0:3d:f8:ca:
#          5e:8b:31:8b:60:08:89:2d:0c:06:5c:52:b7:c4:f9:0a:98:d1:
#          15:5f:9f:12:be:7c:36:63:38:bd:44:a4:7f:e4:26:2b:0a:c4:
#          97:69:0d:e9:8c:e2:c0:10:57:b8:c8:76:12:91:55:f2:48:69:
#          d8:bc:2a:02:5b:0f:44:d4:20:31:db:f4:ba:70:26:5d:90:60:
#          9e:bc:4b:17:09:2f:b4:cb:1e:43:68:c9:07:27:c1:d2:5c:f7:
#          ea:21:b9:68:12:9c:3c:9c:bf:9e:fc:80:5c:9b:63:cd:ec:47:
#          aa:25:27:67:a0:37:f3:00:82:7d:54:d7:a9:f8:e9:2e:13:a3:
#          77:e8:1f:4a
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
# Entrust Root Certification Authority - EC1.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             a6:8b:79:29:00:00:00:00:50:d0:91:f9
#     Signature Algorithm: ecdsa-with-SHA384
#         Issuer: C=US, O=Entrust, Inc., OU=See www.entrust.net/legal-terms, OU=(c) 2012 Entrust, Inc. - for authorized use only, CN=Entrust Root Certification Authority - EC1
#         Validity
#             Not Before: Dec 18 15:25:36 2012 GMT
#             Not After : Dec 18 15:55:36 2037 GMT
#         Subject: C=US, O=Entrust, Inc., OU=See www.entrust.net/legal-terms, OU=(c) 2012 Entrust, Inc. - for authorized use only, CN=Entrust Root Certification Authority - EC1
#         Subject Public Key Info:
#             Public Key Algorithm: id-ecPublicKey
#                 Public-Key: (384 bit)
#                 pub:
#                     04:84:13:c9:d0:ba:6d:41:7b:e2:6c:d0:eb:55:5f:
#                     66:02:1a:24:f4:5b:89:69:47:e3:b8:c2:7d:f1:f2:
#                     02:c5:9f:a0:f6:5b:d5:8b:06:19:86:4f:53:10:6d:
#                     07:24:27:a1:a0:f8:d5:47:19:61:4c:7d:ca:93:27:
#                     ea:74:0c:ef:6f:96:09:fe:63:ec:70:5d:36:ad:67:
#                     77:ae:c9:9d:7c:55:44:3a:a2:63:51:1f:f5:e3:62:
#                     d4:a9:47:07:3e:cc:20
#                 ASN1 OID: secp384r1
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier:
#                 B7:63:E7:1A:DD:8D:E9:08:A6:55:83:A4:E0:6A:50:41:65:11:42:49
#     Signature Algorithm: ecdsa-with-SHA384
#          30:64:02:30:61:79:d8:e5:42:47:df:1c:ae:53:99:17:b6:6f:
#          1c:7d:e1:bf:11:94:d1:03:88:75:e4:8d:89:a4:8a:77:46:de:
#          6d:61:ef:02:f5:fb:b5:df:cc:fe:4e:ff:fe:a9:e6:a7:02:30:
#          5b:99:d7:85:37:06:b5:7b:08:fd:eb:27:8b:4a:94:f9:e1:fa:
#          a7:8e:26:08:e8:7c:92:68:6d:73:d8:6f:26:ac:21:02:b8:99:
#          b7:26:41:5b:25:60:ae:d0:48:1a:ee:06
-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq
RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy
MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD
VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g
Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi
A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt
ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH
Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC
R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX
hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----
# Entrust Root Certification Authority - G2.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 1246989352 (0x4a538c28)
#     Signature Algorithm: sha256WithRSAEncryption
#         Issuer: C=US, O=Entrust, Inc., OU=See www.entrust.net/legal-terms, OU=(c) 2009 Entrust, Inc. - for authorized use only, CN=Entrust Root Certification Authority - G2
#         Validity
#             Not Before: Jul  7 17:25:54 2009 GMT
#             Not After : Dec  7 17:55:54 2030 GMT
#         Subject: C=US, O=Entrust, Inc., OU=See www.entrust.net/legal-terms, OU=(c) 2009 Entrust, Inc. - for authorized use only, CN=Entrust Root Certification Authority - G2
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:ba:84:b6:72:db:9e:0c:6b:e2:99:e9:30:01:a7:
#                     76:ea:32:b8:95:41:1a:c9:da:61:4e:58:72:cf:fe:
#                     f6:82:79:bf:73:61:06:0a:a5:27:d8:b3:5f:d3:45:
#                     4e:1c:72:d6:4e:32:f2:72:8a:0f:f7:83:19:d0:6a:
#                     80:80:00:45:1e:b0:c7:e7:9a:bf:12:57:27:1c:a3:
#                     68:2f:0a:87:bd:6a:6b:0e:5e:65:f3:1c:77:d5:d4:
#                     85:8d:70:21:b4:b3:32:e7:8b:a2:d5:86:39:02:b1:
#                     b8:d2:47:ce:e4:c9:49:c4:3b:a7:de:fb:54:7d:57:
#                     be:f0:e8:6e:c2:79:b2:3a:0b:55:e2:50:98:16:32:
#                     13:5c:2f:78:56:c1:c2:94:b3:f2:5a:e4:27:9a:9f:
#                     24:d7:c6:ec:d0:9b:25:82:e3:cc:c2:c4:45:c5:8c:
#                     97:7a:06:6b:2a:11:9f:a9:0a:6e:48:3b:6f:db:d4:
#                     11:19:42:f7:8f:07:bf:f5:53:5f:9c:3e:f4:17:2c:
#                     e6:69:ac:4e:32:4c:62:77:ea:b7:e8:e5:bb:34:bc:
#                     19:8b:ae:9c:51:e7:b7:7e:b5:53:b1:33:22:e5:6d:
#                     cf:70:3c:1a:fa:e2:9b:67:b6:83:f4:8d:a5:af:62:
#                     4c:4d:e0:58:ac:64:34:12:03:f8:b6:8d:94:63:24:
#                     a4:71
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier:
#                 6A:72:26:7A:D0:1E:EF:7D:E7:3B:69:51:D4:6C:8D:9F:90:12:66:AB
#     Signature Algorithm: sha256WithRSAEncryption
#          79:9f:1d:96:c6:b6:79:3f:22:8d:87:d3:87:03:04:60:6a:6b:
#          9a:2e:59:89:73:11:ac:43:d1:f5:13:ff:8d:39:2b:c0:f2:bd:
#          4f:70:8c:a9:2f:ea:17:c4:0b:54:9e:d4:1b:96:98:33:3c:a8:
#          ad:62:a2:00:76:ab:59:69:6e:06:1d:7e:c4:b9:44:8d:98:af:
#          12:d4:61:db:0a:19:46:47:f3:eb:f7:63:c1:40:05:40:a5:d2:
#          b7:f4:b5:9a:36:bf:a9:88:76:88:04:55:04:2b:9c:87:7f:1a:
#          37:3c:7e:2d:a5:1a:d8:d4:89:5e:ca:bd:ac:3d:6c:d8:6d:af:
#          d5:f3:76:0f:cd:3b:88:38:22:9d:6c:93:9a:c4:3d:bf:82:1b:
#          65:3f:a6:0f:5d:aa:fc:e5:b2:15:ca:b5:ad:c6:bc:3d:d0:84:
#          e8:ea:06:72:b0:4d:39:32:78:bf:3e:11:9c:0b:a4:9d:9a:21:
#          f3:f0:9b:0b:30:78:db:c1:dc:87:43:fe:bc:63:9a:ca:c5:c2:
#          1c:c9:c7:8d:ff:3b:12:58:08:e6:b6:3d:ec:7a:2c:4e:fb:83:
#          96:ce:0c:3c:69:87:54:73:a4:73:c2:93:ff:51:10:ac:15:54:
#          01:d8:fc:05:b1:89:a1:7f:74:83:9a:49:d7:dc:4e:7b:8a:48:
#          6f:8b:45:f6
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
-----END CERTIFICATE-----
# Entrust Root Certification Authority.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 1164660820 (0x456b5054)
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=Entrust, Inc., OU=www.entrust.net/CPS is incorporated by reference, OU=(c) 2006 Entrust, Inc., CN=Entrust Root Certification Authority
#         Validity
#             Not Before: Nov 27 20:23:42 2006 GMT
#             Not After : Nov 27 20:53:42 2026 GMT
#         Subject: C=US, O=Entrust, Inc., OU=www.entrust.net/CPS is incorporated by reference, OU=(c) 2006 Entrust, Inc., CN=Entrust Root Certification Authority
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:b6:95:b6:43:42:fa:c6:6d:2a:6f:48:df:94:4c:
#                     39:57:05:ee:c3:79:11:41:68:36:ed:ec:fe:9a:01:
#                     8f:a1:38:28:fc:f7:10:46:66:2e:4d:1e:1a:b1:1a:
#                     4e:c6:d1:c0:95:88:b0:c9:ff:31:8b:33:03:db:b7:
#                     83:7b:3e:20:84:5e:ed:b2:56:28:a7:f8:e0:b9:40:
#                     71:37:c5:cb:47:0e:97:2a:68:c0:22:95:62:15:db:
#                     47:d9:f5:d0:2b:ff:82:4b:c9:ad:3e:de:4c:db:90:
#                     80:50:3f:09:8a:84:00:ec:30:0a:3d:18:cd:fb:fd:
#                     2a:59:9a:23:95:17:2c:45:9e:1f:6e:43:79:6d:0c:
#                     5c:98:fe:48:a7:c5:23:47:5c:5e:fd:6e:e7:1e:b4:
#                     f6:68:45:d1:86:83:5b:a2:8a:8d:b1:e3:29:80:fe:
#                     25:71:88:ad:be:bc:8f:ac:52:96:4b:aa:51:8d:e4:
#                     13:31:19:e8:4e:4d:9f:db:ac:b3:6a:d5:bc:39:54:
#                     71:ca:7a:7a:7f:90:dd:7d:1d:80:d9:81:bb:59:26:
#                     c2:11:fe:e6:93:e2:f7:80:e4:65:fb:34:37:0e:29:
#                     80:70:4d:af:38:86:2e:9e:7f:57:af:9e:17:ae:eb:
#                     1c:cb:28:21:5f:b6:1c:d8:e7:a2:04:22:f9:d3:da:
#                     d8:cb
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Private Key Usage Period:
#                 Not Before: Nov 27 20:23:42 2006 GMT, Not After: Nov 27 20:53:42 2026 GMT
#             X509v3 Authority Key Identifier:
#                 keyid:68:90:E4:67:A4:A6:53:80:C7:86:66:A4:F1:F7:4B:43:FB:84:BD:6D
#
#             X509v3 Subject Key Identifier:
#                 68:90:E4:67:A4:A6:53:80:C7:86:66:A4:F1:F7:4B:43:FB:84:BD:6D
#             1.2.840.113533.7.65.0:
#                 0...V7.1:4.0....
#     Signature Algorithm: sha1WithRSAEncryption
#          93:d4:30:b0:d7:03:20:2a:d0:f9:63:e8:91:0c:05:20:a9:5f:
#          19:ca:7b:72:4e:d4:b1:db:d0:96:fb:54:5a:19:2c:0c:08:f7:
#          b2:bc:85:a8:9d:7f:6d:3b:52:b3:2a:db:e7:d4:84:8c:63:f6:
#          0f:cb:26:01:91:50:6c:f4:5f:14:e2:93:74:c0:13:9e:30:3a:
#          50:e3:b4:60:c5:1c:f0:22:44:8d:71:47:ac:c8:1a:c9:e9:9b:
#          9a:00:60:13:ff:70:7e:5f:11:4d:49:1b:b3:15:52:7b:c9:54:
#          da:bf:9d:95:af:6b:9a:d8:9e:e9:f1:e4:43:8d:e2:11:44:3a:
#          bf:af:bd:83:42:73:52:8b:aa:bb:a7:29:cf:f5:64:1c:0a:4d:
#          d1:bc:aa:ac:9f:2a:d0:ff:7f:7f:da:7d:ea:b1:ed:30:25:c1:
#          84:da:34:d2:5b:78:83:56:ec:9c:36:c3:26:e2:11:f6:67:49:
#          1d:92:ab:8c:fb:eb:ff:7a:ee:85:4a:a7:50:80:f0:a7:5c:4a:
#          94:2e:5f:05:99:3c:52:41:e0:cd:b4:63:cf:01:43:ba:9c:83:
#          dc:8f:60:3b:f3:5a:b4:b4:7b:ae:da:0b:90:38:75:ef:81:1d:
#          66:d2:f7:57:70:36:b3:bf:fc:28:af:71:25:85:5b:13:fe:1e:
#          7f:5a:b4:3c
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----
# Entrust.net Certification Authority (2048).pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 946069240 (0x3863def8)
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
#         Validity
#             Not Before: Dec 24 17:50:51 1999 GMT
#             Not After : Jul 24 14:15:12 2029 GMT
#         Subject: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048)
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:ad:4d:4b:a9:12:86:b2:ea:a3:20:07:15:16:64:
#                     2a:2b:4b:d1:bf:0b:4a:4d:8e:ed:80:76:a5:67:b7:
#                     78:40:c0:73:42:c8:68:c0:db:53:2b:dd:5e:b8:76:
#                     98:35:93:8b:1a:9d:7c:13:3a:0e:1f:5b:b7:1e:cf:
#                     e5:24:14:1e:b1:81:a9:8d:7d:b8:cc:6b:4b:03:f1:
#                     02:0c:dc:ab:a5:40:24:00:7f:74:94:a1:9d:08:29:
#                     b3:88:0b:f5:87:77:9d:55:cd:e4:c3:7e:d7:6a:64:
#                     ab:85:14:86:95:5b:97:32:50:6f:3d:c8:ba:66:0c:
#                     e3:fc:bd:b8:49:c1:76:89:49:19:fd:c0:a8:bd:89:
#                     a3:67:2f:c6:9f:bc:71:19:60:b8:2d:e9:2c:c9:90:
#                     76:66:7b:94:e2:af:78:d6:65:53:5d:3c:d6:9c:b2:
#                     cf:29:03:f9:2f:a4:50:b2:d4:48:ce:05:32:55:8a:
#                     fd:b2:64:4c:0e:e4:98:07:75:db:7f:df:b9:08:55:
#                     60:85:30:29:f9:7b:48:a4:69:86:e3:35:3f:1e:86:
#                     5d:7a:7a:15:bd:ef:00:8e:15:22:54:17:00:90:26:
#                     93:bc:0e:49:68:91:bf:f8:47:d3:9d:95:42:c1:0e:
#                     4d:df:6f:26:cf:c3:18:21:62:66:43:70:d6:d5:c0:
#                     07:e1
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier:
#                 55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70
#     Signature Algorithm: sha1WithRSAEncryption
#          3b:9b:8f:56:9b:30:e7:53:99:7c:7a:79:a7:4d:97:d7:19:95:
#          90:fb:06:1f:ca:33:7c:46:63:8f:96:66:24:fa:40:1b:21:27:
#          ca:e6:72:73:f2:4f:fe:31:99:fd:c8:0c:4c:68:53:c6:80:82:
#          13:98:fa:b6:ad:da:5d:3d:f1:ce:6e:f6:15:11:94:82:0c:ee:
#          3f:95:af:11:ab:0f:d7:2f:de:1f:03:8f:57:2c:1e:c9:bb:9a:
#          1a:44:95:eb:18:4f:a6:1f:cd:7d:57:10:2f:9b:04:09:5a:84:
#          b5:6e:d8:1d:3a:e1:d6:9e:d1:6c:79:5e:79:1c:14:c5:e3:d0:
#          4c:93:3b:65:3c:ed:df:3d:be:a6:e5:95:1a:c3:b5:19:c3:bd:
#          5e:5b:bb:ff:23:ef:68:19:cb:12:93:27:5c:03:2d:6f:30:d0:
#          1e:b6:1a:ac:de:5a:f7:d1:aa:a8:27:a6:fe:79:81:c4:79:99:
#          33:57:ba:12:b0:a9:e0:42:6c:93:ca:56:de:fe:6d:84:0b:08:
#          8b:7e:8d:ea:d7:98:21:c6:f3:e7:3c:79:2f:5e:9c:d1:4c:15:
#          8d:e1:ec:22:37:cc:9a:43:0b:97:dc:80:90:8d:b3:67:9b:6f:
#          48:08:15:56:cf:bf:f1:2b:7c:5e:9a:76:e9:59:90:c5:7c:83:
#          35:11:65:51
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
fF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----
# GeoTrust Global CA.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 144470 (0x23456)
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
#         Validity
#             Not Before: May 21 04:00:00 2002 GMT
#             Not After : May 21 04:00:00 2022 GMT
#         Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:da:cc:18:63:30:fd:f4:17:23:1a:56:7e:5b:df:
#                     3c:6c:38:e4:71:b7:78:91:d4:bc:a1:d8:4c:f8:a8:
#                     43:b6:03:e9:4d:21:07:08:88:da:58:2f:66:39:29:
#                     bd:05:78:8b:9d:38:e8:05:b7:6a:7e:71:a4:e6:c4:
#                     60:a6:b0:ef:80:e4:89:28:0f:9e:25:d6:ed:83:f3:
#                     ad:a6:91:c7:98:c9:42:18:35:14:9d:ad:98:46:92:
#                     2e:4f:ca:f1:87:43:c1:16:95:57:2d:50:ef:89:2d:
#                     80:7a:57:ad:f2:ee:5f:6b:d2:00:8d:b9:14:f8:14:
#                     15:35:d9:c0:46:a3:7b:72:c8:91:bf:c9:55:2b:cd:
#                     d0:97:3e:9c:26:64:cc:df:ce:83:19:71:ca:4e:e6:
#                     d4:d5:7b:a9:19:cd:55:de:c8:ec:d2:5e:38:53:e5:
#                     5c:4f:8c:2d:fe:50:23:36:fc:66:e6:cb:8e:a4:39:
#                     19:00:b7:95:02:39:91:0b:0e:fe:38:2e:d1:1d:05:
#                     9a:f6:4d:3e:6f:0f:07:1d:af:2c:1e:8f:60:39:e2:
#                     fa:36:53:13:39:d4:5e:26:2b:db:3d:a8:14:bd:32:
#                     eb:18:03:28:52:04:71:e5:ab:33:3d:e1:38:bb:07:
#                     36:84:62:9c:79:ea:16:30:f4:5f:c0:2b:e8:71:6b:
#                     e4:f9
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Subject Key Identifier:
#                 C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
#             X509v3 Authority Key Identifier:
#                 keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
#
#     Signature Algorithm: sha1WithRSAEncryption
#          35:e3:29:6a:e5:2f:5d:54:8e:29:50:94:9f:99:1a:14:e4:8f:
#          78:2a:62:94:a2:27:67:9e:d0:cf:1a:5e:47:e9:c1:b2:a4:cf:
#          dd:41:1a:05:4e:9b:4b:ee:4a:6f:55:52:b3:24:a1:37:0a:eb:
#          64:76:2a:2e:2c:f3:fd:3b:75:90:bf:fa:71:d8:c7:3d:37:d2:
#          b5:05:95:62:b9:a6:de:89:3d:36:7b:38:77:48:97:ac:a6:20:
#          8f:2e:a6:c9:0c:c2:b2:99:45:00:c7:ce:11:51:22:22:e0:a5:
#          ea:b6:15:48:09:64:ea:5e:4f:74:f7:05:3e:c7:8a:52:0c:db:
#          15:b4:bd:6d:9b:e5:c6:b1:54:68:a9:e3:69:90:b6:9a:a5:0f:
#          b8:b9:3f:20:7d:ae:4a:b5:b8:9c:e4:1d:b6:ab:e6:94:a5:c1:
#          c7:83:ad:db:f5:27:87:0e:04:6c:d5:ff:dd:a0:5d:ed:87:52:
#          b7:2b:15:02:ae:39:a6:6a:74:e9:da:c4:e7:bc:4d:34:1e:a9:
#          5c:4d:33:5f:92:09:2f:88:66:5d:77:97:c7:1d:76:13:a9:d5:
#          e5:f1:16:09:11:35:d5:ac:db:24:71:70:2c:98:56:0b:d9:17:
#          b4:d1:e3:51:2b:5e:75:e8:d5:d0:dc:4f:34:ed:c2:05:66:80:
#          a1:cb:e6:33
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
-----END CERTIFICATE-----
# GeoTrust Primary Certification Authority - G2.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             3c:b2:f4:48:0a:00:e2:fe:eb:24:3b:5e:60:3e:c3:6b
#     Signature Algorithm: ecdsa-with-SHA384
#         Issuer: C=US, O=GeoTrust Inc., OU=(c) 2007 GeoTrust Inc. - For authorized use only, CN=GeoTrust Primary Certification Authority - G2
#         Validity
#             Not Before: Nov  5 00:00:00 2007 GMT
#             Not After : Jan 18 23:59:59 2038 GMT
#         Subject: C=US, O=GeoTrust Inc., OU=(c) 2007 GeoTrust Inc. - For authorized use only, CN=GeoTrust Primary Certification Authority - G2
#         Subject Public Key Info:
#             Public Key Algorithm: id-ecPublicKey
#                 Public-Key: (384 bit)
#                 pub:
#                     04:15:b1:e8:fd:03:15:43:e5:ac:eb:87:37:11:62:
#                     ef:d2:83:36:52:7d:45:57:0b:4a:8d:7b:54:3b:3a:
#                     6e:5f:15:02:c0:50:a6:cf:25:2f:7d:ca:48:b8:c7:
#                     50:63:1c:2a:21:08:7c:9a:36:d8:0b:fe:d1:26:c5:
#                     58:31:30:28:25:f3:5d:5d:a3:b8:b6:a5:b4:92:ed:
#                     6c:2c:9f:eb:dd:43:89:a2:3c:4b:48:91:1d:50:ec:
#                     26:df:d6:60:2e:bd:21
#                 ASN1 OID: secp384r1
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Subject Key Identifier:
#                 15:5F:35:57:51:55:FB:25:B2:AD:03:69:FC:01:A3:FA:BE:11:55:D5
#     Signature Algorithm: ecdsa-with-SHA384
#          30:64:02:30:64:96:59:a6:e8:09:de:8b:ba:fa:5a:88:88:f0:
#          1f:91:d3:46:a8:f2:4a:4c:02:63:fb:6c:5f:38:db:2e:41:93:
#          a9:0e:e6:9d:dc:31:1c:b2:a0:a7:18:1c:79:e1:c7:36:02:30:
#          3a:56:af:9a:74:6c:f6:fb:83:e0:33:d3:08:5f:a1:9c:c2:5b:
#          9f:46:d6:b6:cb:91:06:63:a2:06:e7:33:ac:3e:a8:81:12:d0:
#          cb:ba:d0:92:0b:b6:9e:96:aa:04:0f:8a
-----BEGIN CERTIFICATE-----
MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
rD6ogRLQy7rQkgu2npaqBA+K
-----END CERTIFICATE-----
# GeoTrust Primary Certification Authority - G3.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             15:ac:6e:94:19:b2:79:4b:41:f6:27:a9:c3:18:0f:1f
#     Signature Algorithm: sha256WithRSAEncryption
#         Issuer: C=US, O=GeoTrust Inc., OU=(c) 2008 GeoTrust Inc. - For authorized use only, CN=GeoTrust Primary Certification Authority - G3
#         Validity
#             Not Before: Apr  2 00:00:00 2008 GMT
#             Not After : Dec  1 23:59:59 2037 GMT
#         Subject: C=US, O=GeoTrust Inc., OU=(c) 2008 GeoTrust Inc. - For authorized use only, CN=GeoTrust Primary Certification Authority - G3
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:dc:e2:5e:62:58:1d:33:57:39:32:33:fa:eb:cb:
#                     87:8c:a7:d4:4a:dd:06:88:ea:64:8e:31:98:a5:38:
#                     90:1e:98:cf:2e:63:2b:f0:46:bc:44:b2:89:a1:c0:
#                     28:0c:49:70:21:95:9f:64:c0:a6:93:12:02:65:26:
#                     86:c6:a5:89:f0:fa:d7:84:a0:70:af:4f:1a:97:3f:
#                     06:44:d5:c9:eb:72:10:7d:e4:31:28:fb:1c:61:e6:
#                     28:07:44:73:92:22:69:a7:03:88:6c:9d:63:c8:52:
#                     da:98:27:e7:08:4c:70:3e:b4:c9:12:c1:c5:67:83:
#                     5d:33:f3:03:11:ec:6a:d0:53:e2:d1:ba:36:60:94:
#                     80:bb:61:63:6c:5b:17:7e:df:40:94:1e:ab:0d:c2:
#                     21:28:70:88:ff:d6:26:6c:6c:60:04:25:4e:55:7e:
#                     7d:ef:bf:94:48:de:b7:1d:dd:70:8d:05:5f:88:a5:
#                     9b:f2:c2:ee:ea:d1:40:41:6d:62:38:1d:56:06:c5:
#                     03:47:51:20:19:fc:7b:10:0b:0e:62:ae:76:55:bf:
#                     5f:77:be:3e:49:01:53:3d:98:25:03:76:24:5a:1d:
#                     b4:db:89:ea:79:e5:b6:b3:3b:3f:ba:4c:28:41:7f:
#                     06:ac:6a:8e:c1:d0:f6:05:1d:7d:e6:42:86:e3:a5:
#                     d5:47
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Subject Key Identifier:
#                 C4:79:CA:8E:A1:4E:03:1D:1C:DC:6B:DB:31:5B:94:3E:3F:30:7F:2D
#     Signature Algorithm: sha256WithRSAEncryption
#          2d:c5:13:cf:56:80:7b:7a:78:bd:9f:ae:2c:99:e7:ef:da:df:
#          94:5e:09:69:a7:e7:6e:68:8c:bd:72:be:47:a9:0e:97:12:b8:
#          4a:f1:64:d3:39:df:25:34:d4:c1:cd:4e:81:f0:0f:04:c4:24:
#          b3:34:96:c6:a6:aa:30:df:68:61:73:d7:f9:8e:85:89:ef:0e:
#          5e:95:28:4a:2a:27:8f:10:8e:2e:7c:86:c4:02:9e:da:0c:77:
#          65:0e:44:0d:92:fd:fd:b3:16:36:fa:11:0d:1d:8c:0e:07:89:
#          6a:29:56:f7:72:f4:dd:15:9c:77:35:66:57:ab:13:53:d8:8e:
#          c1:40:c5:d7:13:16:5a:72:c7:b7:69:01:c4:7a:b1:83:01:68:
#          7d:8d:41:a1:94:18:c1:25:5c:fc:f0:fe:83:02:87:7c:0d:0d:
#          cf:2e:08:5c:4a:40:0d:3e:ec:81:61:e6:24:db:ca:e0:0e:2d:
#          07:b2:3e:56:dc:8d:f5:41:85:07:48:9b:0c:0b:cb:49:3f:7d:
#          ec:b7:fd:cb:8d:67:89:1a:ab:ed:bb:1e:a3:00:08:08:17:2a:
#          82:5c:31:5d:46:8a:2d:0f:86:9b:74:d9:45:fb:d4:40:b1:7a:
#          aa:68:2d:86:b2:99:22:e1:c1:2b:c7:9c:f8:f3:5f:a8:82:12:
#          eb:19:11:2d
-----BEGIN CERTIFICATE-----
MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
spki4cErx5z481+oghLrGREt
-----END CERTIFICATE-----
# GeoTrust Primary Certification Authority.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             18:ac:b5:6a:fd:69:b6:15:3a:63:6c:af:da:fa:c4:a1
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
#         Validity
#             Not Before: Nov 27 00:00:00 2006 GMT
#             Not After : Jul 16 23:59:59 2036 GMT
#         Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Primary Certification Authority
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:be:b8:15:7b:ff:d4:7c:7d:67:ad:83:64:7b:c8:
#                     42:53:2d:df:f6:84:08:20:61:d6:01:59:6a:9c:44:
#                     11:af:ef:76:fd:95:7e:ce:61:30:bb:7a:83:5f:02:
#                     bd:01:66:ca:ee:15:8d:6f:a1:30:9c:bd:a1:85:9e:
#                     94:3a:f3:56:88:00:31:cf:d8:ee:6a:96:02:d9:ed:
#                     03:8c:fb:75:6d:e7:ea:b8:55:16:05:16:9a:f4:e0:
#                     5e:b1:88:c0:64:85:5c:15:4d:88:c7:b7:ba:e0:75:
#                     e9:ad:05:3d:9d:c7:89:48:e0:bb:28:c8:03:e1:30:
#                     93:64:5e:52:c0:59:70:22:35:57:88:8a:f1:95:0a:
#                     83:d7:bc:31:73:01:34:ed:ef:46:71:e0:6b:02:a8:
#                     35:72:6b:97:9b:66:e0:cb:1c:79:5f:d8:1a:04:68:
#                     1e:47:02:e6:9d:60:e2:36:97:01:df:ce:35:92:df:
#                     be:67:c7:6d:77:59:3b:8f:9d:d6:90:15:94:bc:42:
#                     34:10:c1:39:f9:b1:27:3e:7e:d6:8a:75:c5:b2:af:
#                     96:d3:a2:de:9b:e4:98:be:7d:e1:e9:81:ad:b6:6f:
#                     fc:d7:0e:da:e0:34:b0:0d:1a:77:e7:e3:08:98:ef:
#                     58:fa:9c:84:b7:36:af:c2:df:ac:d2:f4:10:06:70:
#                     71:35
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Subject Key Identifier:
#                 2C:D5:50:41:97:15:8B:F0:8F:36:61:5B:4A:FB:6B:D9:99:C9:33:92
#     Signature Algorithm: sha1WithRSAEncryption
#          5a:70:7f:2c:dd:b7:34:4f:f5:86:51:a9:26:be:4b:b8:aa:f1:
#          71:0d:dc:61:c7:a0:ea:34:1e:7a:77:0f:04:35:e8:27:8f:6c:
#          90:bf:91:16:24:46:3e:4a:4e:ce:2b:16:d5:0b:52:1d:fc:1f:
#          67:a2:02:45:31:4f:ce:f3:fa:03:a7:79:9d:53:6a:d9:da:63:
#          3a:f8:80:d7:d3:99:e1:a5:e1:be:d4:55:71:98:35:3a:be:93:
#          ea:ae:ad:42:b2:90:6f:e0:fc:21:4d:35:63:33:89:49:d6:9b:
#          4e:ca:c7:e7:4e:09:00:f7:da:c7:ef:99:62:99:77:b6:95:22:
#          5e:8a:a0:ab:f4:b8:78:98:ca:38:19:99:c9:72:9e:78:cd:4b:
#          ac:af:19:a0:73:12:2d:fc:c2:41:ba:81:91:da:16:5a:31:b7:
#          f9:b4:71:80:12:48:99:72:73:5a:59:53:c1:63:52:33:ed:a7:
#          c9:d2:39:02:70:fa:e0:b1:42:66:29:aa:9b:51:ed:30:54:22:
#          14:5f:d9:ab:1d:c1:e4:94:f0:f8:f5:2b:f7:ea:ca:78:46:d6:
#          b8:91:fd:a6:0d:2b:1a:14:01:3e:80:f0:42:a0:95:07:5e:6d:
#          cd:cc:4b:a4:45:8d:ab:12:e8:b3:de:5a:e5:a0:7c:e8:0f:22:
#          1d:5a:e9:59
-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
-----END CERTIFICATE-----
# Go Daddy Class 2 Certification Authority.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 0 (0x0)
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
#         Validity
#             Not Before: Jun 29 17:06:20 2004 GMT
#             Not After : Jun 29 17:06:20 2034 GMT
#         Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:de:9d:d7:ea:57:18:49:a1:5b:eb:d7:5f:48:86:
#                     ea:be:dd:ff:e4:ef:67:1c:f4:65:68:b3:57:71:a0:
#                     5e:77:bb:ed:9b:49:e9:70:80:3d:56:18:63:08:6f:
#                     da:f2:cc:d0:3f:7f:02:54:22:54:10:d8:b2:81:d4:
#                     c0:75:3d:4b:7f:c7:77:c3:3e:78:ab:1a:03:b5:20:
#                     6b:2f:6a:2b:b1:c5:88:7e:c4:bb:1e:b0:c1:d8:45:
#                     27:6f:aa:37:58:f7:87:26:d7:d8:2d:f6:a9:17:b7:
#                     1f:72:36:4e:a6:17:3f:65:98:92:db:2a:6e:5d:a2:
#                     fe:88:e0:0b:de:7f:e5:8d:15:e1:eb:cb:3a:d5:e2:
#                     12:a2:13:2d:d8:8e:af:5f:12:3d:a0:08:05:08:b6:
#                     5c:a5:65:38:04:45:99:1e:a3:60:60:74:c5:41:a5:
#                     72:62:1b:62:c5:1f:6f:5f:1a:42:be:02:51:65:a8:
#                     ae:23:18:6a:fc:78:03:a9:4d:7f:80:c3:fa:ab:5a:
#                     fc:a1:40:a4:ca:19:16:fe:b2:c8:ef:5e:73:0d:ee:
#                     77:bd:9a:f6:79:98:bc:b1:07:67:a2:15:0d:dd:a0:
#                     58:c6:44:7b:0a:3e:62:28:5f:ba:41:07:53:58:cf:
#                     11:7e:38:74:c5:f8:ff:b5:69:90:8f:84:74:ea:97:
#                     1b:af
#                 Exponent: 3 (0x3)
#         X509v3 extensions:
#             X509v3 Subject Key Identifier:
#                 D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3
#             X509v3 Authority Key Identifier:
#                 keyid:D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3
#                 DirName:/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority
#                 serial:00
#
#             X509v3 Basic Constraints:
#                 CA:TRUE
#     Signature Algorithm: sha1WithRSAEncryption
#          32:4b:f3:b2:ca:3e:91:fc:12:c6:a1:07:8c:8e:77:a0:33:06:
#          14:5c:90:1e:18:f7:08:a6:3d:0a:19:f9:87:80:11:6e:69:e4:
#          96:17:30:ff:34:91:63:72:38:ee:cc:1c:01:a3:1d:94:28:a4:
#          31:f6:7a:c4:54:d7:f6:e5:31:58:03:a2:cc:ce:62:db:94:45:
#          73:b5:bf:45:c9:24:b5:d5:82:02:ad:23:79:69:8d:b8:b6:4d:
#          ce:cf:4c:ca:33:23:e8:1c:88:aa:9d:8b:41:6e:16:c9:20:e5:
#          89:9e:cd:3b:da:70:f7:7e:99:26:20:14:54:25:ab:6e:73:85:
#          e6:9b:21:9d:0a:6c:82:0e:a8:f8:c2:0c:fa:10:1e:6c:96:ef:
#          87:0d:c4:0f:61:8b:ad:ee:83:2b:95:f8:8e:92:84:72:39:eb:
#          20:ea:83:ed:83:cd:97:6e:08:bc:eb:4e:26:b6:73:2b:e4:d3:
#          f6:4c:fe:26:71:e2:61:11:74:4a:ff:57:1a:87:0f:75:48:2e:
#          cf:51:69:17:a0:02:12:61:95:d5:d1:40:b2:10:4c:ee:c4:ac:
#          10:43:a6:a5:9e:0a:d5:95:62:9a:0d:cf:88:82:c5:32:0c:e4:
#          2b:9f:45:e6:0d:9f:28:9c:b1:b9:2a:5a:57:ad:37:0f:af:1d:
#          7f:db:bd:9f
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
ReYNnyicsbkqWletNw+vHX/bvZ8=
-----END CERTIFICATE-----
# Go Daddy Root Certificate Authority - G2.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 0 (0x0)
#     Signature Algorithm: sha256WithRSAEncryption
#         Issuer: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2
#         Validity
#             Not Before: Sep  1 00:00:00 2009 GMT
#             Not After : Dec 31 23:59:59 2037 GMT
#         Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:bf:71:62:08:f1:fa:59:34:f7:1b:c9:18:a3:f7:
#                     80:49:58:e9:22:83:13:a6:c5:20:43:01:3b:84:f1:
#                     e6:85:49:9f:27:ea:f6:84:1b:4e:a0:b4:db:70:98:
#                     c7:32:01:b1:05:3e:07:4e:ee:f4:fa:4f:2f:59:30:
#                     22:e7:ab:19:56:6b:e2:80:07:fc:f3:16:75:80:39:
#                     51:7b:e5:f9:35:b6:74:4e:a9:8d:82:13:e4:b6:3f:
#                     a9:03:83:fa:a2:be:8a:15:6a:7f:de:0b:c3:b6:19:
#                     14:05:ca:ea:c3:a8:04:94:3b:46:7c:32:0d:f3:00:
#                     66:22:c8:8d:69:6d:36:8c:11:18:b7:d3:b2:1c:60:
#                     b4:38:fa:02:8c:ce:d3:dd:46:07:de:0a:3e:eb:5d:
#                     7c:c8:7c:fb:b0:2b:53:a4:92:62:69:51:25:05:61:
#                     1a:44:81:8c:2c:a9:43:96:23:df:ac:3a:81:9a:0e:
#                     29:c5:1c:a9:e9:5d:1e:b6:9e:9e:30:0a:39:ce:f1:
#                     88:80:fb:4b:5d:cc:32:ec:85:62:43:25:34:02:56:
#                     27:01:91:b4:3b:70:2a:3f:6e:b1:e8:9c:88:01:7d:
#                     9f:d4:f9:db:53:6d:60:9d:bf:2c:e7:58:ab:b8:5f:
#                     46:fc:ce:c4:1b:03:3c:09:eb:49:31:5c:69:46:b3:
#                     e0:47
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Subject Key Identifier:
#                 3A:9A:85:07:10:67:28:B6:EF:F6:BD:05:41:6E:20:C1:94:DA:0F:DE
#     Signature Algorithm: sha256WithRSAEncryption
#          99:db:5d:79:d5:f9:97:59:67:03:61:f1:7e:3b:06:31:75:2d:
#          a1:20:8e:4f:65:87:b4:f7:a6:9c:bc:d8:e9:2f:d0:db:5a:ee:
#          cf:74:8c:73:b4:38:42:da:05:7b:f8:02:75:b8:fd:a5:b1:d7:
#          ae:f6:d7:de:13:cb:53:10:7e:8a:46:d1:97:fa:b7:2e:2b:11:
#          ab:90:b0:27:80:f9:e8:9f:5a:e9:37:9f:ab:e4:df:6c:b3:85:
#          17:9d:3d:d9:24:4f:79:91:35:d6:5f:04:eb:80:83:ab:9a:02:
#          2d:b5:10:f4:d8:90:c7:04:73:40:ed:72:25:a0:a9:9f:ec:9e:
#          ab:68:12:99:57:c6:8f:12:3a:09:a4:bd:44:fd:06:15:37:c1:
#          9b:e4:32:a3:ed:38:e8:d8:64:f3:2c:7e:14:fc:02:ea:9f:cd:
#          ff:07:68:17:db:22:90:38:2d:7a:8d:d1:54:f1:69:e3:5f:33:
#          ca:7a:3d:7b:0a:e3:ca:7f:5f:39:e5:e2:75:ba:c5:76:18:33:
#          ce:2c:f0:2f:4c:ad:f7:b1:e7:ce:4f:a8:c4:9b:4a:54:06:c5:
#          7f:7d:d5:08:0f:e2:1c:fe:7e:17:b8:ac:5e:f6:d4:16:b2:43:
#          09:0c:4d:f6:a7:6b:b4:99:84:65:ca:7a:88:e2:e2:44:be:5c:
#          f7:ea:1c:f5
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
4uJEvlz36hz1
-----END CERTIFICATE-----
# Go Daddy Secure Certification Authority serialNumber=07969287.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 769 (0x301)
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority
#         Validity
#             Not Before: Nov 16 01:54:37 2006 GMT
#             Not After : Nov 16 01:54:37 2026 GMT
#         Subject: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., OU=http://certificates.godaddy.com/repository, CN=Go Daddy Secure Certification Authority/serialNumber=07969287
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:c4:2d:d5:15:8c:9c:26:4c:ec:32:35:eb:5f:b8:
#                     59:01:5a:a6:61:81:59:3b:70:63:ab:e3:dc:3d:c7:
#                     2a:b8:c9:33:d3:79:e4:3a:ed:3c:30:23:84:8e:b3:
#                     30:14:b6:b2:87:c3:3d:95:54:04:9e:df:99:dd:0b:
#                     25:1e:21:de:65:29:7e:35:a8:a9:54:eb:f6:f7:32:
#                     39:d4:26:55:95:ad:ef:fb:fe:58:86:d7:9e:f4:00:
#                     8d:8c:2a:0c:bd:42:04:ce:a7:3f:04:f6:ee:80:f2:
#                     aa:ef:52:a1:69:66:da:be:1a:ad:5d:da:2c:66:ea:
#                     1a:6b:bb:e5:1a:51:4a:00:2f:48:c7:98:75:d8:b9:
#                     29:c8:ee:f8:66:6d:0a:9c:b3:f3:fc:78:7c:a2:f8:
#                     a3:f2:b5:c3:f3:b9:7a:91:c1:a7:e6:25:2e:9c:a8:
#                     ed:12:65:6e:6a:f6:12:44:53:70:30:95:c3:9c:2b:
#                     58:2b:3d:08:74:4a:f2:be:51:b0:bf:87:d0:4c:27:
#                     58:6b:b5:35:c5:9d:af:17:31:f8:0b:8f:ee:ad:81:
#                     36:05:89:08:98:cf:3a:af:25:87:c0:49:ea:a7:fd:
#                     67:f7:45:8e:97:cc:14:39:e2:36:85:b5:7e:1a:37:
#                     fd:16:f6:71:11:9a:74:30:16:fe:13:94:a3:3f:84:
#                     0d:4f
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Subject Key Identifier:
#                 FD:AC:61:32:93:6C:45:D6:E2:EE:85:5F:9A:BA:E7:76:99:68:CC:E7
#             X509v3 Authority Key Identifier:
#                 keyid:D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3
#
#             X509v3 Basic Constraints: critical
#                 CA:TRUE, pathlen:0
#             Authority Information Access:
#                 OCSP - URI:http://ocsp.godaddy.com
#
#             X509v3 CRL Distribution Points:
#
#                 Full Name:
#                   URI:http://certificates.godaddy.com/repository/gdroot.crl
#
#             X509v3 Certificate Policies:
#                 Policy: X509v3 Any Policy
#                   CPS: http://certificates.godaddy.com/repository
#
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#     Signature Algorithm: sha1WithRSAEncryption
#          d2:86:c0:ec:bd:f9:a1:b6:67:ee:66:0b:a2:06:3a:04:50:8e:
#          15:72:ac:4a:74:95:53:cb:37:cb:44:49:ef:07:90:6b:33:d9:
#          96:f0:94:56:a5:13:30:05:3c:85:32:21:7b:c9:c7:0a:a8:24:
#          a4:90:de:46:d3:25:23:14:03:67:c2:10:d6:6f:0f:5d:7b:7a:
#          cc:9f:c5:58:2a:c1:c4:9e:21:a8:5a:f3:ac:a4:46:f3:9e:e4:
#          63:cb:2f:90:a4:29:29:01:d9:72:2c:29:df:37:01:27:bc:4f:
#          ee:68:d3:21:8f:c0:b3:e4:f5:09:ed:d2:10:aa:53:b4:be:f0:
#          cc:59:0b:d6:3b:96:1c:95:24:49:df:ce:ec:fd:a7:48:91:14:
#          45:0e:3a:36:6f:da:45:b3:45:a2:41:c9:d4:d7:44:4e:3e:b9:
#          74:76:d5:a2:13:55:2c:c6:87:a3:b5:99:ac:06:84:87:7f:75:
#          06:fc:bf:14:4c:0e:cc:6e:c4:df:3d:b7:12:71:f4:e8:f1:51:
#          40:22:28:49:e0:1d:4b:87:a8:34:cc:06:a2:dd:12:5a:d1:86:
#          36:64:03:35:6f:6f:77:6e:eb:f2:85:50:98:5e:ab:03:53:ad:
#          91:23:63:1f:16:9c:cd:b9:b2:05:63:3a:e1:f4:68:1b:17:05:
#          35:95:53:ee
-----BEGIN CERTIFICATE-----
MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMx
ITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMTYw
MTU0MzdaFw0yNjExMTYwMTU0MzdaMIHKMQswCQYDVQQGEwJVUzEQMA4GA1UECBMH
QXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEaMBgGA1UEChMRR29EYWRkeS5j
b20sIEluYy4xMzAxBgNVBAsTKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5j
b20vcmVwb3NpdG9yeTEwMC4GA1UEAxMnR28gRGFkZHkgU2VjdXJlIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MREwDwYDVQQFEwgwNzk2OTI4NzCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMQt1RWMnCZM7DI161+4WQFapmGBWTtwY6vj3D3H
KrjJM9N55DrtPDAjhI6zMBS2sofDPZVUBJ7fmd0LJR4h3mUpfjWoqVTr9vcyOdQm
VZWt7/v+WIbXnvQAjYwqDL1CBM6nPwT27oDyqu9SoWlm2r4arV3aLGbqGmu75RpR
SgAvSMeYddi5Kcju+GZtCpyz8/x4fKL4o/K1w/O5epHBp+YlLpyo7RJlbmr2EkRT
cDCVw5wrWCs9CHRK8r5RsL+H0EwnWGu1NcWdrxcx+AuP7q2BNgWJCJjPOq8lh8BJ
6qf9Z/dFjpfMFDniNoW1fho3/Rb2cRGadDAW/hOUoz+EDU8CAwEAAaOCATIwggEu
MB0GA1UdDgQWBBT9rGEyk2xF1uLuhV+auud2mWjM5zAfBgNVHSMEGDAWgBTSxLDS
kdRMEXGzYcs9of7dqGrU4zASBgNVHRMBAf8ECDAGAQH/AgEAMDMGCCsGAQUFBwEB
BCcwJTAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZ29kYWRkeS5jb20wRgYDVR0f
BD8wPTA7oDmgN4Y1aHR0cDovL2NlcnRpZmljYXRlcy5nb2RhZGR5LmNvbS9yZXBv
c2l0b3J5L2dkcm9vdC5jcmwwSwYDVR0gBEQwQjBABgRVHSAAMDgwNgYIKwYBBQUH
AgEWKmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeTAO
BgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBANKGwOy9+aG2Z+5mC6IG
OgRQjhVyrEp0lVPLN8tESe8HkGsz2ZbwlFalEzAFPIUyIXvJxwqoJKSQ3kbTJSMU
A2fCENZvD117esyfxVgqwcSeIaha86ykRvOe5GPLL5CkKSkB2XIsKd83ASe8T+5o
0yGPwLPk9Qnt0hCqU7S+8MxZC9Y7lhyVJEnfzuz9p0iRFEUOOjZv2kWzRaJBydTX
RE4+uXR21aITVSzGh6O1mawGhId/dQb8vxRMDsxuxN89txJx9OjxUUAiKEngHUuH
qDTMBqLdElrRhjZkAzVvb3du6/KFUJheqwNTrZEjYx8WnM25sgVjOuH0aBsXBTWV
U+4=
-----END CERTIFICATE-----
# Thawte Premium Server CA.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 1 (0x1)
#     Signature Algorithm: md5WithRSAEncryption
#         Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
#         Validity
#             Not Before: Aug  1 00:00:00 1996 GMT
#             Not After : Dec 31 23:59:59 2020 GMT
#         Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (1024 bit)
#                 Modulus:
#                     00:d2:36:36:6a:8b:d7:c2:5b:9e:da:81:41:62:8f:
#                     38:ee:49:04:55:d6:d0:ef:1c:1b:95:16:47:ef:18:
#                     48:35:3a:52:f4:2b:6a:06:8f:3b:2f:ea:56:e3:af:
#                     86:8d:9e:17:f7:9e:b4:65:75:02:4d:ef:cb:09:a2:
#                     21:51:d8:9b:d0:67:d0:ba:0d:92:06:14:73:d4:93:
#                     cb:97:2a:00:9c:5c:4e:0c:bc:fa:15:52:fc:f2:44:
#                     6e:da:11:4a:6e:08:9f:2f:2d:e3:f9:aa:3a:86:73:
#                     b6:46:53:58:c8:89:05:bd:83:11:b8:73:3f:aa:07:
#                     8d:f4:42:4d:e7:40:9d:1c:37
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#     Signature Algorithm: md5WithRSAEncryption
#          26:48:2c:16:c2:58:fa:e8:16:74:0c:aa:aa:5f:54:3f:f2:d7:
#          c9:78:60:5e:5e:6e:37:63:22:77:36:7e:b2:17:c4:34:b9:f5:
#          08:85:fc:c9:01:38:ff:4d:be:f2:16:42:43:e7:bb:5a:46:fb:
#          c1:c6:11:1f:f1:4a:b0:28:46:c9:c3:c4:42:7d:bc:fa:ab:59:
#          6e:d5:b7:51:88:11:e3:a4:85:19:6b:82:4c:a4:0c:12:ad:e9:
#          a4:ae:3f:f1:c3:49:65:9a:8c:c5:c8:3e:25:b7:94:99:bb:92:
#          32:71:07:f0:86:5e:ed:50:27:a6:0d:a6:23:f9:bb:cb:a6:07:
#          14:42
-----BEGIN CERTIFICATE-----
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
-----END CERTIFICATE-----
# Thawte Primary Root CA - G2.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             35:fc:26:5c:d9:84:4f:c9:3d:26:3d:57:9b:ae:d7:56
#     Signature Algorithm: ecdsa-with-SHA384
#         Issuer: C=US, O=thawte, Inc., OU=(c) 2007 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA - G2
#         Validity
#             Not Before: Nov  5 00:00:00 2007 GMT
#             Not After : Jan 18 23:59:59 2038 GMT
#         Subject: C=US, O=thawte, Inc., OU=(c) 2007 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA - G2
#         Subject Public Key Info:
#             Public Key Algorithm: id-ecPublicKey
#                 Public-Key: (384 bit)
#                 pub:
#                     04:a2:d5:9c:82:7b:95:9d:f1:52:78:87:fe:8a:16:
#                     bf:05:e6:df:a3:02:4f:0d:07:c6:00:51:ba:0c:02:
#                     52:2d:22:a4:42:39:c4:fe:8f:ea:c9:c1:be:d4:4d:
#                     ff:9f:7a:9e:e2:b1:7c:9a:ad:a7:86:09:73:87:d1:
#                     e7:9a:e3:7a:a5:aa:6e:fb:ba:b3:70:c0:67:88:a2:
#                     35:d4:a3:9a:b1:fd:ad:c2:ef:31:fa:a8:b9:f3:fb:
#                     08:c6:91:d1:fb:29:95
#                 ASN1 OID: secp384r1
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Subject Key Identifier:
#                 9A:D8:00:30:00:E7:6B:7F:85:18:EE:8B:B6:CE:8A:0C:F8:11:E1:BB
#     Signature Algorithm: ecdsa-with-SHA384
#          30:66:02:31:00:dd:f8:e0:57:47:5b:a7:e6:0a:c3:bd:f5:80:
#          8a:97:35:0d:1b:89:3c:54:86:77:28:ca:a1:f4:79:de:b5:e6:
#          38:b0:f0:65:70:8c:7f:02:54:c2:bf:ff:d8:a1:3e:d9:cf:02:
#          31:00:c4:8d:94:fc:dc:53:d2:dc:9d:78:16:1f:15:33:23:53:
#          52:e3:5a:31:5d:9d:ca:ae:bd:13:29:44:0d:27:5b:a8:e7:68:
#          9c:12:f7:58:3f:2e:72:02:57:a3:8f:a1:14:2e
-----BEGIN CERTIFICATE-----
MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
-----END CERTIFICATE-----
# Thawte Primary Root CA - G3.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             60:01:97:b7:46:a7:ea:b4:b4:9a:d6:4b:2f:f7:90:fb
#     Signature Algorithm: sha256WithRSAEncryption
#         Issuer: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2008 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA - G3
#         Validity
#             Not Before: Apr  2 00:00:00 2008 GMT
#             Not After : Dec  1 23:59:59 2037 GMT
#         Subject: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2008 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA - G3
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:b2:bf:27:2c:fb:db:d8:5b:dd:78:7b:1b:9e:77:
#                     66:81:cb:3e:bc:7c:ae:f3:a6:27:9a:34:a3:68:31:
#                     71:38:33:62:e4:f3:71:66:79:b1:a9:65:a3:a5:8b:
#                     d5:8f:60:2d:3f:42:cc:aa:6b:32:c0:23:cb:2c:41:
#                     dd:e4:df:fc:61:9c:e2:73:b2:22:95:11:43:18:5f:
#                     c4:b6:1f:57:6c:0a:05:58:22:c8:36:4c:3a:7c:a5:
#                     d1:cf:86:af:88:a7:44:02:13:74:71:73:0a:42:59:
#                     02:f8:1b:14:6b:42:df:6f:5f:ba:6b:82:a2:9d:5b:
#                     e7:4a:bd:1e:01:72:db:4b:74:e8:3b:7f:7f:7d:1f:
#                     04:b4:26:9b:e0:b4:5a:ac:47:3d:55:b8:d7:b0:26:
#                     52:28:01:31:40:66:d8:d9:24:bd:f6:2a:d8:ec:21:
#                     49:5c:9b:f6:7a:e9:7f:55:35:7e:96:6b:8d:93:93:
#                     27:cb:92:bb:ea:ac:40:c0:9f:c2:f8:80:cf:5d:f4:
#                     5a:dc:ce:74:86:a6:3e:6c:0b:53:ca:bd:92:ce:19:
#                     06:72:e6:0c:5c:38:69:c7:04:d6:bc:6c:ce:5b:f6:
#                     f7:68:9c:dc:25:15:48:88:a1:e9:a9:f8:98:9c:e0:
#                     f3:d5:31:28:61:11:6c:67:96:8d:39:99:cb:c2:45:
#                     24:39
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Subject Key Identifier:
#                 AD:6C:AA:94:60:9C:ED:E4:FF:FA:3E:0A:74:2B:63:03:F7:B6:59:BF
#     Signature Algorithm: sha256WithRSAEncryption
#          1a:40:d8:95:65:ac:09:92:89:c6:39:f4:10:e5:a9:0e:66:53:
#          5d:78:de:fa:24:91:bb:e7:44:51:df:c6:16:34:0a:ef:6a:44:
#          51:ea:2b:07:8a:03:7a:c3:eb:3f:0a:2c:52:16:a0:2b:43:b9:
#          25:90:3f:70:a9:33:25:6d:45:1a:28:3b:27:cf:aa:c3:29:42:
#          1b:df:3b:4c:c0:33:34:5b:41:88:bf:6b:2b:65:af:28:ef:b2:
#          f5:c3:aa:66:ce:7b:56:ee:b7:c8:cb:67:c1:c9:9c:1a:18:b8:
#          c4:c3:49:03:f1:60:0e:50:cd:46:c5:f3:77:79:f7:b6:15:e0:
#          38:db:c7:2f:28:a0:0c:3f:77:26:74:d9:25:12:da:31:da:1a:
#          1e:dc:29:41:91:22:3c:69:a7:bb:02:f2:b6:5c:27:03:89:f4:
#          06:ea:9b:e4:72:82:e3:a1:09:c1:e9:00:19:d3:3e:d4:70:6b:
#          ba:71:a6:aa:58:ae:f4:bb:e9:6c:b6:ef:87:cc:9b:bb:ff:39:
#          e6:56:61:d3:0a:a7:c4:5c:4c:60:7b:05:77:26:7a:bf:d8:07:
#          52:2c:62:f7:70:63:d9:39:bc:6f:1c:c2:79:dc:76:29:af:ce:
#          c5:2c:64:04:5e:88:36:6e:31:d4:40:1a:62:34:36:3f:35:01:
#          ae:ac:63:a0
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
MdRAGmI0Nj81Aa6sY6A=
-----END CERTIFICATE-----
# Thawte Primary Root CA.pem
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number:
#             34:4e:d5:57:20:d5:ed:ec:49:f4:2f:ce:37:db:2b:6d
#     Signature Algorithm: sha1WithRSAEncryption
#         Issuer: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
#         Validity
#             Not Before: Nov 17 00:00:00 2006 GMT
#             Not After : Jul 16 23:59:59 2036 GMT
#         Subject: C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA
#         Subject Public Key Info:
#             Public Key Algorithm: rsaEncryption
#                 Public-Key: (2048 bit)
#                 Modulus:
#                     00:ac:a0:f0:fb:80:59:d4:9c:c7:a4:cf:9d:a1:59:
#                     73:09:10:45:0c:0d:2c:6e:68:f1:6c:5b:48:68:49:
#                     59:37:fc:0b:33:19:c2:77:7f:cc:10:2d:95:34:1c:
#                     e6:eb:4d:09:a7:1c:d2:b8:c9:97:36:02:b7:89:d4:
#                     24:5f:06:c0:cc:44:94:94:8d:02:62:6f:eb:5a:dd:
#                     11:8d:28:9a:5c:84:90:10:7a:0d:bd:74:66:2f:6a:
#                     38:a0:e2:d5:54:44:eb:1d:07:9f:07:ba:6f:ee:e9:
#                     fd:4e:0b:29:f5:3e:84:a0:01:f1:9c:ab:f8:1c:7e:
#                     89:a4:e8:a1:d8:71:65:0d:a3:51:7b:ee:bc:d2:22:
#                     60:0d:b9:5b:9d:df:ba:fc:51:5b:0b:af:98:b2:e9:
#                     2e:e9:04:e8:62:87:de:2b:c8:d7:4e:c1:4c:64:1e:
#                     dd:cf:87:58:ba:4a:4f:ca:68:07:1d:1c:9d:4a:c6:
#                     d5:2f:91:cc:7c:71:72:1c:c5:c0:67:eb:32:fd:c9:
#                     92:5c:94:da:85:c0:9b:bf:53:7d:2b:09:f4:8c:9d:
#                     91:1f:97:6a:52:cb:de:09:36:a4:77:d8:7b:87:50:
#                     44:d5:3e:6e:29:69:fb:39:49:26:1e:09:a5:80:7b:
#                     40:2d:eb:e8:27:85:c9:fe:61:fd:7e:e6:7c:97:1d:
#                     d5:9d
#                 Exponent: 65537 (0x10001)
#         X509v3 extensions:
#             X509v3 Basic Constraints: critical
#                 CA:TRUE
#             X509v3 Key Usage: critical
#                 Certificate Sign, CRL Sign
#             X509v3 Subject Key Identifier:
#                 7B:5B:45:CF:AF:CE:CB:7A:FD:31:92:1A:6A:B6:F3:46:EB:57:48:50
#     Signature Algorithm: sha1WithRSAEncryption
#          79:11:c0:4b:b3:91:b6:fc:f0:e9:67:d4:0d:6e:45:be:55:e8:
#          93:d2:ce:03:3f:ed:da:25:b0:1d:57:cb:1e:3a:76:a0:4c:ec:
#          50:76:e8:64:72:0c:a4:a9:f1:b8:8b:d6:d6:87:84:bb:32:e5:
#          41:11:c0:77:d9:b3:60:9d:eb:1b:d5:d1:6e:44:44:a9:a6:01:
#          ec:55:62:1d:77:b8:5c:8e:48:49:7c:9c:3b:57:11:ac:ad:73:
#          37:8e:2f:78:5c:90:68:47:d9:60:60:e6:fc:07:3d:22:20:17:
#          c4:f7:16:e9:c4:d8:72:f9:c8:73:7c:df:16:2f:15:a9:3e:fd:
#          6a:27:b6:a1:eb:5a:ba:98:1f:d5:e3:4d:64:0a:9d:13:c8:61:
#          ba:f5:39:1c:87:ba:b8:bd:7b:22:7f:f6:fe:ac:40:79:e5:ac:
#          10:6f:3d:8f:1b:79:76:8b:c4:37:b3:21:18:84:e5:36:00:eb:
#          63:20:99:b9:e9:fe:33:04:bb:41:c8:c1:02:f9:44:63:20:9e:
#          81:ce:42:d3:d6:3f:2c:76:d3:63:9c:59:dd:8f:a6:e1:0e:a0:
#          2e:41:f7:2e:95:47:cf:bc:fd:33:f3:f6:0b:61:7e:7e:91:2b:
#          81:47:c2:27:30:ee:a7:10:5d:37:8f:5c:39:2b:e4:04:f0:7b:
#          8d:56:8c:68
-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
jVaMaA==
-----END CERTIFICATE-----
<?php
namespace Dropbox;

/**
 * Helper functions to validate arguments.
 *
 * @internal
 */
class Checker
{
    static function throwError($argName, $argValue, $expectedTypeName)
    {
        if ($argValue === null) throw new \InvalidArgumentException("'$argName' must not be null");

        if (is_object($argValue)) {
            // Class type.
            $argTypeName = get_class($argValue);
        } else {
            // Built-in type.
            $argTypeName = gettype($argValue);
        }
        throw new \InvalidArgumentException("'$argName' has bad type; expecting $expectedTypeName, got $argTypeName");
    }

    static function argResource($argName, $argValue)
    {
        if (!is_resource($argValue)) self::throwError($argName, $argValue, "resource");
    }

    static function argCallable($argName, $argValue)
    {
        if (!is_callable($argValue)) self::throwError($argName, $argValue, "callable");
    }

    static function argBool($argName, $argValue)
    {
        if (!is_bool($argValue)) self::throwError($argName, $argValue, "boolean");
    }

    static function argArray($argName, $argValue)
    {
        if (!is_array($argValue)) self::throwError($argName, $argValue, "array");
    }

    static function argString($argName, $argValue)
    {
        if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
    }

    static function argStringOrNull($argName, $argValue)
    {
        if ($argValue === null) return;
        if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
    }

    static function argStringNonEmpty($argName, $argValue)
    {
        if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
        if (strlen($argValue) === 0) throw new \InvalidArgumentException("'$argName' must be non-empty");
    }

    static function argStringNonEmptyOrNull($argName, $argValue)
    {
        if ($argValue === null) return;
        if (!is_string($argValue)) self::throwError($argName, $argValue, "string");
        if (strlen($argValue) === 0) throw new \InvalidArgumentException("'$argName' must be non-empty");
    }

    static function argNat($argName, $argValue)
    {
        if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
        if ($argValue < 0) throw new \InvalidArgumentException("'$argName' must be non-negative (you passed in $argValue)");
    }

    static function argNatOrNull($argName, $argValue)
    {
        if ($argValue === null) return;
        if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
        if ($argValue < 0) throw new \InvalidArgumentException("'$argName' must be non-negative (you passed in $argValue)");
    }

    static function argIntPositive($argName, $argValue)
    {
        if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
        if ($argValue < 1) throw new \InvalidArgumentException("'$argName' must be positive (you passed in $argValue)");
    }

    static function argIntPositiveOrNull($argName, $argValue)
    {
        if ($argValue === null) return;
        if (!is_int($argValue)) self::throwError($argName, $argValue, "int");
        if ($argValue < 1) throw new \InvalidArgumentException("'$argName' must be positive (you passed in $argValue)");
    }
}
<?php
namespace Dropbox;

/**
 * The class used to make most Dropbox API calls.  You can use this once you've gotten an
 * {@link AccessToken} via {@link WebAuth}.
 *
 * This class is stateless so it can be shared/reused.
 */
class Client
{
    /**
     * The access token used by this client to make authenticated API calls.  You can get an
     * access token via {@link WebAuth}.
     *
     * @return AccessToken
     */
    function getAccessToken() { return $this->accessToken; }

    /** @var AccessToken */
    private $accessToken;

    /**
     * An identifier for the API client, typically of the form "Name/Version".
     * This is used to set the HTTP `User-Agent` header when making API requests.
     * Example: `"PhotoEditServer/1.3"`
     *
     * If you're the author a higher-level library on top of the basic SDK, and the
     * "Photo Edit" app's server code is using your library to access Dropbox, you should append
     * your library's name and version to form the full identifier.  For example,
     * if your library is called "File Picker", you might set this field to:
     * `"PhotoEditServer/1.3 FilePicker/0.1-beta"`
     *
     * The exact format of the `User-Agent` header is described in
     * <a href="http://tools.ietf.org/html/rfc2616#section-3.8">section 3.8 of the HTTP specification</a>.
     *
     * Note that underlying HTTP client may append other things to the `User-Agent`, such as
     * the name of the library being used to actually make the HTTP request (such as cURL).
     *
     * @return string
     */
    function getClientIdentifier() { return $this->clientIdentifier; }

    /** @var string */
    private $clientIdentifier;

    /**
     * The locale of the user of your application.  Some API calls return localized
     * data and error messages; this "user locale" setting determines which locale
     * the server should use to localize those strings.
     *
     * @return null|string
     */
    function getUserLocale() { return $this->userLocale; }

    /** @var null|string */
    private $userLocale;

    /**
     * The {@link Host} object that determines the hostnames we make requests to.
     *
     * @return Host
     */
    function getHost() { return $this->host; }

    /**
     * Constructor.
     *
     * @param string $accessToken
     *     See {@link getAccessToken()}
     * @param string $clientIdentifier
     *     See {@link getClientIdentifier()}
     * @param null|string $userLocale
     *     See {@link getUserLocale()}
     */
    function __construct($accessToken, $clientIdentifier, $userLocale = null)
    {
        self::checkAccessTokenArg("accessToken", $accessToken);
        self::checkClientIdentifierArg("clientIdentifier", $clientIdentifier);
        Checker::argStringNonEmptyOrNull("userLocale", $userLocale);

        $this->accessToken = $accessToken;
        $this->clientIdentifier = $clientIdentifier;
        $this->userLocale = $userLocale;

        // The $host parameter is sort of internal.  We don't include it in the param list because
        // we don't want it to be included in the documentation.  Use PHP arg list hacks to get at
        // it.
        $host = null;
        if (\func_num_args() == 4) {
            $host = \func_get_arg(3);
            Host::checkArgOrNull("host", $host);
        }
        if ($host === null) {
            $host = Host::getDefault();
        }
        $this->host = $host;

        // These fields are redundant, but it makes these values a little more convenient
        // to access.
        $this->apiHost = $host->getApi();
        $this->contentHost = $host->getContent();
    }

    /** @var string */
    private $apiHost;
    /** @var string */
    private $contentHost;

    /**
     * Given a `$base` path for an API endpoint (for example, "/files"), append
     * a Dropbox API file path to the end of that URL.  Special characters in the file will
     * be encoded properly.
     *
     * This is for endpoints like "/files" takes the path on the URL and not as a separate
     * query or POST parameter.
     *
     * @param string $base
     * @param string $path
     * @return string
     */
    function appendFilePath($base, $path)
    {
        return $base . "/auto/" . rawurlencode(substr($path, 1));
    }

    /**
     * Make an API call to disable the access token that you constructed this `Client`
     * with.  After calling this, API calls made with this `Client` will fail.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#disable-token">/disable_access_token</a>.
     *
     * @throws Exception
     */
    function disableAccessToken()
    {
        $response = $this->doPost($this->apiHost, "1/disable_access_token");
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
    }

    /**
     * Make an API call to get basic account and quota information.
     *
     * <code>
     * $client = ...
     * $accountInfo = $client->getAccountInfo();
     * print_r($accountInfo);
     * </code>
     *
     * @return array
     *    See <a href="https://www.dropbox.com/developers/core/docs#account-info">/account/info</a>.
     *
     * @throws Exception
     */
    function getAccountInfo()
    {
        $response = $this->doGet($this->apiHost, "1/account/info");
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Downloads a file from Dropbox.  The file's contents are written to the
     * given `$outStream` and the file's metadata is returned.
     *
     * <code>
     * $client = ...;
     * $fd = fopen("./Frog.jpeg", "wb");
     * $metadata = $client->getFile("/Photos/Frog.jpeg", $fd);
     * fclose($fd);
     * print_r($metadata);
     * </code>
     *
     * @param string $path
     *   The path to the file on Dropbox (UTF-8).
     *
     * @param resource $outStream
     *   If the file exists, the file contents will be written to this stream.
     *
     * @param string|null $rev
     *   If you want the latest revision of the file at the given path, pass in `null`.
     *   If you want a specific version of a file, pass in value of the file metadata's "rev" field.
     *
     * @return null|array
     *   The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
     *   object</a> for the file at the given $path and $rev, or `null` if the file
     *   doesn't exist,
     *
     * @throws Exception
     */
    function getFile($path, $outStream, $rev = null)
    {
        Path::checkArgNonRoot("path", $path);
        Checker::argResource("outStream", $outStream);
        Checker::argStringNonEmptyOrNull("rev", $rev);

        $url = $this->buildUrlForGetOrPut(
            $this->contentHost,
            $this->appendFilePath("1/files", $path),
            array("rev" => $rev));

        $curl = $this->mkCurl($url);
        $metadataCatcher = new DropboxMetadataHeaderCatcher($curl->handle);
        $streamRelay = new CurlStreamRelay($curl->handle, $outStream);

        $response = $curl->exec();

        if ($response->statusCode === 404) return null;

        if ($response->statusCode !== 200) {
            $response->body = $streamRelay->getErrorBody();
            throw RequestUtil::unexpectedStatus($response);
        }

        return $metadataCatcher->getMetadata();
    }

    /**
     * Calling 'uploadFile' with `$numBytes` less than this value, will cause this SDK
     * to use the standard /files_put endpoint.  When `$numBytes` is greater than this
     * value, we'll use the /chunked_upload endpoint.
     *
     * @var int
     */
    private static $AUTO_CHUNKED_UPLOAD_THRESHOLD = 9863168;  // 8 MB

    /**
     * @var int
     */
    private static $DEFAULT_CHUNK_SIZE = 4194304;  // 4 MB

    /**
     * Creates a file on Dropbox, using the data from `$inStream` for the file contents.
     *
     * <code>
     * use \Dropbox as dbx;
     * $client = ...;
     * $fd = fopen("./frog.jpeg", "rb");
     * $md1 = $client->uploadFile("/Photos/Frog.jpeg",
     *                            dbx\WriteMode::add(), $fd);
     * fclose($fd);
     * print_r($md1);
     * $rev = $md1["rev"];
     *
     * // Re-upload with WriteMode::update(...), which will overwrite the
     * // file if it hasn't been modified from our original upload.
     * $fd = fopen("./frog-new.jpeg", "rb");
     * $md2 = $client->uploadFile("/Photos/Frog.jpeg",
     *                            dbx\WriteMode::update($rev), $fd);
     * fclose($fd);
     * print_r($md2);
     * </code>
     *
     * @param string $path
     *    The Dropbox path to save the file to (UTF-8).
     *
     * @param WriteMode $writeMode
     *    What to do if there's already a file at the given path.
     *
     * @param resource $inStream
     *    The data to use for the file contents.
     *
     * @param int|null $numBytes
     *    You can pass in `null` if you don't know.  If you do provide the size, we can
     *    perform a slightly more efficient upload (fewer network round-trips) for files smaller
     *    than 8 MB.
     *
     * @return mixed
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
     *    object</a> for the newly-added file.
     *
     * @throws Exception
     */
    function uploadFile($path, $writeMode, $inStream, $numBytes = null)
    {
        Path::checkArgNonRoot("path", $path);
        WriteMode::checkArg("writeMode", $writeMode);
        Checker::argResource("inStream", $inStream);
        Checker::argNatOrNull("numBytes", $numBytes);

        // If we don't know how many bytes are coming, we have to use chunked upload.
        // If $numBytes is large, we elect to use chunked upload.
        // In all other cases, use regular upload.
        if ($numBytes === null || $numBytes > self::$AUTO_CHUNKED_UPLOAD_THRESHOLD) {
            $metadata = $this->_uploadFileChunked($path, $writeMode, $inStream, $numBytes,
                                                  self::$DEFAULT_CHUNK_SIZE);
        } else {
            $metadata = $this->_uploadFile($path, $writeMode,
                function(Curl $curl) use ($inStream, $numBytes) {
                    $curl->set(CURLOPT_PUT, true);
                    $curl->set(CURLOPT_INFILE, $inStream);
                    $curl->set(CURLOPT_INFILESIZE, $numBytes);
                });
        }

        return $metadata;
    }

    /**
     * Creates a file on Dropbox, using the given $data string as the file contents.
     *
     * <code>
     * use \Dropbox as dbx;
     * $client = ...;
     * $md = $client->uploadFileFromString("/Grocery List.txt",
     *                                     dbx\WriteMode::add(),
     *                                     "1. Coke\n2. Popcorn\n3. Toothpaste\n");
     * print_r($md);
     * </code>
     *
     * @param string $path
     *    The Dropbox path to save the file to (UTF-8).
     *
     * @param WriteMode $writeMode
     *    What to do if there's already a file at the given path.
     *
     * @param string $data
     *    The data to use for the contents of the file.
     *
     * @return mixed
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
     *    object</a> for the newly-added file.
     *
     * @throws Exception
     */
    function uploadFileFromString($path, $writeMode, $data)
    {
        Path::checkArgNonRoot("path", $path);
        WriteMode::checkArg("writeMode", $writeMode);
        Checker::argString("data", $data);

        return $this->_uploadFile($path, $writeMode, function(Curl $curl) use ($data) {
            $curl->set(CURLOPT_CUSTOMREQUEST, "PUT");
            $curl->set(CURLOPT_POSTFIELDS, $data);
            $curl->addHeader("Content-Type: application/octet-stream");
        });
    }

    /**
     * Creates a file on Dropbox, using the data from `$inStream` as the file contents.
     *
     * This version of `uploadFile` splits uploads the file ~4MB chunks at a time and
     * will retry a few times if one chunk fails to upload.  Uses {@link chunkedUploadStart()},
     * {@link chunkedUploadContinue()}, and {@link chunkedUploadFinish()}.
     *
     * @param string $path
     *    The Dropbox path to save the file to (UTF-8).
     *
     * @param WriteMode $writeMode
     *    What to do if there's already a file at the given path.
     *
     * @param resource $inStream
     *    The data to use for the file contents.
     *
     * @param int|null $numBytes
     *    The number of bytes available from $inStream.
     *    You can pass in `null` if you don't know.
     *
     * @param int|null $chunkSize
     *    The number of bytes to upload in each chunk.  You can omit this (or pass in
     *    `null` and the library will use a reasonable default.
     *
     * @return mixed
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
     *    object</a> for the newly-added file.
     *
     * @throws Exception
     */
    function uploadFileChunked($path, $writeMode, $inStream, $numBytes = null, $chunkSize = null)
    {
        if ($chunkSize === null) {
            $chunkSize = self::$DEFAULT_CHUNK_SIZE;
        }

        Path::checkArgNonRoot("path", $path);
        WriteMode::checkArg("writeMode", $writeMode);
        Checker::argResource("inStream", $inStream);
        Checker::argNatOrNull("numBytes", $numBytes);
        Checker::argIntPositive("chunkSize", $chunkSize);

        return $this->_uploadFileChunked($path, $writeMode, $inStream, $numBytes, $chunkSize);
    }

    /**
     * @param string $path
     *
     * @param WriteMode $writeMode
     *    What to do if there's already a file at the given path (UTF-8).
     *
     * @param resource $inStream
     *    The source of data to upload.
     *
     * @param int|null $numBytes
     *    You can pass in `null`.  But if you know how many bytes you expect, pass in
     *    that value and this function will do a sanity check at the end to make sure the number of
     *    bytes read from `$inStream` matches up.
     *
     * @param int $chunkSize
     *
     * @return array
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
     *    object</a> for the newly-added file.
     */
    private function _uploadFileChunked($path, $writeMode, $inStream, $numBytes, $chunkSize)
    {
        Path::checkArg("path", $path);
        WriteMode::checkArg("writeMode", $writeMode);
        Checker::argResource("inStream", $inStream);
        Checker::argNatOrNull("numBytes", $numBytes);
        Checker::argNat("chunkSize", $chunkSize);

        // NOTE: This function performs 3 retries on every call.  This is maybe not the right
        // layer to make retry decisions.  It's also awkward because none of the other calls
        // perform retries.

        assert($chunkSize > 0);

        $data = self::readFully($inStream, $chunkSize);
        $len = strlen($data);

        $client = $this;
        $uploadId = RequestUtil::runWithRetry(3, function() use ($data, $client) {
            return $client->chunkedUploadStart($data);
        });

        $byteOffset = $len;

        while (!feof($inStream)) {
            $data = self::readFully($inStream, $chunkSize);
            $len = strlen($data);

            while (true) {
                $r = RequestUtil::runWithRetry(3,
                    function() use ($client, $uploadId, $byteOffset, $data) {
                        return $client->chunkedUploadContinue($uploadId, $byteOffset, $data);
                    });

                if ($r === true) {  // Chunk got uploaded!
                    $byteOffset += $len;
                    break;
                }
                if ($r === false) {  // Server didn't recognize our upload ID
                    // This is very unlikely since we're uploading all the chunks in sequence.
                    throw new Exception_BadResponse("Server forgot our uploadId");
                }

                // Otherwise, the server is at a different byte offset from us.
                $serverByteOffset = $r;
                assert($serverByteOffset !== $byteOffset);  // chunkedUploadContinue ensures this.
                // An earlier byte offset means the server has lost data we sent earlier.
                if ($serverByteOffset < $byteOffset) throw new Exception_BadResponse(
                    "Server is at an ealier byte offset: us=$byteOffset, server=$serverByteOffset");
                $diff = $serverByteOffset - $byteOffset;
                // If the server is past where we think it could possibly be, something went wrong.
                if ($diff > $len) throw new Exception_BadResponse(
                    "Server is more than a chunk ahead: us=$byteOffset, server=$serverByteOffset");
                // The normal case is that the server is a bit further along than us because of a
                // partially-uploaded chunk.  Finish it off.
                $byteOffset += $diff;
                if ($diff === $len) break;  // If the server is at the end, we're done.
                $data = substr($data, $diff);
            }
        }

        if ($numBytes !== null && $byteOffset !== $numBytes) throw new \InvalidArgumentException(
            "You passed numBytes=$numBytes but the stream had $byteOffset bytes.");

        $metadata = RequestUtil::runWithRetry(3,
            function() use ($client, $uploadId, $path, $writeMode) {
                return $client->chunkedUploadFinish($uploadId, $path, $writeMode);
            });

        return $metadata;
    }

    /**
     * Sometimes fread() returns less than the request number of bytes (for example, when reading
     * from network streams).  This function repeatedly calls fread until the requested number of
     * bytes have been read or we've reached EOF.
     *
     * @param resource $inStream
     * @param int $numBytes
     * @throws StreamReadException
     * @return string
     */
    private static function readFully($inStream, $numBytes)
    {
        Checker::argNat("numBytes", $numBytes);

        $full = '';
        $bytesRemaining = $numBytes;
        while (!feof($inStream) && $bytesRemaining > 0) {
            $part = fread($inStream, $bytesRemaining);
            if ($part === false) throw new StreamReadException("Error reading from \$inStream.");
            $full .= $part;
            $bytesRemaining -= strlen($part);
        }
        return $full;
    }

    /**
     * @param string $path
     * @param WriteMode $writeMode
     * @param callable $curlConfigClosure
     * @return array
     */
    private function _uploadFile($path, $writeMode, $curlConfigClosure)
    {
        Path::checkArg("path", $path);
        WriteMode::checkArg("writeMode", $writeMode);
        Checker::argCallable("curlConfigClosure", $curlConfigClosure);

        $url = $this->buildUrlForGetOrPut(
            $this->contentHost,
            $this->appendFilePath("1/files_put", $path),
            $writeMode->getExtraParams());

        $curl = $this->mkCurl($url);

        $curlConfigClosure($curl);

        $curl->set(CURLOPT_RETURNTRANSFER, true);
        $response = $curl->exec();

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Start a new chunked upload session and upload the first chunk of data.
     *
     * @param string $data
     *     The data to start off the chunked upload session.
     *
     * @return array
     *     A pair of `(string $uploadId, int $byteOffset)`.  `$uploadId`
     *     is a unique identifier for this chunked upload session.  You pass this in to
     *     {@link chunkedUploadContinue} and {@link chuunkedUploadFinish}.  `$byteOffset`
     *     is the number of bytes that were successfully uploaded.
     *
     * @throws Exception
     */
    function chunkedUploadStart($data)
    {
        Checker::argString("data", $data);

        $response = $this->_chunkedUpload(array(), $data);

        if ($response->statusCode === 404) {
            throw new Exception_BadResponse("Got a 404, but we didn't send up an 'upload_id'");
        }

        $correction = self::_chunkedUploadCheckForOffsetCorrection($response);
        if ($correction !== null) throw new Exception_BadResponse(
            "Got an offset-correcting 400 response, but we didn't send an offset");

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        list($uploadId, $byteOffset) = self::_chunkedUploadParse200Response($response->body);
        $len = strlen($data);
        if ($byteOffset !== $len) throw new Exception_BadResponse(
            "We sent $len bytes, but server returned an offset of $byteOffset");

        return $uploadId;
    }

    /**
     * Append another chunk data to a previously-started chunked upload session.
     *
     * @param string $uploadId
     *     The unique identifier for the chunked upload session.  This is obtained via
     *     {@link chunkedUploadStart}.
     *
     * @param int $byteOffset
     *     The number of bytes you think you've already uploaded to the given chunked upload
     *     session.  The server will append the new chunk of data after that point.
     *
     * @param string $data
     *     The data to append to the existing chunked upload session.
     *
     * @return int|bool
     *     If `false`, it means the server didn't know about the given
     *     `$uploadId`.  This may be because the chunked upload session has expired
     *     (they last around 24 hours).
     *     If `true`, the chunk was successfully uploaded.  If an integer, it means
     *     you and the server don't agree on the current `$byteOffset`.  The returned
     *     integer is the server's internal byte offset for the chunked upload session.  You need
     *     to adjust your input to match.
     *
     * @throws Exception
     */
    function chunkedUploadContinue($uploadId, $byteOffset, $data)
    {
        Checker::argStringNonEmpty("uploadId", $uploadId);
        Checker::argNat("byteOffset", $byteOffset);
        Checker::argString("data", $data);

        $response = $this->_chunkedUpload(
            array("upload_id" => $uploadId, "offset" => $byteOffset), $data);

        if ($response->statusCode === 404) {
            // The server doesn't know our upload ID.  Maybe it expired?
            return false;
        }

        $correction = self::_chunkedUploadCheckForOffsetCorrection($response);
        if ($correction !== null) {
            list($correctedUploadId, $correctedByteOffset) = $correction;
            if ($correctedUploadId !== $uploadId) throw new Exception_BadResponse(
                "Corrective 400 upload_id mismatch: us=".
                Util::q($uploadId)." server=".Util::q($correctedUploadId));
            if ($correctedByteOffset === $byteOffset) throw new Exception_BadResponse(
                "Corrective 400 offset is the same as ours: $byteOffset");
            return $correctedByteOffset;
        }

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
        list($retUploadId, $retByteOffset) = self::_chunkedUploadParse200Response($response->body);

        $nextByteOffset = $byteOffset + strlen($data);
        if ($uploadId !== $retUploadId) throw new Exception_BadResponse(
                "upload_id mismatch: us=".Util::q($uploadId) .", server=".Util::q($uploadId));
        if ($nextByteOffset !== $retByteOffset) throw new Exception_BadResponse(
                "next-offset mismatch: us=$nextByteOffset, server=$retByteOffset");

        return true;
    }

    /**
     * @param string $body
     * @return array
     */
    private static function _chunkedUploadParse200Response($body)
    {
        $j = RequestUtil::parseResponseJson($body);
        $uploadId = self::getField($j, "upload_id");
        $byteOffset = self::getField($j, "offset");
        return array($uploadId, $byteOffset);
    }

    /**
     * @param HttpResponse $response
     * @return array|null
     */
    private static function _chunkedUploadCheckForOffsetCorrection($response)
    {
        if ($response->statusCode !== 400) return null;
        $j = json_decode($response->body, true, 10);
        if ($j === null) return null;
        if (!array_key_exists("upload_id", $j) || !array_key_exists("offset", $j)) return null;
        $uploadId = $j["upload_id"];
        $byteOffset = $j["offset"];
        return array($uploadId, $byteOffset);
    }

    /**
     * Creates a file on Dropbox using the accumulated contents of the given chunked upload session.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#commit-chunked-upload">/commit_chunked_upload</a>.
     *
     * @param string $uploadId
     *     The unique identifier for the chunked upload session.  This is obtained via
     *     {@link chunkedUploadStart}.
     *
     * @param string $path
     *    The Dropbox path to save the file to.
     *
     * @param WriteMode $writeMode
     *    What to do if there's already a file at the given path.
     *
     * @return array|null
     *    If `null`, it means the Dropbox server wasn't aware of the
     *    `$uploadId` you gave it.
     *    Otherwise, you get back the
     *    <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
     *    for the newly-created file.
     *
     * @throws Exception
     */
    function chunkedUploadFinish($uploadId, $path, $writeMode)
    {
        Checker::argStringNonEmpty("uploadId", $uploadId);
        Path::checkArgNonRoot("path", $path);
        WriteMode::checkArg("writeMode", $writeMode);

        $params = array_merge(array("upload_id" => $uploadId), $writeMode->getExtraParams());

        $response = $this->doPost(
            $this->contentHost,
            $this->appendFilePath("1/commit_chunked_upload", $path),
            $params);

        if ($response->statusCode === 404) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * @param array $params
     * @param string $data
     * @return HttpResponse
     */
    protected function _chunkedUpload($params, $data)
        // Marked 'protected' so I can override it in testing.
    {
        $url = $this->buildUrlForGetOrPut(
            $this->contentHost, "1/chunked_upload", $params);

        $curl = $this->mkCurl($url);

        // We can't use CURLOPT_PUT because it wants a stream, but we already have $data in memory.
        $curl->set(CURLOPT_CUSTOMREQUEST, "PUT");
        $curl->set(CURLOPT_POSTFIELDS, $data);
        $curl->addHeader("Content-Type: application/octet-stream");

        $curl->set(CURLOPT_RETURNTRANSFER, true);
        return $curl->exec();
    }

    /**
     * Returns the metadata for whatever file or folder is at the given path.
     *
     * <code>
     * $client = ...;
     * $md = $client->getMetadata("/Photos/Frog.jpeg");
     * print_r($md);
     * </code>
     *
     * @param string $path
     *    The Dropbox path to a file or folder (UTF-8).
     *
     * @return array|null
     *    If there is a file or folder at the given path, you'll get back the
     *    <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
     *    for that file or folder.  If not, you'll get back `null`.
     *
     * @throws Exception
     */
    function getMetadata($path)
    {
        Path::checkArg("path", $path);

        return $this->_getMetadata($path, array("list" => "false"));
    }

    /**
     * Returns the metadata for whatever file or folder is at the given path and, if it's a folder,
     * also include the metadata for all the immediate children of that folder.
     *
     * <code>
     * $client = ...;
     * $md = $client->getMetadataWithChildren("/Photos");
     * print_r($md);
     * </code>
     *
     * @param string $path
     *    The Dropbox path to a file or folder (UTF-8).
     *
     * @return array|null
     *    If there is a file or folder at the given path, you'll get back the
     *    <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
     *    for that file or folder, along with all immediate children if it's a folder.  If not,
     *    you'll get back `null`.
     *
     * @throws Exception
     */
    function getMetadataWithChildren($path)
    {
        Path::checkArg("path", $path);

        return $this->_getMetadata($path, array("list" => "true", "file_limit" => "25000"));
    }

    /**
     * @param string $path
     * @param array $params
     * @return array
     */
    private function _getMetadata($path, $params)
    {
        $response = $this->doGet(
            $this->apiHost,
            $this->appendFilePath("1/metadata", $path),
            $params);

        if ($response->statusCode === 404) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        $metadata = RequestUtil::parseResponseJson($response->body);
        if (array_key_exists("is_deleted", $metadata) && $metadata["is_deleted"]) return null;
        return $metadata;
    }

    /**
     * If you've previously retrieved the metadata for a folder and its children, this method will
     * retrieve updated metadata only if something has changed.  This is more efficient than
     * calling {@link getMetadataWithChildren} if you have a cache of previous results.
     *
     * <code>
     * $client = ...;
     * $md = $client->getMetadataWithChildren("/Photos");
     * print_r($md);
     * assert($md["is_dir"], "expecting \"/Photos\" to be a folder");
     *
     * sleep(10);
     *
     * // Now see if anything changed...
     * list($changed, $new_md) = $client->getMetadataWithChildrenIfChanged(
     *                                    "/Photos", $md["hash"]);
     * if ($changed) {
     *     echo "Folder changed.\n";
     *     print_r($new_md);
     * } else {
     *     echo "Folder didn't change.\n";
     * }
     * </code>
     *
     * @param string $path
     *    The Dropbox path to a folder (UTF-8).
     *
     * @param string $previousFolderHash
     *    The "hash" field from the previously retrieved folder metadata.
     *
     * @return array
     *    A `list(boolean $changed, array $metadata)`.  If the metadata hasn't changed,
     *    you'll get `list(false, null)`.  If the metadata of the folder or any of its
     *    children has changed, you'll get `list(true, $newMetadata)`.  $metadata is a
     *    <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>.
     *
     * @throws Exception
     */
    function getMetadataWithChildrenIfChanged($path, $previousFolderHash)
    {
        Path::checkArg("path", $path);
        Checker::argStringNonEmpty("previousFolderHash", $previousFolderHash);

        $params = array("list" => "true", "file_limit" => "25000", "hash" => $previousFolderHash);

        $response = $this->doGet(
            $this->apiHost,
            $this->appendFilePath("1/metadata", $path),
            $params);

        if ($response->statusCode === 304) return array(false, null);
        if ($response->statusCode === 404) return array(true, null);
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        $metadata = RequestUtil::parseResponseJson($response->body);
        if (array_key_exists("is_deleted", $metadata) && $metadata["is_deleted"]) {
            return array(true, null);
        }
        return array(true, $metadata);
    }

    /**
     * A way of letting you keep up with changes to files and folders in a user's Dropbox.
     *
     * @param string|null $cursor
     *    If this is the first time you're calling this, pass in `null`.  Otherwise,
     *    pass in whatever cursor was returned by the previous call.
     *
     * @param string|null $pathPrefix
     *    If `null`, you'll get results for the entire folder (either the user's
     *    entire Dropbox or your App Folder).  If you set `$path_prefix` to
     *    "/Photos/Vacation", you'll only get results for that path and any files and folders
     *    under it.
     *
     * @return array
     *    A <a href="https://www.dropbox.com/developers/core/docs#delta">delta page</a>, which
     *    contains a list of changes to apply along with a new "cursor" that should be passed into
     *    future `getDelta` calls.  If the "reset" field is `true`, you
     *    should clear your local state before applying the changes.  If the "has_more" field is
     *    `true`, call `getDelta` immediately to get more results, otherwise
     *    wait a while (at least 5 minutes) before calling `getDelta` again.
     *
     * @throws Exception
     */
    function getDelta($cursor = null, $pathPrefix = null)
    {
        Checker::argStringNonEmptyOrNull("cursor", $cursor);
        Path::checkArgOrNull("pathPrefix", $pathPrefix);

        $response = $this->doPost($this->apiHost, "1/delta", array(
            "cursor" => $cursor,
            "path_prefix" => $pathPrefix));

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Gets the metadata for all the file revisions (up to a limit) for a given path.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#revisions">/revisions</a>.
     *
     * @param string path
     *    The Dropbox path that you want file revision metadata for (UTF-8).
     *
     * @param int|null limit
     *    The maximum number of revisions to return.
     *
     * @return array|null
     *    A list of <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
     *    objects</a>, one for each file revision.  The later revisions appear first in the list.
     *    If `null`, then there were too many revisions at that path.
     *
     * @throws Exception
     */
    function getRevisions($path, $limit = null)
    {
        Path::checkArgNonRoot("path", $path);
        Checker::argIntPositiveOrNull("limit", $limit);

        $response = $this->doGet(
            $this->apiHost,
            $this->appendFilePath("1/revisions", $path),
            array("rev_limit" => $limit));

        if ($response->statusCode === 406) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Takes a copy of the file at the given revision and saves it over the current copy.  This
     * will create a new revision, but the file contents will match the revision you specified.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#restore">/restore</a>.
     *
     * @param string $path
     *    The Dropbox path of the file to restore (UTF-8).
     *
     * @param string $rev
     *    The revision to restore the contents to.
     *
     * @return mixed
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
     *    object</a>
     *
     * @throws Exception
     */
    function restoreFile($path, $rev)
    {
        Path::checkArgNonRoot("path", $path);
        Checker::argStringNonEmpty("rev", $rev);

        $response = $this->doPost(
            $this->apiHost,
            $this->appendFilePath("1/restore", $path),
            array("rev" => $rev));

        if ($response->statusCode === 404) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Returns metadata for all files and folders whose filename matches the query string.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#search">/search</a>.
     *
     * @param string $basePath
     *    The path to limit the search to (UTF-8).  Pass in "/" to search everything.
     *
     * @param string $query
     *    A space-separated list of substrings to search for.  A file matches only if it contains
     *    all the substrings.
     *
     * @param int|null $limit
     *    The maximum number of results to return.
     *
     * @param bool $includeDeleted
     *    Whether to include deleted files in the results.
     *
     * @return mixed
     *    A list of <a href="https://www.dropbox.com/developers/core/docs#metadata-details>metadata
     *    objects</a> of files that match the search query.
     *
     * @throws Exception
     */
    function searchFileNames($basePath, $query, $limit = null, $includeDeleted = false)
    {
        Path::checkArg("basePath", $basePath);
        Checker::argStringNonEmpty("query", $query);
        Checker::argNatOrNull("limit", $limit);
        Checker::argBool("includeDeleted", $includeDeleted);

        $response = $this->doPost(
            $this->apiHost,
            $this->appendFilePath("1/search", $basePath),
            array(
                "query" => $query,
                "file_limit" => $limit,
                "include_deleted" => $includeDeleted,
            ));

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Creates and returns a public link to a file or folder's "preview page".  This link can be
     * used without authentication.  The preview page may contain a thumbnail or some other
     * preview of the file, along with a download link to download the actual file.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#shares">/shares</a>.
     *
     * @param string $path
     *    The Dropbox path to the file or folder you want to create a shareable link to (UTF-8).
     *
     * @return string
     *    The URL of the preview page.
     *
     * @throws Exception
     */
    function createShareableLink($path)
    {
        Path::checkArg("path", $path);

        $response = $this->doPost(
            $this->apiHost,
            $this->appendFilePath("1/shares", $path),
            array(
                "short_url" => "false",
            ));

        if ($response->statusCode === 404) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        $j = RequestUtil::parseResponseJson($response->body);
        return self::getField($j, "url");
    }

    /**
     * Creates and returns a direct link to a file.  This link can be used without authentication.
     * This link will expire in a few hours.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#media">/media</a>.
     *
     * @param string $path
     *    The Dropbox path to a file or folder (UTF-8).
     *
     * @return array
     *    A `list(string $url, \DateTime $expires)` where `$url` is a direct
     *    link to the requested file and `$expires` is a standard PHP
     *    `\DateTime` representing when `$url` will stop working.
     *
     * @throws Exception
     */
    function createTemporaryDirectLink($path)
    {
        Path::checkArgNonRoot("path", $path);

        $response = $this->doPost(
            $this->apiHost,
            $this->appendFilePath("1/media", $path));

        if ($response->statusCode === 404) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        $j = RequestUtil::parseResponseJson($response->body);
        $url = self::getField($j, "url");
        $expires = self::parseDateTime(self::getField($j, "expires"));
        return array($url, $expires);
    }

    /**
     * Creates and returns a "copy ref" to a file.  A copy ref can be used to copy a file across
     * different Dropbox accounts without downloading and re-uploading.
     *
     * For example: Create a `Client` using the access token from one account and call
     * `createCopyRef`.  Then, create a `Client` using the access token for
     * another account and call `copyFromCopyRef` using the copy ref.  (You need to use
     * the same app key both times.)
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#copy_ref">/copy_ref</a>.
     *
     * @param string path
     *    The Dropbox path of the file or folder you want to create a copy ref for (UTF-8).
     *
     * @return string
     *    The copy ref (just a string that you keep track of).
     *
     * @throws Exception
     */
    function createCopyRef($path)
    {
        Path::checkArg("path", $path);

        $response = $this->doGet(
            $this->apiHost,
            $this->appendFilePath("1/copy_ref", $path));

        if ($response->statusCode === 404) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        $j = RequestUtil::parseResponseJson($response->body);
        return self::getField($j, "copy_ref");
    }

    /**
     * Gets a thumbnail image representation of the file at the given path.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#thumbnails">/thumbnails</a>.
     *
     * @param string $path
     *    The path to the file you want a thumbnail for (UTF-8).
     *
     * @param string $format
     *    One of the two image formats: "jpeg" or "png".
     *
     * @param string $size
     *    One of the predefined image size names, as a string:
     *    <ul>
     *    <li>"xs" - 32x32</li>
     *    <li>"s" - 64x64</li>
     *    <li>"m" - 128x128</li>
     *    <li>"l" - 640x480</li>
     *    <li>"xl" - 1024x768</li>
     *    </ul>
     *
     * @return array|null
     *    If the file exists, you'll get `list(array $metadata, string $data)` where
     *    `$metadata` is the file's
     *    <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
     *    and $data is the raw data for the thumbnail image.  If the file doesn't exist, you'll
     *    get `null`.
     *
     * @throws Exception
     */
    function getThumbnail($path, $format, $size)
    {
        Path::checkArgNonRoot("path", $path);
        Checker::argString("format", $format);
        Checker::argString("size", $size);
        if (!in_array($format, array("jpeg", "png"))) {
            throw new \InvalidArgumentException("Invalid 'format': ".Util::q($format));
        }
        if (!in_array($size, array("xs", "s", "m", "l", "xl"))) {
            throw new \InvalidArgumentException("Invalid 'size': ".Util::q($size));
        }

        $url = $this->buildUrlForGetOrPut(
            $this->contentHost,
            $this->appendFilePath("1/thumbnails", $path),
            array("size" => $size, "format" => $format));

        $curl = $this->mkCurl($url);
        $metadataCatcher = new DropboxMetadataHeaderCatcher($curl->handle);

        $curl->set(CURLOPT_RETURNTRANSFER, true);
        $response = $curl->exec();

        if ($response->statusCode === 404) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        $metadata = $metadataCatcher->getMetadata();
        return array($metadata, $response->body);
    }

    /**
     * Copies a file or folder to a new location
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#fileops-copy">/fileops/copy</a>.
     *
     * @param string $fromPath
     *    The Dropbox path of the file or folder you want to copy (UTF-8).
     *
     * @param string $toPath
     *    The destination Dropbox path (UTF-8).
     *
     * @return mixed
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
     *    object</a> for the new file or folder.
     *
     * @throws Exception
     */
    function copy($fromPath, $toPath)
    {
        Path::checkArg("fromPath", $fromPath);
        Path::checkArgNonRoot("toPath", $toPath);

        $response = $this->doPost(
            $this->apiHost,
            "1/fileops/copy",
            array(
                "root" => "auto",
                "from_path" => $fromPath,
                "to_path" => $toPath,
            ));

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Creates a file or folder based on an existing copy ref (possibly from a different Dropbox
     * account).
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#fileops-copy">/fileops/copy</a>.
     *
     * @param string $copyRef
     *    A copy ref obtained via the {@link createCopyRef()} call.
     *
     * @param string $toPath
     *    The Dropbox path you want to copy the file or folder to (UTF-8).
     *
     * @return mixed
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
     *    object</a> for the new file or folder.
     *
     * @throws Exception
     */
    function copyFromCopyRef($copyRef, $toPath)
    {
        Checker::argStringNonEmpty("copyRef", $copyRef);
        Path::checkArgNonRoot("toPath", $toPath);

        $response = $this->doPost(
            $this->apiHost,
            "1/fileops/copy",
            array(
                "root" => "auto",
                "from_copy_ref" => $copyRef,
                "to_path" => $toPath,
            )
        );

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Creates a folder.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#fileops-create-folder">/fileops/create_folder</a>.
     *
     * @param string $path
     *    The Dropbox path at which to create the folder (UTF-8).
     *
     * @return array|null
     *    If successful, you'll get back the
     *    <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata object</a>
     *    for the newly-created folder.  If not successful, you'll get `null`.
     *
     * @throws Exception
     */
    function createFolder($path)
    {
        Path::checkArgNonRoot("path", $path);

        $response = $this->doPost(
            $this->apiHost,
            "1/fileops/create_folder",
            array(
                "root" => "auto",
                "path" => $path,
            ));

        if ($response->statusCode === 403) return null;
        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Deletes a file or folder
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#fileops-delete">/fileops/delete</a>.
     *
     * @param string $path
     *    The Dropbox path of the file or folder to delete (UTF-8).
     *
     * @return mixed
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
     *    object</a> for the deleted file or folder.
     *
     * @throws Exception
     */
    function delete($path)
    {
        Path::checkArgNonRoot("path", $path);

        $response = $this->doPost(
            $this->apiHost,
            "1/fileops/delete",
            array(
                "root" => "auto",
                "path" => $path,
            ));

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Moves a file or folder to a new location.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#fileops-move">/fileops/move</a>.
     *
     * @param string $fromPath
     *    The source Dropbox path (UTF-8).
     *
     * @param string $toPath
     *    The destination Dropbox path (UTF-8).
     *
     * @return mixed
     *    The <a href="https://www.dropbox.com/developers/core/docs#metadata-details">metadata
     *    object</a> for the destination file or folder.
     *
     * @throws Exception
     */
    function move($fromPath, $toPath)
    {
        Path::checkArgNonRoot("fromPath", $fromPath);
        Path::checkArgNonRoot("toPath", $toPath);

        $response = $this->doPost(
            $this->apiHost,
            "1/fileops/move",
            array(
                "root" => "auto",
                "from_path" => $fromPath,
                "to_path" => $toPath,
            ));

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        return RequestUtil::parseResponseJson($response->body);
    }

    /**
     * Build a URL for making a GET or PUT request.  Will add the "locale"
     * parameter.
     *
     * @param string $host
     *    Either the "API" or "API content" hostname from {@link getHost()}.
     * @param string $path
     *    The "path" part of the URL.  For example, "/account/info".
     * @param array|null $params
     *    URL parameters.  For POST requests, do not put the parameters here.
     *    Include them in the request body instead.
     *
     * @return string
     */
    function buildUrlForGetOrPut($host, $path, $params = null)
    {
        return RequestUtil::buildUrlForGetOrPut($this->userLocale, $host, $path, $params);
    }

    /**
     * Perform an OAuth-2-authorized GET request to the Dropbox API.  Will automatically
     * fill in "User-Agent" and "locale" as well.
     *
     * @param string $host
     *    Either the "API" or "API content" hostname from {@link getHost()}.
     * @param string $path
     *    The "path" part of the URL.  For example, "/account/info".
     * @param array|null $params
     *    GET parameters.
     * @return HttpResponse
     *
     * @throws Exception
     */
    function doGet($host, $path, $params = null)
    {
        Checker::argString("host", $host);
        Checker::argString("path", $path);
        return RequestUtil::doGet($this->clientIdentifier, $this->accessToken, $this->userLocale,
                                  $host, $path, $params);
    }

    /**
     * Perform an OAuth-2-authorized POST request to the Dropbox API.  Will automatically
     * fill in "User-Agent" and "locale" as well.
     *
     * @param string $host
     *    Either the "API" or "API content" hostname from {@link getHost()}.
     * @param string $path
     *    The "path" part of the URL.  For example, "/commit_chunked_upload".
     * @param array|null $params
     *    POST parameters.
     * @return HttpResponse
     *
     * @throws Exception
     */
    function doPost($host, $path, $params = null)
    {
        Checker::argString("host", $host);
        Checker::argString("path", $path);
        return RequestUtil::doPost($this->clientIdentifier, $this->accessToken, $this->userLocale,
                                   $host, $path, $params);
    }

    /**
     * Create a {@link Curl} object that is pre-configured with {@link getClientIdentifier()},
     * and the proper OAuth 2 "Authorization" header.
     *
     * @param string $url
     *    Generate this URL using {@link buildUrl()}.
     *
     * @return Curl
     */
    function mkCurl($url)
    {
        return RequestUtil::mkCurlWithOAuth($this->clientIdentifier, $url, $this->accessToken);
    }

    /**
     * Parses date/time strings returned by the Dropbox API.  The Dropbox API returns date/times
     * formatted like: `"Sat, 21 Aug 2010 22:31:20 +0000"`.
     *
     * @param string $apiDateTimeString
     *    A date/time string returned by the API.
     *
     * @return \DateTime
     *    A standard PHP `\DateTime` instance.
     *
     * @throws Exception_BadResponse
     *    Thrown if `$apiDateTimeString` isn't correctly formatted.
     */
    static function parseDateTime($apiDateTimeString)
    {
        $dt = \DateTime::createFromFormat(self::$dateTimeFormat, $apiDateTimeString);
        if ($dt === false) throw new Exception_BadResponse(
            "Bad date/time from server: ".Util::q($apiDateTimeString));
        return $dt;
    }

    private static $dateTimeFormat = "D, d M Y H:i:s T";

    /**
     * @internal
     */
    static function getField($j, $fieldName)
    {
        if (!array_key_exists($fieldName, $j)) throw new Exception_BadResponse(
            "missing field \"$fieldName\" in ".Util::q($j));
        return $j[$fieldName];
    }

    /**
     * Given an OAuth 2 access token, returns `null` if it is well-formed (though
     * not necessarily valid).  Otherwise, returns a string describing what's wrong with it.
     *
     * @param string $s
     *
     * @return string
     */
    static function getAccessTokenError($s)
    {
        if ($s === null) return "can't be null";
        if (strlen($s) === 0) return "can't be empty";
        if (preg_match('@[^-=_~/A-Za-z0-9\.\+]@', $s) === 1) return "contains invalid character";
        return null;
    }

    /**
     * @internal
     */
    static function checkAccessTokenArg($argName, $accessToken)
    {
        $error = self::getAccessTokenError($accessToken);
        if ($error !== null) throw new \InvalidArgumentException("'$argName' invalid: $error");
    }

    /**
     * @internal
     */
    static function getClientIdentifierError($s)
    {
        if ($s === null) return "can't be null";
        if (strlen($s) === 0) return "can't be empty";
        if (preg_match('@[\x00-\x1f\x7f]@', $s) === 1) return "contains control character";
        return null;
    }

    /**
     * @internal
     */
    static function checkClientIdentifierArg($argName, $accessToken)
    {
        $error = self::getClientIdentifierError($accessToken);
        if ($error !== null) throw new \InvalidArgumentException("'$argName' invalid: $error");
    }
}
<?php
namespace Dropbox;

/**
 * A minimal wrapper around a cURL handle.
 *
 * @internal
 */
final class Curl
{
    /** @var resource */
    public $handle;

    /** @var string[] */
    private $headers = array();

    /**
     * @param string $url
     */
    function __construct($url)
    {
        // Make sure there aren't any spaces in the URL (i.e. the caller forgot to URL-encode).
        if (strpos($url, ' ') !== false) {
            throw new \InvalidArgumentException("Found space in \$url; it should be encoded");
        }

        $this->handle = curl_init($url);

        // NOTE: Though we turn on all the correct SSL settings, many PHP installations
        // don't respect these settings.  Run "examples/test-ssl.php" to run some basic
        // SSL tests to see how well your PHP implementation behaves.

        // Use our own certificate list.
        $this->set(CURLOPT_SSL_VERIFYPEER, true);   // Enforce certificate validation
        $this->set(CURLOPT_SSL_VERIFYHOST, 2);      // Enforce hostname validation

        // Force the use of TLS (SSL v2 and v3 are not secure).
        // TODO: Use "CURL_SSLVERSION_TLSv1" instead of "1" once we can rely on PHP 5.5+.
        $this->set(CURLOPT_SSLVERSION, 1);

        // Limit the set of ciphersuites used.
        global $sslCiphersuiteList;
        if ($sslCiphersuiteList !== null) {
            $this->set(CURLOPT_SSL_CIPHER_LIST, $sslCiphersuiteList);
        }

        list($rootCertsFilePath, $rootCertsFolderPath) = RootCertificates::getPaths();
        // Certificate file.
        $this->set(CURLOPT_CAINFO, $rootCertsFilePath);
        // Certificate folder.  If not specified, some PHP installations will use
        // the system default, even when CURLOPT_CAINFO is specified.
        $this->set(CURLOPT_CAPATH, $rootCertsFolderPath);

        // Limit vulnerability surface area.  Supported in cURL 7.19.4+
        if (defined('CURLOPT_PROTOCOLS')) $this->set(CURLOPT_PROTOCOLS, CURLPROTO_HTTPS);
        if (defined('CURLOPT_REDIR_PROTOCOLS')) $this->set(CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
    }

    /**
     * @param string $header
     */
    function addHeader($header)
    {
        $this->headers[] = $header;
    }

    function exec()
    {
        $this->set(CURLOPT_HTTPHEADER, $this->headers);

        $body = curl_exec($this->handle);
        if ($body === false) {
            throw new Exception_NetworkIO("Error executing HTTP request: " . curl_error($this->handle));
        }

        $statusCode = curl_getinfo($this->handle, CURLINFO_HTTP_CODE);

        return new HttpResponse($statusCode, $body);
    }

    /**
     * @param int $option
     * @param mixed $value
     */
    function set($option, $value)
    {
        curl_setopt($this->handle, $option, $value);
    }

    function __destruct()
    {
        curl_close($this->handle);
    }
}

// Different cURL SSL backends use different names for ciphersuites.
$curlVersion = \curl_version();
$curlSslBackend = $curlVersion['ssl_version'];
if (Util::startsWith($curlSslBackend, "NSS/")) {
    // Can't figure out how to reliably set ciphersuites for NSS.
    $sslCiphersuiteList = null;
}
else {
    // Use the OpenSSL names for all other backends.  We may have to
    // refine this if users report errors.
    $sslCiphersuiteList =
        'ECDHE-RSA-AES256-GCM-SHA384:'.
        'ECDHE-RSA-AES128-GCM-SHA256:'.
        'ECDHE-RSA-AES256-SHA384:'.
        'ECDHE-RSA-AES128-SHA256:'.
        'ECDHE-RSA-AES256-SHA:'.
        'ECDHE-RSA-AES128-SHA:'.
        'ECDHE-RSA-RC4-SHA:'.
        'DHE-RSA-AES256-GCM-SHA384:'.
        'DHE-RSA-AES128-GCM-SHA256:'.
        'DHE-RSA-AES256-SHA256:'.
        'DHE-RSA-AES128-SHA256:'.
        'DHE-RSA-AES256-SHA:'.
        'DHE-RSA-AES128-SHA:'.
        'AES256-GCM-SHA384:'.
        'AES128-GCM-SHA256:'.
        'AES256-SHA256:'.
        'AES128-SHA256:'.
        'AES256-SHA:'.
        'AES128-SHA';
}
<?php
namespace Dropbox;

/**
 * A CURLOPT_WRITEFUNCTION that will write HTTP response data to $outStream if
 * it's an HTTP 200 response.  For all other HTTP status codes, it'll save the
 * output in a string, which you can retrieve it via {@link getErrorBody}.
 *
 * @internal
 */
class CurlStreamRelay
{
    var $outStream;
    var $errorData;
    var $isError;

    function __construct($ch, $outStream)
    {
        $this->outStream = $outStream;
        $this->errorData = array();
        $isError = null;
        curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, 'writeData'));
    }

    function writeData($ch, $data)
    {
        if ($this->isError === null) {
            $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $this->isError = ($statusCode !== 200);
        }

        if ($this->isError) {
            $this->errorData[] = $data;
        } else {
            fwrite($this->outStream, $data);
        }

        return strlen($data);
    }

    function getErrorBody()
    {
        return implode($this->errorData);
    }
}
<?php
namespace Dropbox;

/**
 * If, when loading a serialized {@link RequestToken} or {@link AccessToken}, the input string is
 * malformed, this exception will be thrown.
 */
final class DeserializeException extends \Exception
{
    /**
     * @param string $message
     *
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * @internal
 */
final class DropboxMetadataHeaderCatcher
{
    /**
     * @var mixed
     */
    var $metadata = null;

    /**
     * @var string
     */
    var $error = null;

    /**
     * @var bool
     */
    var $skippedFirstLine = false;

    /**
     * @param resource $ch
     */
    function __construct($ch)
    {
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerFunction'));
    }

    /**
     * @param resource $ch
     * @param string $header
     * @return int
     * @throws Exception_BadResponse
     */
    function headerFunction($ch, $header)
    {
        // The first line is the HTTP status line (Ex: "HTTP/1.1 200 OK").
        if (!$this->skippedFirstLine) {
            $this->skippedFirstLine = true;
            return strlen($header);
        }

        // If we've encountered an error on a previous callback, then there's nothing left to do.
        if ($this->error !== null) {
            return strlen($header);
        }

        // case-insensitive starts-with check.
        $headerValue = Util::stripPrefix($header, "x-dropbox-metadata:");
        if ($headerValue === null) {
            return strlen($header);
        }

        if ($this->metadata !== null) {
            $this->error = "Duplicate X-Dropbox-Metadata header";
            return strlen($header);
        }

        $parsed = json_decode($headerValue, true, 10);

        if ($parsed === null) {
            $this->error = "Bad JSON in X-Dropbox-Metadata header";
            return strlen($header);
        }

        $this->metadata = $parsed;
        return strlen($header);
    }

    function getMetadata()
    {
        if ($this->error !== null) {
            throw new Exception_BadResponse($this->error);
        }
        if ($this->metadata === null) {
            throw new Exception_BadResponse("Missing X-Dropbox-Metadata header");
        }
        return $this->metadata;
    }
}
<?php
namespace Dropbox;

/**
 * Thrown when the server tells us that our request was invalid.  This is typically due to an
 * HTTP 400 response from the server.
 */
final class Exception_BadRequest extends Exception_ProtocolError
{
    /**
     * @internal
     */
    function __construct($message = "")
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * When this SDK can't understand the response from the server.  This could be due to a bug in this
 * SDK or a buggy response from the Dropbox server.
 */
class Exception_BadResponse extends Exception_ProtocolError
{
    /**
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * Thrown when the the Dropbox server responds with an HTTP status code we didn't expect.
 */
final class Exception_BadResponseCode extends Exception_BadResponse
{
    /** @var int */
    private $statusCode;

    /**
     * @param string $message
     * @param int $statusCode
     *
     * @internal
     */
    function __construct($message, $statusCode)
    {
        parent::__construct($message);
        $this->statusCode = $statusCode;
    }

    /**
     * The HTTP status code returned by the Dropbox server.
     *
     * @return int
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }
}
<?php
namespace Dropbox;

/**
 * The Dropbox server said that the access token you used is invalid or expired.  You should
 * probably ask the user to go through the OAuth authorization flow again to get a new access
 * token.
 */
final class Exception_InvalidAccessToken extends Exception
{
    /**
     * @internal
     */
    function __construct($message = "")
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * There was a network I/O error when making the request.
 */
final class Exception_NetworkIO extends Exception
{
    /**
     * @internal
     */
    function __construct($message, $cause = null)
    {
        parent::__construct($message, $cause);
    }
}
<?php
namespace Dropbox;

/**
 * User is over Dropbox storage quota.
 */
final class Exception_OverQuota extends Exception
{
    /**
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * There was an protocol misunderstanding between this SDK and the server.  One of us didn't
 * understand what the other one was saying.
 */
class Exception_ProtocolError extends Exception
{
    /**
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * The Dropbox server said it couldn't fulfil our request right now, but that we should try
 * again later.
 */
final class Exception_RetryLater extends Exception
{
    /**
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * The Dropbox server said that there was an internal error when trying to fulfil our request.
 * This usually corresponds to an HTTP 500 response.
 */
final class Exception_ServerError extends Exception
{
    /** @internal */
    function __construct($message = "")
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * The base class for all API call exceptions.
 */
class Exception extends \Exception
{
    /**
     * @internal
     */
    function __construct($message, $cause = null)
    {
        parent::__construct($message, 0, $cause);
    }
}
<?php
namespace Dropbox;

/**
 * The Dropbox web API accesses three hosts; this structure holds the
 * names of those three hosts.  This is primarily for mocking things out
 * during testing.  Most of the time you won't have to deal with this class
 * directly, and even when you do, you'll just use the default
 * value: {@link Host::getDefault()}.
 *
 * @internal
 */
final class Host
{
    /**
     * Returns a Host object configured with the three standard Dropbox host: "api.dropbox.com",
     * "api-content.dropbox.com", and "www.dropbox.com"
     *
     * @return Host
     */
    static function getDefault()
    {
        if (!self::$defaultValue) {
            self::$defaultValue = new Host("api.dropboxapi.com", "content.dropboxapi.com", "www.dropbox.com");
        }
        return self::$defaultValue;
    }
    private static $defaultValue;

    /** @var string */
    private $api;
    /** @var string */
    private $content;
    /** @var string */
    private $web;

    /**
     * Constructor.
     *
     * @param string $api
     *     See {@link getApi()}
     * @param string $content
     *     See {@link getContent()}
     * @param string $web
     *     See {@link getWeb()}
     */
    function __construct($api, $content, $web)
    {
        $this->api = $api;
        $this->content = $content;
        $this->web = $web;
    }

    /**
     * Returns the host name of the main Dropbox API server.
     * The default is "api.dropbox.com".
     *
     * @return string
     */
    function getApi() { return $this->api; }

    /**
     * Returns the host name of the Dropbox API content server.
     * The default is "api-content.dropbox.com".
     *
     * @return string
     */
    function getContent() { return $this->content; }

    /**
     * Returns the host name of the Dropbox web server.  Used during user authorization.
     * The default is "www.dropbox.com".
     *
     * @return string
     */
    function getWeb() { return $this->web; }

    /**
     * Check that a function argument is of type `Host`.
     *
     * @internal
     */
    static function checkArg($argName, $argValue)
    {
        if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
    }

    /**
     * Check that a function argument is either `null` or of type
     * `Host`.
     *
     * @internal
     */
    static function checkArgOrNull($argName, $argValue)
    {
        if ($argValue === null) return;
        if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
    }

    /**
     * Loads a Host object from the 'auth_host' and 'host_suffix' fields of a JSON object.
     * If those fields aren't present, return `null`.
     *
     * @return Host|null
     *
     * @throws HostLoadException
     */
    static function loadFromJson($jsonObj)
    {
        // Check for the optional 'auth_host' and 'host_suffix' fields.
        $authHost = null;
        if (array_key_exists('auth_host', $jsonObj)) {
            $authHost = $jsonObj["auth_host"];
            if (!is_string($authHost)) {
                throw new HostLoadException("Optional field \"auth_host\" must be a string");
            }
        }
        $hostSuffix = null;
        if (array_key_exists('host_suffix', $jsonObj)) {
            $hostSuffix = $jsonObj["host_suffix"];
            if (!is_string($hostSuffix)) {
                throw new HostLoadException("Optional field \"host_suffix\" must be a string");
            }
        }

        if ($authHost === null && $hostSuffix === null) return null;

        if ($authHost === null) {
            throw new HostLoadException("Can't provide \"host_suffix\" without providing \"auth_host\".");
        }
        if ($hostSuffix === null) {
            throw new HostLoadException("Can't provide \"auth_host\" without providing \"host_suffix\".");
        }
        $api = "api".$hostSuffix;
        $content = "content".$hostSuffix;
        $web = $authHost;
        return new Host($api, $content, $web);
    }
}
<?php
namespace Dropbox;

/**
 * Thrown by the `Host::loadFromJson` method if something goes wrong.
 */
final class HostLoadException extends \Exception
{
    /**
     * @param string $message
     *
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * @internal
 */
final class HttpResponse
{
    public $statusCode;
    public $body;

    function __construct($statusCode, $body)
    {
        $this->statusCode = $statusCode;
        $this->body = $body;
    }
}
<?php
namespace Dropbox;

/**
 * Use with {@link OAuth1Upgrader} to convert old OAuth 1 access tokens
 * to OAuth 2 access tokens.  This SDK doesn't support using OAuth 1
 * access tokens for regular API calls.
 */
class OAuth1AccessToken
{
    /**
     * The OAuth 1 access token key.
     *
     * @return string
     */
    function getKey() { return $this->key; }

    /** @var string */
    private $key;

    /**
     * The OAuth 1 access token secret.
     *
     * Make sure that this is kept a secret.  Someone with your app secret can impesonate your
     * application.  People sometimes ask for help on the Dropbox API forums and
     * copy/paste code that includes their app secret.  Do not do that.
     *
     * @return string
     */
    function getSecret() { return $this->secret; }

    /** @var secret */
    private $secret;

    /**
     * Constructor.
     *
     * @param string $key
     *     {@link getKey()}
     * @param string $secret
     *     {@link getSecret()}
     */
    function __construct($key, $secret)
    {
        AppInfo::checkKeyArg($key);
        AppInfo::checkSecretArg($secret);

        $this->key = $key;
        $this->secret = $secret;
    }

    /**
     * Use this to check that a function argument is of type `AppInfo`
     *
     * @internal
     */
    static function checkArg($argName, $argValue)
    {
        if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
    }
}
<?php
namespace Dropbox;

/**
 * Lets you convert OAuth 1 access tokens to OAuth 2 access tokens.  First call {@link
 * OAuth1AccessTokenUpgrader::createOAuth2AccessToken()} to get an OAuth 2 access token.
 * If that succeeds, call {@link OAuth1AccessTokenUpgrader::disableOAuth1AccessToken()}
 * to disable the OAuth 1 access token.
 *
 * <code>
 * use \Dropbox as dbx;
 * $appInfo = dbx\AppInfo::loadFromJsonFile(...);
 * $clientIdentifier = "my-app/1.0";
 * $oauth1AccessToken = dbx\OAuth1AccessToken(...);
 *
 * $upgrader = new dbx\OAuth1AccessTokenUpgrader($appInfo, $clientIdentifier, ...);
 * $oauth2AccessToken = $upgrader->getOAuth2AccessToken($oauth1AccessToken);
 * $upgrader->disableOAuth1AccessToken($oauth1AccessToken);
 * </code>
 */
class OAuth1Upgrader extends AuthBase
{
    /**
     * Given an existing active OAuth 1 access token, make a Dropbox API call to get a new OAuth 2
     * access token that represents the same user and app.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#oa1-from-oa1">/oauth2/token_from_oauth1</a>.
     *
     * @param OAuth1AccessToken $oauth1AccessToken
     *
     * @return string
     *    The OAuth 2 access token.
     *
     * @throws Exception
     */
    function createOAuth2AccessToken($oauth1AccessToken)
    {
        OAuth1AccessToken::checkArg("oauth1AccessToken", $oauth1AccessToken);

        $response = self::doPost($oauth1AccessToken, "1/oauth2/token_from_oauth1");

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        $parts = RequestUtil::parseResponseJson($response->body);

        if (!array_key_exists('token_type', $parts) || !is_string($parts['token_type'])) {
            throw new Exception_BadResponse("Missing \"token_type\" field.");
        }
        $tokenType = $parts['token_type'];
        if (!array_key_exists('access_token', $parts) || !is_string($parts['access_token'])) {
            throw new Exception_BadResponse("Missing \"access_token\" field.");
        }
        $accessToken = $parts['access_token'];

        if ($tokenType !== "Bearer" && $tokenType !== "bearer") {
            throw new Exception_BadResponse("Unknown \"token_type\"; expecting \"Bearer\", got  "
                . Util::q($tokenType));
        }

        return $accessToken;
    }

    /**
     * Make a Dropbox API call to disable the given OAuth 1 access token.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#disable-token">/disable_access_token</a>.
     *
     * @param OAuth1AccessToken $oauth1AccessToken
     *
     * @throws Exception
     */
    function disableOAuth1AccessToken($oauth1AccessToken)
    {
        OAuth1AccessToken::checkArg("oauth1AccessToken", $oauth1AccessToken);

        $response = self::doPost($oauth1AccessToken, "1/disable_access_token");

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
    }

    /**
     * @param OAuth1AccessToken $oauth1AccessToken
     * @param string $path
     *
     * @return HttpResponse
     *
     * @throws Exception
     */
    private function doPost($oauth1AccessToken, $path)
    {
        // Construct the OAuth 1 header.
        $signature = rawurlencode($this->appInfo->getSecret()) . "&" . rawurlencode($oauth1AccessToken->getSecret());
        $authHeaderValue = "OAuth oauth_signature_method=\"PLAINTEXT\""
             . ", oauth_consumer_key=\"" . rawurlencode($this->appInfo->getKey()) . "\""
             . ", oauth_token=\"" . rawurlencode($oauth1AccessToken->getKey()) . "\""
             . ", oauth_signature=\"" . $signature . "\"";

        return RequestUtil::doPostWithSpecificAuth(
            $this->clientIdentifier, $authHeaderValue, $this->userLocale,
            $this->appInfo->getHost()->getApi(),
            $path,
            null);
    }
}
<?php
namespace Dropbox;

/**
 * Path validation functions.
 */
final class Path
{
    /**
     * Return whether the given path is a valid Dropbox path.
     *
     * @param string $path
     *    The path you want to check for validity.
     *
     * @return bool
     *    Whether the path was valid or not.
     */
    static function isValid($path)
    {
        $error = self::findError($path);
        return ($error === null);
    }

    /**
     * Return whether the given path is a valid non-root Dropbox path.
     * This is the same as {@link isValid} except `"/"` is not allowed.
     *
     * @param string $path
     *    The path you want to check for validity.
     *
     * @return bool
     *    Whether the path was valid or not.
     */
    static function isValidNonRoot($path)
    {
        $error = self::findErrorNonRoot($path);
        return ($error === null);
    }

    /**
     * If the given path is a valid Dropbox path, return `null`,
     * otherwise return an English string error message describing what is wrong with the path.
     *
     * @param string $path
     *    The path you want to check for validity.
     *
     * @return string|null
     *    If the path was valid, return `null`.  Otherwise, returns
     *    an English string describing the problem.
     */
    static function findError($path)
    {
        Checker::argStringNonEmpty("path", $path);

        $matchResult = preg_match('%^(?:
                  [\x09\x0A\x0D\x20-\x7E]            # ASCII
                | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
                | \xE0[\xA0-\xBF][\x80-\xBD]         # excluding overlongs, FFFE, and FFFF
                | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
                | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
                )*$%xs', $path);

        if ($matchResult !== 1) {
            return "must be valid UTF-8; BMP only, no surrogates, no U+FFFE or U+FFFF";
        }

        if ($path[0] !== "/") return "must start with \"/\"";
        $l = strlen($path);
        if ($l === 1) return null;  // Special case for "/"

        if ($path[$l-1] === "/") return "must not end with \"/\"";

        // TODO: More checks.

        return null;
    }

    /**
     * If the given path is a valid non-root Dropbox path, return `null`,
     * otherwise return an English string error message describing what is wrong with the path.
     * This is the same as {@link findError} except `"/"` will yield an error message.
     *
     * @param string $path
     *    The path you want to check for validity.
     *
     * @return string|null
     *    If the path was valid, return `null`.  Otherwise, returns
     *    an English string describing the problem.
     */
    static function findErrorNonRoot($path)
    {
        if ($path == "/") return "root path not allowed";
        return self::findError($path);
    }

    /**
     * Return the last component of a path (the file or folder name).
     *
     * <code>
     * Path::getName("/Misc/Notes.txt") // "Notes.txt"
     * Path::getName("/Misc")           // "Misc"
     * Path::getName("/")               // null
     * </code>
     *
     * @param string $path
     *    The full path you want to get the last component of.
     *
     * @return null|string
     *    The last component of `$path` or `null` if the given
     *    `$path` was `"/"`.
     */
    static function getName($path)
    {
        Checker::argStringNonEmpty("path", $path);

        if ($path[0] !== "/") {
            throw new \InvalidArgumentException("'path' must start with \"/\"");
        }
        $l = strlen($path);
        if ($l === 1) return null;
        if ($path[$l-1] === "/") {
            throw new \InvalidArgumentException("'path' must not end with \"/\"");
        }

        $lastSlash = strrpos($path, "/");
        return substr($path, $lastSlash+1);
    }

    /**
     * @internal
     *
     * @param string $argName
     * @param mixed $value
     * @throws \InvalidArgumentException
     */
    static function checkArg($argName, $value)
    {
        Checker::argStringNonEmpty($argName, $value);

        $error = self::findError($value);
        if ($error !== null) throw new \InvalidArgumentException("'$argName': bad path: $error: ".Util::q($value));
    }

    /**
     * @internal
     *
     * @param string $argName
     * @param mixed $value
     * @throws \InvalidArgumentException
     */
    static function checkArgOrNull($argName, $value)
    {
        if ($value === null) return;
        self::checkArg($argName, $value);
    }

    /**
     * @internal
     *
     * @param string $argName
     * @param mixed $value
     * @throws \InvalidArgumentException
     */
    static function checkArgNonRoot($argName, $value)
    {
        Checker::argStringNonEmpty($argName, $value);

        $error = self::findErrorNonRoot($value);
        if ($error !== null) throw new \InvalidArgumentException("'$argName': bad path: $error: ".Util::q($value));
    }
}
<?php
namespace Dropbox;

if (!function_exists('curl_init')) {
    throw new \Exception("The Dropbox SDK requires the cURL PHP extension, but it looks like you don't have it (couldn't find function \"curl_init\").  Library: \"" . __FILE__ . "\".");
}

if (!function_exists('json_decode')) {
    throw new \Exception("The Dropbox SDK requires the JSON PHP extension, but it looks like you don't have it (couldn't find function \"json_decode\").  Library: \"" . __FILE__ . "\".");
}

// If mbstring.func_overload is set, it changes the behavior of the standard string functions in
// ways that makes this library break.
$mbstring_func_overload = ini_get("mbstring.func_overload");
if ($mbstring_func_overload & 2 == 2) {
    throw new \Exception("The Dropbox SDK doesn't work when mbstring.func_overload is set to overload the standard string functions (value = ".var_export($mbstring_func_overload, true).").  Library: \"" . __FILE__ . "\".");
}

if (strlen((string) PHP_INT_MAX) < 19) {
    // Looks like we're running on a 32-bit build of PHP.  This could cause problems because some of the numbers
    // we use (file sizes, quota, etc) can be larger than 32-bit ints can handle.
    throw new \Exception("The Dropbox SDK uses 64-bit integers, but it looks like we're running on a version of PHP that doesn't support 64-bit integers (PHP_INT_MAX=" . ((string) PHP_INT_MAX) . ").  Library: \"" . __FILE__ . "\"");
}

/**
 * @internal
 */
final class RequestUtil
{
    /**
     * @param string $userLocale
     * @param string $host
     * @param string $path
     * @param array $params
     * @return string
     */
    static function buildUrlForGetOrPut($userLocale, $host, $path, $params = null)
    {
        $url = self::buildUri($host, $path);
        $url .= "?locale=" . rawurlencode($userLocale);

        if ($params !== null) {
            foreach ($params as $key => $value) {
                Checker::argStringNonEmpty("key in 'params'", $key);
                if ($value !== null) {
                    if (is_bool($value)) {
                        $value = $value ? "true" : "false";
                    }
                    else if (is_int($value)) {
                        $value = (string) $value;
                    }
                    else if (!is_string($value)) {
                        throw new \InvalidArgumentException("params['$key'] is not a string, int, or bool");
                    }
                    $url .= "&" . rawurlencode($key) . "=" . rawurlencode($value);
                }
            }
        }
        return $url;
    }

    /**
     * @param string $host
     * @param string $path
     * @return string
     */
    static function buildUri($host, $path)
    {
        Checker::argStringNonEmpty("host", $host);
        Checker::argStringNonEmpty("path", $path);
        return "https://" . $host . "/" . $path;
    }

    /**
     * @param string $clientIdentifier
     * @param string $url
     * @return Curl
     */
    static function mkCurl($clientIdentifier, $url)
    {
        $curl = new Curl($url);

        $curl->set(CURLOPT_CONNECTTIMEOUT, 10);

        // If the transfer speed is below 1kB/sec for 10 sec, abort.
        $curl->set(CURLOPT_LOW_SPEED_LIMIT, 1024);
        $curl->set(CURLOPT_LOW_SPEED_TIME, 10);

        //$curl->set(CURLOPT_VERBOSE, true);  // For debugging.
        // TODO: Figure out how to encode clientIdentifier (urlencode?)
        $curl->addHeader("User-Agent: ".$clientIdentifier." Dropbox-PHP-SDK/".SdkVersion::VERSION);

        return $curl;
    }

    /**
     * @param string $clientIdentifier
     * @param string $url
     * @param string $authHeaderValue
     * @return Curl
     */
    static function mkCurlWithAuth($clientIdentifier, $url, $authHeaderValue)
    {
        $curl = self::mkCurl($clientIdentifier, $url);
        $curl->addHeader("Authorization: $authHeaderValue");
        return $curl;
    }

    /**
     * @param string $clientIdentifier
     * @param string $url
     * @param string $accessToken
     * @return Curl
     */
    static function mkCurlWithOAuth($clientIdentifier, $url, $accessToken)
    {
        return self::mkCurlWithAuth($clientIdentifier, $url, "Bearer $accessToken");
    }

    static function buildPostBody($params)
    {
        if ($params === null) return "";

        $pairs = array();
        foreach ($params as $key => $value) {
            Checker::argStringNonEmpty("key in 'params'", $key);
            if ($value !== null) {
                if (is_bool($value)) {
                    $value = $value ? "true" : "false";
                }
                else if (is_int($value)) {
                    $value = (string) $value;
                }
                else if (!is_string($value)) {
                    throw new \InvalidArgumentException("params['$key'] is not a string, int, or bool");
                }
                $pairs[] = rawurlencode($key) . "=" . rawurlencode((string) $value);
            }
        }
        return implode("&", $pairs);
    }

    /**
     * @param string $clientIdentifier
     * @param string $accessToken
     * @param string $userLocale
     * @param string $host
     * @param string $path
     * @param array|null $params
     *
     * @return HttpResponse
     *
     * @throws Exception
     */
    static function doPost($clientIdentifier, $accessToken, $userLocale, $host, $path, $params = null)
    {
        Checker::argStringNonEmpty("accessToken", $accessToken);

        $url = self::buildUri($host, $path);

        if ($params === null) $params = array();
        $params['locale'] = $userLocale;

        $curl = self::mkCurlWithOAuth($clientIdentifier, $url, $accessToken);
        $curl->set(CURLOPT_POST, true);
        $curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));

        $curl->set(CURLOPT_RETURNTRANSFER, true);
        return $curl->exec();
    }

    /**
     * @param string $clientIdentifier
     * @param string $authHeaderValue
     * @param string $userLocale
     * @param string $host
     * @param string $path
     * @param array|null $params
     *
     * @return HttpResponse
     *
     * @throws Exception
     */
    static function doPostWithSpecificAuth($clientIdentifier, $authHeaderValue, $userLocale, $host, $path, $params = null)
    {
        Checker::argStringNonEmpty("authHeaderValue", $authHeaderValue);

        $url = self::buildUri($host, $path);

        if ($params === null) $params = array();
        $params['locale'] = $userLocale;

        $curl = self::mkCurlWithAuth($clientIdentifier, $url, $authHeaderValue);
        $curl->set(CURLOPT_POST, true);
        $curl->set(CURLOPT_POSTFIELDS, self::buildPostBody($params));

        $curl->set(CURLOPT_RETURNTRANSFER, true);
        return $curl->exec();
    }

    /**
     * @param string $clientIdentifier
     * @param string $accessToken
     * @param string $userLocale
     * @param string $host
     * @param string $path
     * @param array|null $params
     *
     * @return HttpResponse
     *
     * @throws Exception
     */
    static function doGet($clientIdentifier, $accessToken, $userLocale, $host, $path, $params = null)
    {
        Checker::argStringNonEmpty("accessToken", $accessToken);

        $url = self::buildUrlForGetOrPut($userLocale, $host, $path, $params);

        $curl = self::mkCurlWithOAuth($clientIdentifier, $url, $accessToken);
        $curl->set(CURLOPT_HTTPGET, true);
        $curl->set(CURLOPT_RETURNTRANSFER, true);

        return $curl->exec();
    }

    /**
     * @param string $responseBody
     * @return mixed
     * @throws Exception_BadResponse
     */
    static function parseResponseJson($responseBody)
    {
        $obj = json_decode($responseBody, true, 10);
        if ($obj === null) {
            throw new Exception_BadResponse("Got bad JSON from server: $responseBody");
        }
        return $obj;
    }

    static function unexpectedStatus($httpResponse)
    {
        $sc = $httpResponse->statusCode;

        $message = "HTTP status $sc";
        if (is_string($httpResponse->body)) {
            // TODO: Maybe only include the first ~200 chars of the body?
            $message .= "\n".$httpResponse->body;
        }

        if ($sc === 400) return new Exception_BadRequest($message);
        if ($sc === 401) return new Exception_InvalidAccessToken($message);
        if ($sc === 500 || $sc === 502) return new Exception_ServerError($message);
        if ($sc === 503) return new Exception_RetryLater($message);
        if ($sc === 507) return new Exception_OverQuota($message);

        return new Exception_BadResponseCode("Unexpected $message", $sc);
    }

    /**
     * @param int $maxRetries
     *    The number of times to retry it the action if it fails with one of the transient
     *    API errors.  A value of 1 means we'll try the action once and if it fails, we
     *    will retry once.
     *
     * @param callable $action
     *    The the action you want to retry.
     *
     * @return mixed
     *    Whatever is returned by the $action callable.
     */
    static function runWithRetry($maxRetries, $action)
    {
        Checker::argNat("maxRetries", $maxRetries);

        $retryDelay = 1;
        $numRetries = 0;
        while (true) {
            try {
                return $action();
            }
            // These exception types are the ones we think are possibly transient errors.
            catch (Exception_NetworkIO $ex) {
                $savedEx = $ex;
            }
            catch (Exception_ServerError $ex) {
                $savedEx = $ex;
            }
            catch (Exception_RetryLater $ex) {
                $savedEx = $ex;
            }

            // We maxed out our retries.  Propagate the last exception we got.
            if ($numRetries >= $maxRetries) throw $savedEx;

            $numRetries++;
            sleep($retryDelay);
            $retryDelay *= 2;  // Exponential back-off.
        }
        throw new \RuntimeException("unreachable");
    }

}
<?php
namespace Dropbox;

/**
 * See: {@link useExternalPaths()}
 */
class RootCertificates
{
    /* @var boolean */
    private static $useExternalFile = false;

    /* @var string[]|null */
    private static $paths = null;  // A tuple of (rootCertsFilePath, rootCertsFolderPath)

    /**
     * If you're running within a PHAR, call this method before you use the SDK
     * to make any network requests.
     *
     * Normally, the SDK tells cURL to look in the "certs" folder for root certificate
     * information.  But this won't work if this SDK is running from within a PHAR because
     * cURL won't read files that are packaged in a PHAR.
     */
    static function useExternalPaths()
    {
        if (!self::$useExternalFile and self::$paths !== null) {
            throw new \Exception("You called \"useExternalFile\" too late.  The SDK already used the root ".
                                 "certificate file (probably to make an API call).");
        }

        self::$useExternalFile = true;
    }

    private static $originalPath = '/certs/trusted-certs.crt';

    /**
     * @internal
     *
     * @return string[]
     *    A tuple of (rootCertsFilePath, rootCertsFolderPath).  To be used with cURL options CAINFO and CAPATH.
     */
    static function getPaths()
    {
        if (self::$paths === null) {
            if (self::$useExternalFile) {
                try {
                    $baseFolder = sys_get_temp_dir();
                    $file = self::createExternalCaFile($baseFolder);
                    $folder = self::createExternalCaFolder($baseFolder);
                }
                catch (\Exception $ex) {
                    throw new \Exception("Unable to create external root certificate file and folder: ".$ex->getMessage());
                }
            }
            else {
                if (substr(__DIR__, 0, 7) === 'phar://') {
                    throw new \Exception("The code appears to be running in a PHAR.  You need to call \\Dropbox\\RootCertificates\\useExternalPaths() before making any API calls.");
                }
                $file = __DIR__.self::$originalPath;
                $folder = \dirname($file);
            }
            self::$paths = array($file, $folder);
        }

        return self::$paths;
    }

    /**
     * @param string $baseFolder
     *
     * @return string
     */
    private static function createExternalCaFolder($baseFolder)
    {
        // This is hacky, but I can't find a simple way to do this.

        // This process isn't atomic, so give it three tries.
        for ($i = 0; $i < 3; $i++) {
            $path = \tempnam($baseFolder, "dropbox-php-sdk-trusted-certs-empty-dir");
            if ($path === false) {
                throw new \Exception("Couldn't create temp file in folder ".Util::q($baseFolder).".");
            }
            if (!\unlink($path)) {
                throw new \Exception("Couldn't remove temp file to make way for temp dir: ".Util::q($path));
            }
            // TODO: Figure out how to make the folder private on Windows.  The '700' only works on Unix.
            if (!\mkdir($path, 700)) {
                // Someone snuck in between the unlink() and the mkdir() and stole our path.
                throw new \Exception("Couldn't create temp dir: ".Util::q($path));
            }
            \register_shutdown_function(function() use ($path) {
                \rmdir($path);
            });
            return $path;
        }

        throw new \Exception("Unable to create temp dir in ".Util::q($baseFolder).", there's always something in the way.");
    }

    /**
     * @param string $baseFolder
     *
     * @return string
     */
    private static function createExternalCaFile($baseFolder)
    {
        $path = \tempnam($baseFolder, "dropbox-php-sdk-trusted-certs");
        if ($path === false) {
            throw new \Exception("Couldn't create temp file in folder ".Util::q($baseFolder).".");
        }
        \register_shutdown_function(function() use ($path) {
            \unlink($path);
        });

        // NOTE: Can't use the standard PHP copy().  That would clobber the locked-down
        // permissions set by tempnam().
        self::copyInto(__DIR__.self::$originalPath, $path);

        return $path;
    }

    /**
     * @param string $src
     * @param string $dest
     */
    private static function copyInto($src, $dest)
    {
        $srcFd = \fopen($src, "r");
        if ($srcFd === false) {
            throw new \Exception("Couldn't open " . Util::q($src) . " for reading.");
        }
        $destFd = \fopen($dest, "w");
        if ($destFd === false) {
            \fclose($srcFd);
            throw new \Exception("Couldn't open " . Util::q($dest) . " for writing.");
        }

        \stream_copy_to_stream($srcFd, $destFd);

        fclose($srcFd);
        if (!\fclose($destFd)) {
            throw new \Exception("Error closing file ".Util::q($dest).".");
        }
    }
}
<?php
namespace Dropbox;

/**
 * @internal
 */
final class SdkVersion
{
    // When doing a release:
    // 1. Set VERSION to the version you're about to release, tag the relase, then commit+push.
    // 2. Immediately afterwards, append "-dev", then commit+push.
    const VERSION = "1.1.7";
}
<?php
namespace Dropbox;

/**
 * Helper functions for security-related things.
 */
class Security
{
    /**
     * A string equality function that compares strings in a way that isn't suceptible to timing
     * attacks.  An attacker can figure out the length of the string, but not the string's value.
     *
     * Use this when comparing two strings where:
     * - one string could be influenced by an attacker
     * - the other string contains data an attacker shouldn't know
     *
     * @param string $a
     * @param string $b
     * @return bool
     */
    static function stringEquals($a, $b)
    {
        // Be strict with arguments.  PHP's liberal types could get us pwned.
        if (func_num_args() !== 2) {
            throw new \InvalidArgumentException("Expecting 2 args, got ".func_num_args().".");
        }
        Checker::argString("a", $a);
        Checker::argString("b", $b);

        $len = strlen($a);
        if (strlen($b) !== $len) return false;

        $result = 0;
        for ($i = 0; $i < $len; $i++) {
            $result |= ord($a[$i]) ^ ord($b[$i]);
        }
        return $result === 0;
    }

    /**
     * Returns cryptographically strong secure random bytes (as a PHP string).
     *
     * @param int $numBytes
     *    The number of bytes of random data to return.
     *
     * @return string
     */
    static function getRandomBytes($numBytes)
    {
        Checker::argIntPositive("numBytes", $numBytes);

        // openssl_random_pseudo_bytes had some issues prior to PHP 5.3.4
        if (function_exists('openssl_random_pseudo_bytes')
                && version_compare(PHP_VERSION, '5.3.4') >= 0) {
            $s = openssl_random_pseudo_bytes($numBytes, $isCryptoStrong);
            if ($isCryptoStrong) return $s;
        }

        if (function_exists('mcrypt_create_iv')) {
            return mcrypt_create_iv($numBytes);
        }

        // Hopefully the above two options cover all our users.  But if not, there are
        // other platform-specific options we could add.
        throw new \Exception("no suitable random number source available");
    }
}
<?php

namespace Dropbox;

/**
 * Call the `test()` method.
 */
class SSLTester
{
    /**
     * Peforms a few basic tests of your PHP installation's SSL implementation to see
     * if it insecure in an obvious way.  Results are written with "echo" and the output
     * is HTML-safe.
     *
     * @return bool
     *    Returns `true` if all the tests passed.
     */
    static function test()
    {
        $hostOs = php_uname('s').' '.php_uname('r');
        $phpVersion = phpversion();
        $curlVersionInfo = \curl_version();
        $curlVersion = $curlVersionInfo['version'];
        $curlSslBackend = $curlVersionInfo['ssl_version'];

        echo "-----------------------------------------------------------------------------\n";
        echo "Testing your PHP installation's SSL implementation for a few obvious problems...\n";
        echo "-----------------------------------------------------------------------------\n";
        echo "- Host OS: $hostOs\n";
        echo "- PHP version: $phpVersion\n";
        echo "- cURL version: $curlVersion\n";
        echo "- cURL SSL backend: $curlSslBackend\n";

        echo "Basic SSL tests\n";
        $basicFailures = self::testMulti(array(
            array("www.dropbox.com", 'testAllowed'),
            array("www.digicert.com", 'testAllowed'),
            array("www.v.dropbox.com", 'testHostnameMismatch'),
            array("testssl-expire.disig.sk", 'testUntrustedCert'),
        ));

        echo "Pinned certificate tests\n";
        $pinnedCertFailures = self::testMulti(array(
            array("www.verisign.com", 'testUntrustedCert'),
            array("www.globalsign.fr", 'testUntrustedCert'),
        ));

        if ($basicFailures) {
            echo "-----------------------------------------------------------------------------\n";
            echo "WARNING: Your PHP installation's SSL support is COMPLETELY INSECURE.\n";
            echo "Your app's communication with the Dropbox API servers can be viewed and\n";
            echo "manipulated by others.  Try upgrading your version of PHP.\n";
            echo "-----------------------------------------------------------------------------\n";
            return false;
        }
        else if ($pinnedCertFailures) {
            echo "-----------------------------------------------------------------------------\n";
            echo "WARNING: Your PHP installation's cURL module doesn't support SSL certificate\n";
            echo "pinning, which is an important security feature of the Dropbox SDK.\n";
            echo "\n";
            echo "This SDK uses CURLOPT_CAINFO and CURLOPT_CAPATH to tell PHP cURL to only trust\n";
            echo "our custom certificate list.  But your PHP installation's cURL module seems to\n";
            echo "trust certificates that aren't on that list.\n";
            echo "\n";
            echo "More information on SSL certificate pinning:\n";
            echo "https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#What_Is_Pinning.3F\n";
            echo "-----------------------------------------------------------------------------\n";
            return false;
        }
        else {
            return true;
        }
    }

    private static function testMulti($tests)
    {
        $anyFailed = false;
        foreach ($tests as $test) {
            list($host, $testType) = $test;

            echo " - ".str_pad("$testType ($host) ", 50, ".");
            $url = "https://$host/";
            $passed = self::$testType($url);
            if ($passed) {
                echo " ok\n";
            } else {
                echo " FAILED\n";
                $anyFailed = true;
            }
        }
        return $anyFailed;
    }

    private static function testAllowed($url)
    {
        $curl = RequestUtil::mkCurl("test-ssl", $url);
        $curl->set(CURLOPT_RETURNTRANSFER, true);
        $curl->exec();
        return true;
    }

    private static function testUntrustedCert($url)
    {
        return self::testDisallowed($url, 'Error executing HTTP request: SSL certificate problem, verify that the CA cert is OK');
    }

    private static function testHostnameMismatch($url)
    {
        return self::testDisallowed($url, 'Error executing HTTP request: SSL certificate problem: Invalid certificate chain');
    }

    private static function testDisallowed($url, $expectedExceptionMessage)
    {
        $curl = RequestUtil::mkCurl("test-ssl", $url);
        $curl->set(CURLOPT_RETURNTRANSFER, true);
        try {
            $curl->exec();
        }
        catch (Exception_NetworkIO $ex) {
            if (strpos($ex->getMessage(), $expectedExceptionMessage) == 0) {
                return true;
            } else {
                throw $ex;
            }
        }
        return false;
    }
}
<?php
namespace Dropbox;

/**
 * Thrown when there's an error reading from a stream that was passed in by the caller.
 */
class StreamReadException extends \Exception
{
    /**
     * @internal
     */
    function __construct($message, $cause = null)
    {
        parent::__construct($message, 0, $cause);
    }
}
<?php
// Throw exceptions on all PHP errors/warnings/notices.
// We'd like to do this in all situations (and not just when running tests), but
// this is a global setting and other code might not be ready for it.
/** @internal */
function error_to_exception($errno, $errstr, $errfile, $errline, $context)
{
    // If the error is being suppressed with '@', don't throw an exception.
    if (error_reporting() === 0) return;

    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
set_error_handler('error_to_exception');
<?php
namespace Dropbox;

class Util
{
    const SPECIAL_ESCAPE_IN  = "\r\n\t\\\"";
    const SPECIAL_ESCAPE_OUT = "rnt\\\"";

    /**
     * Return a double-quoted version of the given string, using PHP-escape sequences
     * for all non-printable and non-ASCII characters.
     *
     * @param string $string
     *
     * @return string
     */
    public static function q($string)
    {
        # HACK: "self::SPECIAL_ESCAPE_OUT[...]" is not valid syntax in PHP 5.3, so put
        # it in a local variable first.
        $special_escape_out = self::SPECIAL_ESCAPE_OUT;

        $r = "\"";
        $len = \strlen($string);
        for ($i = 0; $i < $len; $i++) {
            $c = $string[$i];
            $escape_i = \strpos(self::SPECIAL_ESCAPE_IN, $c);
            if ($escape_i !== false) {
                // Characters with a special escape code.
                $r .= "\\";
                $r .= $special_escape_out[$escape_i];
            }
            else if ($c >= "\x20" and $c <= "\x7e") {
                // Printable characters.
                $r .= $c;
            }
            else {
                // Generic hex escape code.
                $r .= "\\x";
                $r .= \bin2hex($c);
            }
        }
        $r .= "\"";
        return $r;
    }

    /**
     * If the given string begins with the UTF-8 BOM (byte order mark), remove it and
     * return whatever is left.  Otherwise, return the original string untouched.
     *
     * Though it's not recommended for UTF-8 to have a BOM, the standard allows it to
     * support software that isn't Unicode-aware.
     *
     * @param string $string
     *    A UTF-8 encoded string.
     *
     * @return string
     */
    public static function stripUtf8Bom($string)
    {
        if (strlen($string) == 0) return $string;

        if (\substr_compare($string, "\xEF\xBB\xBF", 0, 3) === 0) {
            $string = \substr($string, 3);
        }
        return $string;
    }

    /**
     * Return whether `$s` starts with `$prefix`.
     *
     * @param string $s
     * @param string $prefix
     * @param bool $caseInsensitive
     *
     * @return bool
     */
    public static function startsWith($s, $prefix, $caseInsensitive = false)
    {
        // substr_compare errors if $main_str is zero-length, so handle that
        // case specially here.
        if (\strlen($s) == 0) {
            return strlen($prefix) == 0;
        }

        return \substr_compare($s, $prefix, 0, strlen($prefix), $caseInsensitive) == 0;
    }

    /**
     * If `$s` starts with `$prefix`, return `$s` with `$prefix` removed.  Otherwise,
     * return `null`.
     *
     * @param string $s
     * @param string $prefix
     * @param bool $caseInsensitive
     *
     * @return string|null
     */
    public static function stripPrefix($s, $prefix, $caseInsensitive = false)
    {
        // substr_compare errors if $main_str is zero-length, so handle that
        // case specially here.
        if (strlen($s) == 0) {
            if (strlen($prefix) == 0) {
                return $s;
            } else {
                return null;
            }
        }

        $prefix_length = strlen($prefix);
        if (\substr_compare($s, $prefix, 0, strlen($prefix), $caseInsensitive) == 0) {
            return substr($s, $prefix_length);
        }
    }
}
<?php
namespace Dropbox;

/**
 * A contract for a class which provides simple get/set/clear access to a single string
 * value.  {@link ArrayEntryStore} provides an implementation of this for storing a value
 * in a single array element.
 *
 * Example implementation for a Memcache-based backing store:
 *
 * <code>
 * class MemcacheValueStore implements ValueStore
 * {
 *     private $key;
 *     private $memcache;
 *
 *     function __construct($memcache, $key)
 *     {
 *         $this->memcache = $memcache;
 *         $this->key = $key;
 *     }
 *
 *     function get()
 *     {
 *         $value = $this->memcache->get($this->getKey());
 *         return $value === false ? null : base64_decode($value);
 *     }
 *
 *     function set($value)
 *     {
 *         $this->memcache->set($this->key, base64_encode($value));
 *     }
 *
 *     function clear()
 *     {
 *         $this->memcache->delete($this->key);
 *     }
 * }
 * </code>
 */
interface ValueStore
{
    /**
     * Returns the entry's current value or `null` if nothing is set.
     *
     * @return string
     */
    function get();

    /**
     * Set the entry to the given value.
     *
     * @param string $value
     */
    function set($value);

    /**
     * Remove the value.
     */
    function clear();
}
<?php
namespace Dropbox;

/**
 * OAuth 2 "authorization code" flow.  (This SDK does not support the "token" flow.)
 *
 * Use {@link start} and {@link finish} to guide your
 * user through the process of giving your app access to their Dropbox account.
 * At the end, you will have an access token, which you can pass to {@link Client}
 * and start making API calls.
 *
 * Example:
 *
 * <code>
 * use \Dropbox as dbx;
 *
 * function getWebAuth()
 * {
 *    $appInfo = dbx\AppInfo::loadFromJsonFile(...);
 *    $clientIdentifier = "my-app/1.0";
 *    $redirectUri = "https://example.org/dropbox-auth-finish";
 *    $csrfTokenStore = new dbx\ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token');
 *    return new dbx\WebAuth($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, ...);
 * }
 *
 * // ----------------------------------------------------------
 * // In the URL handler for "/dropbox-auth-start"
 *
 * $authorizeUrl = getWebAuth()->start();
 * header("Location: $authorizeUrl");
 *
 * // ----------------------------------------------------------
 * // In the URL handler for "/dropbox-auth-finish"
 *
 * try {
 *    list($accessToken, $userId, $urlState) = getWebAuth()->finish($_GET);
 *    assert($urlState === null);  // Since we didn't pass anything in start()
 * }
 * catch (dbx\WebAuthException_BadRequest $ex) {
 *    error_log("/dropbox-auth-finish: bad request: " . $ex->getMessage());
 *    // Respond with an HTTP 400 and display error page...
 * }
 * catch (dbx\WebAuthException_BadState $ex) {
 *    // Auth session expired.  Restart the auth process.
 *    header('Location: /dropbox-auth-start');
 * }
 * catch (dbx\WebAuthException_Csrf $ex) {
 *    error_log("/dropbox-auth-finish: CSRF mismatch: " . $ex->getMessage());
 *    // Respond with HTTP 403 and display error page...
 * }
 * catch (dbx\WebAuthException_NotApproved $ex) {
 *    error_log("/dropbox-auth-finish: not approved: " . $ex->getMessage());
 * }
 * catch (dbx\WebAuthException_Provider $ex) {
 *    error_log("/dropbox-auth-finish: error redirect from Dropbox: " . $ex->getMessage());
 * }
 * catch (dbx\Exception $ex) {
 *    error_log("/dropbox-auth-finish: error communicating with Dropbox API: " . $ex->getMessage());
 * }
 *
 * // We can now use $accessToken to make API requests.
 * $client = dbx\Client($accessToken, ...);
 * </code>
 *
 */
class WebAuth extends WebAuthBase
{
    /**
     * The URI that the Dropbox server will redirect the user to after the user finishes
     * authorizing your app.  This URI must be HTTPS-based and
     * <a href="https://www.dropbox.com/developers/apps">pre-registered with Dropbox</a>,
     * though "localhost"-based and "127.0.0.1"-based URIs are allowed without pre-registration
     * and can be either HTTP or HTTPS.
     *
     * @return string
     */
    function getRedirectUri() { return $this->redirectUri; }

    /** @var string */
    private $redirectUri;

    /**
     * A object that lets us save CSRF token string to the user's session.  If you're using the
     * standard PHP `$_SESSION`, you can pass in something like
     * `new ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token')`.
     *
     * If you're not using $_SESSION, you might have to create your own class that provides
     * the same `get()`/`set()`/`clear()` methods as
     * {@link ArrayEntryStore}.
     *
     * @return ValueStore
     */
    function getCsrfTokenStore() { return $this->csrfTokenStore; }

    /** @var object */
    private $csrfTokenStore;

    /**
     * Constructor.
     *
     * @param AppInfo $appInfo
     *     See {@link getAppInfo()}
     * @param string $clientIdentifier
     *     See {@link getClientIdentifier()}
     * @param null|string $redirectUri
     *     See {@link getRedirectUri()}
     * @param null|ValueStore $csrfTokenStore
     *     See {@link getCsrfTokenStore()}
     * @param null|string $userLocale
     *     See {@link getUserLocale()}
     */
    function __construct($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, $userLocale = null)
    {
        parent::__construct($appInfo, $clientIdentifier, $userLocale);

        Checker::argStringNonEmpty("redirectUri", $redirectUri);

        $this->csrfTokenStore = $csrfTokenStore;
        $this->redirectUri = $redirectUri;
    }

    /**
     * Starts the OAuth 2 authorization process, which involves redirecting the user to the
     * returned authorization URL (a URL on the Dropbox website).  When the user then
     * either approves or denies your app access, Dropbox will redirect them to the
     * `$redirectUri` given to constructor, at which point you should
     * call {@link finish()} to complete the authorization process.
     *
     * This function will also save a CSRF token using the `$csrfTokenStore` given to
     * the constructor.  This CSRF token will be checked on {@link finish()} to prevent
     * request forgery.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#oa2-authorize">/oauth2/authorize</a>.
     *
     * @param string|null $urlState
     *    Any data you would like to keep in the URL through the authorization process.
     *    This exact state will be returned to you by {@link finish()}.
     *
     * @param boolean|null $forceReapprove
     *    If a user has already approved your app, Dropbox may skip the "approve" step and
     *    redirect immediately to your callback URL.  Setting this to `true` tells
     *    Dropbox to never skip the "approve" step.
     *
     * @return array
     *    The URL to redirect the user to.
     *
     * @throws Exception
     */
    function start($urlState = null, $forceReapprove = false)
    {
        Checker::argStringOrNull("urlState", $urlState);

        $csrfToken = self::encodeCsrfToken(Security::getRandomBytes(16));
        $state = $csrfToken;
        if ($urlState !== null) {
            $state .= "|";
            $state .= $urlState;
        }
        $this->csrfTokenStore->set($csrfToken);

        return $this->_getAuthorizeUrl($this->redirectUri, $state, $forceReapprove);
    }

    private static function encodeCsrfToken($string)
    {
        return strtr(base64_encode($string), '+/', '-_');
    }

    /**
     * Call this after the user has visited the authorize URL ({@link start()}), approved your app,
     * and was redirected to your redirect URI.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#oa2-token">/oauth2/token</a>.
     *
     * @param array $queryParams
     *    The query parameters on the GET request to your redirect URI.
     *
     * @return array
     *    A `list(string $accessToken, string $userId, string $urlState)`, where
     *    `$accessToken` can be used to construct a {@link Client}, `$userId`
     *    is the user ID of the user's Dropbox account, and `$urlState` is the
     *    value you originally passed in to {@link start()}.
     *
     * @throws Exception
     *    Thrown if there's an error getting the access token from Dropbox.
     * @throws WebAuthException_BadRequest
     * @throws WebAuthException_BadState
     * @throws WebAuthException_Csrf
     * @throws WebAuthException_NotApproved
     * @throws WebAuthException_Provider
     */
    function finish($queryParams)
    {
        Checker::argArray("queryParams", $queryParams);

        $csrfTokenFromSession = $this->csrfTokenStore->get();
        Checker::argStringOrNull("this->csrfTokenStore->get()", $csrfTokenFromSession);

        // Check well-formedness of request.

        if (!isset($queryParams['state'])) {
            throw new WebAuthException_BadRequest("Missing query parameter 'state'.");
        }
        $state = $queryParams['state'];
        Checker::argString("queryParams['state']", $state);

        $error = null;
        $errorDescription = null;
        if (isset($queryParams['error'])) {
            $error = $queryParams['error'];
            Checker::argString("queryParams['error']", $error);
            if (isset($queryParams['error_description'])) {
                $errorDescription = $queryParams['error_description'];
                Checker::argString("queryParams['error_description']", $errorDescription);
            }
        }

        $code = null;
        if (isset($queryParams['code'])) {
            $code = $queryParams['code'];
            Checker::argString("queryParams['code']", $code);
        }

        if ($code !== null && $error !== null) {
            throw new WebAuthException_BadRequest("Query parameters 'code' and 'error' are both set;".
                                                 " only one must be set.");
        }
        if ($code === null && $error === null) {
            throw new WebAuthException_BadRequest("Neither query parameter 'code' or 'error' is set.");
        }

        // Check CSRF token

        if ($csrfTokenFromSession === null) {
            throw new WebAuthException_BadState();
        }

        $splitPos = strpos($state, "|");
        if ($splitPos === false) {
            $givenCsrfToken = $state;
            $urlState = null;
        } else {
            $givenCsrfToken = substr($state, 0, $splitPos);
            $urlState = substr($state, $splitPos + 1);
        }
        if (!Security::stringEquals($csrfTokenFromSession, $givenCsrfToken)) {
            throw new WebAuthException_Csrf("Expected ".Util::q($csrfTokenFromSession) .
                                           ", got ".Util::q($givenCsrfToken) .".");
        }
        $this->csrfTokenStore->clear();

        // Check for error identifier

        if ($error !== null) {
            if ($error === 'access_denied') {
                // When the user clicks "Deny".
                if ($errorDescription === null) {
                    throw new WebAuthException_NotApproved("No additional description from Dropbox.");
                } else {
                    throw new WebAuthException_NotApproved("Additional description from Dropbox: $errorDescription");
                }
            } else {
                // All other errors.
                $fullMessage = $error;
                if ($errorDescription !== null) {
                    $fullMessage .= ": ";
                    $fullMessage .= $errorDescription;
                }
                throw new WebAuthException_Provider($fullMessage);
            }
        }

        // If everything went ok, make the network call to get an access token.

        list($accessToken, $userId) = $this->_finish($code, $this->redirectUri);
        return array($accessToken, $userId, $urlState);
    }
}
<?php
namespace Dropbox;

/**
 * The base class for the two auth options.
 */
class WebAuthBase extends AuthBase
{
    protected function _getAuthorizeUrl($redirectUri, $state, $forceReapprove = false)
    {
        if ($forceReapprove === false) {
            $forceReapprove = null;  // Don't include it in the URL if it's the default value.
        }
        return RequestUtil::buildUrlForGetOrPut(
            $this->userLocale,
            $this->appInfo->getHost()->getWeb(),
            "1/oauth2/authorize",
            array(
                "client_id" => $this->appInfo->getKey(),
                "response_type" => "code",
                "redirect_uri" => $redirectUri,
                "state" => $state,
                "force_reapprove" => $forceReapprove,
            ));
    }

    protected function _finish($code, $originalRedirectUri)
    {
        // This endpoint requires "Basic" auth.
        $clientCredentials = $this->appInfo->getKey().":".$this->appInfo->getSecret();
        $authHeaderValue = "Basic ".base64_encode($clientCredentials);

        $response = RequestUtil::doPostWithSpecificAuth(
            $this->clientIdentifier, $authHeaderValue, $this->userLocale,
            $this->appInfo->getHost()->getApi(),
            "1/oauth2/token",
            array(
                "grant_type" => "authorization_code",
                "code" => $code,
                "redirect_uri" => $originalRedirectUri,
            ));

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);

        $parts = RequestUtil::parseResponseJson($response->body);

        if (!array_key_exists('token_type', $parts) || !is_string($parts['token_type'])) {
            throw new Exception_BadResponse("Missing \"token_type\" field.");
        }
        $tokenType = $parts['token_type'];
        if (!array_key_exists('access_token', $parts) || !is_string($parts['access_token'])) {
            throw new Exception_BadResponse("Missing \"access_token\" field.");
        }
        $accessToken = $parts['access_token'];
        if (!array_key_exists('uid', $parts) || !is_string($parts['uid'])) {
            throw new Exception_BadResponse("Missing \"uid\" string field.");
        }
        $userId = $parts['uid'];

        if ($tokenType !== "Bearer" && $tokenType !== "bearer") {
            throw new Exception_BadResponse("Unknown \"token_type\"; expecting \"Bearer\", got  "
                                            .Util::q($tokenType));
        }

        return array($accessToken, $userId);
    }
}
<?php
namespace Dropbox;

/**
 * Thrown if the redirect URL was missing parameters or if the given parameters were not valid.
 *
 * The recommended action is to show an HTTP 400 error page.
 */
class WebAuthException_BadRequest extends \Exception
{
    /**
     * @param string $message
     *
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * Thrown if all the parameters are correct, but there's no CSRF token in the session.  This
 * probably means that the session expired.
 *
 * The recommended action is to redirect the user's browser to try the approval process again.
 */
class WebAuthException_BadState extends \Exception
{
    /**
     * @internal
     */
    function __construct()
    {
        parent::__construct("Missing CSRF token in session.");
    }
}
<?php
namespace Dropbox;

/**
 * Thrown if the given 'state' parameter doesn't contain the CSRF token from the user's session.
 * This is blocked to prevent CSRF attacks.
 *
 * The recommended action is to respond with an HTTP 403 error page.
 */
class WebAuthException_Csrf extends \Exception
{
    /**
     * @param string $message
     *
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * Thrown if the user chose not to grant your app access to their Dropbox account.
 */
class WebAuthException_NotApproved extends \Exception
{
    /**
     * @param string $message
     *
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * Thrown if Dropbox returns some other error about the authorization request.
 */
class WebAuthException_Provider extends \Exception
{
    /**
     * @param string $message
     *
     * @internal
     */
    function __construct($message)
    {
        parent::__construct($message);
    }
}
<?php
namespace Dropbox;

/**
 * OAuth 2 code-based authorization for apps that can't provide a redirect URI, typically
 * command-line example apps.
 *
 * Use {@link start()} and {@link getToken()} to guide your
 * user through the process of giving your app access to their Dropbox account.  At the end, you
 * will have an {@link AccessToken}, which you can pass to {@link Client} and start making
 * API calls.
 *
 * Example:
 *
 * <code>
 * use \Dropbox as dbx;
 * $appInfo = dbx\AppInfo::loadFromJsonFile(...);
 * $clientIdentifier = "my-app/1.0";
 * $webAuth = new dbx\WebAuthNoRedirect($appInfo, $clientIdentifier, ...);
 *
 * $authorizeUrl = $webAuth->start();
 *
 * print("1. Go to: $authorizeUrl\n");
 * print("2. Click "Allow" (you might have to log in first).\n");
 * print("3. Copy the authorization code.\n");
 * print("Enter the authorization code here: ");
 * $code = \trim(\fgets(STDIN));
 *
 * try {
 *    list($accessToken, $userId) = $webAuth->finish($code);
 * }
 * catch (dbx\Exception $ex) {
 *    print("Error communicating with Dropbox API: " . $ex->getMessage() . "\n");
 * }
 *
 * $client = dbx\Client($accessToken, $clientIdentifier, ...);
 * </code>
 */
class WebAuthNoRedirect extends WebAuthBase
{
    /**
     * Returns the URL of the authorization page the user must visit.  If the user approves
     * your app, they will be shown the authorization code on the web page.  They will need to
     * copy/paste that code into your application so your app can pass it to
     * {@link finish}.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#oa2-authorize">/oauth2/authorize</a>.
     *
     * @return string
     *    An authorization URL.  Direct the user's browser to this URL.  After the user decides
     *    whether to authorize your app or not, Dropbox will show the user an authorization code,
     *    which the user will need to give to your application (e.g. via copy/paste).
     */
    function start()
    {
        return $this->_getAuthorizeUrl(null, null);
    }

    /**
     * Call this after the user has visited the authorize URL returned by {@link start()},
     * approved your app, was presented with an authorization code by Dropbox, and has copy/paste'd
     * that authorization code into your app.
     *
     * See <a href="https://www.dropbox.com/developers/core/docs#oa2-token">/oauth2/token</a>.
     *
     * @param string $code
     *    The authorization code provided to the user by Dropbox.
     *
     * @return array
     *    A `list(string $accessToken, string $userId)`, where
     *    `$accessToken` can be used to construct a {@link Client} and
     *    `$userId` is the user ID of the user's Dropbox account.
     *
     * @throws Exception
     *    Thrown if there's an error getting the access token from Dropbox.
     */
    function finish($code)
    {
        Checker::argStringNonEmpty("code", $code);
        return $this->_finish($code, null);
    }
}
<?php
namespace Dropbox;

/**
 * Describes how a file should be saved when it is written to Dropbox.
 */
final class WriteMode
{
    /**
     * The URL parameters to pass to the file uploading endpoint to achieve the
     * desired write mode.
     *
     * @var array
     */
    private $extraParams;

    /**
     * @internal
     */
    private function __construct($extraParams)
    {
        $this->extraParams = $extraParams;
    }

    /**
     * @internal
     */
    function getExtraParams()
    {
        return $this->extraParams;
    }

    /**
     * Returns a {@link WriteMode} for adding a new file.  If a file at the specified path already
     * exists, the new file will be renamed automatically.
     *
     * For example, if you're trying to upload a file to "/Notes/Groceries.txt", but there's
     * already a file there, your file will be written to "/Notes/Groceries (1).txt".
     *
     * You can determine whether your file was renamed by checking the "path" field of the
     * metadata object returned by the API call.
     *
     * @return WriteMode
     */
    static function add()
    {
        if (self::$addInstance === null) {
            self::$addInstance = new WriteMode(array("overwrite" => "false"));
        }
        return self::$addInstance;
    }
    private static $addInstance = null;

    /**
     * Returns a {@link WriteMode} for forcing a file to be at a certain path.  If there's already
     * a file at that path, the existing file will be overwritten.  If there's a folder at that
     * path, however, it will not be overwritten and the API call will fail.
     *
     * @return WriteMode
     */
    static function force()
    {
        if (self::$forceInstance === null) {
            self::$forceInstance = new WriteMode(array("overwrite" => "true"));
        }
        return self::$forceInstance;
    }
    private static $forceInstance = null;

    /**
     * Returns a {@link WriteMode} for updating an existing file.  This is useful for when you
     * have downloaded a file, made modifications, and want to save your modifications back to
     * Dropbox.  You need to specify the revision of the copy of the file you downloaded (it's
     * the "rev" parameter of the file's metadata object).
     *
     * If, when you attempt to save, the revision of the file currently on Dropbox matches
     * $revToReplace, the file on Dropbox will be overwritten with the new contents you provide.
     *
     * If the revision of the file currently on Dropbox doesn't match $revToReplace, Dropbox will
     * create a new file and save your contents to that file.  For example, if the original file
     * path is "/Notes/Groceries.txt", the new file's path might be
     * "/Notes/Groceries (conflicted copy).txt".
     *
     * You can determine whether your file was renamed by checking the "path" field of the
     * metadata object returned by the API call.
     *
     * @param string $revToReplace
     * @return WriteMode
     */
    static function update($revToReplace)
    {
        return new WriteMode(array("parent_rev" => $revToReplace));
    }

    /**
     * Check that a function argument is of type `WriteMode`.
     *
     * @internal
     */
    static function checkArg($argName, $argValue)
    {
        if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
    }

    /**
     * Check that a function argument is either `null` or of type
     * `WriteMode`.
     *
     * @internal
     */
    static function checkArgOrNull($argName, $argValue)
    {
        if ($argValue === null) return;
        if (!($argValue instanceof self)) Checker::throwError($argName, $argValue, __CLASS__);
    }
}
Copyright (c) 2013 Dropbox Inc., http://www.dropbox.com/

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 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.
# Dropbox SDK for PHP 5.3+

A PHP library to access [Dropbox's HTTP-based API](http://dropbox.com/developers/core/docs).

License: [MIT](License.txt)

Requirements:
  * PHP 5.3+, [with 64-bit integers](http://stackoverflow.com/questions/864058/how-to-have-64-bit-integer-on-php).
  * PHP [cURL extension](http://php.net/manual/en/curl.installation.php) with SSL enabled (it's usually built-in).
  * Must not be using [`mbstring.func_overload`](http://www.php.net/manual/en/mbstring.overload.php) to overload PHP's standard string functions.

[SDK API docs.](http://dropbox.github.io/dropbox-sdk-php/api-docs/v1.1.x/)

## Setup

If you're using [Composer](http://getcomposer.org/) for your project's dependencies, add the following to your "composer.json":

```
"require": {
  "dropbox/dropbox-sdk": "1.1.*"
}
```

If you're not using Composer, download the code, copy the "lib/" folder into your project somewhere, and include the "lib/Dropbox/autoload.php" in your code.  For example, if you copied the "lib/" and named it "dropbox-sdk/", you would do:

```php
// Do this only if you're not using a global autoloader (such as Composer's).
require_once "dropbox-sdk/Dropbox/autoload.php";
```

IMPORTANT: Many PHP installations have an insecure SSL implementation.  To check if your PHP installation is insecure, run the included "examples/test-ssl.php" script, either on the command line or via your web server.

## Get a Dropbox API key

You need a Dropbox API key to make API requests.
  * Go to: [https://dropbox.com/developers/apps](https://dropbox.com/developers/apps)
  * If you've already registered an app, click on the "Options" link to see the app's API key and secret.
  * Otherwise, click "Create an app" to register an app.  Choose "Dropbox API app", then "Files and datastores", then "Yes" or "No" [depending on your needs](https://www.dropbox.com/developers/reference#permissions).

Save the API key to a JSON file called, say, "test.app":

```
{
  "key": "Your Dropbox API app key",
  "secret": "Your Dropbox API app secret"
}
```

## Using the Dropbox API

Before your app can access a Dropbox user's files, the user must authorize your application using OAuth 2.  Successfully completing this authorization flow gives you an _access token_ for the user's Dropbox account, which grants you the ability to make Dropbox API calls to access their files.

  * Authorization example for a simple web app: [Web File Browser example](examples/web-file-browser.php)
  * Authorization example for a command-line tool: [Command-Line Authorization example](examples/authorize.php)

Once you have an access token, create a [`Client`](http://dropbox.github.io/dropbox-sdk-php/api-docs/v1.1.x/class-Dropbox.Client.html) and start making API calls.

You only need to perform the authorization process once per user.  Once you have an access token for a user, save it somewhere persistent, like in a database.  The next time that user visits your app, you can skip the authorization process and go straight to making API calls.

## Running the Examples and Tests

1. Download this repository.
2. Save your Dropbox API key in a file called "test.app".  See: [Get a Dropbox API key](#get-a-dropbox-api-key), above.

### authorize.php

This example runs through the OAuth 2 authorization flow.

```
./examples/authorize.php test.app test.auth
```

This produces a file named "test.auth" that has the access token.  This file can passed in to the other examples.

### account-info.php

A trivial example that calls the /account/info API endpoint.

```
./examples/account-info.php test.auth
```

(You must first generate "test.auth" using the "authorize" example above.)

### web-file-browser.php

A tiny web app that runs through the OAuth 2 authorization flow and then uses Dropbox API calls to let the user browse their Dropbox files.

**Required**: copy one of the ".app" files you created previously to "examples/web-file-browser.app".

**Using PHP built-in web server (PHP 5.4+).**

1. Go to the Dropbox API [app console](https://www.dropbox.com/developers/apps), go to the API app you configured in "web-file-browser.app", go to the _OAuth redirect URIs_ section, and add "http://localhost:5000/dropbox-auth-finish".
2. Run "`php web-file-browser.php`".
3. Point your browser at "http://localhost:5000/".

**Using an existing web server setup.**

1. Copy the entire SDK folder to your web server's document path.  For example, let's say the script is accessible at "http://localhost/~scooby/dropbox/examples/web-file-browser.php".
2. Go to the Dropbox API [app console](https://www.dropbox.com/developers/apps), go to the API app you configured in "web-file-browser.app", go to the _OAuth redirect URIs_ section, and add "http://localhost/~scooby/dropbox/examples/web-file-browser.php/dropbox-auth-finish".
3. Point your browser at "http://localhost/~scooby/dropbox/examples/web-file-browser.php".

### Running the Tests

1. run: `composer install --dev` to download the dependencies.  (You'll need [Composer](http://getcomposer.org/download/).)
2. Put an "auth info" file in "test/test.auth".  (You can generate "test/test.auth" using the "authorize.php" example script.)

```
./vendor/bin/phpunit test/
```
#!/bin/sh

# Locate the script file.  Cross symlinks if necessary.
loc="$0"
while [ -h "$loc" ]; do
	ls=`ls -ld "$loc"`
	link=`expr "$ls" : '.*-> \(.*\)$'`
	if expr "$link" : '/.*' > /dev/null; then
		loc="$link"  # Absolute link
	else
		loc="`dirname "$loc"`/$link"  # Relative link
	fi
done

script_dir=`dirname "$loc"`
top_dir=`cd "$script_dir/.."; pwd`

"$top_dir/vendor/bin/phpcs" -s --standard="$script_dir/codesniffer.xml" "$top_dir/"{lib,test,examples}
#!/bin/sh

# 1. Install the php-cs-fixer command-line tool: https://github.com/fabpot/PHP-CS-Fixer
# 2. Run this script.

# Locate the script file.  Cross symlinks if necessary.
loc="$0"
while [ -h "$loc" ]; do
	ls=`ls -ld "$loc"`
	link=`expr "$ls" : '.*-> \(.*\)$'`
	if expr "$link" : '/.*' > /dev/null; then
		loc="$link"  # Absolute link
	else
		loc="`dirname "$loc"`/$link"  # Relative link
	fi
done

script_dir=`dirname "$loc"`
top_dir=`cd "$script_dir/.."; pwd`

php-cs-fixer fix "$top_dir/"{lib,test,examples} --dry-run --verbose --diff \
    --fixers=-phpdoc_params,-visibility,-return,-braces,-psr0,-elseif,-function_declaration

# NOTE(cakoose): Reasons for excluding these fixers:
# - phpdoc_params: This puts two spaces after the '@param', which I didn't like.
# - visibility: In PHP, if you don't specify a visibility modifier, it's 'public'.  This seems a
#   reasonable default and I don't see any advantages to being verbose and saying 'public' each
#   time.
# - return: This is annoying for two-line functions where you check something and then return.
# - braces: Different brace styles don't really bother me, but I'm not strongly opposed to forcing
#   one style, though.
# - psr0: I don't object to what this is trying to enforce, but it makes breaking changes to your
#   code.
# - elseif: I'm not strongly opposed, but I don't see the advantage.
# - function_declaration: This probably enforces some good rules, but it also requires a space
#   between "function" and "(...)" in an anonymous function expression.
<?xml version="1.0"?>
<!-- This file just disables errors that we don't agree with.  However, it would be nice if it
     enforced things that we wanted. -->
<ruleset name="Dropbox PHP SDK Standard">
    <rule ref="PSR2">

        <!-- Definitely exclude these rules -->

        <exclude name="Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore"/>
        <exclude name="Generic.ControlStructures.InlineControlStructure.NotAllowed"/>
        <exclude name="PSR2.Methods.FunctionCallSignature.CloseBracketLine"/>
        <exclude name="PSR2.Methods.FunctionCallSignature.MultipleArguments"/>
        <exclude name="PSR2.Methods.FunctionCallSignature.ContentAfterOpenBracket"/>
        <exclude name="PSR2.Methods.FunctionCallSignature.Indent"/>
        <exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
        <exclude name="PSR2.ControlStructures.ElseIfDeclaration.NotAllowed"/>

        <exclude name="Squiz.Scope.MethodScope.Missing"/>
        <exclude name="PSR2.Classes.PropertyDeclaration.ScopeMissing"/>
        <exclude name="PSR2.Classes.PropertyDeclaration.VarUsed"/>
            <!-- The convention we use: Use "private" for private members.  Use no modifier
                 for public members.  TODO: Write a checker to enforce this. -->

        <!-- Rules that are good, but too broad -->

        <exclude name="Squiz.Functions.MultiLineFunctionDeclaration.BraceOnSameLine"/>
            <!-- This rule triggers on single-line functions. -->
        <exclude name="Squiz.ControlStructures.ControlSignature.SpaceAfterCloseBrace"/>
            <!-- This rule prevents putting "else" on the next line. -->
        <exclude name="PSR2.Methods.FunctionCallSignature.ContentAfterOpenBracket"/>
        <exclude name="Squiz.Functions.MultiLineFunctionDeclaration.BraceSpacing"/>
            <!-- These two rules prevents single-line comments before open brace line. -->
        <exclude name="Squiz.ControlStructures.ControlSignature.NewlineAfterOpenBrace"/>
            <!-- This rule prevents putting single-line comments after open braces. -->
        <exclude name="PSR1.Files.SideEffects.FoundWithSymbols"/>
            <!-- This rule creates problems for two files:
                 autoload.php - Though global side-effects on includes are generally bad, I think
                     in this case it's better than forcing the client code to explicitly call a
                     function to register the autoloader.
                 Curl.php - We configure the ciphersuite depending on the Curl configuration.  All
                     the variables are namespaced, so I don't think pollution is a big concern.
            -->

        <!-- Rules that I'm unsure about. -->

        <exclude name="PSR2.Classes.ClassDeclaration.CloseBraceAfterBody"/>
        <exclude name="Generic.Files.LineLength.TooLong"/>
    </rule>

    <!-- Special cases -->

    <rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
        <exclude-pattern>lib/Dropbox/WebAuthException/*</exclude-pattern>
        <exclude-pattern>lib/Dropbox/Exception/*</exclude-pattern>
            <!-- The naming of Exception casses was a historical hack to satisfy some PSR, I
                 think.  We can change "Exception_BadRequest" to "Exception\BadRequest" if we
                 break backwards compatibility. -->
    </rule>
    <rule ref="PSR1.Classes.ClassDeclaration.MissingNamespace">
        <exclude-pattern>test/*</exclude-pattern>
    </rule>
    <rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
        <exclude-pattern>test/*</exclude-pattern>
    </rule>
</ruleset>
#! /bin/sh
set -e

title="Dropbox SDK for PHP"
out_path="build/api-docs"

# Locate the script file.  Cross symlinks if necessary.
loc="$0"
while [ -h "$loc" ]; do
	ls=`ls -ld "$loc"`
	link=`expr "$ls" : '.*-> \(.*\)$'`
	if expr "$link" : '/.*' > /dev/null; then
		loc="$link"  # Absolute link
	else
		loc="`dirname "$loc"`/$link"  # Relative link
	fi
done

script_dir=`dirname "$loc"`
top_dir=`dirname "$dir"`

out_path="$top_dir/$out_path"
[ ! -e "$out_path" ] || rm -r "$out_path"

echo "Docs: $out_path"

api_gen="$top_dir/vendor/bin/apigen"

exec "$api_gen" generate \
    --title="$title" \
    --source='lib' \
    --destination="$out_path" \
    --charset='utf8' \
    --groups=none \
    --access-levels=public --debug
<?php

require_once __DIR__.'/../lib/Dropbox/strict.php';

use \Dropbox as dbx;

PHPUnit_Framework_Error_Notice::$enabled = true;
PHPUnit_Framework_Error_Warning::$enabled = true;

class ClientTest extends PHPUnit_Framework_TestCase
{
    private $client;

    const E_ACCENT = "\xc3\xa9";  # UTF-8 sequence for "e with accute accent"

    function __construct()
    {
        $authFile = __DIR__."/test.auth";

        try {
            list($accessToken, $host) = dbx\AuthInfo::loadFromJsonFile($authFile);
        } catch (dbx\AuthInfoLoadException $ex) {
            echo "Error loading auth-info: ".$ex->getMessage()."\n";
            die;
        }

        $userLocale = "en";
        $this->client = new dbx\Client($accessToken, "sdk-tests", $userLocale, $host);
    }

    private $testFolder;

    private function p($path = null)
    {
        if ($path === null) return $this->testFolder;
        return "{$this->testFolder}/$path";
    }

    protected function setUp()
    {
        // Create a new folder for the tests to work with.
        $timestamp = \date('Y-M-d H.i.s', \time());
        $basePath = "/PHP SDK Tests/$timestamp";

        $tryPath = $basePath;
        $result = $this->client->createFolder($basePath);
        $i = 2;
        while ($result == null) {
            $tryPath = "$basePath ($i)";
            $i++;
            if ($i >= 100) throw new Exception("Unable to create folder \"$basePath\"");
            $result = $this->client->createFolder($basePath);
        }

        $this->testFolder = $tryPath;
    }

    function tearDown()
    {
        @unlink("test-dest.txt");
        @unlink("test-source.txt");

        $this->client->delete($this->testFolder);
    }

    function writeTempFile($size)
    {
        $fd = tmpfile();

        $chars = "\nabcdefghijklmnopqrstuvwxyz0123456789";
        for ($i = 0; $i < $size; $i++) {
            fwrite($fd, $chars[rand() % strlen($chars)]);
        }

        fseek($fd, 0);

        return $fd;
    }

    private function addFile($path, $size, $writeMode = null)
    {
        if ($writeMode === null) $writeMode = dbx\WriteMode::add();

        $fd = $this->writeTempFile($size);
        $result = $this->client->uploadFile($path, $writeMode, $fd, $size);
        fclose($fd);
        $this->assertEquals($size, $result['bytes']);

        return $result;
    }

    private function fetchUrl($url)
    {
        //sadly, https doesn't work out of the box on windows for functions
        //like file_get_contents, so let's make this easy for devs

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        $ret = curl_exec($ch);

        curl_close($ch);

        return $ret;
    }

    function testUploadAndDownload()
    {
        $localPathSource = "test-source.txt";
        $localPathDest = "test-dest.txt";
        $contents = "A simple test file";
        file_put_contents($localPathSource, $contents);

        $remotePath = $this->p("test-fil".self::E_ACCENT.".txt");

        $fp = fopen($localPathSource, "rb");
        $up = $this->client->uploadFile($remotePath, dbx\WriteMode::add(), $fp);
        fclose($fp);
        $this->assertEquals($up["path"], $remotePath);

        $fd = fopen($localPathDest, "wb");
        $down = $this->client->getFile($remotePath, $fd);
        fclose($fd);

        $this->assertEquals($up['bytes'], $down['bytes']);
        $this->assertEquals($up['bytes'], filesize($localPathSource));
        $this->assertEquals(filesize($localPathDest), filesize($localPathSource));
    }

    function testMetadata()
    {
        $this->addFile($this->p("a.txt"), 100);

        $md = $this->client->getMetadataWithChildren($this->p());
        $this->assertEquals(1, count($md['contents']));

        // folder metadata should be the same
        $hash = $md['hash'];
        list($changed, $new_md) = $this->client->getMetadataWithChildrenIfChanged($this->p(), $hash);
        $this->assertFalse($changed);
        $this->assertEquals($new_md, null);

        $this->addFile($this->p("b.txt"), 100);

        // folder metadata should be different (since we added another file)
        $hash = $md['hash'];
        list($changed, $new_md) = $this->client->getMetadataWithChildrenIfChanged($this->p(), $hash);
        $this->assertTrue($changed);
        $this->assertEquals(2, count($new_md['contents']));
    }

    function testDelta()
    {
        // eat up all the deltas to the point where we should expect exactly
        // one after we add a new file
        $result = $this->client->getDelta();
        $this->assertTrue($result['reset']);
        $start = $result['cursor'];

        do {
            $result = $this->client->getDelta($start);
            $start = $result['cursor'];
        } while ($result['has_more']);

        $path = $this->p("make a delta.txt");

        $this->addFile($path, 100);
        $result = $this->client->getDelta($start);
        $this->assertEquals(1, count($result['entries']));
        $this->assertEquals($path, $result['entries'][0][1]["path"]);
    }

    function testDeltaPathPrefix()
    {
        // eat up all the deltas to the point where we should expect exactly
        // one after we add a new file
        $pathPrefix = $this->p("folder");
        $result = $this->client->getDelta(null, $pathPrefix);
        $this->assertTrue($result['reset']);
        $this->assertEquals($result['entries'], array());
        $cursor = $result['cursor'];

        $this->addFile($this->p("stuff.txt"), 1);
        $this->client->createFolder($this->p("folder"));
        $this->client->createFolder($this->p("folder2"));
        $this->addFile($this->p("folder/a.txt"), 2);
        $this->addFile($this->p("folder/b.txt"), 3);
        $this->addFile($this->p("folder2/a.txt"), 4);
        $this->addFile($this->p("folder.txt"), 5);

        $allEntries = array();

        do {
            $result = $this->client->getDelta($cursor, $pathPrefix);
            $cursor = $result['cursor'];
            foreach ($result['entries'] as $entry) {
                $allEntries[$entry[1]['path']] = $entry[1]['bytes'];
            }
        } while ($result['has_more']);

        $this->assertEquals($allEntries, array(
            $this->p("folder") => 0,
            $this->p("folder/a.txt") => 2,
            $this->p("folder/b.txt") => 3));
    }

    function testRevisions()
    {
        $path = $this->p("revisions.txt");
        $this->addFile($path, 100, dbx\WriteMode::force());
        $this->addFile($path, 200, dbx\WriteMode::force());
        $this->addFile($path, 300, dbx\WriteMode::force());

        $revs = $this->client->getRevisions($path);
        $this->assertEquals(count($revs), 3);

        $revs = $this->client->getRevisions($path, 2);
        $this->assertEquals(count($revs), 2);
        $this->assertEquals(300, $revs[0]['bytes']);
        $this->assertEquals(200, $revs[1]['bytes']);
    }

    function testRestore()
    {
        $path = $this->p("revisions.txt");
        $resultA = $this->addFile($path, 100);
        $this->addFile($path, 200);

        $result = $this->client->restoreFile($path, $resultA['rev']);
        $this->assertEquals(100, $result['bytes']);

        $final = $this->client->getMetadata($path);
        $this->assertEquals(100, $final['bytes']);
    }

    function testSearch()
    {
        $this->addFile($this->p("search - a.txt"), 100);
        $this->client->createFolder($this->p("sub"));
        $this->addFile($this->p("sub/search - b.txt"), 200);
        $this->addFile($this->p("search - c.txt"), 200);
        $this->client->delete($this->p("search - c.txt"));

        $result = $this->client->searchFileNames($this->p(), "search");
        $this->assertEquals(2, count($result));

        $result = $this->client->searchFileNames($this->p(), "search", 1);
        $this->assertEquals(1, count($result));

        $result = $this->client->searchFileNames($this->p("sub"), "search");
        $this->assertEquals(1, count($result));

        $result = $this->client->searchFileNames($this->p(), "search", null, true);
        $this->assertEquals(3, count($result));
    }

    function testShares()
    {
        $contents = "A shared text file";
        $remotePath = $this->p("share-me.txt");
        $this->client->uploadFileFromString($remotePath, dbx\WriteMode::add(), $contents);

        $url = $this->client->createShareableLink($remotePath);
        $fetchedStr = $this->fetchUrl($url);
        assert(strlen($fetchedStr) > 5 * strlen($contents)); //should get a big page back
    }

    function testMedia()
    {
        $contents = "A media text file";

        $remotePath = $this->p("media-me.txt");
        $this->client->uploadFileFromString($remotePath, dbx\WriteMode::add(), $contents);

        list($url, $expires) = $this->client->createTemporaryDirectLink($remotePath);
        $fetchedStr = $this->fetchUrl($url);

        $this->assertEquals($contents, $fetchedStr);
    }

    function testCopyRef()
    {
        $source = $this->p("copy-ref me.txt");
        $dest = $this->p("ok - copied ref.txt");
        $size = 1024;

        $this->addFile($source, $size);
        $ref = $this->client->createCopyRef($source);

        $result = $this->client->copyFromCopyRef($ref, $dest);
        $this->assertEquals($size, $result['bytes']);

        $result = $this->client->getMetadataWithChildren($this->p());
        $this->assertEquals(2, count($result['contents']));
    }

    function testThumbnail()
    {
        $remotePath = $this->p("image.jpg");
        $localPath = __DIR__."/upload.jpg";
        $fp = fopen($localPath, "rb");
        $this->client->uploadFile($remotePath, dbx\WriteMode::add(), $fp);
        fclose($fp);

        list($md1, $data1) = $this->client->getThumbnail($remotePath, "jpeg", "xs");
        $this->assertTrue(self::isJpeg($data1));

        list($md2, $data2) = $this->client->getThumbnail($remotePath, "jpeg", "s");
        $this->assertTrue(self::isJpeg($data1));
        $this->assertGreaterThan(strlen($data1), strlen($data2));

        list($md3, $data3) = $this->client->getThumbnail($remotePath, "png", "s");
        $this->assertTrue(self::isPng($data3));
    }

    static function isJpeg($data)
    {
        $first_two = substr($data, 0, 2);
        $last_two = substr($data, -2);
        return ($first_two === "\xFF\xD8") && ($last_two === "\xFF\xD9");
    }

    static function isPng($data)
    {
        return substr($data, 0, 8) === "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
    }

    function testChunkedUpload()
    {
        $fd = $this->writeTempFile(1200);
        $contents = stream_get_contents($fd);
        fseek($fd, 0);

        $remotePath = $this->p("chunked-upload.txt");
        $this->client->uploadFileChunked($remotePath, dbx\WriteMode::add(), $fd, null, 512);

        $fd = tmpfile();
        $this->client->getFile($remotePath, $fd);
        fseek($fd, 0);
        $fetched = stream_get_contents($fd);
        fclose($fd);

        $this->assertEquals($contents, $fetched);
    }

    function testChunkedUploadWithSize()
    {
        $fd = $this->writeTempFile(1200);
        $contents = stream_get_contents($fd);
        fseek($fd, 0);

        $remotePath = $this->p("chunked-upload.txt");
        $this->client->uploadFileChunked($remotePath, dbx\WriteMode::add(), $fd, 1200, 512);

        $fd = tmpfile();
        $this->client->getFile($remotePath, $fd);
        fseek($fd, 0);
        $fetched = stream_get_contents($fd);
        fclose($fd);

        $this->assertEquals($contents, $fetched);
    }

    function testChunkedUploadWithFailures()
    {
        $client = new ClientForChunkedUploadWithFailures(
            $this->client->getAccessToken(),
            $this->client->getClientIdentifier(),
            $this->client->getUserLocale(),
            $this->client->getHost());

        $fd = $this->writeTempFile(10);
        $contents = stream_get_contents($fd);

        $remotePath = $this->p("chunked-upload.txt");

        // Simulate losing a request or response.
        foreach (array('lose-request', 'lose-response') as $secondInstruction) {
            // Upload.
            $client->callCounter = 0;
            $client->instructions = array('ok', $secondInstruction);
            fseek($fd, 0);
            $md = $client->uploadFileChunked($remotePath, dbx\WriteMode::add(), $fd, 10, 4);
            $this->assertEquals($client->callCounter, 4);

            // Download and verify.
            $outFd = tmpfile();
            $this->client->getFile($md['path'], $outFd);
            fseek($outFd, 0);
            $fetched = stream_get_contents($outFd);
            $this->assertEquals($fetched, $contents);
        }

        // Failing four times in a row should cause us to give up.
        $client->callCounter = 0;
        $client->instructions =
            array('ok', 'lose-request', 'lose-request', 'lose-request', 'lose-response');
        fseek($fd, 0);
        try {
            $client->uploadFileChunked($remotePath, dbx\WriteMode::add(), $fd, 10, 4);
            assert(false);  // Should never get here.
        }
        catch (dbx\Exception_NetworkIO $ex) {
            // We want thie exception to happen.
            $this->assertEquals($ex->getMessage(), "simulate lose-response");
            $this->assertEquals($client->callCounter, 5);
        }
    }

    // --------------- Test File Operations -------------------
    function testCopy()
    {
        $source = $this->p("copy m".self::E_ACCENT.".txt");
        $dest = $this->p("ok - copi".self::E_ACCENT."d.txt");
        $size = 1024;

        $this->addFile($source, $size);
        $result = $this->client->copy($source, $dest);
        $this->assertEquals($size, $result['bytes']);
        $this->assertEquals($dest, $result['path']);

        $result = $this->client->getMetadataWithChildren($this->p());
        $this->assertEquals(2, count($result['contents']));
    }

    function testCreateFolder()
    {
        $result = $this->client->getMetadataWithChildren($this->p());
        $this->assertEquals(0, count($result['contents']));

        $this->client->createFolder($this->p("a"));

        $result = $this->client->getMetadataWithChildren($this->p());
        $this->assertEquals(1, count($result['contents']));

        $result = $this->client->getMetadata($this->p("a"));
        $this->assertTrue($result['is_dir']);
    }

    function testDelete()
    {
        $path = $this->p("delete me.txt");
        $size = 1024;

        $this->addFile($path, $size);
        $this->client->delete($path);

        $result = $this->client->getMetadataWithChildren($this->p());
        $this->assertEquals(0, count($result['contents']));
    }

    function testMove()
    {
        $source = $this->p("move me.txt");
        $dest = $this->p("ok - moved.txt");
        $size = 1024;

        $this->addFile($source, $size);
        $result = $this->client->getMetadataWithChildren($this->p());
        $this->assertEquals(1, count($result['contents']));

        $result = $this->client->move($source, $dest);
        $this->assertEquals($size, $result['bytes']);

        $result = $this->client->getMetadataWithChildren($this->p());
        $this->assertEquals(1, count($result['contents']));
    }
}

class ClientForChunkedUploadWithFailures extends dbx\Client
{
    public $callCounter = 0;
    public $instructions = array();
    public $callsToAllow = 0;
    public $callsToFail = 0;

    protected function _chunkedUpload($params, $data)
    {
        $this->callCounter += 1;

        if (count($this->instructions) > 0) {
            $instruction = array_shift($this->instructions);
        } else {
            $instruction = 'ok';
        }

        if ($instruction === 'lose-request') {
            throw new dbx\Exception_NetworkIO("simulate lose-request");
        }
        else if ($instruction === 'lose-response') {
            parent::_chunkedUpload($params, $data);
            throw new dbx\Exception_NetworkIO("simulate lose-response");
        }
        else if ($instruction === 'ok') {
            return parent::_chunkedUpload($params, $data);
        }
        else {
            throw new \InvalidArgumentException("invalid instruction: \"$instruction\"");
        }
    }
}
<?php

require_once __DIR__.'/../lib/Dropbox/strict.php';

use \Dropbox as dbx;

class ConfigLoadTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
    }

    protected function tearDown()
    {
        @unlink("test.json");
    }

    function testMissingAppJson()
    {
        $this->setExpectedException('\Dropbox\AppInfoLoadException');
        dbx\AppInfo::loadFromJsonFile("missing.json");
    }

    function testBadAppJson()
    {
        $this->setExpectedException('\Dropbox\AppInfoLoadException');
        file_put_contents("test.json", "Not JSON.  At all.");
        dbx\AppInfo::loadFromJsonFile("test.json");
    }

    function testNonHashAppJson()
    {
        $this->setExpectedException('\Dropbox\AppInfoLoadException');
        file_put_contents("test.json", json_encode(123, true));
        dbx\AppInfo::loadFromJsonFile("test.json");
    }

    function testBadAppJsonFields()
    {
        $correct = array(
            "key" => "an_app_key",
            "secret" => "an_app_secret",
        );

        // check that we detect every missing field
        foreach ($correct as $key => $value) {
            $tmp = $correct;
            unset($tmp[$key]);

            file_put_contents("test.json", json_encode($tmp, true));

            try {
                dbx\AppInfo::loadFromJsonFile("test.json");
                $this->fail("Expected exception");
            }
            catch (dbx\AppInfoLoadException $e) {
                // Expecting this exception.
            }
        }

        // check that we detect non-string fields
        foreach ($correct as $key => $value) {
            $tmp = $correct;
            $tmp[$key] = 123;

            file_put_contents("test.json", json_encode($tmp, true));

            try {
                dbx\AppInfo::loadFromJsonFile("test.json");
                $this->fail("Expected exception");
            }
            catch (dbx\AppInfoLoadException $e) {
                // Expecting this exception.
            }
        }
    }

    function testAppJsonServer()
    {
        $correct = array(
            "key" => "an_app_key",
            "secret" => "an_app_secret",
            "access_type" => "AppFolder",
            "auth_host" => "www.dropbox-auth.com",
            "host_suffix" => ".droppishbox.com",
        );

        $str = json_encode($correct, true);
        self::tryAppJsonServer($str);
        self::tryAppJsonServer("\xEF\xBB\xBF".$str);  // UTF-8 byte order mark
    }

    function tryAppJsonServer($str)
    {
        file_put_contents("test.json", $str);
        $appInfo = dbx\AppInfo::loadFromJsonFile("test.json");
        $this->assertEquals($appInfo->getHost()->getContent(), "content.droppishbox.com");
        $this->assertEquals($appInfo->getHost()->getApi(), "api.droppishbox.com");
        $this->assertEquals($appInfo->getHost()->getWeb(), "www.dropbox-auth.com");
    }

    function testMissingAuthJson()
    {
        $this->setExpectedException('\Dropbox\AuthInfoLoadException');
        dbx\AuthInfo::loadFromJsonFile("missing.json");
    }

    function testBadAuthJson()
    {
        $this->setExpectedException('\Dropbox\AuthInfoLoadException');
        file_put_contents("test.json", "Not JSON.  At all.");
        dbx\AuthInfo::loadFromJsonFile("test.json");
    }

    function testNonHashAuthJson()
    {
        $this->setExpectedException('\Dropbox\AuthInfoLoadException');
        file_put_contents("test.json", json_encode(123, true));
        dbx\AuthInfo::loadFromJsonFile("test.json");
    }

    function testBadAuthJsonFields()
    {
        $minimal = array(
            "access_token" => "an_access_token",
        );

        // check that we detect every missing field
        foreach ($minimal as $key => $value) {
            $tmp = $minimal;
            unset($tmp[$key]);

            file_put_contents("test.json", json_encode($tmp, true));

            try {
                dbx\AuthInfo::loadFromJsonFile("test.json");
                $this->fail("Expected exception");
            }
            catch (dbx\AuthInfoLoadException $e) {
                // Expecting this exception.
            }
        }

        $correct = array(
            "access_token" => "an_access_token",
            "auth_host" => "auth.test-server.com",
            "host_suffix" => ".test-server.com",
        );

        // check that we detect non-string fields
        foreach ($correct as $key => $value) {
            $tmp = $correct;
            $tmp[$key] = 123;

            file_put_contents("test.json", json_encode($tmp, true));

            try {
                dbx\AuthInfo::loadFromJsonFile("test.json");
                $this->fail("Expected exception");
            }
            catch (dbx\AuthInfoLoadException $e) {
                // Expecting this exception.
            }
        }
    }

}
 JFIF  ` `   xPhotoshop 3.0 8BIM     ?Z %G   t  © Corbis.  All Rights Reserved.P Corbis 8BIM%     7/cƀ5~ICC_PROFILE   appl   mntrRGB XYZ      acspAPPL    appl                       -appl                                               desc     odscm  x  lcprt     8wtpt     rXYZ  0   gXYZ  D   bXYZ  X   rTRC  l   chad  |   ,bTRC  l   gTRC  l   desc       Generic RGB Profile           Generic RGB Profile                                                  mluc          skSK   (  xhrHR   (  caES   $  ptBR   &  ukUA   *  frFU   (  <zhTW     ditIT   (  znbNO   &  koKR     csCZ   "  heIL      deDE   ,  huHU   (  JsvSE   &  zhCN     rjaJP     roRO   $  elGR   "  ptPO   &  nlNL   (  esES   &  thTH   $  6trTR   "  ZfiFI   (  |plPL   ,  ruRU   "  arEG   &  enUS   &  daDK   .  > Va e o b e c n    R G B   p r o f i l G e n e r i
 k i   R G B   p r o f i l P e r f i l   R G B   g e n  r i c P e r f i l   R G B   G e n  r i c o030;L=89  ?@>D09;   R G B P r o f i l   g  n  r i q u e   R V Bu(   R G B  r_icϏ P r o f i l o   R G B   g e n e r i c o G e n e r i s k   R G B - p r o f i l|   R G B  \| O b e c n    R G B   p r o f i l   R G B   A l l g e m e i n e s   R G B - P r o f i l  l t a l  n o s   R G B   p r o f i lfn   R G B  cϏeNN ,   R G B  000000 P r o f i l   R G B   g e n e r i c     R G B P e r f i l   R G B   g e n  r i c o A l g e m e e n   R G B - p r o f i e lB#D%L   R G B  1H'D G e n e l   R G B   P r o f i l i Y l e i n e n   R G B - p r o f i i l i U n i w e r s a l n y   p r o f i l   R G B1I89  ?@>D8;L   R G BEDA  *91JA   R G B  'D9'E G e n e r i c   R G B   P r o f i l e G e n e r e l   R G B - b e s k r i v e l s etext    Copyright 2007 Apple Inc., all rights reserved. XYZ       R    XYZ       tM  =  XYZ       Zu  s  4XYZ       (    6curv         sf32     B  &        l:Exif  MM *                  n       v(       2       ~;           !   i              `      `   2009:03:12 13:48:35 Corbis  © Corbis.  All Rights Reserved.         
          82      82                   2008:02:18 05:07:31 2008:02:18 05:07:31 @http://ns.adobe.com/xap/1.0/ <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.1.2">
   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
      <rdf:Description rdf:about=""
            xmlns:xmp="http://ns.adobe.com/xap/1.0/">
         <xmp:ModifyDate>2009-03-12T13:48:35</xmp:ModifyDate>
         <xmp:CreateDate>2008-02-18T05:07:31</xmp:CreateDate>
         <xmp:Rating>4</xmp:Rating>
      </rdf:Description>
      <rdf:Description rdf:about=""
            xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
         <photoshop:DateCreated>2008-02-18T05:07:31</photoshop:DateCreated>
      </rdf:Description>
      <rdf:Description rdf:about=""
            xmlns:dc="http://purl.org/dc/elements/1.1/">
         <dc:creator>
            <rdf:Seq>
               <rdf:li>Corbis</rdf:li>
            </rdf:Seq>
         </dc:creator>
         <dc:rights>
            <rdf:Alt>
               <rdf:li xml:lang="x-default">© Corbis.  All Rights Reserved.</rdf:li>
            </rdf:Alt>
         </dc:rights>
      </rdf:Description>
   </rdf:RDF>
</x:xmpmeta>
 C 	

		

	
 C		  "            	
    } !1AQa"q2#BR$3br	
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz        	
   w !1AQaq"2B	#3Rbr
$4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz   ? =W!{d =J__ZΒz)ljt
ZT;Aņ`0};y"GJjm'Uw3ۭB=zձdMqW'x>h >sx`E9 9&piԜ1Te dqN[Ҟ\PU$E9$
stОm	ݍzg)  svZ[ڼqӃ 232
;xQ;dާفޝIzs2Tt"8=	qqxz` g86& g$.:sҀW*IsUٲz~7 $sԬl U{U7pFjKӥ>Ry,XRcN=qSӽN郓(ض6 ښYӭ.A*<g'>֜I2B Jr@wW%}1֧RI$.Wދ#HET%0ێ{U8䚯 ץ	&]JI+\U9YvqR=jӃӡR|g֯U8ےy'yjb<xy_j]"BAϡԪ"$EX73p]4݊>Axq㸫@8 g8R E(]R"@h\yqN8n(幤i3ߏCQ23>0 <q_$<p^91v9hF1G>=
cBqSkwdFqqZrSU~?Rv	{wr 6v?rx#FG<D J[As烊@zu?Ξ[P7G< :BmHjQ':SsP%Y"_z$x8  b#CfI3U1+kpI8zx5Fr8[>tmejۂc<}hLy?ss]3[*wPZ?V7p秵XSN>3 8vc>¯s#۩dlV$q$##_=>(=+HUӯd6\1 '94p@zmYh)OU' y p 8:

wRwؑB'+? i[3Npr8Pl{S9c!G_jBi	6A#QNd#<MMqK>r鵾b?	ǔU^N2Hi-;2yIy 1OZ,99 )npOJZjf[yPiSquzgIypy?5ǩR@OT|6¥	1ӌ:+L8cUzqH'zd_zT0w~`#m8xV<`mǠ*mpi.sq@qґr3EFGQTDj	#R=jcNqSCg 
35&£s==*=SQn6))I!'-8qڤpz4qZb?'>ƟgbEqab`rj7Ԩ"$v(
z"${SόQΖH'RFx0G8szPoKc)DK~qVOHFVeq Gn(	\c<zR:U՞gBGQ8늁q'<TZVR@YpyϸZ~K$3ߏMKSWrb7rfTO>A9sRaVȞW3]WjN٭7`=EFr@RhbfC׮;Ӗ0;y'k 6@ 2! |uХN!zSJoAqP'SW)}*֡ > j5Ӱ;H\_j0v/k`cGfcvl cUZ!ӭW1e Ar=hCʙN["I0H`d#zӣ,yI3J=֮,E:JaU N={։ʹU@CT7
Î{B ǧ^*`&<p
u(- V< G@;ՄN[><Tv櫔E/+
	%ךS9'r6M6HЮ7LD1U#g zI`=kEr9mq<E$'6)Ws
F NRhc:T21{Uܱ! W$R!<1HlI9'ҞyԢ!N3[KYJ#8-\ΦK*bF1֬*8k|qWFsyy&:<ۜg$ׂxoozEco|a9tP27,j r
yX	+j p&vE[C컖ӬUҭ{ėW0 uMyտW+DѵoznRh7PD$0+,m!^:Y?>k/x4twkw#ia .$*;"%"𽵟#O >)cniaS^|sY_5kQ̾r|䗽=N^tl-8!`-3cԦsZPAX./xO_܍:+8Dqǒ<޶@aԚ(sJ	kvog:pvI
;#=ulȭ`qNi$8#cSv屌ծ\UfCWsW+ 㯥C *Qu;~S{T9g؋vzHs58ԋnw`Ɲȳ)cE|9;n8c$ǭE|߃icOT=
'#4H6aGjx #5t@' II1ФRA<I㱑Ce݂MGͻ#\!< sL)ygjiˮ<G-Ww
Ɲ|B=Vׅ[OQ&ٺo zx9*m0F1޾C.W615{j{~'axxy)We'}]FGz&3qZ7>1KOh0߄-miyiw֌i0J^[{_/|P>:W&~{71j2ȋ*AN&|7RjX\eWҜ\UW3t 
8Csu!cƃ-#ȠW~ZxZ滦ڋ=qJ |)<ď\v}63!.HEc3 I-1IK%M3>|:F;+JVR $VF	wuw&X^9MkDA3^ż%c䍢r}	д?'ᰱK="זo;T{uޫ%Ƒ,z"?1Hc|LN)q哋dQ~;,kNhꮖ.]A{zT>GI=kïڛFEk'u$/>9uD9{iq爂Id?'l+濲87K̰wPi+Iy5Mmj8?
<Ʀ<]Ll 9lhyxcϽ3oNzHJk6-V`AcdqfE<ׁAR}(l 9#c
1GZɀrnL r8WG=?DWw\h riݎFFqsYN- $Ol11W(0i:jEM(3{T0'Z^G'<v㸧(a}
kJ#8jpA;je@qw("$:u5icZ8xҞ3N2;kU2ANF:ޥ mgӌ?*~Q[zVpzt֛xP|dJqp}>TÌGbIu9ڄsU<
{㵂N)
=) 36<`ӵ/x\of2z{5h5P
>W 
~g?	53<ַ;7bD۲MS"vsiKo_oCω<Sh'Fh[:q5cˍ;W:-r~8= 0?Ŵ5zFO(|1Z#@_c熕Vٟ$1Hv=3_>('(:rJm)YNɵJIguxKFketwgP6-%평|G
62!'a&sפcV
◍ .ElN^S6~ 2==b@WMgeƙxHWhdpXw1NT+Nߩ}FE8& CSǊo/<w^
0V׿t?!ڦ++qjg_[^|m	b}v8D^( A-̾Lm-ͽ%j0 T+hO;x_I4:d]! 'f?{Ye䘜|~M%K^k˷,3j=YӵڊK-=m{ǃ d>&tW1|@6+mNMFntU\vS޾_|v~ xׁd,
AH.ߌlw|MǇmgWt|S~Pb@c*?$״x篽~ƂXiI~GI(F6押 ~Vc|!kӴ{R7{2Okśq9瞵>y};la(F
+XRNSmSuy }j@h/㥛k
&k/,q^F<.  y_9߶#?O?1M=UC?7#"{8q?xⷀ~ z
wMFi6Ճ Ky獽GY^b U)ccSÙwK_>"|i#w	flg_؁]	9"!*~Ϳ?fZǿS=}1 N$#h826Eka bL tm^fR!cv \ul?&._ֆZt?ODc  aGZ_! T4?d 
[6S#'$XlWO0U*'<Ǖփ;A*EYAMTSI .xVUt<!uAVk]X7 w cRv݌~U{Oռ=k:tn{y+"5c8QF6jN-!m8xR88)YnZ *z:oaNMDLR{hUgdݘ V2:χ:޽w]+i>qnQkў7 %&qvM½8/u$ob0cVdiףZ]^9k  U< zU]ztn4xJ!w{լH'-BE"ݲ9ϵr:}KSx3Y^@	Z0YJT3YOR:_<^ר#Np(MuJI-r Y]._}߭mO^zwؐ[ŀ%㏥VkAoykWrl'i[PYϰ_? $|^tktO	KLjZMP̣#!@H"`~~ x}SM]gX1d]r{l
@Rp~|\]4j6=ym ۩mn{2R'Nfןoy+r(<kq=;;RR'-s I ףMᛣyx_វ+o l)0|R_~kkhLzE e4 +7B+2t7ﴑ09<jf5LY99`waw 97}gvi*x,PJ_3G|7
G\:CO%~fxW&O&Wy	85S4E-joω5lCp"Nc;[iQ4ib=$~ 	 5h!
Q^1u*R3__3Zg%K}~ )sX=c`0c8> MhPaS5ExH#Z` gt=px} ^X	=u9ͻ3].0IϦj?p=*{CI*Qkч^Q<4<c8*e2Mj2~^qBmN u9qzTF)F2m^i7<IOs}6g&qCzU0OlT9FkˑXF;S՗pEFEHVcpdਓNmzDi>m"lwvQ~GҞg(z(lm?NՊ/"eI9ϿzC2IakqAsM0Uxtd??7>jڝwZB%Ҟ:n%JۑC&lU3A]?u]/FwIe	hY;aWzÏExsS/3[]Hd:.7U>	OسaZQ\C0'?.2pPjt0nV߽%}/dϹ{dXj'uʖ]ZZ/E־+|sv˪\s{eȂ!SnqՋ2Uk4_ux.vu0=O8̀`:Wx	-Ϡ
9fH[0_)kwT%IiU=,ƕ
է^XNRm*]֝ӥ%}>._Km,NtDo&;eQ'@W `n*'/YgOOG=Y]6y˂FR̀guv3./S
JwsC&e&j..[d>=|N4<uY~ewd?G|~
Ь95Ǽ9I#c,KSx'qF?F.-Fd/4v3
,_pTQW9>2Wj=]m_R(y}o^}8?3t3~Q8LAZZ88ϽT#' T
s1HԊp@~`>օ]XxOևG4Q
|E7r2<Gp}+?4i?ZdZv<3nimORcUMKVEe,}Њ3婤w[?[315y{ {Mj`/y_F'A#Fp}
{l|uͥqOw.9~~۟\7>gƏ n\kU_5%YeO$7u?6څ;34Q!;~b8`s=rF+'jʆ!ZKk}:
ZeGojzLr,DKug&n2* +jw7sq 68-ˏߞ-}471xɶ
R)a@b FF<P֠G}w3	۸^ܹ=;6NMύxu xQѓw`%\]~~͟ hE"i~ V#2[$*ùv,y,ٮ|4xΉ.Mgw
y|!GLo^_.m4vMީ?FwV(斿.U-owcMBy^MzfbƯPvOXqGb
F+.o+z4(m4f&#-ĺ5ֺ44ԢO2~wM)'f?b(sC^Ө)һ0ߓZ_ĀA!}GEЯ5VCmFB(ϿJξ.iU캅,3bٝh7zEj"yrv<~'ݵnV><~Yб`+ެ%~OjF>~ǯ
u=< #?翧 ^OGSNXz,v%Rj[fkڟ^L'Y4>7ؾL_n/4׈I;M|/<{aS- LLzq:z] ugVZqmw1!"'zW FYΥ:FF`,0rOʻI5/8ѯ8 /1U!<T]b֟G5qeWuPFpO¾@On[|"7Pg#<hŭ2=o"g2,q]+ʒB-#h34>!.fZQqP<?_ɸ+c40j/5?=KvM䴉zx+?00zX_Uӿ࡟Yc?3|=䒗k۔Һo¿?XkV[̓Bi\4ㆎ%̞T#0c@xެM?:Tek~k=ւ )BO\Ty0Pucr͸w㚒2r?\)T9^sڥG|'>	Ux8}v' tMO1PNNsSHP=*] >8YؼQRIQ:9⩋?+1mrP{jJӎO+:Frqx2p{U	JeP8T'ҷSJflO\M(
1X9bvFp"
)P +DhWOt6r)sWpOa]:i8 .?
~ݭ>s+d1V%_5ե&Oҭ&@Y:6_hU&pv{UI4¯ڲeCS6}1ڥKV*xzs]@:gIm5Y
e@ѥ''ڽ";;Yܲďg^#~++KK7DFsdWYjPə_l-@{q*{}rB:tDŌZDIJa$6|+zi:d_omBDVQsRʃz$ώ5	NSH@_KT->9pX\
 -ﴏkAiDo%XI$M8xxB+Qk--J/v[-ޫ5C|b4o/NσD/wMs~P.Ps*z}GxɤZxjo~8]x'_Fx<*gw$!~^x{\,]Fj6҉;}#.)5S$߽ךS;`8'x(-Kl[?À})nxU.6`ʁ9G^_1ӯJ\КROj[*U%NkTnc4$OJE$aZxrF;i#\OU52V2A~)Tjٌ18V8;sRS3V*a <TC.2;KӚT:cIX1 1U]\ >ߜds@=@?\SdJElrOj[QWDpJV'Ic%eCl^&sOSqg-
^KR2_wRŤY	 pۯN)%	m	G4l0ApA|8vC5E?.oW7$bpGҳ7WG-GuHN`iwaCJnQ.
]GYNeB쫀I ޿"XiIVv-Z+٫߱\nrk𮷧K5͌ȠzIxXKN%utUR㟁K
퉘=t`
p(O#)<O7#xYmz#Ilǉ #̍# z)uL*}{W/{6P d%F s紪Hg0bۛͭkkKՓJ{.:w--O{_[lڳn q bk=ٍt_ԏP}Qd>- &Lpܑƿxq  $BGI
Rx}iɤxGNӥ
}[1NM|Gf%E=f  ǳbE8:9yc1'NhμY^u#GX\\f|L& O(=SZB,{>_WUĺ7Yi&LQt9_?(|u:R/4ؖ{+g\T1_ǯK?Ma]+ie]#UrW!pv7ʫ[i/iTB3O]Wo@&ݾkailȮS9(2KIv9tǶ}Gj隿<;챐_;<\yb2 \ 
'⿥R.~Os?"Cʖ8Om歯:|&kVף3N*9Ws_1W~q~> dM,2w<c㚊e_MrD}-aƩT(<sַoN\)Ϯy_Sl|eHkR -UfC9Ջ݃R6>QzQ(6ycP|ϩ]KYA=CK\bd}8Vyڶ*2! 97L'f<w9<dW޴$m<z!&ٜ&ɦ5˜H ~UlϚpfdTⴌifbx9U	$|g'uOK)A&`>jDT.v=jrLkriC/szN*IigOZ{
LhJ0=+BUjM"0bvqS]TU!y=3ރ9'Fse~;`H;HlrHEWHT9.
qZ18$c:V0"#s\صG>>Vi\y%v;A=H=p+|kO	t/67b5*cnBa@3
e>r՜9={W˫QF/tĝVB~bHˁ.y'~}+NEzaqkox7᭟h^
4²wDKPnn=K&$
җDӥǐ@ ]~#vqڼO>$imBF-˃}5y\9f51V,UZty9Oj$%(w=1syibo.ZGBI$ҩAnMrgҾ3$y ]bJ\tam ~t ViA=1\݄Tz)JG#Z{6
x:W~ݪG|IeQ}~}aـ\UӤ9$}+
SŢmy V6g&>sv
9Q}+6Qzg!GDƛnM$N4ûwI`	P=1Sr1dlƛ#PpRxۑvYH>@NݨX{r8qZ+,g,G@8mU@ sRc(8LO&%sc 15YI'+qHWs6qwL8x?[':
|%ju\g*Dl2H20o'S""Ğ	ֵ5мB~`E(l6\#0`A(%?wl\_OzoؿY}au`pgGps_v)49-Z뭴꾳Y&[v}?-:tr-e@#^ilL\k鿉 m:]5ݜ_`z574Ag|K0nM~s0̪ڜZ]ĳP	%k ŗ~bɶeE2?N 
+۾O/p̭2
I =k1c;V}k}CN*mnY8ࣨe
JmlH漷&&|?i	1
?8@!|W1Wz9T=zJ^x(p=ޟMyøw>z{lWW[Lw+)/>(A5ԐT&0\SGIǽrڒ$S9u2E=Do"Ky$eLA_oA<!;1>!qK9v1rB}<kF\ 0xNyq__S/u7FcyogN0pz5/ ZX5
m)ڪA
GeeFY.[>QBo|s7|P-iQ"e2!RPzz PԠ#NH
B[
rpqߞ9esq0kycbrt!^KN{h$㟛rbR`r\{9OA4U|Qo'f{oV	T㸑\zC\]s	y%:5=72Q|k,v#uph(Ԓ]<6Go֦[aЯc9=<03뎝+UZZB)$Zej?ºhgTnm`fe}nHmN:sXZA^븚 \JȞR
]1
>	fO c=k64jBAbw`&yV!kn5QO=sz.Fʞ1Edz̼XWRC9t%q\vj#oҩ4Np3ڬL0߮qYnf$/󤑔kGtq=*r1\9b9#=Mt6Ʌ}bOQK3sϸ=T'H	%4lGZhic;bm
X;vzEI  }:UE  e簩1s6E\q֬`O1?:JOMqN,I|Sec G|Š*]9Htȱ<u
q
}w ֻQcUԵw7;d/8.~fN MhZ]ūa꡼)Jr`G(~5Υi x|uh.}\n08ZoĬK\䔏#Qٿfkf2U [f[DIzB@Ŏ^~:4R;2AڍKI 7
pS_33KMԔo(JLt|G-˹| ⤕\\ ; *Yc _Q[Z;_13Z^Q03ֽJt	>+ BrZ@F@8xzg>uöwQ&y[do,\8\bǅ5|++,]\	>3R:ֹx54Q5$hr:HhŨ+{T%k6lg(W:Z]AZcKoęnެIic
K:K)ן*r)$Yu5;g n1X^FIBFj8
1"?6qTC 29|_)RT_Mu#(67O3>8Xً9 (I'YW:v\M!苖''ڰvxP8٧C'9 *'qXֻ;iWvu4>}m|?uΌVr9$Ͽj<Cgd~L
L,Sp+3xv~ޛ%e$0Dfd%.6ڭ><
h}Tgm<lq6$q>RIr˴!?3RSSTz4 /O|al|N=Ռ]J,Ԁ 	$d|qzWѺ[4d[;vᮍ2*
aT#
;rpạ\qv\LyA9-zqvƟxzܻ0by3 
8h۟վ>؍2w;קx%|nf]$X^ r5I*)+?˫Gw)η21,o!}_߲}τ.ag۝: 8"\}+b`3׫yv߅1^Sۼo|;Rwh k/2=|[]gjv2}5\Ʌ\NX5\]Y>g`҅/#+Pݗq$s^K
Qp@ 
A2l$+Iby!-$zy+UJO5R8]rkP^/)}`|)Zk6ˌَnqx!,;I>k
M%  "i4J{`|&NTsz!#5M"k ׯUOpՈ9R@UId`%+8Vyc,%xႠ+ǰzWJ(Ow8ۿzȢ{g$u)qOPGoy=m">B8׾j6{hAr
NCg#%P7KDpskL	s1u/ٟ%7"	tvVDo}^ߚVhvG~ -|C5
:ǂdGt88]^W=<lz,AXSP0@HQpfl<:3ש)sQmQuCL6=+um$zIbx	0݌{Tӊѧc"I5I9+[pw>ڞ1u)
11d-5"7 [F?

#i頜UcԨ'ts=VTʎo=]){T1>Ft`~5@]}j"geHTV0}Nj܆?x}q£ց0{)nqjP~z0yɪQ3K+T&ws+}`gYK[sޟ6U"4w/^U|&*V䎭ڏ=I`*Zߜ};75ʬprOL.09lM7E
5Szd.e~-.#[ @O^Xdg;Z g=i"'sJQI`$'I"vZ*y=j"]xZ]<io)[;jT*U l @׃'߄NEAbc|/RHsN~+tDվ0JishXCuVs_O5|$+ö+|d#{3 sJm:ZW^^r9GuUvы9䑁<~"w\h,uqM/|sTA¼Q/%\z~u#䠓GQ2}ǿO4O*vRuUуpzʫUQ	ݹ_@c-
x
>.^!K\氾rG"a[PVaH<dvzO0cqNpqq}MiXY_w>T
Wcۤ:fØیdfl1~ƛxnYu[GMe}g2
pGҿ# >! Y~Oh+&<md
 {W?iu\vY|
jf	9fT0猎y*Բ*Ut _:Sjg,z~l<W(wGm+τ |+hٺQM}:nChZiS(h,.xrI8\N	'*r;17Msӥ$d
8ʏ7*s]Klܒg=ryZǡZ68D(UsҗdcRIP:}*`%$Aos>ڹwRc z϶ګur(;w(q9kj*:)Tr<Seh~E]rT1ϯ=uxk|G:e}vYܔrr<IJ֯_Sl>NgGx]K{i:?<չXn݊'Z
N8$|aoq%Ho.ebHM>_˓5=\_G5⤲}IYq6F3@\ma]'guE,26aſE\mz5ËZF7g*z&IGSÜY]LbK+@#fNx{q&CĲ󞜌SOGCU*WcU06,e%s9bc.Pa^[J
!I9Fq}1]p\)Ą2{W\J.c.  ץh7ḵ(؎ACrGQ+fݟOKƒ+[.zW{Mĉ_DVW}a2\iאŬrE"FV-Q0	-,sz`xo ?|#gvqN_]rcbO96Epk	C\V_2#V./uj^&wwk;C,Q<-QIdHH4H+SO]uAjF3r1׭x{S+i|"]x
RikVSV:>5&a~Ϛ=rR;3㓏WU{wy럔
k6)G(NGFFk"tڶ'qw0odwP7I?(8ǧ  NXRV1kyʧ~}dvu8WߵՖ~>:mglVY,ڿ3p檵qACG_g)팜obzvDٝ/COqeѳl"uޣ?l]NI$0O/>sECTHZH6#$U\X똝͸u<?^+>P['hq e,z⧿m;`tɏ)K3`ӚZ(I_st%N]>\ |*y;Z}7v"$X: @$pr+vqp.t-ZR<[̓BjZmd|;xSG*,GWO[2~̔^Xbp0{Xw*%S[8zϺWG	#@9|T)p,95}o5B4iAn>C%#rDȸzm=:U4Pmw_`ڼиų#v`z-I /d*Grӱf<`]8y6V9ii81S{IOvq[rz5ql#un%U#;U=#o^56!ls^9ctJϛL)tޅ(:.噰:r)ՔGjN	OSsIrO=e8I׃YZFjЊ5 g#+_d<"r?OʩF`5fITc׮(F4 d~"zTlCץi)sF1<T$7NGW9"Ks׸Cܡn$(f@z¿k?h}J=KNYV":rPsH=?V iA#~m֚^̿$c2y=pXYu4BkFY#1\M<=g,L}G/:Qiu7Pue<OkT³xoN+}4>_T]SK-l@Fi 1ҿ&a`2BLkMcM?^1Bɐ~YT1X_Kk#qt{V<hcbIݑߦ+oo %K : _jO$I*}H >Z$h@9'?R*
tZrAw:ycn-:cZVqpX@k}ڋ0,)
w??lZNIӓ^\=Ռ#;H݅&Ovk7}r65[IIS TU71pRtF| x9؟>3xL@vC
@[#{¬QD#@	'O|]ƗŞ-WM%n#ޒ D  ~?oCþӴw4C23''J-- ŬKO7҄^,1榍9VN~c{|KG}cEX.VE1m{[2PW̗m\r3]֓WHm.[no$٧K&.{O+x'6;Ks_:=;[@ i`}SEgǁϕ/u ߴG) O5RˣjMMB/fL\1(Xż%{g;8ʴP	a
@~k:ÿӮ]SR]Km̠|Ud+0˯qb0O~kn@	!]j^FvN=Oj։%$WzC&<L}ۈ~a㷍Iiίi(Q)$~f;	GjWyy,Kz#r@xf䟻ڽFc9Geu<WE蹟7GekzޗED,9,8Pg8|mn i=Ĩ`
I;Tc~A˩ir_Iq,6	c8h?cǬ~>-l-]B13 I4[!dڼ9K-ukTr[xRncc-RGwnGtgĒQ>U3Epňq s6|[ĩA:y/W9n^i;{x- v2ẅ28 1SFJ<?Ǻ嶥ugD
A2Le$@nsy^4R}:9>8|8UKŪE엏<d䪈2dU߆*^kΔdJP 0ԂzqZ˒mrTWl>Y,Izwm?{9^.e_ƼG;̫3H#y8$?杵=Hl{(ɿ	1<p{ys^ɥ6LYv8|%zr޼@ӖkČnydxAgz=Oz7aPѱюN2 zq_GP>_:y(w6PGge`7q 
6 e>4B@Nt/XwOm!ϼ~W¿'_[yT{~YkӾ||c4 צST\Zn`Ѻ,s3FT_2Nkz_<Q0~)82tI[~=l3h<~(xcĿu
jSxڎtQX[Iee#*G}iݴ 7\ ^':{Z~*'^o8nB/k>O8 BK}?TQ>'<񚙣& wNs  
Hm;|ǒEfPz_¼⦏qfi=ޙ$#NI|(8'
)|
*$1 תS ONw@ WqhQ54+fVbxJ0*=@N~ҒIY`q
<dul
6IN.=ek;t88=Kwb{aYrlzoKtw[oVGe9rvFp0sk62'=hbI\l`7jX4rVY#Թ#p@A+#A	eTBڞ ;o:kم+n(%5k	ILZgxFh]z؝o xJ?5o\ >+Hrk&}@$8dj 
s+.|1Ym桺ۛۚ7qg'^:n+S;mj2saW$޸o.8#&c}C$uV8^޻JLފi6}8
1, A6X5ׄ'͑R}Y"b76}:w2SD^'C+G8ϽJ5q恹b]-Na[qZ	m"5[ichZd~U鎕:(V0LqִRPp>ޕl>'IW 6ZcK9{Akgޣr@)s6hՉdԬp9aYqOkA鎵J	 έ@Xyf鞦 !w4VbGƭM%vCw] DFwØ'nuȖB<2O~DA]˫Iqq+K#6 f9'rzWҟ'oY?P:ݵɗE'X q˓$󍆋Hͼ{4*V 0C8$!'k\ny1 3wV.u% ܢv%xNFKʂ=*8"Ag+Aj&<.u$+ <''hA۲v.	'r*
K}Oc~*tZDyPKvY5lYMpA'I9i-#
h0?^k2`uX	9q3<,dTFZ\oo-@ue}|^dnAP[ *-ݖO|rzEyYSVg'F=q_`:tP2.1ǧ^=k4Sz&9{{Lc0T.:?|6*wJC~o+_Z%u{Qu-wAWuqTXe3 U﯍C nniMҬB$c%3NF[䞕7~&]']_1&7ȡ[yN)fgV޽w#UiV<DIuF?V@ ܷy/ϟ?j/bK=kZlm=(X$88Ʒs* R0dj.3ڱlXH"hVT]LU9
?y'8JB?שUW}u<L1</Ui`eiV<,kຏRO1qiCV`obMbvPsn8 O54>ɬZn.ͪ7e;Ucw=F2GJڴsݙ(>o;=2;mEe-cdivsD/mi$$0K.N>l
ᵸn.u
VV,Lp~cPFILVy8۟4|]2]οN|(8Zxr\ K8 {~?>8<{1i'rr"m ( sU4]VMRuWYN~VwUn-?ts_I}{zNDhch`s '
6O+Yas}4V2T&/Σ.BVܹM|!9+럜<i6<yʌ}PVB%Q,*xYQ
܊M2,i"[ٸ@ʅN8\R<Ux$-~P
ɐl^;Yʧ4՘}1m29XΥr<sqCs"L)r[9t/m@	$N
;mfI 
P`P #T6\G;suW;ոf1x'ګ&Aa\UrIv1n)PGq< 	Hbw~Xg9V)6O]҂~@m EdP7qH
u[KK=Obvnq\v8NuGYI/Ĳ12?:mVm#?W>&v2!
pN=^FڃO^Ml;ӫ)ټ+⋿xPi|ɠ?A8kwč7ž󘛝`N0pk.0AG Z5x-gځY7ui!7KЃUr8 b2[ksʥ)ReuD
Wv'k4+o<1CÅ4믱ʦKܟq ⾆VFs5ӳjzjKBԅm* c Xm
dF:YJ1ʰ<EQe^8s<kk[ƽl/tf `zȯoL񦹢	~Ԛek+.<>/Wm\KD/+[Wɩ_ϩjv=v U#O^s|`'3!,S9?U)҃H}';J= ["y 

1r<V_۱άU~vjs*1_Ĥ2zEc4wqj%d4:ʸdaCuygԚis4]+ǹ<qT%V-r*<Mc㟀ř/kYHO3B
by-~NS]OTd9O~+ǭT,
uf[u-Yż'=* +d'HM9.UӡN3+.f=k+`_-rC\U
%FdǃzVg#v990[p3ocʼi]ᙔ~td8 "2p{Vu΄q\b8ڨNSۭ2\$LFqj5H?C^.63оߔƝ!Ht>mF8VȨxpg37+=\vzxa`]>۽;a#N80Tj6HN+R]&@@謥^	o~jS8>U!㛯?"̉hqBZ.;,!q ܐ=~h C[m2b:ީ&uP`y%?3(an}ZS;g~3$(lǢZn0r_X 7trOFT+Hm岈<)
 7֧ x>5Yn<=EdB0H(u iwyHl
LQ(y[+f$!W/+* 7 pa^S]?_D}KYkpU3Ս~i3f.=k㿃Cc/\kaHm$;4ѫna cW%KM'6lx,8V'qTn/d$b"_2_@'WHpQ' k!ML$I_ߟږB!Cex?VHd|.rzc!,nˀsۚn8]iI<`ncm0Ma|l<?O1꺎bKo= e1 |,<]ǃD#n|,#2;a@O?Y
ߋ|Lġ|+LPĥCVn'|&լ=X壦>0x7MG9 XLpE+oc $	.zuczb	e
ڣnH	f8 $N>lW kqH 8:~Rsu[-60Y92˸:`9s%ȷ-q+,)`	/?5sw9h%x䷠=+~
@|M_CT06\:rsջ t )U4"h`iuW7	Ҿ̱".(7&inY\sr!g+?H~ (ZZZF |]-)Ɵl[+6Չ_PZ 4cvaO
O^=kEiuc1G_٬ee֟e_ًE4(8gsѻ S^+nmXe\~a<v3"O S:>
'CZ۶j1_2bw3^;k	3|N{|~xv-)#%Mr}jU6/s+xjFthCkiLӺVZ> LϺw}{;]]ss?v57q6,T.N<;mJQ=i6-5+Y]ڔDFJ9w(dgɵļ;K` (d-(Q<]}Du-gӖQ#ӕ(ڽK3Sxm324qfᰪ1KVQƱ-o0#pP$wxO3Xh 9ɫVcIR!YeH6IxSt93Lyb]3!u;nFA''c5#B9'8~Vm+
J"desg׿Sm2N[OӟΘr*OAJOk.TuUqBVLG͗rG^Ó8ǭtr	4C' x8qG\Wa$̓0ʫ08~X,?ܑMiZ9b>U\g>PF}_G/a|*}2rHiQ8`sqV.~^'vsV9stAz033ݹ׏CIWxbBXӰ Tl@qFk!I^1$g>ƫ$TweŭN_&~#4/kykVm	8H T gګMsogxhKcmp90Np	RWwś<`{ *ٵ11}ʲ09Xr zj0'x  ]'JTtjAˏ{z sԟZ?j˭[NJ9	H_?E=p
iE  g8a1hZ\zR7Y-L-kh4xَq_ mdψ*}3#6DEҿ 16{9_#I-#?0\E> r	ԅ}}jQLw=1_+^g9YLk*xtPۉHbO=Wb?uR	pYJ.ԌU95b)??#L?Y)(eS'J8,gӀ+	3IC/HPna"=ub?!_BZL1Rz>J}̿|`z# bSl85|&x`	tIcIns(=EM}*6RyY*TzzI#ҴΛD
ki<c9v:]2nz[?~I#;G7`+uóFE׵t*ָo
#r;Kes^6*sHRϸ@I{9[k咙W<0C
*Ġdab	Qu0[`#+f+DַN=Iq,ju"C-q/5<oV͕4p3iQ95FM; 'ڽ!M@HGr}kXfR]E*Mnx0bOq*~xF{df]5JnG|U&ӀcB^,[0K,a#KO@O
~|G-ď*_A,7O<?8H<G7 b_>%!G{{GaDJqǵ;\-lԵxΟlM3rrzxb' mV<~a<[Buk9&}]Y0b23/}[n IWkjY@I.uoj6ŝ?xsWiGB}᎟tH<^?Oys[UQھ'4u.w$KqlYA 9rq`ag9[}~[r9b|ijg`H܁ۊM"uʍ/֭|Uo~jb(4Gt|:=etA0 5eb#Oml?xNCT$)y1iw7;>\Gkm 8  }49%+irFrW9  .XضeR0ß8b-
zw6{ľ"V|!0YqC
-HKT|)\d1}&>,s{y	З2svZdyd79ydvry,O$Suy>._K֥-,JXU,O KƩ[sCgek=,pELPĐI"tc l!In>(%eO<qsxwr'_վ*PA	y&<~;[߆#xc6y0Z719i$o$Y'5s閷4(T}]46Ua4p^]㒩-tg6&kYeK II,fq8ԸRpW-bO`xZ@FmO{!=Q Psʾ|u⦧Y?q^_6DB8`H5I^iZ-ŏ: 5kRKfk$#Wm2@'#rqkM2Y3I`$ة RFs OαN3b,$Z{g}B@*lmm#WJӛڿeYnTrN	9 rFOp$H\ںgNo,"+iRH2pdsW#8dT3t8<ǭc)++]}c5EĊ
e#1K)c;3!$1*ǞǟOʘJr,lcl0*u# uf#s
Y1 
w)?37 z{ 9FwW5lG<z#;s==	d)x38 wְl>Txq>޺--0.C e@<vڽl-;n).cSoxSI嶲c[{R}}*i
ot, 8G8ϱ1H|Đ| 'xί@E
~ M6rxRǩB&FH猉arT0dnp~~ӯ5]JK1*<nv',NSg1u<DHpXh%9^m|"&x᦭1|E5=$PryXTd\ iJq^:e	\Y$CUR_14gߴV <!O4"8"N]}}؎luDlĪ<ҵ]3v>go7wj?'@[Q`+Ȝ
L[al]լA4G# ޽L.6TGϱSp?DjE?ѪUc'愹t8)|G<<;(n\mݓm}
xl1kZ5Q}K260{*jJq_n}3)$q\*
TדJ)7sҪݑVёAi&nۧO HH WdZXo.q$D|UXx气\4IX2 Rx_T3Dg
 D]=¤w?=+xOC_s}I3+=RO%edWyL,jd̟vHP؂
r֫*oCxSI96gI"SwB1ǵf9SMhg֊ ʞj(=%q`b9'ЍPN{c˻Fpp}g.Pz^ g,nld
ւ&6҃E#>u=})ؘpNy
ӌU4(PZ1ؕnG+ǫ}vԆ9$^s.N>nI"J:$-֥s4.F>O,+܃'`W&"J&kGҘ!qSFC``/;ڸz*7F|d2@F<U	P⶧TT~jN]so%ުD+" 6W_N7>./-)9lР9WMǻWA n[,P3}?Z?k 0h3 ^oʽlLBm$pQ/I} C{kh+\613԰H'? ,Ym4$חhZ{8>w@[ӱMQ/NG/W΃X픳|*}NI?ؘnCxw/ 1Gw-̺v&s%C5Fvmrx|Y˧A0brO_ _^S7
POTy1j B󁎿k~(C88'9)vQ<;vIV(JIU0+R]ΊI&FOS}>rO!twK/`DHp=`k<7C_eAz79L¸3*zڄ_$M^pEDsۂI= ZͺL.}&~rxĖ?|cjLeҬFx!z~~US ,}'
9n|s}Aci0 L?IԦ4{Y"01ǭf]>ǵH ;b9ƵFfEp<?߃[8f]I.[鱪Jzx=~Z@y/<Q:SF:v	88= ڜlcd+^0k?ۗUӴ*Teu~gмHsD\WZ+?/p-vw Hs2:G
hŶ~x&K=LԸ'N8$yi0eQ#A?13<˨ h5S0a2)
ptϡZ3Ew	cBR='$}?_eXrI-f<UHA<z=q9["G&s)E#?0!H=sq:q]
ݜ!ZL#F$P qrĞrJ	U?k*aٟ6x#֣Fo9NBq?NR>qfUN z:u5=#?:~c$)RǁaY+?0TAB'$d>7,w0  ki[r"Y7J87d.s .}y,ŕ<۬1 {H[b(960m	./O>Q_ګHFXd²&[玸zJ.p[OUygj}mk|;м7ma;ngDx-]Fk+F+o)}0j/q`f_?  ,~·
Of]"i4AJS'=.4Jz`n/;E}sH|LZOVH
H,dNAq_&  {7~Y2qoږwQن7S؟\<IeoxR{A<;$l$^[? En^dqvhiQRS~1&᷌wxTHK)x[9˵1J$YWNӟc_&旭wY𿈬榡nζ~:glrVn?5(|7EʽT.76naa<oP~^3ʑIcY5 ]}޼ިLt  *J^ԮC8(ՙq<*f[sA5hIz/amqQcdZ86iگM|Tbĝ+ۃYS	1֪j1sX7(	}<t?PT3;!`ԑJΪ>5t6mL>&6j^=ҭ1/`|gK) (>Qa ^<wC K&(J=9{ ~( (<
<$K9.E
6y[<&2>rIW-1}q_F_鿴 W7
ahkV1A}DCҼT?Wt?ʬ~#^af\ь{tⲦ
d/JE*9MI"}2MH,?w5uO+ɽɍcSy
+M83x'z~M\AXlp
[XA>@'8YIm}(/)8%NNj:H0ܨ=r+U'k#ka>@<oa*\iE~ZshuQKzՑ'8q\w]y.Gy0Tk'ejz*zJ ~^:'CĶ! 9ow).d27=~~>	E[MM(;..g+  ze+YV
6JE>szHVѥ,VEP? +	J`5#)|S{C ֯m?V>Q-z6JTD>BwpN"`#9՟~/O
1!:>|;k'Ė1i$!w
$3^_JjnU\xڊTWt{썭iI
Zy<9e6IU@'$dv$pI551 W-~ Ϫ .Cŕ֟n!
G&8e+*$Ԟ1>"i x\p?u$G~Q榯t<*VdbxR1_ǽAgvp[p!;?Ҵo`V[g
#L=GkQH`1g$WF<:dTe3Ӝ;#'_&׈^0$V:0 k\F8?PkiyBq U:iTh__t[\k:ψ<Om=;h?9A3_6:]i8tP$vѠD
579PW9®ZGc%PH.
&'(g#)>ޢ=l5>pX1SӊroXʨ8<W|C%#XHm<t:4okWV>ޓA#95xhNu{am~~)INc&C3ZQ=}k$5ipYXjkwPnX-Fy`q~)"A{wZ7 fv+G <]DmRwh{R+H.LF0~W
	<	V;s.y\#Uuq}QDbv! 'Rx,b䴪%`z??˜1=+sY#矴i<fvsT8s޹ILj,f<&9~~9	iP0ň :5p
H@ʌJ0r#6+!w"E9֟&0|Xv'L*8eYwg=NGB;Wz	Ɍ-@yR8Nqu!p08a@";w 5:D@2 keiXE%	~oӌvWᡤSE~!ěEEQPb@˒O^^;=#Z>nk
"61߯Y-Jr+w|iVP_i_w%׾(id(^KrY_VPæ:]<+xǢ(QoY$#ksr  ~U;'Ʈ!ǫӢVyW)GfoV_Kլʿ$#r3NUo~9qiy6p[LTLJ;n2p85P)H'5^)isk/''z\w2܃^)/	i~3
߆|Ao%ƛ.fHlR"s@=rhߴN{ooE
R$ u
7HR|'
={nTyx+?*> |=~x=?Ui4F>\譃~7FIA5,#r~n  ~H h2%xrm&
6Ƚ>S8IY^$X.fhdQ  u ;ҕҩV#\SG4%{/ʯ 弪ctەb,6Qe 6d	בP)9 .<\ |-I4:TShֶX|օV8C<#|WkWA%ܱu^jQܯd#7c隴ۭ㑡v\Ķ#3
On}5F.vu$n A(냰G#ʾ~ I
K\inah'
kR$
G+WUF3׺WQy/Oþ$t^N89sǦx..gJ^?eq c>SĲ`՘O^;(u;-6<
=iWYvVH9Z*ghZqjf8ʠv=G5]~¤fI=qUKgkJ,I4#8|1Ol
{e}Q_ڊlJ/9?^kF[k	rF}kҧ՚ܐT jyl:2T e`ۓ)ց~M
IG'Y8XjVa+'7\^7'ij٬X-Ѧ@YI[_S3/	P!~ǁ+bOytREGg9KC_	i 
A㏉!ugĢ]ws-t sڿv|㞜Wig<dOYK}c
BG\#Ν /jR8>Z֍G:M nhSjX7oW0%בd	anr4↧-aaeݎݚ__hEŢFB9QR1$$ a+&/&)g?~*xǖbZimy%7!i2@Ppr'.%Ym{
N@	/
#|Ϗy[os j_zvwiU&Qz1 ڸ2lRc\j-'l9)t?28AIY U5NvnN0z?Fsi9CñmϯSǽydmѱ z=kܫFt*8KtxfEW  A)Rώ[uѭ)
UUt9#{ JIyJG\yv
:Hȶ 
==}9"#6Z#2 t:5BVhDCڧk{uN1\bYBhd
V?AGp+:]xuqi,Ia(`zzsJ)X,}"rri6Ā[^}.iO/O|Ķᲅ٤_F"wl\}+_n?e5;ai6KxX!	ǽpiF]9)D:{U')WnMhr+*ulhf~>`7s)G~jQdOo:V{\`=_Rx_ؖz<s\,S,c%}B_Zp $~6ğ$]VQ:8IؼUhbQ\dfPJ2j0scMQ>qg 
I6 ヒl
+miaXc.$>b&Cl9<|A\wVSy&~+_4*č=q.;!2w9#½)=t<siC"(%
@Oojg2fby-~5$ꖓ+̻(%۞R+2PYu <@kўAPUlq ]9T+
K.[{F*] 1<c)ȈDs`9 NsR.I&	WcH7"Lc|~Rz~֍,df@x8I82NkMNjѹ
tmotZ>,cW,Q#ҿN-pj2+xӧ7sec<d))02=kFӯoAgm(جF 8_cR}8牌{/ͳAz5_IמLP}\WA<9_	++Bt㕇Fr
9ϱ8k7%TyRLr<̷sڤ:qC_SgCۉ,n~q':c_ׂK}@ ኃ?OZx|uSľ
ڍG|7ko|:wͼCxH"6Lv%/O[s2Jrd0~17^fm0!Ku.Br0z:WG8,mEv2
:LWL^J|ǙJ2R[JIt<. 	;|t~4? ,#e,=rU,;Wp\#> OZ⌤=hQREW ]@jU'%g<I 6d瑆O=ͦgwaizg}Ȳ	].pz47
J_-Qr8zNH^@е;=Ş	->bH5VUym py *фmSֿѓY7ѵ*'LlsxfJ\+$S<I# _?QI{VH~
 oJ.!|ݤ7y8b|sx? _E!xs.NyWKb>זݻRwHj]Ee+I*Q)z޲+lUE|.N*9zL޵:KۊX:sLgw~lmfքO9ViqNA5 >R09ke5	@JIfyx\zUdFJp.';kg91y5,.j̩2U9S 
Sҟ96+H~adg_
Wͥ <ymk+ƵllKgO&
}9w s/4&l`-![k{+ %ZgY7& B}h} #HOh|x/5wrȄ 6Ҽ _)p
mD vHڸ4	>S~iVi,מ&֭wK'
Y WA_غn}Ebr:۾=Vo	Ү.tnwShPݺ?mK$UZ3<ta3AV=ҍ5Orz׈|U
v89GOSVHnnnZ8q	֛G6nq)9; H'<A;pw08 
-Bk[n:ClB2(U6RsycǱ1c#w>,VڔI2ȋkI?p))V|m98YX.^ǟIY峁4X=16G[i*Lf]oM"W=k/"KaJH"in#&CgVR/M{G̃aW{q\}-(((pcj/5}f+{+io.nK{h3
6F9f$`rp+ΫWMKM$}_~(h~ݬMC wMwVZ'L-dFFW s/[5x #K<D5.GN~hnNs*ڞRn䑃w9\S78$~25)m((܍
`Ua6^+._1d$Q}@-z,[L_5v5-׊/WG11ģ
d_=̏l*BYM_zF6;#-j>BCr+|$9$r59S5Aq7KpPcx*4%nҦ7)XCLS'.> g%s5jҼk1(ݵA9#gB	F3ҽ;+\!7G=1Kܟ{9ҲPK4[As0y}EmjL|B(
67`@=OGcIcB bXaS\ՒS*c
08?y?T(18N /5HC}qy(eBAsbkv;ؼsb$?s-C:%UNyb㊣ v@[q1X#qWٶ"$aRTHG7|>}գnsϫeIqg0M|8l#$+O}i
7T;i:iƣaԄw	_ >qejr.ȣhO	xVI"cx-ޗ_S x[Ư]?D}҂5ppr*rP̻rk!GN+l}Cp{sR+X zӕ(mqB8 '?m%qs>T
{gn5HSV,nn~AwjrTRϘ9V`6;s	id28]aZGZYl*xw|m0R u=:W	5
#.# ߩvNG-xsN/))[;&,IH'dk	s[0jZ|7dHREV\5mF	T6K>l~<WeSXҬ|GVWAq?Q,,<b\ps[
[V{3*FF}N)\9#qTQUy9;p"nB\Dr=:Z nm?<Lٚg`Q&Cϕ'ݯuHVV,Y#sIGyyO$
`R4ҋlT, Oyq]W7;mZֱy$v,&H{ex$ _pYɩV֟2~ +HOnfSRv |m(d}ԞNy
2ON=?
xD`;FrHU<Å0_V<>k Dx}{	]4f_`Ul1]߼yGk=w H$|7HbGcyN& &s2V?MBFW''iR~зgh>\c\ LGv@50U5my3%:^{֪4eakuɪ'׊up1ZF!$i1FO_#<C܎kRI]rq1,~r+ kA(};aW-7葜I7?xWELE
]dϱ#2>IGOZ?aڬ;kr㯆US| c ?8]佈.2٪ d:eu;rqoZCIåtwє־ .m 4')vZ88rkŢ
˨G]ٮS?%D];RR#e;=$gjp(J*kTޚ.1^C~$f9FLt
i+_j{yv=nimL^QQLӓ,Z?~moc_Cns$v$wK[i3kuW3W)0;@ 8ܬC_~ѿ`{Aq4ƫWă$Tҽ.=#+mŚ,sٌoYyĖk)hJLL-iZԊLM2w^¾v5iCx _b+ki%GgI 
+A"!I =q#sĩRVԦű폂 9QV=3wcJ=^=Dq(BGɑ=^[73SRqx`xSukw3QILw2p;uӤ>l	#+X*CqޭK3ܺ0 ý1ퟥy*2UFՏAf}&u
r6hPy'k ؟zd-FKko3,,`PضO^2Mwr $oW P.^4%
 gql3`:T#8nǡB?[6źԑ2K_1D@uݏιC2ȿ*t4pԔ#לֈZ6si.
"$cѶl~/gVýeTW7CjIkXPcӒ˿ŋ7ruⶣ
1Zȇv>|A~'yBEh8d|Q7]lb0̱KĠ+&;3'n>/X >W'6$vzdGnww8+6o3zCn*ym
H\3OI5̚i reAb;$c
ϙ>ffVP8;s޹۹7Fф;ͿfQ9FH֜7VVd!-'x\=Č+$aW;0{8oZMpS^
±4S`!a1ǂC+j,,෮Rrqz#i+02#b #vk=$I*nz :}^,Nz ԨNw=p9UT
GgnW\c >^1\vuMYۆsyXgxn1G5o`7Vr=*.B`@OʳDi!$y`9A_K~~UBXkϥRdJas>Qz|W٩JjSH ZEjD9/a_tLn_
B{WlkkƟbi&gÀ#0wNeǥ} +o2"U<~괇8돽_IфpѦݒv<RuoV >ƫAE,rI)Rim"G%E|2XeNW>K zTs.}k11.k־6;9O
G.y5쨩^57M&At	x,䪌98HOZRىPO~u5ӛH";>< FA&ݿf:ck:_Ƌ_A=o#%ݽd	WVQJeeIh/o|K_[x{ǒ(i
'9p[Y%؈G' 
m8T(ˋ$G'^+7E_ɢx
yU828aqVtZep;[S)FV>ோ:j|%itٔW	}ß@W*d	Pp<};|%xGkI ]YFS{##sC|9T)ɐ!#+=:<웷cU,ZaF+ҮNѰ}+_agE5ޣglA#܃ko]4Ԡ ZiB`0=[#޾׵J᷊4{td.dYIXn}rAXN I|+m  ɬ HıMZc$K(n0?N.~\qҾmW|||7G]L#wjN˸$d4
 s\u}VncүzK6V9O8VVn#{p# n|%}R:veYHēQc9<ӣJ5%S+GZFF~_I<X<xͷ->XTX'B%jqu=x+Hc |G+"Ѿe\w;Uq!w6/^~x22c3s֫}^Ysj~y$qN8jwfF?ZQE|턀0zpsW$܉ǠXzoi͒"br jtOmUĆzvsSc`m S&BЇ,Hcȸ3"sfe
pABԝB{ջv-]=MgoS|ߎ5e
NHQ)8FV5JmyinE&I '4+FԵiO$zڸ00aV>)ێ-Z	ㇻOK,xO; UfHbprw@⾮νd4:YL3E$
`1rM|߅·cf`%턭	mXr#yS׈tZ-,9 *y1]Ҥߦ8>N<\`f(wsRSs^;)nUp[Ps|N\2;Hzׇk iYۄ͖H`n߇oϞuWʹ.o~zI|>ހTHZXeNsxDA<c]P!9VHc֛s,MWcrSU&­!PR)Y3t6@
",$WNGs¸k`s` y<滍;|ŀ\R''chJTz!{+uFz	;/- iK2<J鍪&@,!;r)
{Wѯ+<#~V18rw=쾟4*=yH,F}j߂`ՄlŌ`<ҙ1P#*sңr3nw/\ӱFs\{,pOZt^77yLq
2Fv.~w~,xdHoHprr2O^k↽ t]Ixn+xi0sy.e k)";>3$uky8:o	--4H&.rD
BN	cq#ɵ{fVɁ;I@p=kKt],k<jK|F>CzbZ-ryE<+n1ʨ#cf_%&KN8I܉7rd<vN8E`~A@<֔
8#b1 N;!gNOSו^)J2؅/
siW lvԮ~y?F䂌f	 \iWv51W(&h!Hu?c$~u7iV>t{",K,B#kh#0YR &F>o{Z}m_51SO;8Vj"R{O>W;.Кm׾"q\-*H6r[8ӚRo5،b8^o;e=y8,ep#ulW!'cֹx*BvZRv1=j7acMwO⫴x>rc*<q֠sy~y۔?J}ږ3g`H'fR25a&X=Tzbߝ"K#z5ԁH gM9(rŸ5LM!9NȆwnxKY*WdҤw#Wp%N+~~9l,. dsF[Xd1׍AN+Ǆ!8(i
]Vr]D766ィz"LRX]-Q-n'dG^y]1[':Tf$yw+p1 <=3w-VnZFi;&=y$uhR-nC$o)FSB츈On8>i?]sG;Zu
=`3F+R`c#s֮xX޾Vk5>t*Sĩ;!A.Ri9896mpk_-^XYyn~u$r:+pT)Il=R2D >_qRHD
_j$$1pk[xdh"A@ǧa9Ԍ[Y#9m&cL4V+$KX6;+c4focYӼW>'<m&+J=O?~~OxmK$cQccv>m^43jr>
 (^W+wɬDseu[i%ֵr	sVaPzK1)nچĚŁw1-Ti<T,~]ρs9i· 3N1[=ybm%u}@9	s64-Q@O8cЂO4bovD@#:j̍ݻ㊮k=ɹqhʞYGzÉ` gky8Iz˝I@E	`:JaУ
ӽ_U3П*&M$F̹	Idgpְ#_AT9{ɾVreq"	M6VVP;E5k	 rOV^p#*O=?ͺ`B<ߝP&5ܼqZ\z-֞}Oc%ΐ OFƳk>I'W'8
z fhwXVർլ]Iı?x6s!z#%ȓw%I }}VTTk~GtO&jiQ<dq?e\VH..6[4tzEڴe);'P:כi$&(MG7|V<HN:ѩ:)vPRInpb+xlgIKejcEf$ ]
h(ָ麒vc*:;E VѼ>-WΌ1%x'K+F{%<Ucbv
!9{F#.%cozlv}B7./HH>Wv6ީ[PYm
Ơt `
A0#9
W9]B {FGn
a$qS|ڹo&,h$5hA8#uem( EǦrɌHڐ[I:bCu8ݳCzv>Sx@d[+S_,n
_np|g+دE!Ki2U68
r_?/ʓ+x$=}O|uմ"E+wu42o!"Pa.s^('BϳDZ}%;'k}Fb,mX,&3=ʟ8BP<W7vRIpȒo,+ RA' q_ڿ'{hd"PR"PG3U I~&Nw6%ǖX˟ob+pԒLq
TsEՇ%Co㲜ɮ:xE`̃ {Vx"X">]lP9rH*Gt	bzg?y9;=W2ٷ
2s)%a( gMl_ۏ'<;y*-;(c6SP15)Jwg\MT;<raf6w#o>;;YiZ=Đp
9s~vܬX;-'s~|+nYigHb?639ēǇiW1%n^bѾȱmQsaȍHIuٔF-M$h: (%Hf8߬%Sӡx<2N0]YB *dh'Zm|G{{
ٻYÑΘcBZytC~-;nF?+?L{jt8v9j&9Xvx<_|#%$9+C 4cSqH	LgHx=9{!ޫF@]&xqƈMw/
?J3Vm%VBHҭI7LB](p?vt5U|7-D7&pO?z'8;Ո մ)|j}ys-]In:3"&3^&7L6l] 2ZHJ8_rb߫חj,ZfY1vuqtJޤaա.Gu'dc9ҟ2e%JuYbfĈ  6$wsC\]#I<=#fvUGRIٓ#dשx]?|wQ٧&YlTFJ2Ķ1↙eq0V#$x#2xܱ2;0S ǛQFֽ?3Ho%T*Ď3?]Cbeer }A2̒Y%&"HwCp;B>k2q[-Q|+q mmk_g@u*wuajP=kO,+榎IF2
9r?QұcTC2{d\՞Ҽ>X:]j</ntk9[1k͒fm	oCm򈊍'HoqcVEc eRj)WY_	I]I<&nl~f$wg 2G'jrV5w*sH8h6_$vDkA=gh$Uw:?q0Et9[VFfqk e\nyZHr| fVu8P
TRm.,D
	"ʺX'7dcRvVFk'
0;Ф`[c G;sTfv2s8
CTv&вEs>*|8am~~mZ8rMWӎG64n8=OY^6r~U} gxR.񏂟	n<WRX063A/=Ґ~4abȬ_үn%6c&N3P+OX៉^#~Oa
ܖ#B(N^bNǔ7ŏVzF&ɡ5& a7
ξ-*XiJO3iW u˔Y<'/.BGU<^j62]NM"̹cq=_ONt]fR6 _|~|CmSNy/;i[%!܇Gg+qukPtT~ǉfh<=K^񷆵RK:%4
EVW[lQz˃5ޑ*	bM#J~=͔i	}ˎK
Ϯ+㏄ߵ6eŲA!&0z0c^12ڗC¥ԇ=IuCـU ȯqãϬDa"RwS+7mg4q=2
ǵ~HG"2}ISbkFXGR,R#kTvrɸGMHd$8{5KnHrszn˻8,u_3"ul}=z#(sK)~ҡPZMzt_hx gК^.$]nîGֳu,3S=}А
FTbK)@ǴȔ}h'{V욿tP~bF~|\ݗr8,>EN]w Z]<͞fڇp']O.ds=֖7r{ȿ|.Ou)fb8pmdcWsY0Xh}DkRA$-nm!`V}r 	so71jZ&bӴ{5k)^k )"*u_ V]u~ߪ>}.8fI6_FI@aq'폙m%^rNCn(BH핸7t_g~+_v
Y!57A,_<)4h=\Au̰QA26ؤ]19<W]~y-?2/.JZp-PsӨ'9%kT2A[wz^kNr<1$wHY @<Zu%[8#!BƎSq5kJa0hlIFS>@>ޙ&1fչT߄[ባ:4ۄo,;QH`0P@؃<*6U5;QO<=OaQmabe{JQŴ&7eT߅O9鞜t+koyPyfDK[uy3yAax#
{kW,3MB;:Q囯^k᭵~Ill'HN)b3ary^{
Y 3Q ?5[I7W7jyK,xas:WA}W-jl$Y8>W,YU2ǧ)}*i|es'ۚ'lx9}k|Kemu$p>u<X畒r :5#}
vj;2(NZ|Am^IX`#'#'ڶ##mEs	(}?F'Om$_o ֡8;=nr|8ZJJ$bӬZd[ ~\Ο⿰}I3d~u1pkA_1b~+өJN\Z$)9;:ռ3j;'V_gf(&k9zN{%698JTRMHHH*À3z}kjNidl8?5:f]htv}N6f;Shp{z GE{΋\kQDX;+2u__>/>8Dm{HV[2;Z\~Mհ#CZ<5	}h|Agi7wC?r#O3?y8;i§$S+ <}Ebjgc'؇WI.nuV0fVb^"#UUS?)<u{/ėVWWqIц1AЃ޾>5ki.%:nH ~Ud
=m 
|?<FtTO<3V}#ĺ(x+܌ϱ]9KB-iV	# ̹V*9+T<[hc,#qXO?5	EVjplmRp퀣SZ9iJIm5RROGi_i)Tɍն	9p{;ۍ,Vw rT!#lz}y៏&%zϴ{6g 8W^_\^=ʓG*}GC=:םFyWj+~YEi okij0[XD+"_)V2# 
C-ڇ./ӬOGKq#ݓ^7%ŵ[r  =I9h>=2/to-ycxXCڃ<drx"Ļm?N&beF)d^ȩ~)xZבk eޣ[֭oٶžQUoȅ؛sWEm4I,$$_2(wu>6{W
i \aPqKXjq#fȾtO$(c"2SԴhA.:QWK_x>"cn+HēT{M2VW>͊,2zs[ bH=jy RIR~\60SLci<c
6mS֭$ٸTEW,l h(*0I玵L\bA
:-ˍGcf4i9aRO m$lw {R4hi;_E]z}
iivjʫ@*dCo\<J͡p}G Kj-LIsx$a#i tƧ۔z:/YϦ\hZɎ, P'c6p;˴K7xSL|{]j<N%XK)<۵@wq ctx^M>=@Ii@;TȮW^ 3F<Ҷ K;H73X>UW	Oٷ>Zf͎Y< ,*'ׇ[|]/ſM
!FO8iQ8smr_	. 1L<ΤG\It=*:"3_[׵LumgR Sd  f<`{X./lnb$rȬWlğjSxIi$e`ݲ^E's[<㞕dd E#m
~6MV&vRE+Zc\g)24n7)iBz
Cv0x__ͷ c/%}6'ȎZ5EaN8ޝgB*՟IGF12q^qZ+:*/܌ {åsVcdeR[H 㯭k \Vp	v;j
Kȭ,n#!yb *Jmm-|dc
z梂>KNhլēm\5<fB7'}k;Ԃ1N$j0
 x-xwom5ݧk)%%=rxqYF}
M+Ѧ,cgCz,`ЁEwVu*HP:񛏊#4Z'uejfbaust/?m	IE>5'%nW+f<{C(kWϫ=sĕA g_*ο4d4q{T%i#wܪ傆\FS.~UF@?jKFu;KH%5{rČ On k+ī
wMVndt[`;0FEvSTjv'5}Vx;_FD!*d+v+gRL쪛B̀IN?֗(W;)YcRQUM+n  1Xѥvq$NU#Ԁm$:ױS)SiqU]?>xvYoIz-쥱YOot/?y-[UwY!	y~\~nwz{qXm/
`:|w%טg眓2ϻO/YRң`e#v\W[k-N89nz]+O?dΞ.Lv; H2O֗6LfRw}ߠ㱯 h֯B$Fԋa#2955yi2G0;k/|9agV	Ӷo<a>Ti-LyqB7= k͎-\QF&
Ld3W|^&4bdR03 _^M֙?
f^0$F4r0lҺ,t+aЦmnc֥Zu+.nuk:U7Wg
wc+Oxko
_J*QO|8߸maT|cS'iEV67al QT$RZGj!bPxgT}[G\gЏZctojhMѫ9FyϷz-#vM-rpPG>ح.N]4T7r=`i[_ #R,}>D/Τx =+b
SKLw2^@ׁ<eRj66&49>g}*V+%iɟ~KfqJF_8+%m>I彗aׯ_ƹEfh~{xpkfIO|,+ΣӎGQuߋ7E%ݔy%t<aN1&jfu#M$Ǟ|Eo7I2 LGoAmDΗ06F2zfthڜ:ɩ'A'ڣ7id֗ZiwְM V;s&uB @}JMFiKytU=+omo^Y|2|rs]f-o5-)k]i:s#}jJNf8Ͻqi$$Lkq-Ķ|t*rܧv:r[_J xVOdѮ`u62mG~.itGVZ_ֲ<*2crHuhO=#vb7D*GϡGuK&q
kg7֬d,j)Bd4-/OI[{t>4kl2-]SWt Qq2xCѤao=̎I7%8*r<|>.mi5Y$;I'Rr  ^Hx7QVM'PЬ|;:b閚wpqo*WhR~Vakn]ʕrB/k kl7+F[s/PjSKrKŉhLp1睵ͩ2#$s\'I#j1|9mH6UUQv3"I$0>nzT<Dr8Z+s'CkFY{,2H}<k͆Up\7/
Ff<5ΐ~{nll UxVÞRЬu]CQZ)WAsq5\goLT{nhu<
c:dk^Y\l^
3dc_YxY[|#67Ve$W;{W3\>Vwm2$CI!6I'3>C ^zWT97L.قP0){V3Ԕ__ 3O?IX$l,Ca6R@篥r/$z_ylenu/879n Ѽk_^E]f8OvRq[/5o/\hɤFѡ7(rۈYoe_x9/2rXx3HjS'onmlTg$d¹Wnx"HEk(vRN^[Jм1_7얲j38w4:9HAZ?nOiPBH X \fw.p_$ZŰh>+\ŧ$q¥	gNjoe"A1؝/N`y#<׺5/nlcUMW@LU-8<
m
x\.we27׋".V&#w\A*̮(.d_}.1<_ozs,[I;IcT6yeTe+"Z8H5e-K2[H<a0`= 6ظ43	NNp9S<oHGJ\gMj_\Ʊ^\6hopÑOzdI4@w,*&iNs(OԻdcLJ(a\TO_bF2>P
WTK-ihe)Sua}FG*do]YI|-$TQ*tl.Xֳ61hIH8XųH#B1#P
mjXd'`?0g%ϝhvVq$lHw+98<&nii42
WʑYeP[;4ho<Rʒ"AX\_[G},Ĳݙ)#j^7w[s\YyWZٹimlt#S)[w kt>8Za+hb2=1@ەa寖d>-uAn?2yeJ3HNHq_7W7C֗kxg	2o"!XocY⥶<~ĖֶIsh优dsiG]~M:|&>:|cҼO-׏<P51m--GLu+(H8fcC[hF5k{
6ib$eHBn,6',<,TKx}+G>gڭ\ӽ| jE2hV[P*v zk?Qm}OLG`c[]p?~b?8VB>ƾ, w]OW~ r=<;v)gu߇bK4wvj+=F󜓏M1I4).>:gi'-ïs6pvwm}W^O#CÒ(/O3tr	 O ,F𵝆-t{M
QKe[WxȮ;W4u?ĚBLbwA~SN:WD)KgOFCM+iq»'A,`v|=}hxn7Q{4qFvx2+|\ɺ2C :zS%Xʠzq+_-#?j#-S9VA_#.KPll0p}Bo 
[Zh55t95imU}g= #Jմ6;
I-#S<`ó[i ԼDXE_j}Uh幷/	%f%[F8~$3~8ǈu=U8P-͡!W n3߽q}]޾mFexc P0r>+KRv_fVy08ؼr8kŦc<54i<)eO.ZXy29bG=z*W-}U- Y3I
&\);{>$xvr:tX#Ww`w2O=MG|_ ^
M5f]JP^IwYܵֆ.Z&tߵ n_ykuH<  s1$px<r+;a~?|8x]s-}X %du2T)3\O]#m֫kx%ŵ`
P;`{WN}@`<C@q*ֱQ~6ڌp4gn P*2$&x?~|fFi=?PmcF 7 r|2q86v<eyOI"X%+'"`a u[R~1WRV?W_4 	Y}O[CeX%B9Hsگj5OxF
3zpR[-nйRaʃn0NWr+ ʷյ{dXv`vާ<I OJ_vޭ_[^ݰI"|G=TUmJyOM+z Fzb[Υ6闍}u"-	0r#qP>lu~v;*]θ>(zwې'i䔳dx90D3+Hm\䬤X:Rv=*5Suz\&0ֿL g߈5'KiCk6qfX9(VW~i-ʊo)z⥊G<Lme_w҅Qn~>'t/j>DpoE<_	p%9/i$a-+JеɚL(fe񴎾ɘtnKzk8+^<k.G2F5 R@8<Njw8*ԌU֝4?LO5
蓽EɋVK_-m5'76~b}qn$OVF)ޝR|d0a:Dm\wRiq,2$ɸ6YY;km)fLqt@zT$ngLIHx?xƋk>#5y]v:ƋW39 y4gYt#-xagPPdGxmeOw;z:]n%o^Mc܈#(`0ѕn18OIHtkK_?KUixIF[L[R0CTW_|CĖr(ZJe"F P9 ןGo⶙[ x[_?(6ܑ^uiz͛]Kmx6NNhYzr'n348Ak߄t^O!c-ۉf;`cyu2ӾxHxDωuk}/5!sgeoh_!;p] OH%t-Xi\fF֠*#jt<ׄ2d{A4k{>q.@瓑֡ 4"tO}M پ-m_7ý)f.t9OWʷ.dQ cź[WOsygqB;E
e;RŁvp2^o%ɒ"$+oMޭH g"X|2l彃Frmp(*X!^I$WY[GEo~)u.y}6g9]ݮʃ(PCg 
oJm+Wҵ#{n/`Df`Z=SyƑPHTa8#K5Ѧֺ\Y[r.$NXjkOB: _15o5x-q{شK+x0HB*s{ٮn4dWb^F'$X$޽.菬o΍
4w7ˀ98Ѡڶqք.Q$y'}k8}Y̨:gɮt")OpdCAs+u_x%<]_heιЦsyC;r02䴲k9{)mS:eHl>էm^_{;)t}*pNGJ	n嫎>ҋAk(@+l.yl5|\ҖFbrEp&p1^h׮EO3&pI@ƳV+,gnlfZ@b3x n q5 ~Z~NSuᴵG4FmZiW99ĄJIzo?	_<Yod.ܳmأnHq\Z74hgFna(e}a8Vbx>v,m&5Ar9#l#4GatR'Ru2 a`)`;Uk^&1	]ɔ`Tl1yMNrlt7%-9+'=s[OE4SKuHQ FHpPoȟ8/4X2<aqt47?M,͡궗[!жlQU'
!9# k	^	 )`q=k^-RCԯ 	TL/,Lp:aJqEJ&ԡ⫴% *=F)<YZ0j
r7IHoi #H	$6gv1^h]<oĒI)m4j;y@3c<ô#mbBk~ V:,§kiWgdfrBK0A۸0RK BX=^בDfM[VsR;yGC  ZEH{n6$Ү	b7 cbݮ-t.YV;ۻu2b.v< k\J\wmdqwqHXP۸x#y?}OaXѳ&Zæ76[tT.H e;71>lsmAu$]KX12f}rÂOZh^&-fx*'e)Y+Mth.$;i"W`Xyd2r9+ OFY	dH-ɌV7v*2[\N$,f俕OaCbҬS%pűུ.B\6\\nWwgWwʠxܶ6בx0^jv3U(rʀ6
n}zlIV崚9o$`0XHbIYEM/$Ht	4-&DW+1*NtiikۡK
X0l(EeTdU[}nMJHJCF){e_n.dWnaro{~dfԢzZ]-̐O#`#`@K+{DѮ/tҤh!;P+#"~pF]8[d/,GiL5Qo%r習(6h^ƨvH0ߜ
@?0j4_VM
	&#X(&e+H0ee HCz|2Ny#h?̡Ua =mgԵ	n#KӏڒKZMqc8*rAܕb$IdXb
2,쯅.' 5'yy˶F6H
jЯ,wnR*v?06s·t Þ	{mH[Kr;a!fR;pUp3]6wC%2KIk#͊ТUWpsfP $,[PSlq5E'NH^4G I6ݬF8WnS?TŸυ
ik.LV=Bt0Bp˓.޵f54񭵕v܈$H f<rYV<A]_P}NK$u-F"JbS9;YV{	0=Hhc%a$gi6j0l2rr:KW]}
}>a+?ɥ/RƱmsmo5&1&2Il1ɸ@I(NMcKjͨ;$я03pWL.  tQ{}6(Km%ī`#n# FAkQl澖ɘcd{&'PZ=?
?SUW 4==ԒDh2-pA¥Ė_F _Y$bQ7uJ2p3x^+{+I#<2Ȏ$)uԭ^4;h/oܛud	&d_5ߜy5ezK7ESw5{vOlDGN[^WwlOYyyq$Z;("5Y
I݂c6=i7:>"eHLDU`AMp$d-ZX[vɎV3mTaqX=|o\k]+<($Wkfu1 w-uBF]7AY~Dh@d6;H6 4׮.(%̷+Uwˍ3Si=;VdIaGڑ5R'dvFäܝ͵;ǋ.t |aŜ%
͕Ť	*Ҫ*mX*co^r4 {FG]U8dQaR ?x;S^XjmtvЎv
I^@ka^dʗUBASlRXū˲FW<i>|0k'lLopeΐ1P
ͷ!`s/dv\~+ؠYogB$rK(\7r73y՗l}HPW8(c֬˫K[}}d[u7\=@*lfϔ.HcݹBUX>|)Cx:Jj0WӧYb9#<  [0oco}=ftK[9u$,"0¹%v}sZ1E5QK2+K"1;Hh%xrA,	;n\KIc{8Ξ$6r<{_z]	ҹɘg>gWVos5-֭'}'OH,u+_<R"+YX!%YF<u<#|>
'0mk:cvUI0W
H~FцtV#KjV4M-}HemYosr_hw3G¹CrKX(E$PkV /QKF%5 eVQ1v`҄&[,T(·˖$e*˾iLx`Pg,=2iFI	Mw$SK7ut5D"qA5Opֲ4[ddv̂E1{7/dT}zu>ja-]i^ Ն+qIl&AbZFNK>C֍(sMnS7/b$L`$\/kӲƱxdQ-raye)"*H b7
9,ppk_iwOZՐFDu$@>0
~P
e/йa)H_q-x;S 
Z%8G2Xu;}`7JsWVZX`qmW7ϵJ(m[q$ⷹ"K;dd#.Ѹda@Xek+u[v'¶,	A	',¿.^㶟|#%Ɠ^j/Zyy;/#%T3!ޭf>	DMQ*@69>DH%I!A8W}R}!	Ye F Xofb6/{V}jzz-֛wIgh&(!QDfV9eE52+پkioy2CKE??ju3Jaond;FDr_n	m/:-q'Ao\-농U?.UW_&ơuehR췮֓:+'dv P5|-ƈ*_êZBql$tx,b)$
:߯G)$X|u,Fufu5%ed5 F,TREKh6p~l]Nyuشh@1О漻7&;vdJ,䫱2(88ijĞ$uCPkXFC=/U"LFpİ'(*W7xjKh/.~,3oaHwi
TFܝVxUq}=)YHHl	!J1sƺzK6ڎ

(F`CE̶M^|Ao *
$\A23m݂ʥg'gq1_'xNٵsq"Ƅ4Rd2	t@J2➩·zնI4X[x[c$fU~LɽAݳ$1\.є^.^UDa 3<`7ݣ aF0PFͫ޿F/ dKM6\5Dj2y|ETJNpoY/4_
x!<2U\*ʝ[ BXB#k
w2^$
ʻ c"1 0/E;~^dZ֛ssuܼ1˧R4^J@5o*2}RDev$··+ֳ-.$At:
Rv|^м/us^[YQstf*	>ڵյֺmƥf` aTBl֍ޡ%i[0c*"M1/KIJn޿ֆ3N5_.TX?1d-+odRj~+.n$q]Ӝe|2\^0.K$Q1[%EI$/((!C cƖ7";k׷lϐIVcFRO
$ oO)I[\z3Ɵػ2LH$1d܌$M1Uw/hUHLmByBa`I<ڡ]RP75w\r(F
 s*MfDkkY~h`ZX嗸Jr˒%[}W@k6Kpa3:,"؍Y>Uu
dlyl4U֭n4$kukY%YyS#&ђ2X%vUX.SMu+mEu0Eo!wI'$`(3+]>Os*Vkef4@܌r3Q&޿ZJZۯ]mnEZLwZ*6b$GldVS|?15^aqq[;k*CI̹t 
_JG% e{u۰{aDqj.&BU\q:5
3P',SYVdm,jvӥ);HUe;/5$y#y DHe!V\(O#=uBQ%.گL+n[/&W%ŕK[XZyg|kIx%I9Tgm
YZG/yEE}g99Ko%&Kws6pQVhnE) y2gia״׵c,4A"Yat,IUVڧw$60+.^Qko4k$E[;>Q+OMOkq6Zi-~*(A\vNhi'o]\<?php

require_once __DIR__.'/../lib/Dropbox/strict.php';

use \Dropbox as dbx;

class UtilTest extends PHPUnit_Framework_TestCase
{
    function testQ()
    {
        $this->assertEquals(dbx\Util::q(""), "\"\"");
        $this->assertEquals(dbx\Util::q("abcd"), "\"abcd\"");
        $this->assertEquals(dbx\Util::q(" \" \r \n \\ \x00 \x7e \x7f \xff "),
            '" \" \r \n \\\\ \x00 ~ \x7f \xff "');
    }
}
<?php

require_once __DIR__.'/../lib/Dropbox/strict.php';

use \Dropbox as dbx;

class ValidationTest extends PHPUnit_Framework_TestCase
{
    function testAccessToken()
    {
        $bad = array(
            null,
            "",
            "!AZaz09-_./~+",
            "abcdefg\n",
            "abcdefg\t",
            "abcdefg ",
            "abc\ndefg",
            "abc\tdefg",
            "abc defg",
            "\nabcdefg",
            "\tabcdefg",
            " abcdefg",
        );
        $good = array(
            "1=",
            "1",
            "abcdefg",
            "AZaz09-_./~+",
            "AZaz09-_./~+=",
            "AZaz09-_./~+==============",
            ".000000000000000000000000.",
        );

        foreach ($bad as $t) {
            try {
                new dbx\Client($t, "MyApp/1.0");
                assert(false);
            }
            catch (\InvalidArgumentException $ex) {
                // This is what we expect.
            }
        }

        foreach ($good as $t) {
            new dbx\Client($t, "MyApp/1.0");
        }
    }

    function testClientIdentifier()
    {
        $bad = array(
            null,
            "",
            "abcd\nefg",
            "abcd\x00efg",
            "abcd\x1fefg",
            "abcd\x7fefg",
            "abcd\n",
            "abcd\x00",
            "abcd\x1f",
            "abcd\x7f",
            "\nefg",
            "\x00efg",
            "\x1fefg",
            "\x7fefg",
        );
        $e_accent = "\xc3\xa9";  # UTF-8 sequence for "e with accute accent"
        $good = array(
            "MyApp/1.0 (Mosaic 1.0 compatible)",
            " MyApp/1.0 (Mosaic 1.0 compatible) ",
            "MyApp/1.0 (Mosaic 1.0 compatibl${e_accent}) ",
        );

        $appInfo = new dbx\AppInfo("abcd", "efgh");

        foreach ($bad as $clientIdentifier) {
            try {
                new dbx\Client("abcd", $clientIdentifier);
                assert(false);
            }
            catch (\InvalidArgumentException $ex) {
                // This is what we expect.
            }
            try {
                new dbx\WebAuthBase($appInfo, $clientIdentifier);
                assert(false);
            }
            catch (\InvalidArgumentException $ex) {
                // This is what we expect.
            }
        }

        foreach ($good as $clientIdentifier) {
            new dbx\Client("abcd", $clientIdentifier);
            new dbx\WebAuthBase($appInfo, $clientIdentifier);
        }
    }

    function testPath()
    {
        $good = array(
            "/",
            "/hello",
            "/",
            "/hello-\xe2\xa2\xac",  // Valid UTF-8
        );

        $bad = array(
            "hello",
            "/hello/",
            "/hello-\xf0\x90\x8d\x88",  // Not in Unicode BMP.
            "/hello-\xed\xa0\x80",  // UTF-16 surrogate
            "/hello-\xed\xaf\xbf",  // UTF-16 surrogate
            "/hello-\xed\xb0\x80",  // UTF-16 surrogate
            "/hello-\xed\xbf\xff",  // UTF-16 surrogate
        );

        foreach ($good as $path) {
            dbx\Path::checkArg('whatever', $path);
        }

        foreach ($bad as $path) {
            try {
                dbx\Path::checkArg('whatever', $path);
                assert(false, "Failed on ".$path);
            }
            catch (\InvalidArgumentException $ex) {
                // This is what we expect.
            }
        }
    }
}
language: php

sudo: false

php:
  - 5.5
  - 5.6
  - 7.0
  - 7.1
  - hhvm

before_script:
  - curl --version
  - composer install --no-interaction --prefer-source --dev
  - ~/.nvm/nvm.sh install v0.6.14
  - ~/.nvm/nvm.sh run v0.6.14
  - '[ "$TRAVIS_PHP_VERSION" != "7.0" ] || echo "xdebug.overload_var_dump = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini'

script: make test

matrix:
  allow_failures:
    - php: hhvm
  fast_finish: true

before_deploy:
  - rvm 1.9.3 do gem install mime-types -v 2.6.2
  - make package

deploy:
  provider: releases
  api_key:
    secure: UpypqlYgsU68QT/x40YzhHXvzWjFwCNo9d+G8KAdm7U9+blFfcWhV1aMdzugvPMl6woXgvJj7qHq5tAL4v6oswCORhpSBfLgOQVFaica5LiHsvWlAedOhxGmnJqMTwuepjBCxXhs3+I8Kof1n4oUL9gKytXjOVCX/f7XU1HiinU=
  file:
    - build/artifacts/guzzle.phar
    - build/artifacts/guzzle.zip
  on:
    repo: guzzle/guzzle
    tags: true
    all_branches: true
    php: 5.5
# CHANGELOG

## 6.2.2 - 2016-10-08

* Allow to pass nullable Response to delay callable
* Only add scheme when host is present
* Fix drain case where content-length is the literal string zero
* Obfuscate in-URL credentials in exceptions

## 6.2.1 - 2016-07-18

* Address HTTP_PROXY security vulnerability, CVE-2016-5385:
  https://httpoxy.org/
* Fixing timeout bug with StreamHandler:
  https://github.com/guzzle/guzzle/pull/1488
* Only read up to `Content-Length` in PHP StreamHandler to avoid timeouts when
  a server does not honor `Connection: close`.
* Ignore URI fragment when sending requests.

## 6.2.0 - 2016-03-21

* Feature: added `GuzzleHttp\json_encode` and `GuzzleHttp\json_decode`.
  https://github.com/guzzle/guzzle/pull/1389
* Bug fix: Fix sleep calculation when waiting for delayed requests.
  https://github.com/guzzle/guzzle/pull/1324
* Feature: More flexible history containers.
  https://github.com/guzzle/guzzle/pull/1373
* Bug fix: defer sink stream opening in StreamHandler.
  https://github.com/guzzle/guzzle/pull/1377
* Bug fix: do not attempt to escape cookie values.
  https://github.com/guzzle/guzzle/pull/1406
* Feature: report original content encoding and length on decoded responses.
  https://github.com/guzzle/guzzle/pull/1409
* Bug fix: rewind seekable request bodies before dispatching to cURL.
  https://github.com/guzzle/guzzle/pull/1422
* Bug fix: provide an empty string to `http_build_query` for HHVM workaround.
  https://github.com/guzzle/guzzle/pull/1367

## 6.1.1 - 2015-11-22

* Bug fix: Proxy::wrapSync() now correctly proxies to the appropriate handler
  https://github.com/guzzle/guzzle/commit/911bcbc8b434adce64e223a6d1d14e9a8f63e4e4
* Feature: HandlerStack is now more generic.
  https://github.com/guzzle/guzzle/commit/f2102941331cda544745eedd97fc8fd46e1ee33e
* Bug fix: setting verify to false in the StreamHandler now disables peer
  verification. https://github.com/guzzle/guzzle/issues/1256
* Feature: Middleware now uses an exception factory, including more error
  context. https://github.com/guzzle/guzzle/pull/1282
* Feature: better support for disabled functions.
  https://github.com/guzzle/guzzle/pull/1287
* Bug fix: fixed regression where MockHandler was not using `sink`.
  https://github.com/guzzle/guzzle/pull/1292

## 6.1.0 - 2015-09-08

* Feature: Added the `on_stats` request option to provide access to transfer
  statistics for requests. https://github.com/guzzle/guzzle/pull/1202
* Feature: Added the ability to persist session cookies in CookieJars.
  https://github.com/guzzle/guzzle/pull/1195
* Feature: Some compatibility updates for Google APP Engine
  https://github.com/guzzle/guzzle/pull/1216
* Feature: Added support for NO_PROXY to prevent the use of a proxy based on
  a simple set of rules. https://github.com/guzzle/guzzle/pull/1197
* Feature: Cookies can now contain square brackets.
  https://github.com/guzzle/guzzle/pull/1237
* Bug fix: Now correctly parsing `=` inside of quotes in Cookies.
  https://github.com/guzzle/guzzle/pull/1232
* Bug fix: Cusotm cURL options now correctly override curl options of the
  same name. https://github.com/guzzle/guzzle/pull/1221
* Bug fix: Content-Type header is now added when using an explicitly provided
  multipart body. https://github.com/guzzle/guzzle/pull/1218
* Bug fix: Now ignoring Set-Cookie headers that have no name.
* Bug fix: Reason phrase is no longer cast to an int in some cases in the
  cURL handler. https://github.com/guzzle/guzzle/pull/1187
* Bug fix: Remove the Authorization header when redirecting if the Host
  header changes. https://github.com/guzzle/guzzle/pull/1207
* Bug fix: Cookie path matching fixes
  https://github.com/guzzle/guzzle/issues/1129
* Bug fix: Fixing the cURL `body_as_string` setting
  https://github.com/guzzle/guzzle/pull/1201
* Bug fix: quotes are no longer stripped when parsing cookies.
  https://github.com/guzzle/guzzle/issues/1172
* Bug fix: `form_params` and `query` now always uses the `&` separator.
  https://github.com/guzzle/guzzle/pull/1163
* Bug fix: Adding a Content-Length to PHP stream wrapper requests if not set.
  https://github.com/guzzle/guzzle/pull/1189

## 6.0.2 - 2015-07-04

* Fixed a memory leak in the curl handlers in which references to callbacks
  were not being removed by `curl_reset`.
* Cookies are now extracted properly before redirects.
* Cookies now allow more character ranges.
* Decoded Content-Encoding responses are now modified to correctly reflect
  their state if the encoding was automatically removed by a handler. This
  means that the `Content-Encoding` header may be removed an the
  `Content-Length` modified to reflect the message size after removing the
  encoding.
* Added a more explicit error message when trying to use `form_params` and
  `multipart` in the same request.
* Several fixes for HHVM support.
* Functions are now conditionally required using an additional level of
  indirection to help with global Composer installations.

## 6.0.1 - 2015-05-27

* Fixed a bug with serializing the `query` request option where the `&`
  separator was missing.
* Added a better error message for when `body` is provided as an array. Please
  use `form_params` or `multipart` instead.
* Various doc fixes.

## 6.0.0 - 2015-05-26

* See the UPGRADING.md document for more information.
* Added `multipart` and `form_params` request options.
* Added `synchronous` request option.
* Added the `on_headers` request option.
* Fixed `expect` handling.
* No longer adding default middlewares in the client ctor. These need to be
  present on the provided handler in order to work.
* Requests are no longer initiated when sending async requests with the
  CurlMultiHandler. This prevents unexpected recursion from requests completing
  while ticking the cURL loop.
* Removed the semantics of setting `default` to `true`. This is no longer
  required now that the cURL loop is not ticked for async requests.
* Added request and response logging middleware.
* No longer allowing self signed certificates when using the StreamHandler.
* Ensuring that `sink` is valid if saving to a file.
* Request exceptions now include a "handler context" which provides handler
  specific contextual information.
* Added `GuzzleHttp\RequestOptions` to allow request options to be applied
  using constants.
* `$maxHandles` has been removed from CurlMultiHandler.
* `MultipartPostBody` is now part of the `guzzlehttp/psr7` package.

## 5.3.0 - 2015-05-19

* Mock now supports `save_to`
* Marked `AbstractRequestEvent::getTransaction()` as public.
* Fixed a bug in which multiple headers using different casing would overwrite
  previous headers in the associative array.
* Added `Utils::getDefaultHandler()`
* Marked `GuzzleHttp\Client::getDefaultUserAgent` as deprecated.
* URL scheme is now always lowercased.

## 6.0.0-beta.1

* Requires PHP >= 5.5
* Updated to use PSR-7
  * Requires immutable messages, which basically means an event based system
    owned by a request instance is no longer possible.
  * Utilizing the [Guzzle PSR-7 package](https://github.com/guzzle/psr7).
  * Removed the dependency on `guzzlehttp/streams`. These stream abstractions
    are available in the `guzzlehttp/psr7` package under the `GuzzleHttp\Psr7`
    namespace.
* Added middleware and handler system
  * Replaced the Guzzle event and subscriber system with a middleware system.
  * No longer depends on RingPHP, but rather places the HTTP handlers directly
    in Guzzle, operating on PSR-7 messages.
  * Retry logic is now encapsulated in `GuzzleHttp\Middleware::retry`, which
    means the `guzzlehttp/retry-subscriber` is now obsolete.
  * Mocking responses is now handled using `GuzzleHttp\Handler\MockHandler`.
* Asynchronous responses
  * No longer supports the `future` request option to send an async request.
    Instead, use one of the `*Async` methods of a client (e.g., `requestAsync`,
    `getAsync`, etc.).
  * Utilizing `GuzzleHttp\Promise` instead of React's promise library to avoid
    recursion required by chaining and forwarding react promises. See
    https://github.com/guzzle/promises
  * Added `requestAsync` and `sendAsync` to send request asynchronously.
  * Added magic methods for `getAsync()`, `postAsync()`, etc. to send requests
    asynchronously.
* Request options
  * POST and form updates
    * Added the `form_fields` and `form_files` request options.
    * Removed the `GuzzleHttp\Post` namespace.
    * The `body` request option no longer accepts an array for POST requests.
  * The `exceptions` request option has been deprecated in favor of the
    `http_errors` request options.
  * The `save_to` request option has been deprecated in favor of `sink` request
    option.
* Clients no longer accept an array of URI template string and variables for
  URI variables. You will need to expand URI templates before passing them
  into a client constructor or request method.
* Client methods `get()`, `post()`, `put()`, `patch()`, `options()`, etc. are
  now magic methods that will send synchronous requests.
* Replaced `Utils.php` with plain functions in `functions.php`.
* Removed `GuzzleHttp\Collection`.
* Removed `GuzzleHttp\BatchResults`. Batched pool results are now returned as
  an array.
* Removed `GuzzleHttp\Query`. Query string handling is now handled using an
  associative array passed into the `query` request option. The query string
  is serialized using PHP's `http_build_query`. If you need more control, you
  can pass the query string in as a string.
* `GuzzleHttp\QueryParser` has been replaced with the
  `GuzzleHttp\Psr7\parse_query`.

## 5.2.0 - 2015-01-27

* Added `AppliesHeadersInterface` to make applying headers to a request based
  on the body more generic and not specific to `PostBodyInterface`.
* Reduced the number of stack frames needed to send requests.
* Nested futures are now resolved in the client rather than the RequestFsm
* Finishing state transitions is now handled in the RequestFsm rather than the
  RingBridge.
* Added a guard in the Pool class to not use recursion for request retries.

## 5.1.0 - 2014-12-19

* Pool class no longer uses recursion when a request is intercepted.
* The size of a Pool can now be dynamically adjusted using a callback.
  See https://github.com/guzzle/guzzle/pull/943.
* Setting a request option to `null` when creating a request with a client will
  ensure that the option is not set. This allows you to overwrite default
  request options on a per-request basis.
  See https://github.com/guzzle/guzzle/pull/937.
* Added the ability to limit which protocols are allowed for redirects by
  specifying a `protocols` array in the `allow_redirects` request option.
* Nested futures due to retries are now resolved when waiting for synchronous
  responses. See https://github.com/guzzle/guzzle/pull/947.
* `"0"` is now an allowed URI path. See
  https://github.com/guzzle/guzzle/pull/935.
* `Query` no longer typehints on the `$query` argument in the constructor,
  allowing for strings and arrays.
* Exceptions thrown in the `end` event are now correctly wrapped with Guzzle
  specific exceptions if necessary.

## 5.0.3 - 2014-11-03

This change updates query strings so that they are treated as un-encoded values
by default where the value represents an un-encoded value to send over the
wire. A Query object then encodes the value before sending over the wire. This
means that even value query string values (e.g., ":") are url encoded. This
makes the Query class match PHP's http_build_query function. However, if you
want to send requests over the wire using valid query string characters that do
not need to be encoded, then you can provide a string to Url::setQuery() and
pass true as the second argument to specify that the query string is a raw
string that should not be parsed or encoded (unless a call to getQuery() is
subsequently made, forcing the query-string to be converted into a Query
object).

## 5.0.2 - 2014-10-30

* Added a trailing `\r\n` to multipart/form-data payloads. See
  https://github.com/guzzle/guzzle/pull/871
* Added a `GuzzleHttp\Pool::send()` convenience method to match the docs.
* Status codes are now returned as integers. See
  https://github.com/guzzle/guzzle/issues/881
* No longer overwriting an existing `application/x-www-form-urlencoded` header
  when sending POST requests, allowing for customized headers. See
  https://github.com/guzzle/guzzle/issues/877
* Improved path URL serialization.

  * No longer double percent-encoding characters in the path or query string if
    they are already encoded.
  * Now properly encoding the supplied path to a URL object, instead of only
    encoding ' ' and '?'.
  * Note: This has been changed in 5.0.3 to now encode query string values by
    default unless the `rawString` argument is provided when setting the query
    string on a URL: Now allowing many more characters to be present in the
    query string without being percent encoded. See http://tools.ietf.org/html/rfc3986#appendix-A

## 5.0.1 - 2014-10-16

Bugfix release.

* Fixed an issue where connection errors still returned response object in
  error and end events event though the response is unusable. This has been
  corrected so that a response is not returned in the `getResponse` method of
  these events if the response did not complete. https://github.com/guzzle/guzzle/issues/867
* Fixed an issue where transfer statistics were not being populated in the
  RingBridge. https://github.com/guzzle/guzzle/issues/866

## 5.0.0 - 2014-10-12

Adding support for non-blocking responses and some minor API cleanup.

### New Features

* Added support for non-blocking responses based on `guzzlehttp/guzzle-ring`.
* Added a public API for creating a default HTTP adapter.
* Updated the redirect plugin to be non-blocking so that redirects are sent
  concurrently. Other plugins like this can now be updated to be non-blocking.
* Added a "progress" event so that you can get upload and download progress
  events.
* Added `GuzzleHttp\Pool` which implements FutureInterface and transfers
  requests concurrently using a capped pool size as efficiently as possible.
* Added `hasListeners()` to EmitterInterface.
* Removed `GuzzleHttp\ClientInterface::sendAll` and marked
  `GuzzleHttp\Client::sendAll` as deprecated (it's still there, just not the
  recommended way).

### Breaking changes

The breaking changes in this release are relatively minor. The biggest thing to
look out for is that request and response objects no longer implement fluent
interfaces.

* Removed the fluent interfaces (i.e., `return $this`) from requests,
  responses, `GuzzleHttp\Collection`, `GuzzleHttp\Url`,
  `GuzzleHttp\Query`, `GuzzleHttp\Post\PostBody`, and
  `GuzzleHttp\Cookie\SetCookie`. This blog post provides a good outline of
  why I did this: http://ocramius.github.io/blog/fluent-interfaces-are-evil/.
  This also makes the Guzzle message interfaces compatible with the current
  PSR-7 message proposal.
* Removed "functions.php", so that Guzzle is truly PSR-4 compliant. Except
  for the HTTP request functions from function.php, these functions are now
  implemented in `GuzzleHttp\Utils` using camelCase. `GuzzleHttp\json_decode`
  moved to `GuzzleHttp\Utils::jsonDecode`. `GuzzleHttp\get_path` moved to
  `GuzzleHttp\Utils::getPath`. `GuzzleHttp\set_path` moved to
  `GuzzleHttp\Utils::setPath`. `GuzzleHttp\batch` should now be
  `GuzzleHttp\Pool::batch`, which returns an `objectStorage`. Using functions.php
  caused problems for many users: they aren't PSR-4 compliant, require an
  explicit include, and needed an if-guard to ensure that the functions are not
  declared multiple times.
* Rewrote adapter layer.
    * Removing all classes from `GuzzleHttp\Adapter`, these are now
      implemented as callables that are stored in `GuzzleHttp\Ring\Client`.
    * Removed the concept of "parallel adapters". Sending requests serially or
      concurrently is now handled using a single adapter.
    * Moved `GuzzleHttp\Adapter\Transaction` to `GuzzleHttp\Transaction`. The
      Transaction object now exposes the request, response, and client as public
      properties. The getters and setters have been removed.
* Removed the "headers" event. This event was only useful for changing the
  body a response once the headers of the response were known. You can implement
  a similar behavior in a number of ways. One example might be to use a
  FnStream that has access to the transaction being sent. For example, when the
  first byte is written, you could check if the response headers match your
  expectations, and if so, change the actual stream body that is being
  written to.
* Removed the `asArray` parameter from
  `GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
  value as an array, then use the newly added `getHeaderAsArray()` method of
  `MessageInterface`. This change makes the Guzzle interfaces compatible with
  the PSR-7 interfaces.
* `GuzzleHttp\Message\MessageFactory` no longer allows subclasses to add
  custom request options using double-dispatch (this was an implementation
  detail). Instead, you should now provide an associative array to the
  constructor which is a mapping of the request option name mapping to a
  function that applies the option value to a request.
* Removed the concept of "throwImmediately" from exceptions and error events.
  This control mechanism was used to stop a transfer of concurrent requests
  from completing. This can now be handled by throwing the exception or by
  cancelling a pool of requests or each outstanding future request individually.
* Updated to "GuzzleHttp\Streams" 3.0.
    * `GuzzleHttp\Stream\StreamInterface::getContents()` no longer accepts a
      `maxLen` parameter. This update makes the Guzzle streams project
      compatible with the current PSR-7 proposal.
    * `GuzzleHttp\Stream\Stream::__construct`,
      `GuzzleHttp\Stream\Stream::factory`, and
      `GuzzleHttp\Stream\Utils::create` no longer accept a size in the second
      argument. They now accept an associative array of options, including the
      "size" key and "metadata" key which can be used to provide custom metadata.

## 4.2.2 - 2014-09-08

* Fixed a memory leak in the CurlAdapter when reusing cURL handles.
* No longer using `request_fulluri` in stream adapter proxies.
* Relative redirects are now based on the last response, not the first response.

## 4.2.1 - 2014-08-19

* Ensuring that the StreamAdapter does not always add a Content-Type header
* Adding automated github releases with a phar and zip

## 4.2.0 - 2014-08-17

* Now merging in default options using a case-insensitive comparison.
  Closes https://github.com/guzzle/guzzle/issues/767
* Added the ability to automatically decode `Content-Encoding` response bodies
  using the `decode_content` request option. This is set to `true` by default
  to decode the response body if it comes over the wire with a
  `Content-Encoding`. Set this value to `false` to disable decoding the
  response content, and pass a string to provide a request `Accept-Encoding`
  header and turn on automatic response decoding. This feature now allows you
  to pass an `Accept-Encoding` header in the headers of a request but still
  disable automatic response decoding.
  Closes https://github.com/guzzle/guzzle/issues/764
* Added the ability to throw an exception immediately when transferring
  requests in parallel. Closes https://github.com/guzzle/guzzle/issues/760
* Updating guzzlehttp/streams dependency to ~2.1
* No longer utilizing the now deprecated namespaced methods from the stream
  package.

## 4.1.8 - 2014-08-14

* Fixed an issue in the CurlFactory that caused setting the `stream=false`
  request option to throw an exception.
  See: https://github.com/guzzle/guzzle/issues/769
* TransactionIterator now calls rewind on the inner iterator.
  See: https://github.com/guzzle/guzzle/pull/765
* You can now set the `Content-Type` header to `multipart/form-data`
  when creating POST requests to force multipart bodies.
  See https://github.com/guzzle/guzzle/issues/768

## 4.1.7 - 2014-08-07

* Fixed an error in the HistoryPlugin that caused the same request and response
  to be logged multiple times when an HTTP protocol error occurs.
* Ensuring that cURL does not add a default Content-Type when no Content-Type
  has been supplied by the user. This prevents the adapter layer from modifying
  the request that is sent over the wire after any listeners may have already
  put the request in a desired state (e.g., signed the request).
* Throwing an exception when you attempt to send requests that have the
  "stream" set to true in parallel using the MultiAdapter.
* Only calling curl_multi_select when there are active cURL handles. This was
  previously changed and caused performance problems on some systems due to PHP
  always selecting until the maximum select timeout.
* Fixed a bug where multipart/form-data POST fields were not correctly
  aggregated (e.g., values with "&").

## 4.1.6 - 2014-08-03

* Added helper methods to make it easier to represent messages as strings,
  including getting the start line and getting headers as a string.

## 4.1.5 - 2014-08-02

* Automatically retrying cURL "Connection died, retrying a fresh connect"
  errors when possible.
* cURL implementation cleanup
* Allowing multiple event subscriber listeners to be registered per event by
  passing an array of arrays of listener configuration.

## 4.1.4 - 2014-07-22

* Fixed a bug that caused multi-part POST requests with more than one field to
  serialize incorrectly.
* Paths can now be set to "0"
* `ResponseInterface::xml` now accepts a `libxml_options` option and added a
  missing default argument that was required when parsing XML response bodies.
* A `save_to` stream is now created lazily, which means that files are not
  created on disk unless a request succeeds.

## 4.1.3 - 2014-07-15

* Various fixes to multipart/form-data POST uploads
* Wrapping function.php in an if-statement to ensure Guzzle can be used
  globally and in a Composer install
* Fixed an issue with generating and merging in events to an event array
* POST headers are only applied before sending a request to allow you to change
  the query aggregator used before uploading
* Added much more robust query string parsing
* Fixed various parsing and normalization issues with URLs
* Fixing an issue where multi-valued headers were not being utilized correctly
  in the StreamAdapter

## 4.1.2 - 2014-06-18

* Added support for sending payloads with GET requests

## 4.1.1 - 2014-06-08

* Fixed an issue related to using custom message factory options in subclasses
* Fixed an issue with nested form fields in a multi-part POST
* Fixed an issue with using the `json` request option for POST requests
* Added `ToArrayInterface` to `GuzzleHttp\Cookie\CookieJar`

## 4.1.0 - 2014-05-27

* Added a `json` request option to easily serialize JSON payloads.
* Added a `GuzzleHttp\json_decode()` wrapper to safely parse JSON.
* Added `setPort()` and `getPort()` to `GuzzleHttp\Message\RequestInterface`.
* Added the ability to provide an emitter to a client in the client constructor.
* Added the ability to persist a cookie session using $_SESSION.
* Added a trait that can be used to add event listeners to an iterator.
* Removed request method constants from RequestInterface.
* Fixed warning when invalid request start-lines are received.
* Updated MessageFactory to work with custom request option methods.
* Updated cacert bundle to latest build.

4.0.2 (2014-04-16)
------------------

* Proxy requests using the StreamAdapter now properly use request_fulluri (#632)
* Added the ability to set scalars as POST fields (#628)

## 4.0.1 - 2014-04-04

* The HTTP status code of a response is now set as the exception code of
  RequestException objects.
* 303 redirects will now correctly switch from POST to GET requests.
* The default parallel adapter of a client now correctly uses the MultiAdapter.
* HasDataTrait now initializes the internal data array as an empty array so
  that the toArray() method always returns an array.

## 4.0.0 - 2014-03-29

* For more information on the 4.0 transition, see:
  http://mtdowling.com/blog/2014/03/15/guzzle-4-rc/
* For information on changes and upgrading, see:
  https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40
* Added `GuzzleHttp\batch()` as a convenience function for sending requests in
  parallel without needing to write asynchronous code.
* Restructured how events are added to `GuzzleHttp\ClientInterface::sendAll()`.
  You can now pass a callable or an array of associative arrays where each
  associative array contains the "fn", "priority", and "once" keys.

## 4.0.0.rc-2 - 2014-03-25

* Removed `getConfig()` and `setConfig()` from clients to avoid confusion
  around whether things like base_url, message_factory, etc. should be able to
  be retrieved or modified.
* Added `getDefaultOption()` and `setDefaultOption()` to ClientInterface
* functions.php functions were renamed using snake_case to match PHP idioms
* Added support for `HTTP_PROXY`, `HTTPS_PROXY`, and
  `GUZZLE_CURL_SELECT_TIMEOUT` environment variables
* Added the ability to specify custom `sendAll()` event priorities
* Added the ability to specify custom stream context options to the stream
  adapter.
* Added a functions.php function for `get_path()` and `set_path()`
* CurlAdapter and MultiAdapter now use a callable to generate curl resources
* MockAdapter now properly reads a body and emits a `headers` event
* Updated Url class to check if a scheme and host are set before adding ":"
  and "//". This allows empty Url (e.g., "") to be serialized as "".
* Parsing invalid XML no longer emits warnings
* Curl classes now properly throw AdapterExceptions
* Various performance optimizations
* Streams are created with the faster `Stream\create()` function
* Marked deprecation_proxy() as internal
* Test server is now a collection of static methods on a class

## 4.0.0-rc.1 - 2014-03-15

* See https://github.com/guzzle/guzzle/blob/master/UPGRADING.md#3x-to-40

## 3.8.1 - 2014-01-28

* Bug: Always using GET requests when redirecting from a 303 response
* Bug: CURLOPT_SSL_VERIFYHOST is now correctly set to false when setting `$certificateAuthority` to false in
  `Guzzle\Http\ClientInterface::setSslVerification()`
* Bug: RedirectPlugin now uses strict RFC 3986 compliance when combining a base URL with a relative URL
* Bug: The body of a request can now be set to `"0"`
* Sending PHP stream requests no longer forces `HTTP/1.0`
* Adding more information to ExceptionCollection exceptions so that users have more context, including a stack trace of
  each sub-exception
* Updated the `$ref` attribute in service descriptions to merge over any existing parameters of a schema (rather than
  clobbering everything).
* Merging URLs will now use the query string object from the relative URL (thus allowing custom query aggregators)
* Query strings are now parsed in a way that they do no convert empty keys with no value to have a dangling `=`.
  For example `foo&bar=baz` is now correctly parsed and recognized as `foo&bar=baz` rather than `foo=&bar=baz`.
* Now properly escaping the regular expression delimiter when matching Cookie domains.
* Network access is now disabled when loading XML documents

## 3.8.0 - 2013-12-05

* Added the ability to define a POST name for a file
* JSON response parsing now properly walks additionalProperties
* cURL error code 18 is now retried automatically in the BackoffPlugin
* Fixed a cURL error when URLs contain fragments
* Fixed an issue in the BackoffPlugin retry event where it was trying to access all exceptions as if they were
  CurlExceptions
* CURLOPT_PROGRESS function fix for PHP 5.5 (69fcc1e)
* Added the ability for Guzzle to work with older versions of cURL that do not support `CURLOPT_TIMEOUT_MS`
* Fixed a bug that was encountered when parsing empty header parameters
* UriTemplate now has a `setRegex()` method to match the docs
* The `debug` request parameter now checks if it is truthy rather than if it exists
* Setting the `debug` request parameter to true shows verbose cURL output instead of using the LogPlugin
* Added the ability to combine URLs using strict RFC 3986 compliance
* Command objects can now return the validation errors encountered by the command
* Various fixes to cache revalidation (#437 and 29797e5)
* Various fixes to the AsyncPlugin
* Cleaned up build scripts

## 3.7.4 - 2013-10-02

* Bug fix: 0 is now an allowed value in a description parameter that has a default value (#430)
* Bug fix: SchemaFormatter now returns an integer when formatting to a Unix timestamp
  (see https://github.com/aws/aws-sdk-php/issues/147)
* Bug fix: Cleaned up and fixed URL dot segment removal to properly resolve internal dots
* Minimum PHP version is now properly specified as 5.3.3 (up from 5.3.2) (#420)
* Updated the bundled cacert.pem (#419)
* OauthPlugin now supports adding authentication to headers or query string (#425)

## 3.7.3 - 2013-09-08

* Added the ability to get the exception associated with a request/command when using `MultiTransferException` and
  `CommandTransferException`.
* Setting `additionalParameters` of a response to false is now honored when parsing responses with a service description
* Schemas are only injected into response models when explicitly configured.
* No longer guessing Content-Type based on the path of a request. Content-Type is now only guessed based on the path of
  an EntityBody.
* Bug fix: ChunkedIterator can now properly chunk a \Traversable as well as an \Iterator.
* Bug fix: FilterIterator now relies on `\Iterator` instead of `\Traversable`.
* Bug fix: Gracefully handling malformed responses in RequestMediator::writeResponseBody()
* Bug fix: Replaced call to canCache with canCacheRequest in the CallbackCanCacheStrategy of the CachePlugin
* Bug fix: Visiting XML attributes first before visiting XML children when serializing requests
* Bug fix: Properly parsing headers that contain commas contained in quotes
* Bug fix: mimetype guessing based on a filename is now case-insensitive

## 3.7.2 - 2013-08-02

* Bug fix: Properly URL encoding paths when using the PHP-only version of the UriTemplate expander
  See https://github.com/guzzle/guzzle/issues/371
* Bug fix: Cookie domains are now matched correctly according to RFC 6265
  See https://github.com/guzzle/guzzle/issues/377
* Bug fix: GET parameters are now used when calculating an OAuth signature
* Bug fix: Fixed an issue with cache revalidation where the If-None-Match header was being double quoted
* `Guzzle\Common\AbstractHasDispatcher::dispatch()` now returns the event that was dispatched
* `Guzzle\Http\QueryString::factory()` now guesses the most appropriate query aggregator to used based on the input.
  See https://github.com/guzzle/guzzle/issues/379
* Added a way to add custom domain objects to service description parsing using the `operation.parse_class` event. See
  https://github.com/guzzle/guzzle/pull/380
* cURL multi cleanup and optimizations

## 3.7.1 - 2013-07-05

* Bug fix: Setting default options on a client now works
* Bug fix: Setting options on HEAD requests now works. See #352
* Bug fix: Moving stream factory before send event to before building the stream. See #353
* Bug fix: Cookies no longer match on IP addresses per RFC 6265
* Bug fix: Correctly parsing header parameters that are in `<>` and quotes
* Added `cert` and `ssl_key` as request options
* `Host` header can now diverge from the host part of a URL if the header is set manually
* `Guzzle\Service\Command\LocationVisitor\Request\XmlVisitor` was rewritten to change from using SimpleXML to XMLWriter
* OAuth parameters are only added via the plugin if they aren't already set
* Exceptions are now thrown when a URL cannot be parsed
* Returning `false` if `Guzzle\Http\EntityBody::getContentMd5()` fails
* Not setting a `Content-MD5` on a command if calculating the Content-MD5 fails via the CommandContentMd5Plugin

## 3.7.0 - 2013-06-10

* See UPGRADING.md for more information on how to upgrade.
* Requests now support the ability to specify an array of $options when creating a request to more easily modify a
  request. You can pass a 'request.options' configuration setting to a client to apply default request options to
  every request created by a client (e.g. default query string variables, headers, curl options, etc.).
* Added a static facade class that allows you to use Guzzle with static methods and mount the class to `\Guzzle`.
  See `Guzzle\Http\StaticClient::mount`.
* Added `command.request_options` to `Guzzle\Service\Command\AbstractCommand` to pass request options to requests
      created by a command (e.g. custom headers, query string variables, timeout settings, etc.).
* Stream size in `Guzzle\Stream\PhpStreamRequestFactory` will now be set if Content-Length is returned in the
  headers of a response
* Added `Guzzle\Common\Collection::setPath($path, $value)` to set a value into an array using a nested key
  (e.g. `$collection->setPath('foo/baz/bar', 'test'); echo $collection['foo']['bar']['bar'];`)
* ServiceBuilders now support storing and retrieving arbitrary data
* CachePlugin can now purge all resources for a given URI
* CachePlugin can automatically purge matching cached items when a non-idempotent request is sent to a resource
* CachePlugin now uses the Vary header to determine if a resource is a cache hit
* `Guzzle\Http\Message\Response` now implements `\Serializable`
* Added `Guzzle\Cache\CacheAdapterFactory::fromCache()` to more easily create cache adapters
* `Guzzle\Service\ClientInterface::execute()` now accepts an array, single command, or Traversable
* Fixed a bug in `Guzzle\Http\Message\Header\Link::addLink()`
* Better handling of calculating the size of a stream in `Guzzle\Stream\Stream` using fstat() and caching the size
* `Guzzle\Common\Exception\ExceptionCollection` now creates a more readable exception message
* Fixing BC break: Added back the MonologLogAdapter implementation rather than extending from PsrLog so that older
  Symfony users can still use the old version of Monolog.
* Fixing BC break: Added the implementation back in for `Guzzle\Http\Message\AbstractMessage::getTokenizedHeader()`.
  Now triggering an E_USER_DEPRECATED warning when used. Use `$message->getHeader()->parseParams()`.
* Several performance improvements to `Guzzle\Common\Collection`
* Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
  createRequest, head, delete, put, patch, post, options, prepareRequest
* Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
* Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
* Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
  `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
  resource, string, or EntityBody into the $options parameter to specify the download location of the response.
* Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
  default `array()`
* Added `Guzzle\Stream\StreamInterface::isRepeatable`
* Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
  $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
  $client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))`.
* Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use $client->getConfig()->getPath('request.options/headers')`.
* Removed `Guzzle\Http\ClientInterface::expandTemplate()`
* Removed `Guzzle\Http\ClientInterface::setRequestFactory()`
* Removed `Guzzle\Http\ClientInterface::getCurlMulti()`
* Removed `Guzzle\Http\Message\RequestInterface::canCache`
* Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`
* Removed `Guzzle\Http\Message\RequestInterface::isRedirect`
* Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.
* You can now enable E_USER_DEPRECATED warnings to see if you are using a deprecated method by setting
  `Guzzle\Common\Version::$emitWarnings` to true.
* Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use
      `$request->getResponseBody()->isRepeatable()` instead.
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
  `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
* Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use
  `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
* Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
* Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
* Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
* Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand.
  These will work through Guzzle 4.0
* Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use [request.options][params].
* Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
* Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use $client->getConfig()->getPath('request.options/headers')`.
* Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use $client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`.
* Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
* Marked `Guzzle\Common\Collection::inject()` as deprecated.
* Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest');`
* CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
  CacheStorageInterface. These two objects and interface will be removed in a future version.
* Always setting X-cache headers on cached responses
* Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
* `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
  $request, Response $response);`
* `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
* `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
* Added `CacheStorageInterface::purge($url)`
* `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
  $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
  CanCacheStrategyInterface $canCache = null)`
* Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`

## 3.6.0 - 2013-05-29

* ServiceDescription now implements ToArrayInterface
* Added command.hidden_params to blacklist certain headers from being treated as additionalParameters
* Guzzle can now correctly parse incomplete URLs
* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
  HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
  CacheControl header implementation.
* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
  Guzzle\Http\Curl\RequestMediator
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
  directly via interfaces
* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
  but are a no-op until removed.
* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
  `Guzzle\Service\Command\ArrayCommandInterface`.
* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
  on a request while the request is still being transferred
* The ability to case-insensitively search for header values
* Guzzle\Http\Message\Header::hasExactHeader
* Guzzle\Http\Message\Header::raw. Use getAll()
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
  instead.
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess
* Added the ability to cast Model objects to a string to view debug information.

## 3.5.0 - 2013-05-13

* Bug: Fixed a regression so that request responses are parsed only once per oncomplete event rather than multiple times
* Bug: Better cleanup of one-time events across the board (when an event is meant to fire once, it will now remove
  itself from the EventDispatcher)
* Bug: `Guzzle\Log\MessageFormatter` now properly writes "total_time" and "connect_time" values
* Bug: Cloning an EntityEnclosingRequest now clones the EntityBody too
* Bug: Fixed an undefined index error when parsing nested JSON responses with a sentAs parameter that reference a
  non-existent key
* Bug: All __call() method arguments are now required (helps with mocking frameworks)
* Deprecating Response::getRequest() and now using a shallow clone of a request object to remove a circular reference
  to help with refcount based garbage collection of resources created by sending a request
* Deprecating ZF1 cache and log adapters. These will be removed in the next major version.
* Deprecating `Response::getPreviousResponse()` (method signature still exists, but it's deprecated). Use the
  HistoryPlugin for a history.
* Added a `responseBody` alias for the `response_body` location
* Refactored internals to no longer rely on Response::getRequest()
* HistoryPlugin can now be cast to a string
* HistoryPlugin now logs transactions rather than requests and responses to more accurately keep track of the requests
  and responses that are sent over the wire
* Added `getEffectiveUrl()` and `getRedirectCount()` to Response objects

## 3.4.3 - 2013-04-30

* Bug fix: Fixing bug introduced in 3.4.2 where redirect responses are duplicated on the final redirected response
* Added a check to re-extract the temp cacert bundle from the phar before sending each request

## 3.4.2 - 2013-04-29

* Bug fix: Stream objects now work correctly with "a" and "a+" modes
* Bug fix: Removing `Transfer-Encoding: chunked` header when a Content-Length is present
* Bug fix: AsyncPlugin no longer forces HEAD requests
* Bug fix: DateTime timezones are now properly handled when using the service description schema formatter
* Bug fix: CachePlugin now properly handles stale-if-error directives when a request to the origin server fails
* Setting a response on a request will write to the custom request body from the response body if one is specified
* LogPlugin now writes to php://output when STDERR is undefined
* Added the ability to set multiple POST files for the same key in a single call
* application/x-www-form-urlencoded POSTs now use the utf-8 charset by default
* Added the ability to queue CurlExceptions to the MockPlugin
* Cleaned up how manual responses are queued on requests (removed "queued_response" and now using request.before_send)
* Configuration loading now allows remote files

## 3.4.1 - 2013-04-16

* Large refactoring to how CurlMulti handles work. There is now a proxy that sits in front of a pool of CurlMulti
  handles. This greatly simplifies the implementation, fixes a couple bugs, and provides a small performance boost.
* Exceptions are now properly grouped when sending requests in parallel
* Redirects are now properly aggregated when a multi transaction fails
* Redirects now set the response on the original object even in the event of a failure
* Bug fix: Model names are now properly set even when using $refs
* Added support for PHP 5.5's CurlFile to prevent warnings with the deprecated @ syntax
* Added support for oauth_callback in OAuth signatures
* Added support for oauth_verifier in OAuth signatures
* Added support to attempt to retrieve a command first literally, then ucfirst, the with inflection

## 3.4.0 - 2013-04-11

* Bug fix: URLs are now resolved correctly based on http://tools.ietf.org/html/rfc3986#section-5.2. #289
* Bug fix: Absolute URLs with a path in a service description will now properly override the base URL. #289
* Bug fix: Parsing a query string with a single PHP array value will now result in an array. #263
* Bug fix: Better normalization of the User-Agent header to prevent duplicate headers. #264.
* Bug fix: Added `number` type to service descriptions.
* Bug fix: empty parameters are removed from an OAuth signature
* Bug fix: Revalidating a cache entry prefers the Last-Modified over the Date header
* Bug fix: Fixed "array to string" error when validating a union of types in a service description
* Bug fix: Removed code that attempted to determine the size of a stream when data is written to the stream
* Bug fix: Not including an `oauth_token` if the value is null in the OauthPlugin.
* Bug fix: Now correctly aggregating successful requests and failed requests in CurlMulti when a redirect occurs.
* The new default CURLOPT_TIMEOUT setting has been increased to 150 seconds so that Guzzle works on poor connections.
* Added a feature to EntityEnclosingRequest::setBody() that will automatically set the Content-Type of the request if
  the Content-Type can be determined based on the entity body or the path of the request.
* Added the ability to overwrite configuration settings in a client when grabbing a throwaway client from a builder.
* Added support for a PSR-3 LogAdapter.
* Added a `command.after_prepare` event
* Added `oauth_callback` parameter to the OauthPlugin
* Added the ability to create a custom stream class when using a stream factory
* Added a CachingEntityBody decorator
* Added support for `additionalParameters` in service descriptions to define how custom parameters are serialized.
* The bundled SSL certificate is now provided in the phar file and extracted when running Guzzle from a phar.
* You can now send any EntityEnclosingRequest with POST fields or POST files and cURL will handle creating bodies
* POST requests using a custom entity body are now treated exactly like PUT requests but with a custom cURL method. This
  means that the redirect behavior of POST requests with custom bodies will not be the same as POST requests that use
  POST fields or files (the latter is only used when emulating a form POST in the browser).
* Lots of cleanup to CurlHandle::factory and RequestFactory::createRequest

## 3.3.1 - 2013-03-10

* Added the ability to create PHP streaming responses from HTTP requests
* Bug fix: Running any filters when parsing response headers with service descriptions
* Bug fix: OauthPlugin fixes to allow for multi-dimensional array signing, and sorting parameters before signing
* Bug fix: Removed the adding of default empty arrays and false Booleans to responses in order to be consistent across
  response location visitors.
* Bug fix: Removed the possibility of creating configuration files with circular dependencies
* RequestFactory::create() now uses the key of a POST file when setting the POST file name
* Added xmlAllowEmpty to serialize an XML body even if no XML specific parameters are set

## 3.3.0 - 2013-03-03

* A large number of performance optimizations have been made
* Bug fix: Added 'wb' as a valid write mode for streams
* Bug fix: `Guzzle\Http\Message\Response::json()` now allows scalar values to be returned
* Bug fix: Fixed bug in `Guzzle\Http\Message\Response` where wrapping quotes were stripped from `getEtag()`
* BC: Removed `Guzzle\Http\Utils` class
* BC: Setting a service description on a client will no longer modify the client's command factories.
* BC: Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using
  the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
* BC: `Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getSteamType()` are no longer converted to
  lowercase
* Operation parameter objects are now lazy loaded internally
* Added ErrorResponsePlugin that can throw errors for responses defined in service description operations' errorResponses
* Added support for instantiating responseType=class responseClass classes. Classes must implement
  `Guzzle\Service\Command\ResponseClassInterface`
* Added support for additionalProperties for top-level parameters in responseType=model responseClasses. These
  additional properties also support locations and can be used to parse JSON responses where the outermost part of the
  JSON is an array
* Added support for nested renaming of JSON models (rename sentAs to name)
* CachePlugin
    * Added support for stale-if-error so that the CachePlugin can now serve stale content from the cache on error
    * Debug headers can now added to cached response in the CachePlugin

## 3.2.0 - 2013-02-14

* CurlMulti is no longer reused globally. A new multi object is created per-client. This helps to isolate clients.
* URLs with no path no longer contain a "/" by default
* Guzzle\Http\QueryString does no longer manages the leading "?". This is now handled in Guzzle\Http\Url.
* BadResponseException no longer includes the full request and response message
* Adding setData() to Guzzle\Service\Description\ServiceDescriptionInterface
* Adding getResponseBody() to Guzzle\Http\Message\RequestInterface
* Various updates to classes to use ServiceDescriptionInterface type hints rather than ServiceDescription
* Header values can now be normalized into distinct values when multiple headers are combined with a comma separated list
* xmlEncoding can now be customized for the XML declaration of a XML service description operation
* Guzzle\Http\QueryString now uses Guzzle\Http\QueryAggregator\QueryAggregatorInterface objects to add custom value
  aggregation and no longer uses callbacks
* The URL encoding implementation of Guzzle\Http\QueryString can now be customized
* Bug fix: Filters were not always invoked for array service description parameters
* Bug fix: Redirects now use a target response body rather than a temporary response body
* Bug fix: The default exponential backoff BackoffPlugin was not giving when the request threshold was exceeded
* Bug fix: Guzzle now takes the first found value when grabbing Cache-Control directives

## 3.1.2 - 2013-01-27

* Refactored how operation responses are parsed. Visitors now include a before() method responsible for parsing the
  response body. For example, the XmlVisitor now parses the XML response into an array in the before() method.
* Fixed an issue where cURL would not automatically decompress responses when the Accept-Encoding header was sent
* CURLOPT_SSL_VERIFYHOST is never set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
* Fixed a bug where redirect responses were not chained correctly using getPreviousResponse()
* Setting default headers on a client after setting the user-agent will not erase the user-agent setting

## 3.1.1 - 2013-01-20

* Adding wildcard support to Guzzle\Common\Collection::getPath()
* Adding alias support to ServiceBuilder configs
* Adding Guzzle\Service\Resource\CompositeResourceIteratorFactory and cleaning up factory interface

## 3.1.0 - 2013-01-12

* BC: CurlException now extends from RequestException rather than BadResponseException
* BC: Renamed Guzzle\Plugin\Cache\CanCacheStrategyInterface::canCache() to canCacheRequest() and added CanCacheResponse()
* Added getData to ServiceDescriptionInterface
* Added context array to RequestInterface::setState()
* Bug: Removing hard dependency on the BackoffPlugin from Guzzle\Http
* Bug: Adding required content-type when JSON request visitor adds JSON to a command
* Bug: Fixing the serialization of a service description with custom data
* Made it easier to deal with exceptions thrown when transferring commands or requests in parallel by providing
  an array of successful and failed responses
* Moved getPath from Guzzle\Service\Resource\Model to Guzzle\Common\Collection
* Added Guzzle\Http\IoEmittingEntityBody
* Moved command filtration from validators to location visitors
* Added `extends` attributes to service description parameters
* Added getModels to ServiceDescriptionInterface

## 3.0.7 - 2012-12-19

* Fixing phar detection when forcing a cacert to system if null or true
* Allowing filename to be passed to `Guzzle\Http\Message\Request::setResponseBody()`
* Cleaning up `Guzzle\Common\Collection::inject` method
* Adding a response_body location to service descriptions

## 3.0.6 - 2012-12-09

* CurlMulti performance improvements
* Adding setErrorResponses() to Operation
* composer.json tweaks

## 3.0.5 - 2012-11-18

* Bug: Fixing an infinite recursion bug caused from revalidating with the CachePlugin
* Bug: Response body can now be a string containing "0"
* Bug: Using Guzzle inside of a phar uses system by default but now allows for a custom cacert
* Bug: QueryString::fromString now properly parses query string parameters that contain equal signs
* Added support for XML attributes in service description responses
* DefaultRequestSerializer now supports array URI parameter values for URI template expansion
* Added better mimetype guessing to requests and post files

## 3.0.4 - 2012-11-11

* Bug: Fixed a bug when adding multiple cookies to a request to use the correct glue value
* Bug: Cookies can now be added that have a name, domain, or value set to "0"
* Bug: Using the system cacert bundle when using the Phar
* Added json and xml methods to Response to make it easier to parse JSON and XML response data into data structures
* Enhanced cookie jar de-duplication
* Added the ability to enable strict cookie jars that throw exceptions when invalid cookies are added
* Added setStream to StreamInterface to actually make it possible to implement custom rewind behavior for entity bodies
* Added the ability to create any sort of hash for a stream rather than just an MD5 hash

## 3.0.3 - 2012-11-04

* Implementing redirects in PHP rather than cURL
* Added PECL URI template extension and using as default parser if available
* Bug: Fixed Content-Length parsing of Response factory
* Adding rewind() method to entity bodies and streams. Allows for custom rewinding of non-repeatable streams.
* Adding ToArrayInterface throughout library
* Fixing OauthPlugin to create unique nonce values per request

## 3.0.2 - 2012-10-25

* Magic methods are enabled by default on clients
* Magic methods return the result of a command
* Service clients no longer require a base_url option in the factory
* Bug: Fixed an issue with URI templates where null template variables were being expanded

## 3.0.1 - 2012-10-22

* Models can now be used like regular collection objects by calling filter, map, etc.
* Models no longer require a Parameter structure or initial data in the constructor
* Added a custom AppendIterator to get around a PHP bug with the `\AppendIterator`

## 3.0.0 - 2012-10-15

* Rewrote service description format to be based on Swagger
    * Now based on JSON schema
    * Added nested input structures and nested response models
    * Support for JSON and XML input and output models
    * Renamed `commands` to `operations`
    * Removed dot class notation
    * Removed custom types
* Broke the project into smaller top-level namespaces to be more component friendly
* Removed support for XML configs and descriptions. Use arrays or JSON files.
* Removed the Validation component and Inspector
* Moved all cookie code to Guzzle\Plugin\Cookie
* Magic methods on a Guzzle\Service\Client now return the command un-executed.
* Calling getResult() or getResponse() on a command will lazily execute the command if needed.
* Now shipping with cURL's CA certs and using it by default
* Added previousResponse() method to response objects
* No longer sending Accept and Accept-Encoding headers on every request
* Only sending an Expect header by default when a payload is greater than 1MB
* Added/moved client options:
    * curl.blacklist to curl.option.blacklist
    * Added ssl.certificate_authority
* Added a Guzzle\Iterator component
* Moved plugins from Guzzle\Http\Plugin to Guzzle\Plugin
* Added a more robust backoff retry strategy (replaced the ExponentialBackoffPlugin)
* Added a more robust caching plugin
* Added setBody to response objects
* Updating LogPlugin to use a more flexible MessageFormatter
* Added a completely revamped build process
* Cleaning up Collection class and removing default values from the get method
* Fixed ZF2 cache adapters

## 2.8.8 - 2012-10-15

* Bug: Fixed a cookie issue that caused dot prefixed domains to not match where popular browsers did

## 2.8.7 - 2012-09-30

* Bug: Fixed config file aliases for JSON includes
* Bug: Fixed cookie bug on a request object by using CookieParser to parse cookies on requests
* Bug: Removing the path to a file when sending a Content-Disposition header on a POST upload
* Bug: Hardening request and response parsing to account for missing parts
* Bug: Fixed PEAR packaging
* Bug: Fixed Request::getInfo
* Bug: Fixed cases where CURLM_CALL_MULTI_PERFORM return codes were causing curl transactions to fail
* Adding the ability for the namespace Iterator factory to look in multiple directories
* Added more getters/setters/removers from service descriptions
* Added the ability to remove POST fields from OAuth signatures
* OAuth plugin now supports 2-legged OAuth

## 2.8.6 - 2012-09-05

* Added the ability to modify and build service descriptions
* Added the use of visitors to apply parameters to locations in service descriptions using the dynamic command
* Added a `json` parameter location
* Now allowing dot notation for classes in the CacheAdapterFactory
* Using the union of two arrays rather than an array_merge when extending service builder services and service params
* Ensuring that a service is a string before doing strpos() checks on it when substituting services for references
  in service builder config files.
* Services defined in two different config files that include one another will by default replace the previously
  defined service, but you can now create services that extend themselves and merge their settings over the previous
* The JsonLoader now supports aliasing filenames with different filenames. This allows you to alias something like
  '_default' with a default JSON configuration file.

## 2.8.5 - 2012-08-29

* Bug: Suppressed empty arrays from URI templates
* Bug: Added the missing $options argument from ServiceDescription::factory to enable caching
* Added support for HTTP responses that do not contain a reason phrase in the start-line
* AbstractCommand commands are now invokable
* Added a way to get the data used when signing an Oauth request before a request is sent

## 2.8.4 - 2012-08-15

* Bug: Custom delay time calculations are no longer ignored in the ExponentialBackoffPlugin
* Added the ability to transfer entity bodies as a string rather than streamed. This gets around curl error 65. Set `body_as_string` in a request's curl options to enable.
* Added a StreamInterface, EntityBodyInterface, and added ftell() to Guzzle\Common\Stream
* Added an AbstractEntityBodyDecorator and a ReadLimitEntityBody decorator to transfer only a subset of a decorated stream
* Stream and EntityBody objects will now return the file position to the previous position after a read required operation (e.g. getContentMd5())
* Added additional response status codes
* Removed SSL information from the default User-Agent header
* DELETE requests can now send an entity body
* Added an EventDispatcher to the ExponentialBackoffPlugin and added an ExponentialBackoffLogger to log backoff retries
* Added the ability of the MockPlugin to consume mocked request bodies
* LogPlugin now exposes request and response objects in the extras array

## 2.8.3 - 2012-07-30

* Bug: Fixed a case where empty POST requests were sent as GET requests
* Bug: Fixed a bug in ExponentialBackoffPlugin that caused fatal errors when retrying an EntityEnclosingRequest that does not have a body
* Bug: Setting the response body of a request to null after completing a request, not when setting the state of a request to new
* Added multiple inheritance to service description commands
* Added an ApiCommandInterface and added `getParamNames()` and `hasParam()`
* Removed the default 2mb size cutoff from the Md5ValidatorPlugin so that it now defaults to validating everything
* Changed CurlMulti::perform to pass a smaller timeout to CurlMulti::executeHandles

## 2.8.2 - 2012-07-24

* Bug: Query string values set to 0 are no longer dropped from the query string
* Bug: A Collection object is no longer created each time a call is made to `Guzzle\Service\Command\AbstractCommand::getRequestHeaders()`
* Bug: `+` is now treated as an encoded space when parsing query strings
* QueryString and Collection performance improvements
* Allowing dot notation for class paths in filters attribute of a service descriptions

## 2.8.1 - 2012-07-16

* Loosening Event Dispatcher dependency
* POST redirects can now be customized using CURLOPT_POSTREDIR

## 2.8.0 - 2012-07-15

* BC: Guzzle\Http\Query
    * Query strings with empty variables will always show an equal sign unless the variable is set to QueryString::BLANK (e.g. ?acl= vs ?acl)
    * Changed isEncodingValues() and isEncodingFields() to isUrlEncoding()
    * Changed setEncodeValues(bool) and setEncodeFields(bool) to useUrlEncoding(bool)
    * Changed the aggregation functions of QueryString to be static methods
    * Can now use fromString() with querystrings that have a leading ?
* cURL configuration values can be specified in service descriptions using `curl.` prefixed parameters
* Content-Length is set to 0 before emitting the request.before_send event when sending an empty request body
* Cookies are no longer URL decoded by default
* Bug: URI template variables set to null are no longer expanded

## 2.7.2 - 2012-07-02

* BC: Moving things to get ready for subtree splits. Moving Inflection into Common. Moving Guzzle\Http\Parser to Guzzle\Parser.
* BC: Removing Guzzle\Common\Batch\Batch::count() and replacing it with isEmpty()
* CachePlugin now allows for a custom request parameter function to check if a request can be cached
* Bug fix: CachePlugin now only caches GET and HEAD requests by default
* Bug fix: Using header glue when transferring headers over the wire
* Allowing deeply nested arrays for composite variables in URI templates
* Batch divisors can now return iterators or arrays

## 2.7.1 - 2012-06-26

* Minor patch to update version number in UA string
* Updating build process

## 2.7.0 - 2012-06-25

* BC: Inflection classes moved to Guzzle\Inflection. No longer static methods. Can now inject custom inflectors into classes.
* BC: Removed magic setX methods from commands
* BC: Magic methods mapped to service description commands are now inflected in the command factory rather than the client __call() method
* Verbose cURL options are no longer enabled by default. Set curl.debug to true on a client to enable.
* Bug: Now allowing colons in a response start-line (e.g. HTTP/1.1 503 Service Unavailable: Back-end server is at capacity)
* Guzzle\Service\Resource\ResourceIteratorApplyBatched now internally uses the Guzzle\Common\Batch namespace
* Added Guzzle\Service\Plugin namespace and a PluginCollectionPlugin
* Added the ability to set POST fields and files in a service description
* Guzzle\Http\EntityBody::factory() now accepts objects with a __toString() method
* Adding a command.before_prepare event to clients
* Added BatchClosureTransfer and BatchClosureDivisor
* BatchTransferException now includes references to the batch divisor and transfer strategies
* Fixed some tests so that they pass more reliably
* Added Guzzle\Common\Log\ArrayLogAdapter

## 2.6.6 - 2012-06-10

* BC: Removing Guzzle\Http\Plugin\BatchQueuePlugin
* BC: Removing Guzzle\Service\Command\CommandSet
* Adding generic batching system (replaces the batch queue plugin and command set)
* Updating ZF cache and log adapters and now using ZF's composer repository
* Bug: Setting the name of each ApiParam when creating through an ApiCommand
* Adding result_type, result_doc, deprecated, and doc_url to service descriptions
* Bug: Changed the default cookie header casing back to 'Cookie'

## 2.6.5 - 2012-06-03

* BC: Renaming Guzzle\Http\Message\RequestInterface::getResourceUri() to getResource()
* BC: Removing unused AUTH_BASIC and AUTH_DIGEST constants from
* BC: Guzzle\Http\Cookie is now used to manage Set-Cookie data, not Cookie data
* BC: Renaming methods in the CookieJarInterface
* Moving almost all cookie logic out of the CookiePlugin and into the Cookie or CookieJar implementations
* Making the default glue for HTTP headers ';' instead of ','
* Adding a removeValue to Guzzle\Http\Message\Header
* Adding getCookies() to request interface.
* Making it easier to add event subscribers to HasDispatcherInterface classes. Can now directly call addSubscriber()

## 2.6.4 - 2012-05-30

* BC: Cleaning up how POST files are stored in EntityEnclosingRequest objects. Adding PostFile class.
* BC: Moving ApiCommand specific functionality from the Inspector and on to the ApiCommand
* Bug: Fixing magic method command calls on clients
* Bug: Email constraint only validates strings
* Bug: Aggregate POST fields when POST files are present in curl handle
* Bug: Fixing default User-Agent header
* Bug: Only appending or prepending parameters in commands if they are specified
* Bug: Not requiring response reason phrases or status codes to match a predefined list of codes
* Allowing the use of dot notation for class namespaces when using instance_of constraint
* Added any_match validation constraint
* Added an AsyncPlugin
* Passing request object to the calculateWait method of the ExponentialBackoffPlugin
* Allowing the result of a command object to be changed
* Parsing location and type sub values when instantiating a service description rather than over and over at runtime

## 2.6.3 - 2012-05-23

* [BC] Guzzle\Common\FromConfigInterface no longer requires any config options.
* [BC] Refactoring how POST files are stored on an EntityEnclosingRequest. They are now separate from POST fields.
* You can now use an array of data when creating PUT request bodies in the request factory.
* Removing the requirement that HTTPS requests needed a Cache-Control: public directive to be cacheable.
* [Http] Adding support for Content-Type in multipart POST uploads per upload
* [Http] Added support for uploading multiple files using the same name (foo[0], foo[1])
* Adding more POST data operations for easier manipulation of POST data.
* You can now set empty POST fields.
* The body of a request is only shown on EntityEnclosingRequest objects that do not use POST files.
* Split the Guzzle\Service\Inspector::validateConfig method into two methods. One to initialize when a command is created, and one to validate.
* CS updates

## 2.6.2 - 2012-05-19

* [Http] Better handling of nested scope requests in CurlMulti.  Requests are now always prepares in the send() method rather than the addRequest() method.

## 2.6.1 - 2012-05-19

* [BC] Removing 'path' support in service descriptions.  Use 'uri'.
* [BC] Guzzle\Service\Inspector::parseDocBlock is now protected. Adding getApiParamsForClass() with cache.
* [BC] Removing Guzzle\Common\NullObject.  Use https://github.com/mtdowling/NullObject if you need it.
* [BC] Removing Guzzle\Common\XmlElement.
* All commands, both dynamic and concrete, have ApiCommand objects.
* Adding a fix for CurlMulti so that if all of the connections encounter some sort of curl error, then the loop exits.
* Adding checks to EntityEnclosingRequest so that empty POST files and fields are ignored.
* Making the method signature of Guzzle\Service\Builder\ServiceBuilder::factory more flexible.

## 2.6.0 - 2012-05-15

* [BC] Moving Guzzle\Service\Builder to Guzzle\Service\Builder\ServiceBuilder
* [BC] Executing a Command returns the result of the command rather than the command
* [BC] Moving all HTTP parsing logic to Guzzle\Http\Parsers. Allows for faster C implementations if needed.
* [BC] Changing the Guzzle\Http\Message\Response::setProtocol() method to accept a protocol and version in separate args.
* [BC] Moving ResourceIterator* to Guzzle\Service\Resource
* [BC] Completely refactored ResourceIterators to iterate over a cloned command object
* [BC] Moved Guzzle\Http\UriTemplate to Guzzle\Http\Parser\UriTemplate\UriTemplate
* [BC] Guzzle\Guzzle is now deprecated
* Moving Guzzle\Common\Guzzle::inject to Guzzle\Common\Collection::inject
* Adding Guzzle\Version class to give version information about Guzzle
* Adding Guzzle\Http\Utils class to provide getDefaultUserAgent() and getHttpDate()
* Adding Guzzle\Curl\CurlVersion to manage caching curl_version() data
* ServiceDescription and ServiceBuilder are now cacheable using similar configs
* Changing the format of XML and JSON service builder configs.  Backwards compatible.
* Cleaned up Cookie parsing
* Trimming the default Guzzle User-Agent header
* Adding a setOnComplete() method to Commands that is called when a command completes
* Keeping track of requests that were mocked in the MockPlugin
* Fixed a caching bug in the CacheAdapterFactory
* Inspector objects can be injected into a Command object
* Refactoring a lot of code and tests to be case insensitive when dealing with headers
* Adding Guzzle\Http\Message\HeaderComparison for easy comparison of HTTP headers using a DSL
* Adding the ability to set global option overrides to service builder configs
* Adding the ability to include other service builder config files from within XML and JSON files
* Moving the parseQuery method out of Url and on to QueryString::fromString() as a static factory method.

## 2.5.0 - 2012-05-08

* Major performance improvements
* [BC] Simplifying Guzzle\Common\Collection.  Please check to see if you are using features that are now deprecated.
* [BC] Using a custom validation system that allows a flyweight implementation for much faster validation. No longer using Symfony2 Validation component.
* [BC] No longer supporting "{{ }}" for injecting into command or UriTemplates.  Use "{}"
* Added the ability to passed parameters to all requests created by a client
* Added callback functionality to the ExponentialBackoffPlugin
* Using microtime in ExponentialBackoffPlugin to allow more granular backoff strategies.
* Rewinding request stream bodies when retrying requests
* Exception is thrown when JSON response body cannot be decoded
* Added configurable magic method calls to clients and commands.  This is off by default.
* Fixed a defect that added a hash to every parsed URL part
* Fixed duplicate none generation for OauthPlugin.
* Emitting an event each time a client is generated by a ServiceBuilder
* Using an ApiParams object instead of a Collection for parameters of an ApiCommand
* cache.* request parameters should be renamed to params.cache.*
* Added the ability to set arbitrary curl options on requests (disable_wire, progress, etc.). See CurlHandle.
* Added the ability to disable type validation of service descriptions
* ServiceDescriptions and ServiceBuilders are now Serializable
{
    "name": "guzzlehttp/guzzle",
    "type": "library",
    "description": "Guzzle is a PHP HTTP client library",
    "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
    "homepage": "http://guzzlephp.org/",
    "license": "MIT",
    "authors": [
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        }
    ],
    "require": {
        "php": ">=5.5",
        "guzzlehttp/psr7": "^1.3.1",
        "guzzlehttp/promises": "^1.0"
    },
    "require-dev": {
        "ext-curl": "*",
        "phpunit/phpunit": "^4.0",
        "psr/log": "^1.0"
    },
    "autoload": {
        "files": ["src/functions_include.php"],
        "psr-4": {
            "GuzzleHttp\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "GuzzleHttp\\Tests\\": "tests/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "6.2-dev"
        }
    }
}
Copyright (c) 2011-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS 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.
Guzzle, PHP HTTP client
=======================

[![Build Status](https://travis-ci.org/guzzle/guzzle.svg?branch=master)](https://travis-ci.org/guzzle/guzzle)

Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and
trivial to integrate with web services.

- Simple interface for building query strings, POST requests, streaming large
  uploads, streaming large downloads, using HTTP cookies, uploading JSON data,
  etc...
- Can send both synchronous and asynchronous requests using the same interface.
- Uses PSR-7 interfaces for requests, responses, and streams. This allows you
  to utilize other PSR-7 compatible libraries with Guzzle.
- Abstracts away the underlying HTTP transport, allowing you to write
  environment and transport agnostic code; i.e., no hard dependency on cURL,
  PHP streams, sockets, or non-blocking event loops.
- Middleware system allows you to augment and compose client behavior.

```php
$client = new \GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.github.com/user', [
    'auth' => ['user', 'pass']
]);
echo $res->getStatusCode();
// 200
echo $res->getHeaderLine('content-type');
// 'application/json; charset=utf8'
echo $res->getBody();
// {"type":"User"...'

// Send an asynchronous request.
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
$promise = $client->sendAsync($request)->then(function ($response) {
    echo 'I completed! ' . $response->getBody();
});
$promise->wait();
```

## Help and docs

- [Documentation](http://guzzlephp.org/)
- [stackoverflow](http://stackoverflow.com/questions/tagged/guzzle)
- [Gitter](https://gitter.im/guzzle/guzzle)


## Installing Guzzle

The recommended way to install Guzzle is through
[Composer](http://getcomposer.org).

```bash
# Install Composer
curl -sS https://getcomposer.org/installer | php
```

Next, run the Composer command to install the latest stable version of Guzzle:

```bash
php composer.phar require guzzlehttp/guzzle
```

After installing, you need to require Composer's autoloader:

```php
require 'vendor/autoload.php';
```

You can then later update Guzzle using composer:

 ```bash
composer.phar update
 ```


## Version Guidance

| Version | Status      | Packagist           | Namespace    | Repo                | Docs                | PSR-7 |
|---------|-------------|---------------------|--------------|---------------------|---------------------|-------|
| 3.x     | EOL         | `guzzle/guzzle`     | `Guzzle`     | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No    |
| 4.x     | EOL         | `guzzlehttp/guzzle` | `GuzzleHttp` | N/A                 | N/A                 | No    |
| 5.x     | Maintained  | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No    |
| 6.x     | Latest      | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes   |

[guzzle-3-repo]: https://github.com/guzzle/guzzle3
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
[guzzle-6-repo]: https://github.com/guzzle/guzzle
[guzzle-3-docs]: http://guzzle3.readthedocs.org/en/latest/
[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/
[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/
<?php
namespace GuzzleHttp;

use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * @method ResponseInterface get(string|UriInterface $uri, array $options = [])
 * @method ResponseInterface head(string|UriInterface $uri, array $options = [])
 * @method ResponseInterface put(string|UriInterface $uri, array $options = [])
 * @method ResponseInterface post(string|UriInterface $uri, array $options = [])
 * @method ResponseInterface patch(string|UriInterface $uri, array $options = [])
 * @method ResponseInterface delete(string|UriInterface $uri, array $options = [])
 * @method Promise\PromiseInterface getAsync(string|UriInterface $uri, array $options = [])
 * @method Promise\PromiseInterface headAsync(string|UriInterface $uri, array $options = [])
 * @method Promise\PromiseInterface putAsync(string|UriInterface $uri, array $options = [])
 * @method Promise\PromiseInterface postAsync(string|UriInterface $uri, array $options = [])
 * @method Promise\PromiseInterface patchAsync(string|UriInterface $uri, array $options = [])
 * @method Promise\PromiseInterface deleteAsync(string|UriInterface $uri, array $options = [])
 */
class Client implements ClientInterface
{
    /** @var array Default request options */
    private $config;

    /**
     * Clients accept an array of constructor parameters.
     *
     * Here's an example of creating a client using a base_uri and an array of
     * default request options to apply to each request:
     *
     *     $client = new Client([
     *         'base_uri'        => 'http://www.foo.com/1.0/',
     *         'timeout'         => 0,
     *         'allow_redirects' => false,
     *         'proxy'           => '192.168.16.1:10'
     *     ]);
     *
     * Client configuration settings include the following options:
     *
     * - handler: (callable) Function that transfers HTTP requests over the
     *   wire. The function is called with a Psr7\Http\Message\RequestInterface
     *   and array of transfer options, and must return a
     *   GuzzleHttp\Promise\PromiseInterface that is fulfilled with a
     *   Psr7\Http\Message\ResponseInterface on success. "handler" is a
     *   constructor only option that cannot be overridden in per/request
     *   options. If no handler is provided, a default handler will be created
     *   that enables all of the request options below by attaching all of the
     *   default middleware to the handler.
     * - base_uri: (string|UriInterface) Base URI of the client that is merged
     *   into relative URIs. Can be a string or instance of UriInterface.
     * - **: any request option
     *
     * @param array $config Client configuration settings.
     *
     * @see \GuzzleHttp\RequestOptions for a list of available request options.
     */
    public function __construct(array $config = [])
    {
        if (!isset($config['handler'])) {
            $config['handler'] = HandlerStack::create();
        }

        // Convert the base_uri to a UriInterface
        if (isset($config['base_uri'])) {
            $config['base_uri'] = Psr7\uri_for($config['base_uri']);
        }

        $this->configureDefaults($config);
    }

    public function __call($method, $args)
    {
        if (count($args) < 1) {
            throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
        }

        $uri = $args[0];
        $opts = isset($args[1]) ? $args[1] : [];

        return substr($method, -5) === 'Async'
            ? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
            : $this->request($method, $uri, $opts);
    }

    public function sendAsync(RequestInterface $request, array $options = [])
    {
        // Merge the base URI into the request URI if needed.
        $options = $this->prepareDefaults($options);

        return $this->transfer(
            $request->withUri($this->buildUri($request->getUri(), $options), $request->hasHeader('Host')),
            $options
        );
    }

    public function send(RequestInterface $request, array $options = [])
    {
        $options[RequestOptions::SYNCHRONOUS] = true;
        return $this->sendAsync($request, $options)->wait();
    }

    public function requestAsync($method, $uri = '', array $options = [])
    {
        $options = $this->prepareDefaults($options);
        // Remove request modifying parameter because it can be done up-front.
        $headers = isset($options['headers']) ? $options['headers'] : [];
        $body = isset($options['body']) ? $options['body'] : null;
        $version = isset($options['version']) ? $options['version'] : '1.1';
        // Merge the URI into the base URI.
        $uri = $this->buildUri($uri, $options);
        if (is_array($body)) {
            $this->invalidBody();
        }
        $request = new Psr7\Request($method, $uri, $headers, $body, $version);
        // Remove the option so that they are not doubly-applied.
        unset($options['headers'], $options['body'], $options['version']);

        return $this->transfer($request, $options);
    }

    public function request($method, $uri = '', array $options = [])
    {
        $options[RequestOptions::SYNCHRONOUS] = true;
        return $this->requestAsync($method, $uri, $options)->wait();
    }

    public function getConfig($option = null)
    {
        return $option === null
            ? $this->config
            : (isset($this->config[$option]) ? $this->config[$option] : null);
    }

    private function buildUri($uri, array $config)
    {
        // for BC we accept null which would otherwise fail in uri_for
        $uri = Psr7\uri_for($uri === null ? '' : $uri);

        if (isset($config['base_uri'])) {
            $uri = Psr7\Uri::resolve(Psr7\uri_for($config['base_uri']), $uri);
        }

        return $uri->getScheme() === '' && $uri->getHost() !== '' ? $uri->withScheme('http') : $uri;
    }

    /**
     * Configures the default options for a client.
     *
     * @param array $config
     */
    private function configureDefaults(array $config)
    {
        $defaults = [
            'allow_redirects' => RedirectMiddleware::$defaultSettings,
            'http_errors'     => true,
            'decode_content'  => true,
            'verify'          => true,
            'cookies'         => false
        ];

        // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set.

        // We can only trust the HTTP_PROXY environment variable in a CLI
        // process due to the fact that PHP has no reliable mechanism to
        // get environment variables that start with "HTTP_".
        if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) {
            $defaults['proxy']['http'] = getenv('HTTP_PROXY');
        }

        if ($proxy = getenv('HTTPS_PROXY')) {
            $defaults['proxy']['https'] = $proxy;
        }

        if ($noProxy = getenv('NO_PROXY')) {
            $cleanedNoProxy = str_replace(' ', '', $noProxy);
            $defaults['proxy']['no'] = explode(',', $cleanedNoProxy);
        }

        $this->config = $config + $defaults;

        if (!empty($config['cookies']) && $config['cookies'] === true) {
            $this->config['cookies'] = new CookieJar();
        }

        // Add the default user-agent header.
        if (!isset($this->config['headers'])) {
            $this->config['headers'] = ['User-Agent' => default_user_agent()];
        } else {
            // Add the User-Agent header if one was not already set.
            foreach (array_keys($this->config['headers']) as $name) {
                if (strtolower($name) === 'user-agent') {
                    return;
                }
            }
            $this->config['headers']['User-Agent'] = default_user_agent();
        }
    }

    /**
     * Merges default options into the array.
     *
     * @param array $options Options to modify by reference
     *
     * @return array
     */
    private function prepareDefaults($options)
    {
        $defaults = $this->config;

        if (!empty($defaults['headers'])) {
            // Default headers are only added if they are not present.
            $defaults['_conditional'] = $defaults['headers'];
            unset($defaults['headers']);
        }

        // Special handling for headers is required as they are added as
        // conditional headers and as headers passed to a request ctor.
        if (array_key_exists('headers', $options)) {
            // Allows default headers to be unset.
            if ($options['headers'] === null) {
                $defaults['_conditional'] = null;
                unset($options['headers']);
            } elseif (!is_array($options['headers'])) {
                throw new \InvalidArgumentException('headers must be an array');
            }
        }

        // Shallow merge defaults underneath options.
        $result = $options + $defaults;

        // Remove null values.
        foreach ($result as $k => $v) {
            if ($v === null) {
                unset($result[$k]);
            }
        }

        return $result;
    }

    /**
     * Transfers the given request and applies request options.
     *
     * The URI of the request is not modified and the request options are used
     * as-is without merging in default options.
     *
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return Promise\PromiseInterface
     */
    private function transfer(RequestInterface $request, array $options)
    {
        // save_to -> sink
        if (isset($options['save_to'])) {
            $options['sink'] = $options['save_to'];
            unset($options['save_to']);
        }

        // exceptions -> http_errors
        if (isset($options['exceptions'])) {
            $options['http_errors'] = $options['exceptions'];
            unset($options['exceptions']);
        }

        $request = $this->applyOptions($request, $options);
        $handler = $options['handler'];

        try {
            return Promise\promise_for($handler($request, $options));
        } catch (\Exception $e) {
            return Promise\rejection_for($e);
        }
    }

    /**
     * Applies the array of request options to a request.
     *
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return RequestInterface
     */
    private function applyOptions(RequestInterface $request, array &$options)
    {
        $modify = [];

        if (isset($options['form_params'])) {
            if (isset($options['multipart'])) {
                throw new \InvalidArgumentException('You cannot use '
                    . 'form_params and multipart at the same time. Use the '
                    . 'form_params option if you want to send application/'
                    . 'x-www-form-urlencoded requests, and the multipart '
                    . 'option to send multipart/form-data requests.');
            }
            $options['body'] = http_build_query($options['form_params'], '', '&');
            unset($options['form_params']);
            $options['_conditional']['Content-Type'] = 'application/x-www-form-urlencoded';
        }

        if (isset($options['multipart'])) {
            $options['body'] = new Psr7\MultipartStream($options['multipart']);
            unset($options['multipart']);
        }

        if (isset($options['json'])) {
            $options['body'] = \GuzzleHttp\json_encode($options['json']);
            unset($options['json']);
            $options['_conditional']['Content-Type'] = 'application/json';
        }

        if (!empty($options['decode_content'])
            && $options['decode_content'] !== true
        ) {
            $modify['set_headers']['Accept-Encoding'] = $options['decode_content'];
        }

        if (isset($options['headers'])) {
            if (isset($modify['set_headers'])) {
                $modify['set_headers'] = $options['headers'] + $modify['set_headers'];
            } else {
                $modify['set_headers'] = $options['headers'];
            }
            unset($options['headers']);
        }

        if (isset($options['body'])) {
            if (is_array($options['body'])) {
                $this->invalidBody();
            }
            $modify['body'] = Psr7\stream_for($options['body']);
            unset($options['body']);
        }

        if (!empty($options['auth']) && is_array($options['auth'])) {
            $value = $options['auth'];
            $type = isset($value[2]) ? strtolower($value[2]) : 'basic';
            switch ($type) {
                case 'basic':
                    $modify['set_headers']['Authorization'] = 'Basic '
                        . base64_encode("$value[0]:$value[1]");
                    break;
                case 'digest':
                    // @todo: Do not rely on curl
                    $options['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_DIGEST;
                    $options['curl'][CURLOPT_USERPWD] = "$value[0]:$value[1]";
                    break;
            }
        }

        if (isset($options['query'])) {
            $value = $options['query'];
            if (is_array($value)) {
                $value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
            }
            if (!is_string($value)) {
                throw new \InvalidArgumentException('query must be a string or array');
            }
            $modify['query'] = $value;
            unset($options['query']);
        }

        // Ensure that sink is not an invalid value.
        if (isset($options['sink'])) {
            // TODO: Add more sink validation?
            if (is_bool($options['sink'])) {
                throw new \InvalidArgumentException('sink must not be a boolean');
            }
        }

        $request = Psr7\modify_request($request, $modify);
        if ($request->getBody() instanceof Psr7\MultipartStream) {
            // Use a multipart/form-data POST if a Content-Type is not set.
            $options['_conditional']['Content-Type'] = 'multipart/form-data; boundary='
                . $request->getBody()->getBoundary();
        }

        // Merge in conditional headers if they are not present.
        if (isset($options['_conditional'])) {
            // Build up the changes so it's in a single clone of the message.
            $modify = [];
            foreach ($options['_conditional'] as $k => $v) {
                if (!$request->hasHeader($k)) {
                    $modify['set_headers'][$k] = $v;
                }
            }
            $request = Psr7\modify_request($request, $modify);
            // Don't pass this internal value along to middleware/handlers.
            unset($options['_conditional']);
        }

        return $request;
    }

    private function invalidBody()
    {
        throw new \InvalidArgumentException('Passing in the "body" request '
            . 'option as an array to send a POST request has been deprecated. '
            . 'Please use the "form_params" request option to send a '
            . 'application/x-www-form-urlencoded request, or a the "multipart" '
            . 'request option to send a multipart/form-data request.');
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Client interface for sending HTTP requests.
 */
interface ClientInterface
{
    const VERSION = '6.2.1';

    /**
     * Send an HTTP request.
     *
     * @param RequestInterface $request Request to send
     * @param array            $options Request options to apply to the given
     *                                  request and to the transfer.
     *
     * @return ResponseInterface
     * @throws GuzzleException
     */
    public function send(RequestInterface $request, array $options = []);

    /**
     * Asynchronously send an HTTP request.
     *
     * @param RequestInterface $request Request to send
     * @param array            $options Request options to apply to the given
     *                                  request and to the transfer.
     *
     * @return PromiseInterface
     */
    public function sendAsync(RequestInterface $request, array $options = []);

    /**
     * Create and send an HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string              $method  HTTP method.
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @return ResponseInterface
     * @throws GuzzleException
     */
    public function request($method, $uri, array $options = []);

    /**
     * Create and send an asynchronous HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well. Use an array to provide a URL
     * template and additional variables to use in the URL template expansion.
     *
     * @param string              $method  HTTP method
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply.
     *
     * @return PromiseInterface
     */
    public function requestAsync($method, $uri, array $options = []);

    /**
     * Get a client configuration option.
     *
     * These options include default request options of the client, a "handler"
     * (if utilized by the concrete client), and a "base_uri" if utilized by
     * the concrete client.
     *
     * @param string|null $option The config option to retrieve.
     *
     * @return mixed
     */
    public function getConfig($option = null);
}
<?php
namespace GuzzleHttp\Cookie;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Cookie jar that stores cookies as an array
 */
class CookieJar implements CookieJarInterface
{
    /** @var SetCookie[] Loaded cookie data */
    private $cookies = [];

    /** @var bool */
    private $strictMode;

    /**
     * @param bool $strictMode   Set to true to throw exceptions when invalid
     *                           cookies are added to the cookie jar.
     * @param array $cookieArray Array of SetCookie objects or a hash of
     *                           arrays that can be used with the SetCookie
     *                           constructor
     */
    public function __construct($strictMode = false, $cookieArray = [])
    {
        $this->strictMode = $strictMode;

        foreach ($cookieArray as $cookie) {
            if (!($cookie instanceof SetCookie)) {
                $cookie = new SetCookie($cookie);
            }
            $this->setCookie($cookie);
        }
    }

    /**
     * Create a new Cookie jar from an associative array and domain.
     *
     * @param array  $cookies Cookies to create the jar from
     * @param string $domain  Domain to set the cookies to
     *
     * @return self
     */
    public static function fromArray(array $cookies, $domain)
    {
        $cookieJar = new self();
        foreach ($cookies as $name => $value) {
            $cookieJar->setCookie(new SetCookie([
                'Domain'  => $domain,
                'Name'    => $name,
                'Value'   => $value,
                'Discard' => true
            ]));
        }

        return $cookieJar;
    }

    /**
     * @deprecated
     */
    public static function getCookieValue($value)
    {
        return $value;
    }

    /**
     * Evaluate if this cookie should be persisted to storage
     * that survives between requests.
     *
     * @param SetCookie $cookie Being evaluated.
     * @param bool $allowSessionCookies If we should persist session cookies
     * @return bool
     */
    public static function shouldPersist(
        SetCookie $cookie,
        $allowSessionCookies = false
    ) {
        if ($cookie->getExpires() || $allowSessionCookies) {
            if (!$cookie->getDiscard()) {
                return true;
            }
        }

        return false;
    }

    public function toArray()
    {
        return array_map(function (SetCookie $cookie) {
            return $cookie->toArray();
        }, $this->getIterator()->getArrayCopy());
    }

    public function clear($domain = null, $path = null, $name = null)
    {
        if (!$domain) {
            $this->cookies = [];
            return;
        } elseif (!$path) {
            $this->cookies = array_filter(
                $this->cookies,
                function (SetCookie $cookie) use ($path, $domain) {
                    return !$cookie->matchesDomain($domain);
                }
            );
        } elseif (!$name) {
            $this->cookies = array_filter(
                $this->cookies,
                function (SetCookie $cookie) use ($path, $domain) {
                    return !($cookie->matchesPath($path) &&
                        $cookie->matchesDomain($domain));
                }
            );
        } else {
            $this->cookies = array_filter(
                $this->cookies,
                function (SetCookie $cookie) use ($path, $domain, $name) {
                    return !($cookie->getName() == $name &&
                        $cookie->matchesPath($path) &&
                        $cookie->matchesDomain($domain));
                }
            );
        }
    }

    public function clearSessionCookies()
    {
        $this->cookies = array_filter(
            $this->cookies,
            function (SetCookie $cookie) {
                return !$cookie->getDiscard() && $cookie->getExpires();
            }
        );
    }

    public function setCookie(SetCookie $cookie)
    {
        // If the name string is empty (but not 0), ignore the set-cookie
        // string entirely.
        $name = $cookie->getName();
        if (!$name && $name !== '0') {
            return false;
        }

        // Only allow cookies with set and valid domain, name, value
        $result = $cookie->validate();
        if ($result !== true) {
            if ($this->strictMode) {
                throw new \RuntimeException('Invalid cookie: ' . $result);
            } else {
                $this->removeCookieIfEmpty($cookie);
                return false;
            }
        }

        // Resolve conflicts with previously set cookies
        foreach ($this->cookies as $i => $c) {

            // Two cookies are identical, when their path, and domain are
            // identical.
            if ($c->getPath() != $cookie->getPath() ||
                $c->getDomain() != $cookie->getDomain() ||
                $c->getName() != $cookie->getName()
            ) {
                continue;
            }

            // The previously set cookie is a discard cookie and this one is
            // not so allow the new cookie to be set
            if (!$cookie->getDiscard() && $c->getDiscard()) {
                unset($this->cookies[$i]);
                continue;
            }

            // If the new cookie's expiration is further into the future, then
            // replace the old cookie
            if ($cookie->getExpires() > $c->getExpires()) {
                unset($this->cookies[$i]);
                continue;
            }

            // If the value has changed, we better change it
            if ($cookie->getValue() !== $c->getValue()) {
                unset($this->cookies[$i]);
                continue;
            }

            // The cookie exists, so no need to continue
            return false;
        }

        $this->cookies[] = $cookie;

        return true;
    }

    public function count()
    {
        return count($this->cookies);
    }

    public function getIterator()
    {
        return new \ArrayIterator(array_values($this->cookies));
    }

    public function extractCookies(
        RequestInterface $request,
        ResponseInterface $response
    ) {
        if ($cookieHeader = $response->getHeader('Set-Cookie')) {
            foreach ($cookieHeader as $cookie) {
                $sc = SetCookie::fromString($cookie);
                if (!$sc->getDomain()) {
                    $sc->setDomain($request->getUri()->getHost());
                }
                $this->setCookie($sc);
            }
        }
    }

    public function withCookieHeader(RequestInterface $request)
    {
        $values = [];
        $uri = $request->getUri();
        $scheme = $uri->getScheme();
        $host = $uri->getHost();
        $path = $uri->getPath() ?: '/';

        foreach ($this->cookies as $cookie) {
            if ($cookie->matchesPath($path) &&
                $cookie->matchesDomain($host) &&
                !$cookie->isExpired() &&
                (!$cookie->getSecure() || $scheme === 'https')
            ) {
                $values[] = $cookie->getName() . '='
                    . $cookie->getValue();
            }
        }

        return $values
            ? $request->withHeader('Cookie', implode('; ', $values))
            : $request;
    }

    /**
     * If a cookie already exists and the server asks to set it again with a
     * null value, the cookie must be deleted.
     *
     * @param SetCookie $cookie
     */
    private function removeCookieIfEmpty(SetCookie $cookie)
    {
        $cookieValue = $cookie->getValue();
        if ($cookieValue === null || $cookieValue === '') {
            $this->clear(
                $cookie->getDomain(),
                $cookie->getPath(),
                $cookie->getName()
            );
        }
    }
}
<?php
namespace GuzzleHttp\Cookie;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Stores HTTP cookies.
 *
 * It extracts cookies from HTTP requests, and returns them in HTTP responses.
 * CookieJarInterface instances automatically expire contained cookies when
 * necessary. Subclasses are also responsible for storing and retrieving
 * cookies from a file, database, etc.
 *
 * @link http://docs.python.org/2/library/cookielib.html Inspiration
 */
interface CookieJarInterface extends \Countable, \IteratorAggregate
{
    /**
     * Create a request with added cookie headers.
     *
     * If no matching cookies are found in the cookie jar, then no Cookie
     * header is added to the request and the same request is returned.
     *
     * @param RequestInterface $request Request object to modify.
     *
     * @return RequestInterface returns the modified request.
     */
    public function withCookieHeader(RequestInterface $request);

    /**
     * Extract cookies from an HTTP response and store them in the CookieJar.
     *
     * @param RequestInterface  $request  Request that was sent
     * @param ResponseInterface $response Response that was received
     */
    public function extractCookies(
        RequestInterface $request,
        ResponseInterface $response
    );

    /**
     * Sets a cookie in the cookie jar.
     *
     * @param SetCookie $cookie Cookie to set.
     *
     * @return bool Returns true on success or false on failure
     */
    public function setCookie(SetCookie $cookie);

    /**
     * Remove cookies currently held in the cookie jar.
     *
     * Invoking this method without arguments will empty the whole cookie jar.
     * If given a $domain argument only cookies belonging to that domain will
     * be removed. If given a $domain and $path argument, cookies belonging to
     * the specified path within that domain are removed. If given all three
     * arguments, then the cookie with the specified name, path and domain is
     * removed.
     *
     * @param string $domain Clears cookies matching a domain
     * @param string $path   Clears cookies matching a domain and path
     * @param string $name   Clears cookies matching a domain, path, and name
     *
     * @return CookieJarInterface
     */
    public function clear($domain = null, $path = null, $name = null);

    /**
     * Discard all sessions cookies.
     *
     * Removes cookies that don't have an expire field or a have a discard
     * field set to true. To be called when the user agent shuts down according
     * to RFC 2965.
     */
    public function clearSessionCookies();

    /**
     * Converts the cookie jar to an array.
     *
     * @return array
     */
    public function toArray();
}
<?php
namespace GuzzleHttp\Cookie;

/**
 * Persists non-session cookies using a JSON formatted file
 */
class FileCookieJar extends CookieJar
{
    /** @var string filename */
    private $filename;

    /** @var bool Control whether to persist session cookies or not. */
    private $storeSessionCookies;

    /**
     * Create a new FileCookieJar object
     *
     * @param string $cookieFile        File to store the cookie data
     * @param bool $storeSessionCookies Set to true to store session cookies
     *                                  in the cookie jar.
     *
     * @throws \RuntimeException if the file cannot be found or created
     */
    public function __construct($cookieFile, $storeSessionCookies = false)
    {
        $this->filename = $cookieFile;
        $this->storeSessionCookies = $storeSessionCookies;

        if (file_exists($cookieFile)) {
            $this->load($cookieFile);
        }
    }

    /**
     * Saves the file when shutting down
     */
    public function __destruct()
    {
        $this->save($this->filename);
    }

    /**
     * Saves the cookies to a file.
     *
     * @param string $filename File to save
     * @throws \RuntimeException if the file cannot be found or created
     */
    public function save($filename)
    {
        $json = [];
        foreach ($this as $cookie) {
            /** @var SetCookie $cookie */
            if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
                $json[] = $cookie->toArray();
            }
        }

        $jsonStr = \GuzzleHttp\json_encode($json);
        if (false === file_put_contents($filename, $jsonStr)) {
            throw new \RuntimeException("Unable to save file {$filename}");
        }
    }

    /**
     * Load cookies from a JSON formatted file.
     *
     * Old cookies are kept unless overwritten by newly loaded ones.
     *
     * @param string $filename Cookie file to load.
     * @throws \RuntimeException if the file cannot be loaded.
     */
    public function load($filename)
    {
        $json = file_get_contents($filename);
        if (false === $json) {
            throw new \RuntimeException("Unable to load file {$filename}");
        } elseif ($json === '') {
            return;
        }

        $data = \GuzzleHttp\json_decode($json, true);
        if (is_array($data)) {
            foreach (json_decode($json, true) as $cookie) {
                $this->setCookie(new SetCookie($cookie));
            }
        } elseif (strlen($data)) {
            throw new \RuntimeException("Invalid cookie file: {$filename}");
        }
    }
}
<?php
namespace GuzzleHttp\Cookie;

/**
 * Persists cookies in the client session
 */
class SessionCookieJar extends CookieJar
{
    /** @var string session key */
    private $sessionKey;
    
    /** @var bool Control whether to persist session cookies or not. */
    private $storeSessionCookies;

    /**
     * Create a new SessionCookieJar object
     *
     * @param string $sessionKey        Session key name to store the cookie 
     *                                  data in session
     * @param bool $storeSessionCookies Set to true to store session cookies
     *                                  in the cookie jar.
     */
    public function __construct($sessionKey, $storeSessionCookies = false)
    {
        $this->sessionKey = $sessionKey;
        $this->storeSessionCookies = $storeSessionCookies;
        $this->load();
    }

    /**
     * Saves cookies to session when shutting down
     */
    public function __destruct()
    {
        $this->save();
    }

    /**
     * Save cookies to the client session
     */
    public function save()
    {
        $json = [];
        foreach ($this as $cookie) {
            /** @var SetCookie $cookie */
            if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
                $json[] = $cookie->toArray();
            }
        }

        $_SESSION[$this->sessionKey] = json_encode($json);
    }

    /**
     * Load the contents of the client session into the data array
     */
    protected function load()
    {
        if (!isset($_SESSION[$this->sessionKey])) {
            return;
        }
        $data = json_decode($_SESSION[$this->sessionKey], true);
        if (is_array($data)) {
            foreach ($data as $cookie) {
                $this->setCookie(new SetCookie($cookie));
            }
        } elseif (strlen($data)) {
            throw new \RuntimeException("Invalid cookie data");
        }
    }
}
<?php
namespace GuzzleHttp\Cookie;

/**
 * Set-Cookie object
 */
class SetCookie
{
    /** @var array */
    private static $defaults = [
        'Name'     => null,
        'Value'    => null,
        'Domain'   => null,
        'Path'     => '/',
        'Max-Age'  => null,
        'Expires'  => null,
        'Secure'   => false,
        'Discard'  => false,
        'HttpOnly' => false
    ];

    /** @var array Cookie data */
    private $data;

    /**
     * Create a new SetCookie object from a string
     *
     * @param string $cookie Set-Cookie header string
     *
     * @return self
     */
    public static function fromString($cookie)
    {
        // Create the default return array
        $data = self::$defaults;
        // Explode the cookie string using a series of semicolons
        $pieces = array_filter(array_map('trim', explode(';', $cookie)));
        // The name of the cookie (first kvp) must include an equal sign.
        if (empty($pieces) || !strpos($pieces[0], '=')) {
            return new self($data);
        }

        // Add the cookie pieces into the parsed data array
        foreach ($pieces as $part) {

            $cookieParts = explode('=', $part, 2);
            $key = trim($cookieParts[0]);
            $value = isset($cookieParts[1])
                ? trim($cookieParts[1], " \n\r\t\0\x0B")
                : true;

            // Only check for non-cookies when cookies have been found
            if (empty($data['Name'])) {
                $data['Name'] = $key;
                $data['Value'] = $value;
            } else {
                foreach (array_keys(self::$defaults) as $search) {
                    if (!strcasecmp($search, $key)) {
                        $data[$search] = $value;
                        continue 2;
                    }
                }
                $data[$key] = $value;
            }
        }

        return new self($data);
    }

    /**
     * @param array $data Array of cookie data provided by a Cookie parser
     */
    public function __construct(array $data = [])
    {
        $this->data = array_replace(self::$defaults, $data);
        // Extract the Expires value and turn it into a UNIX timestamp if needed
        if (!$this->getExpires() && $this->getMaxAge()) {
            // Calculate the Expires date
            $this->setExpires(time() + $this->getMaxAge());
        } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
            $this->setExpires($this->getExpires());
        }
    }

    public function __toString()
    {
        $str = $this->data['Name'] . '=' . $this->data['Value'] . '; ';
        foreach ($this->data as $k => $v) {
            if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
                if ($k === 'Expires') {
                    $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; ';
                } else {
                    $str .= ($v === true ? $k : "{$k}={$v}") . '; ';
                }
            }
        }

        return rtrim($str, '; ');
    }

    public function toArray()
    {
        return $this->data;
    }

    /**
     * Get the cookie name
     *
     * @return string
     */
    public function getName()
    {
        return $this->data['Name'];
    }

    /**
     * Set the cookie name
     *
     * @param string $name Cookie name
     */
    public function setName($name)
    {
        $this->data['Name'] = $name;
    }

    /**
     * Get the cookie value
     *
     * @return string
     */
    public function getValue()
    {
        return $this->data['Value'];
    }

    /**
     * Set the cookie value
     *
     * @param string $value Cookie value
     */
    public function setValue($value)
    {
        $this->data['Value'] = $value;
    }

    /**
     * Get the domain
     *
     * @return string|null
     */
    public function getDomain()
    {
        return $this->data['Domain'];
    }

    /**
     * Set the domain of the cookie
     *
     * @param string $domain
     */
    public function setDomain($domain)
    {
        $this->data['Domain'] = $domain;
    }

    /**
     * Get the path
     *
     * @return string
     */
    public function getPath()
    {
        return $this->data['Path'];
    }

    /**
     * Set the path of the cookie
     *
     * @param string $path Path of the cookie
     */
    public function setPath($path)
    {
        $this->data['Path'] = $path;
    }

    /**
     * Maximum lifetime of the cookie in seconds
     *
     * @return int|null
     */
    public function getMaxAge()
    {
        return $this->data['Max-Age'];
    }

    /**
     * Set the max-age of the cookie
     *
     * @param int $maxAge Max age of the cookie in seconds
     */
    public function setMaxAge($maxAge)
    {
        $this->data['Max-Age'] = $maxAge;
    }

    /**
     * The UNIX timestamp when the cookie Expires
     *
     * @return mixed
     */
    public function getExpires()
    {
        return $this->data['Expires'];
    }

    /**
     * Set the unix timestamp for which the cookie will expire
     *
     * @param int $timestamp Unix timestamp
     */
    public function setExpires($timestamp)
    {
        $this->data['Expires'] = is_numeric($timestamp)
            ? (int) $timestamp
            : strtotime($timestamp);
    }

    /**
     * Get whether or not this is a secure cookie
     *
     * @return null|bool
     */
    public function getSecure()
    {
        return $this->data['Secure'];
    }

    /**
     * Set whether or not the cookie is secure
     *
     * @param bool $secure Set to true or false if secure
     */
    public function setSecure($secure)
    {
        $this->data['Secure'] = $secure;
    }

    /**
     * Get whether or not this is a session cookie
     *
     * @return null|bool
     */
    public function getDiscard()
    {
        return $this->data['Discard'];
    }

    /**
     * Set whether or not this is a session cookie
     *
     * @param bool $discard Set to true or false if this is a session cookie
     */
    public function setDiscard($discard)
    {
        $this->data['Discard'] = $discard;
    }

    /**
     * Get whether or not this is an HTTP only cookie
     *
     * @return bool
     */
    public function getHttpOnly()
    {
        return $this->data['HttpOnly'];
    }

    /**
     * Set whether or not this is an HTTP only cookie
     *
     * @param bool $httpOnly Set to true or false if this is HTTP only
     */
    public function setHttpOnly($httpOnly)
    {
        $this->data['HttpOnly'] = $httpOnly;
    }

    /**
     * Check if the cookie matches a path value.
     *
     * A request-path path-matches a given cookie-path if at least one of
     * the following conditions holds:
     *
     * - The cookie-path and the request-path are identical.
     * - The cookie-path is a prefix of the request-path, and the last
     *   character of the cookie-path is %x2F ("/").
     * - The cookie-path is a prefix of the request-path, and the first
     *   character of the request-path that is not included in the cookie-
     *   path is a %x2F ("/") character.
     *
     * @param string $requestPath Path to check against
     *
     * @return bool
     */
    public function matchesPath($requestPath)
    {
        $cookiePath = $this->getPath();

        // Match on exact matches or when path is the default empty "/"
        if ($cookiePath === '/' || $cookiePath == $requestPath) {
            return true;
        }

        // Ensure that the cookie-path is a prefix of the request path.
        if (0 !== strpos($requestPath, $cookiePath)) {
            return false;
        }

        // Match if the last character of the cookie-path is "/"
        if (substr($cookiePath, -1, 1) === '/') {
            return true;
        }

        // Match if the first character not included in cookie path is "/"
        return substr($requestPath, strlen($cookiePath), 1) === '/';
    }

    /**
     * Check if the cookie matches a domain value
     *
     * @param string $domain Domain to check against
     *
     * @return bool
     */
    public function matchesDomain($domain)
    {
        // Remove the leading '.' as per spec in RFC 6265.
        // http://tools.ietf.org/html/rfc6265#section-5.2.3
        $cookieDomain = ltrim($this->getDomain(), '.');

        // Domain not set or exact match.
        if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
            return true;
        }

        // Matching the subdomain according to RFC 6265.
        // http://tools.ietf.org/html/rfc6265#section-5.1.3
        if (filter_var($domain, FILTER_VALIDATE_IP)) {
            return false;
        }

        return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/', $domain);
    }

    /**
     * Check if the cookie is expired
     *
     * @return bool
     */
    public function isExpired()
    {
        return $this->getExpires() && time() > $this->getExpires();
    }

    /**
     * Check if the cookie is valid according to RFC 6265
     *
     * @return bool|string Returns true if valid or an error message if invalid
     */
    public function validate()
    {
        // Names must not be empty, but can be 0
        $name = $this->getName();
        if (empty($name) && !is_numeric($name)) {
            return 'The cookie name must not be empty';
        }

        // Check if any of the invalid characters are present in the cookie name
        if (preg_match(
            '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
            $name)
        ) {
            return 'Cookie name must not contain invalid characters: ASCII '
                . 'Control characters (0-31;127), space, tab and the '
                . 'following characters: ()<>@,;:\"/?={}';
        }

        // Value must not be empty, but can be 0
        $value = $this->getValue();
        if (empty($value) && !is_numeric($value)) {
            return 'The cookie value must not be empty';
        }

        // Domains must not be empty, but can be 0
        // A "0" is not a valid internet domain, but may be used as server name
        // in a private network.
        $domain = $this->getDomain();
        if (empty($domain) && !is_numeric($domain)) {
            return 'The cookie domain must not be empty';
        }

        return true;
    }
}
<?php
namespace GuzzleHttp\Exception;

/**
 * Exception when an HTTP error occurs (4xx or 5xx error)
 */
class BadResponseException extends RequestException {}
<?php
namespace GuzzleHttp\Exception;

/**
 * Exception when a client error is encountered (4xx codes)
 */
class ClientException extends BadResponseException {}
<?php
namespace GuzzleHttp\Exception;

use Psr\Http\Message\RequestInterface;

/**
 * Exception thrown when a connection cannot be established.
 *
 * Note that no response is present for a ConnectException
 */
class ConnectException extends RequestException
{
    public function __construct(
        $message,
        RequestInterface $request,
        \Exception $previous = null,
        array $handlerContext = []
    ) {
        parent::__construct($message, $request, null, $previous, $handlerContext);
    }

    /**
     * @return null
     */
    public function getResponse()
    {
        return null;
    }

    /**
     * @return bool
     */
    public function hasResponse()
    {
        return false;
    }
}
<?php
namespace GuzzleHttp\Exception;

interface GuzzleException {}
<?php
namespace GuzzleHttp\Exception;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\UriInterface;

/**
 * HTTP Request exception
 */
class RequestException extends TransferException
{
    /** @var RequestInterface */
    private $request;

    /** @var ResponseInterface */
    private $response;

    /** @var array */
    private $handlerContext;

    public function __construct(
        $message,
        RequestInterface $request,
        ResponseInterface $response = null,
        \Exception $previous = null,
        array $handlerContext = []
    ) {
        // Set the code of the exception if the response is set and not future.
        $code = $response && !($response instanceof PromiseInterface)
            ? $response->getStatusCode()
            : 0;
        parent::__construct($message, $code, $previous);
        $this->request = $request;
        $this->response = $response;
        $this->handlerContext = $handlerContext;
    }

    /**
     * Wrap non-RequestExceptions with a RequestException
     *
     * @param RequestInterface $request
     * @param \Exception       $e
     *
     * @return RequestException
     */
    public static function wrapException(RequestInterface $request, \Exception $e)
    {
        return $e instanceof RequestException
            ? $e
            : new RequestException($e->getMessage(), $request, null, $e);
    }

    /**
     * Factory method to create a new exception with a normalized error message
     *
     * @param RequestInterface  $request  Request
     * @param ResponseInterface $response Response received
     * @param \Exception        $previous Previous exception
     * @param array             $ctx      Optional handler context.
     *
     * @return self
     */
    public static function create(
        RequestInterface $request,
        ResponseInterface $response = null,
        \Exception $previous = null,
        array $ctx = []
    ) {
        if (!$response) {
            return new self(
                'Error completing request',
                $request,
                null,
                $previous,
                $ctx
            );
        }

        $level = (int) floor($response->getStatusCode() / 100);
        if ($level === 4) {
            $label = 'Client error';
            $className = __NAMESPACE__ . '\\ClientException';
        } elseif ($level === 5) {
            $label = 'Server error';
            $className = __NAMESPACE__ . '\\ServerException';
        } else {
            $label = 'Unsuccessful request';
            $className = __CLASS__;
        }

        $uri = $request->getUri();
        $uri = static::obfuscateUri($uri);

        // Server Error: `GET /` resulted in a `404 Not Found` response:
        // <html> ... (truncated)
        $message = sprintf(
            '%s: `%s` resulted in a `%s` response',
            $label,
            $request->getMethod() . ' ' . $uri,
            $response->getStatusCode() . ' ' . $response->getReasonPhrase()
        );

        $summary = static::getResponseBodySummary($response);

        if ($summary !== null) {
            $message .= ":\n{$summary}\n";
        }

        return new $className($message, $request, $response, $previous, $ctx);
    }

    /**
     * Get a short summary of the response
     *
     * Will return `null` if the response is not printable.
     *
     * @param ResponseInterface $response
     *
     * @return string|null
     */
    public static function getResponseBodySummary(ResponseInterface $response)
    {
        $body = $response->getBody();

        if (!$body->isSeekable()) {
            return null;
        }

        $size = $body->getSize();
        $summary = $body->read(120);
        $body->rewind();

        if ($size > 120) {
            $summary .= ' (truncated...)';
        }

        // Matches any printable character, including unicode characters:
        // letters, marks, numbers, punctuation, spacing, and separators.
        if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/', $summary)) {
            return null;
        }

        return $summary;
    }

    /**
     * Obfuscates URI if there is an username and a password present
     *
     * @param UriInterface $uri
     *
     * @return UriInterface
     */
    private static function obfuscateUri($uri)
    {
        $userInfo = $uri->getUserInfo();

        if (false !== ($pos = strpos($userInfo, ':'))) {
            return $uri->withUserInfo(substr($userInfo, 0, $pos), '***');
        }

        return $uri;
    }

    /**
     * Get the request that caused the exception
     *
     * @return RequestInterface
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * Get the associated response
     *
     * @return ResponseInterface|null
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Check if a response was received
     *
     * @return bool
     */
    public function hasResponse()
    {
        return $this->response !== null;
    }

    /**
     * Get contextual information about the error from the underlying handler.
     *
     * The contents of this array will vary depending on which handler you are
     * using. It may also be just an empty array. Relying on this data will
     * couple you to a specific handler, but can give more debug information
     * when needed.
     *
     * @return array
     */
    public function getHandlerContext()
    {
        return $this->handlerContext;
    }
}
<?php
namespace GuzzleHttp\Exception;

use Psr\Http\Message\StreamInterface;

/**
 * Exception thrown when a seek fails on a stream.
 */
class SeekException extends \RuntimeException implements GuzzleException
{
    private $stream;

    public function __construct(StreamInterface $stream, $pos = 0, $msg = '')
    {
        $this->stream = $stream;
        $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
        parent::__construct($msg);
    }

    /**
     * @return StreamInterface
     */
    public function getStream()
    {
        return $this->stream;
    }
}
<?php
namespace GuzzleHttp\Exception;

/**
 * Exception when a server error is encountered (5xx codes)
 */
class ServerException extends BadResponseException {}
<?php
namespace GuzzleHttp\Exception;

class TooManyRedirectsException extends RequestException {}
<?php
namespace GuzzleHttp\Exception;

class TransferException extends \RuntimeException implements GuzzleException {}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\Handler\CurlMultiHandler;
use GuzzleHttp\Handler\Proxy;
use GuzzleHttp\Handler\StreamHandler;

/**
 * Expands a URI template
 *
 * @param string $template  URI template
 * @param array  $variables Template variables
 *
 * @return string
 */
function uri_template($template, array $variables)
{
    if (extension_loaded('uri_template')) {
        // @codeCoverageIgnoreStart
        return \uri_template($template, $variables);
        // @codeCoverageIgnoreEnd
    }

    static $uriTemplate;
    if (!$uriTemplate) {
        $uriTemplate = new UriTemplate();
    }

    return $uriTemplate->expand($template, $variables);
}

/**
 * Debug function used to describe the provided value type and class.
 *
 * @param mixed $input
 *
 * @return string Returns a string containing the type of the variable and
 *                if a class is provided, the class name.
 */
function describe_type($input)
{
    switch (gettype($input)) {
        case 'object':
            return 'object(' . get_class($input) . ')';
        case 'array':
            return 'array(' . count($input) . ')';
        default:
            ob_start();
            var_dump($input);
            // normalize float vs double
            return str_replace('double(', 'float(', rtrim(ob_get_clean()));
    }
}

/**
 * Parses an array of header lines into an associative array of headers.
 *
 * @param array $lines Header lines array of strings in the following
 *                     format: "Name: Value"
 * @return array
 */
function headers_from_lines($lines)
{
    $headers = [];

    foreach ($lines as $line) {
        $parts = explode(':', $line, 2);
        $headers[trim($parts[0])][] = isset($parts[1])
            ? trim($parts[1])
            : null;
    }

    return $headers;
}

/**
 * Returns a debug stream based on the provided variable.
 *
 * @param mixed $value Optional value
 *
 * @return resource
 */
function debug_resource($value = null)
{
    if (is_resource($value)) {
        return $value;
    } elseif (defined('STDOUT')) {
        return STDOUT;
    }

    return fopen('php://output', 'w');
}

/**
 * Chooses and creates a default handler to use based on the environment.
 *
 * The returned handler is not wrapped by any default middlewares.
 *
 * @throws \RuntimeException if no viable Handler is available.
 * @return callable Returns the best handler for the given system.
 */
function choose_handler()
{
    $handler = null;
    if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
        $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
    } elseif (function_exists('curl_exec')) {
        $handler = new CurlHandler();
    } elseif (function_exists('curl_multi_exec')) {
        $handler = new CurlMultiHandler();
    }

    if (ini_get('allow_url_fopen')) {
        $handler = $handler
            ? Proxy::wrapStreaming($handler, new StreamHandler())
            : new StreamHandler();
    } elseif (!$handler) {
        throw new \RuntimeException('GuzzleHttp requires cURL, the '
            . 'allow_url_fopen ini setting, or a custom HTTP handler.');
    }

    return $handler;
}

/**
 * Get the default User-Agent string to use with Guzzle
 *
 * @return string
 */
function default_user_agent()
{
    static $defaultAgent = '';

    if (!$defaultAgent) {
        $defaultAgent = 'GuzzleHttp/' . Client::VERSION;
        if (extension_loaded('curl') && function_exists('curl_version')) {
            $defaultAgent .= ' curl/' . \curl_version()['version'];
        }
        $defaultAgent .= ' PHP/' . PHP_VERSION;
    }

    return $defaultAgent;
}

/**
 * Returns the default cacert bundle for the current system.
 *
 * First, the openssl.cafile and curl.cainfo php.ini settings are checked.
 * If those settings are not configured, then the common locations for
 * bundles found on Red Hat, CentOS, Fedora, Ubuntu, Debian, FreeBSD, OS X
 * and Windows are checked. If any of these file locations are found on
 * disk, they will be utilized.
 *
 * Note: the result of this function is cached for subsequent calls.
 *
 * @return string
 * @throws \RuntimeException if no bundle can be found.
 */
function default_ca_bundle()
{
    static $cached = null;
    static $cafiles = [
        // Red Hat, CentOS, Fedora (provided by the ca-certificates package)
        '/etc/pki/tls/certs/ca-bundle.crt',
        // Ubuntu, Debian (provided by the ca-certificates package)
        '/etc/ssl/certs/ca-certificates.crt',
        // FreeBSD (provided by the ca_root_nss package)
        '/usr/local/share/certs/ca-root-nss.crt',
        // OS X provided by homebrew (using the default path)
        '/usr/local/etc/openssl/cert.pem',
        // Google app engine
        '/etc/ca-certificates.crt',
        // Windows?
        'C:\\windows\\system32\\curl-ca-bundle.crt',
        'C:\\windows\\curl-ca-bundle.crt',
    ];

    if ($cached) {
        return $cached;
    }

    if ($ca = ini_get('openssl.cafile')) {
        return $cached = $ca;
    }

    if ($ca = ini_get('curl.cainfo')) {
        return $cached = $ca;
    }

    foreach ($cafiles as $filename) {
        if (file_exists($filename)) {
            return $cached = $filename;
        }
    }

    throw new \RuntimeException(<<< EOT
No system CA bundle could be found in any of the the common system locations.
PHP versions earlier than 5.6 are not properly configured to use the system's
CA bundle by default. In order to verify peer certificates, you will need to
supply the path on disk to a certificate bundle to the 'verify' request
option: http://docs.guzzlephp.org/en/latest/clients.html#verify. If you do not
need a specific certificate bundle, then Mozilla provides a commonly used CA
bundle which can be downloaded here (provided by the maintainer of cURL):
https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt. Once
you have a CA bundle available on disk, you can set the 'openssl.cafile' PHP
ini setting to point to the path to the file, allowing you to omit the 'verify'
request option. See http://curl.haxx.se/docs/sslcerts.html for more
information.
EOT
    );
}

/**
 * Creates an associative array of lowercase header names to the actual
 * header casing.
 *
 * @param array $headers
 *
 * @return array
 */
function normalize_header_keys(array $headers)
{
    $result = [];
    foreach (array_keys($headers) as $key) {
        $result[strtolower($key)] = $key;
    }

    return $result;
}

/**
 * Returns true if the provided host matches any of the no proxy areas.
 *
 * This method will strip a port from the host if it is present. Each pattern
 * can be matched with an exact match (e.g., "foo.com" == "foo.com") or a
 * partial match: (e.g., "foo.com" == "baz.foo.com" and ".foo.com" ==
 * "baz.foo.com", but ".foo.com" != "foo.com").
 *
 * Areas are matched in the following cases:
 * 1. "*" (without quotes) always matches any hosts.
 * 2. An exact match.
 * 3. The area starts with "." and the area is the last part of the host. e.g.
 *    '.mit.edu' will match any host that ends with '.mit.edu'.
 *
 * @param string $host         Host to check against the patterns.
 * @param array  $noProxyArray An array of host patterns.
 *
 * @return bool
 */
function is_host_in_noproxy($host, array $noProxyArray)
{
    if (strlen($host) === 0) {
        throw new \InvalidArgumentException('Empty host provided');
    }

    // Strip port if present.
    if (strpos($host, ':')) {
        $host = explode($host, ':', 2)[0];
    }

    foreach ($noProxyArray as $area) {
        // Always match on wildcards.
        if ($area === '*') {
            return true;
        } elseif (empty($area)) {
            // Don't match on empty values.
            continue;
        } elseif ($area === $host) {
            // Exact matches.
            return true;
        } else {
            // Special match if the area when prefixed with ".". Remove any
            // existing leading "." and add a new leading ".".
            $area = '.' . ltrim($area, '.');
            if (substr($host, -(strlen($area))) === $area) {
                return true;
            }
        }
    }

    return false;
}

/**
 * Wrapper for json_decode that throws when an error occurs.
 *
 * @param string $json    JSON data to parse
 * @param bool $assoc     When true, returned objects will be converted
 *                        into associative arrays.
 * @param int    $depth   User specified recursion depth.
 * @param int    $options Bitmask of JSON decode options.
 *
 * @return mixed
 * @throws \InvalidArgumentException if the JSON cannot be decoded.
 * @link http://www.php.net/manual/en/function.json-decode.php
 */
function json_decode($json, $assoc = false, $depth = 512, $options = 0)
{
    $data = \json_decode($json, $assoc, $depth, $options);
    if (JSON_ERROR_NONE !== json_last_error()) {
        throw new \InvalidArgumentException(
            'json_decode error: ' . json_last_error_msg());
    }

    return $data;
}

/**
 * Wrapper for JSON encoding that throws when an error occurs.
 *
 * @param mixed $value   The value being encoded
 * @param int    $options JSON encode option bitmask
 * @param int    $depth   Set the maximum depth. Must be greater than zero.
 *
 * @return string
 * @throws \InvalidArgumentException if the JSON cannot be encoded.
 * @link http://www.php.net/manual/en/function.json-encode.php
 */
function json_encode($value, $options = 0, $depth = 512)
{
    $json = \json_encode($value, $options, $depth);
    if (JSON_ERROR_NONE !== json_last_error()) {
        throw new \InvalidArgumentException(
            'json_encode error: ' . json_last_error_msg());
    }

    return $json;
}
<?php

// Don't redefine the functions if included multiple times.
if (!function_exists('GuzzleHttp\uri_template')) {
    require __DIR__ . '/functions.php';
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;

/**
 * Creates curl resources from a request
 */
class CurlFactory implements CurlFactoryInterface
{
    /** @var array */
    private $handles;

    /** @var int Total number of idle handles to keep in cache */
    private $maxHandles;

    /**
     * @param int $maxHandles Maximum number of idle handles.
     */
    public function __construct($maxHandles)
    {
        $this->maxHandles = $maxHandles;
    }

    public function create(RequestInterface $request, array $options)
    {
        if (isset($options['curl']['body_as_string'])) {
            $options['_body_as_string'] = $options['curl']['body_as_string'];
            unset($options['curl']['body_as_string']);
        }

        $easy = new EasyHandle;
        $easy->request = $request;
        $easy->options = $options;
        $conf = $this->getDefaultConf($easy);
        $this->applyMethod($easy, $conf);
        $this->applyHandlerOptions($easy, $conf);
        $this->applyHeaders($easy, $conf);
        unset($conf['_headers']);

        // Add handler options from the request configuration options
        if (isset($options['curl'])) {
            $conf = array_replace($conf, $options['curl']);
        }

        $conf[CURLOPT_HEADERFUNCTION] = $this->createHeaderFn($easy);
        $easy->handle = $this->handles
            ? array_pop($this->handles)
            : curl_init();
        curl_setopt_array($easy->handle, $conf);

        return $easy;
    }

    public function release(EasyHandle $easy)
    {
        $resource = $easy->handle;
        unset($easy->handle);

        if (count($this->handles) >= $this->maxHandles) {
            curl_close($resource);
        } else {
            // Remove all callback functions as they can hold onto references
            // and are not cleaned up by curl_reset. Using curl_setopt_array
            // does not work for some reason, so removing each one
            // individually.
            curl_setopt($resource, CURLOPT_HEADERFUNCTION, null);
            curl_setopt($resource, CURLOPT_READFUNCTION, null);
            curl_setopt($resource, CURLOPT_WRITEFUNCTION, null);
            curl_setopt($resource, CURLOPT_PROGRESSFUNCTION, null);
            curl_reset($resource);
            $this->handles[] = $resource;
        }
    }

    /**
     * Completes a cURL transaction, either returning a response promise or a
     * rejected promise.
     *
     * @param callable             $handler
     * @param EasyHandle           $easy
     * @param CurlFactoryInterface $factory Dictates how the handle is released
     *
     * @return \GuzzleHttp\Promise\PromiseInterface
     */
    public static function finish(
        callable $handler,
        EasyHandle $easy,
        CurlFactoryInterface $factory
    ) {
        if (isset($easy->options['on_stats'])) {
            self::invokeStats($easy);
        }

        if (!$easy->response || $easy->errno) {
            return self::finishError($handler, $easy, $factory);
        }

        // Return the response if it is present and there is no error.
        $factory->release($easy);

        // Rewind the body of the response if possible.
        $body = $easy->response->getBody();
        if ($body->isSeekable()) {
            $body->rewind();
        }

        return new FulfilledPromise($easy->response);
    }

    private static function invokeStats(EasyHandle $easy)
    {
        $curlStats = curl_getinfo($easy->handle);
        $stats = new TransferStats(
            $easy->request,
            $easy->response,
            $curlStats['total_time'],
            $easy->errno,
            $curlStats
        );
        call_user_func($easy->options['on_stats'], $stats);
    }

    private static function finishError(
        callable $handler,
        EasyHandle $easy,
        CurlFactoryInterface $factory
    ) {
        // Get error information and release the handle to the factory.
        $ctx = [
            'errno' => $easy->errno,
            'error' => curl_error($easy->handle),
        ] + curl_getinfo($easy->handle);
        $factory->release($easy);

        // Retry when nothing is present or when curl failed to rewind.
        if (empty($easy->options['_err_message'])
            && (!$easy->errno || $easy->errno == 65)
        ) {
            return self::retryFailedRewind($handler, $easy, $ctx);
        }

        return self::createRejection($easy, $ctx);
    }

    private static function createRejection(EasyHandle $easy, array $ctx)
    {
        static $connectionErrors = [
            CURLE_OPERATION_TIMEOUTED  => true,
            CURLE_COULDNT_RESOLVE_HOST => true,
            CURLE_COULDNT_CONNECT      => true,
            CURLE_SSL_CONNECT_ERROR    => true,
            CURLE_GOT_NOTHING          => true,
        ];

        // If an exception was encountered during the onHeaders event, then
        // return a rejected promise that wraps that exception.
        if ($easy->onHeadersException) {
            return new RejectedPromise(
                new RequestException(
                    'An error was encountered during the on_headers event',
                    $easy->request,
                    $easy->response,
                    $easy->onHeadersException,
                    $ctx
                )
            );
        }

        $message = sprintf(
            'cURL error %s: %s (%s)',
            $ctx['errno'],
            $ctx['error'],
            'see http://curl.haxx.se/libcurl/c/libcurl-errors.html'
        );

        // Create a connection exception if it was a specific error code.
        $error = isset($connectionErrors[$easy->errno])
            ? new ConnectException($message, $easy->request, null, $ctx)
            : new RequestException($message, $easy->request, $easy->response, null, $ctx);

        return new RejectedPromise($error);
    }

    private function getDefaultConf(EasyHandle $easy)
    {
        $conf = [
            '_headers'             => $easy->request->getHeaders(),
            CURLOPT_CUSTOMREQUEST  => $easy->request->getMethod(),
            CURLOPT_URL            => (string) $easy->request->getUri()->withFragment(''),
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_HEADER         => false,
            CURLOPT_CONNECTTIMEOUT => 150,
        ];

        if (defined('CURLOPT_PROTOCOLS')) {
            $conf[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }

        $version = $easy->request->getProtocolVersion();
        if ($version == 1.1) {
            $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
        } elseif ($version == 2.0) {
            $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
        } else {
            $conf[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
        }

        return $conf;
    }

    private function applyMethod(EasyHandle $easy, array &$conf)
    {
        $body = $easy->request->getBody();
        $size = $body->getSize();

        if ($size === null || $size > 0) {
            $this->applyBody($easy->request, $easy->options, $conf);
            return;
        }

        $method = $easy->request->getMethod();
        if ($method === 'PUT' || $method === 'POST') {
            // See http://tools.ietf.org/html/rfc7230#section-3.3.2
            if (!$easy->request->hasHeader('Content-Length')) {
                $conf[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
            }
        } elseif ($method === 'HEAD') {
            $conf[CURLOPT_NOBODY] = true;
            unset(
                $conf[CURLOPT_WRITEFUNCTION],
                $conf[CURLOPT_READFUNCTION],
                $conf[CURLOPT_FILE],
                $conf[CURLOPT_INFILE]
            );
        }
    }

    private function applyBody(RequestInterface $request, array $options, array &$conf)
    {
        $size = $request->hasHeader('Content-Length')
            ? (int) $request->getHeaderLine('Content-Length')
            : null;

        // Send the body as a string if the size is less than 1MB OR if the
        // [curl][body_as_string] request value is set.
        if (($size !== null && $size < 1000000) ||
            !empty($options['_body_as_string'])
        ) {
            $conf[CURLOPT_POSTFIELDS] = (string) $request->getBody();
            // Don't duplicate the Content-Length header
            $this->removeHeader('Content-Length', $conf);
            $this->removeHeader('Transfer-Encoding', $conf);
        } else {
            $conf[CURLOPT_UPLOAD] = true;
            if ($size !== null) {
                $conf[CURLOPT_INFILESIZE] = $size;
                $this->removeHeader('Content-Length', $conf);
            }
            $body = $request->getBody();
            if ($body->isSeekable()) {
                $body->rewind();
            }
            $conf[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
                return $body->read($length);
            };
        }

        // If the Expect header is not present, prevent curl from adding it
        if (!$request->hasHeader('Expect')) {
            $conf[CURLOPT_HTTPHEADER][] = 'Expect:';
        }

        // cURL sometimes adds a content-type by default. Prevent this.
        if (!$request->hasHeader('Content-Type')) {
            $conf[CURLOPT_HTTPHEADER][] = 'Content-Type:';
        }
    }

    private function applyHeaders(EasyHandle $easy, array &$conf)
    {
        foreach ($conf['_headers'] as $name => $values) {
            foreach ($values as $value) {
                $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
            }
        }

        // Remove the Accept header if one was not set
        if (!$easy->request->hasHeader('Accept')) {
            $conf[CURLOPT_HTTPHEADER][] = 'Accept:';
        }
    }

    /**
     * Remove a header from the options array.
     *
     * @param string $name    Case-insensitive header to remove
     * @param array  $options Array of options to modify
     */
    private function removeHeader($name, array &$options)
    {
        foreach (array_keys($options['_headers']) as $key) {
            if (!strcasecmp($key, $name)) {
                unset($options['_headers'][$key]);
                return;
            }
        }
    }

    private function applyHandlerOptions(EasyHandle $easy, array &$conf)
    {
        $options = $easy->options;
        if (isset($options['verify'])) {
            if ($options['verify'] === false) {
                unset($conf[CURLOPT_CAINFO]);
                $conf[CURLOPT_SSL_VERIFYHOST] = 0;
                $conf[CURLOPT_SSL_VERIFYPEER] = false;
            } else {
                $conf[CURLOPT_SSL_VERIFYHOST] = 2;
                $conf[CURLOPT_SSL_VERIFYPEER] = true;
                if (is_string($options['verify'])) {
                    $conf[CURLOPT_CAINFO] = $options['verify'];
                    if (!file_exists($options['verify'])) {
                        throw new \InvalidArgumentException(
                            "SSL CA bundle not found: {$options['verify']}"
                        );
                    }
                }
            }
        }

        if (!empty($options['decode_content'])) {
            $accept = $easy->request->getHeaderLine('Accept-Encoding');
            if ($accept) {
                $conf[CURLOPT_ENCODING] = $accept;
            } else {
                $conf[CURLOPT_ENCODING] = '';
                // Don't let curl send the header over the wire
                $conf[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
            }
        }

        if (isset($options['sink'])) {
            $sink = $options['sink'];
            if (!is_string($sink)) {
                $sink = \GuzzleHttp\Psr7\stream_for($sink);
            } elseif (!is_dir(dirname($sink))) {
                // Ensure that the directory exists before failing in curl.
                throw new \RuntimeException(sprintf(
                    'Directory %s does not exist for sink value of %s',
                    dirname($sink),
                    $sink
                ));
            } else {
                $sink = new LazyOpenStream($sink, 'w+');
            }
            $easy->sink = $sink;
            $conf[CURLOPT_WRITEFUNCTION] = function ($ch, $write) use ($sink) {
                return $sink->write($write);
            };
        } else {
            // Use a default temp stream if no sink was set.
            $conf[CURLOPT_FILE] = fopen('php://temp', 'w+');
            $easy->sink = Psr7\stream_for($conf[CURLOPT_FILE]);
        }

        if (isset($options['timeout'])) {
            $conf[CURLOPT_TIMEOUT_MS] = $options['timeout'] * 1000;
        }

        if (isset($options['connect_timeout'])) {
            $conf[CURLOPT_CONNECTTIMEOUT_MS] = $options['connect_timeout'] * 1000;
        }

        if (isset($options['proxy'])) {
            if (!is_array($options['proxy'])) {
                $conf[CURLOPT_PROXY] = $options['proxy'];
            } else {
                $scheme = $easy->request->getUri()->getScheme();
                if (isset($options['proxy'][$scheme])) {
                    $host = $easy->request->getUri()->getHost();
                    if (!isset($options['proxy']['no']) ||
                        !\GuzzleHttp\is_host_in_noproxy($host, $options['proxy']['no'])
                    ) {
                        $conf[CURLOPT_PROXY] = $options['proxy'][$scheme];
                    }
                }
            }
        }

        if (isset($options['cert'])) {
            $cert = $options['cert'];
            if (is_array($cert)) {
                $conf[CURLOPT_SSLCERTPASSWD] = $cert[1];
                $cert = $cert[0];
            }
            if (!file_exists($cert)) {
                throw new \InvalidArgumentException(
                    "SSL certificate not found: {$cert}"
                );
            }
            $conf[CURLOPT_SSLCERT] = $cert;
        }

        if (isset($options['ssl_key'])) {
            $sslKey = $options['ssl_key'];
            if (is_array($sslKey)) {
                $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1];
                $sslKey = $sslKey[0];
            }
            if (!file_exists($sslKey)) {
                throw new \InvalidArgumentException(
                    "SSL private key not found: {$sslKey}"
                );
            }
            $conf[CURLOPT_SSLKEY] = $sslKey;
        }

        if (isset($options['progress'])) {
            $progress = $options['progress'];
            if (!is_callable($progress)) {
                throw new \InvalidArgumentException(
                    'progress client option must be callable'
                );
            }
            $conf[CURLOPT_NOPROGRESS] = false;
            $conf[CURLOPT_PROGRESSFUNCTION] = function () use ($progress) {
                $args = func_get_args();
                // PHP 5.5 pushed the handle onto the start of the args
                if (is_resource($args[0])) {
                    array_shift($args);
                }
                call_user_func_array($progress, $args);
            };
        }

        if (!empty($options['debug'])) {
            $conf[CURLOPT_STDERR] = \GuzzleHttp\debug_resource($options['debug']);
            $conf[CURLOPT_VERBOSE] = true;
        }
    }

    /**
     * This function ensures that a response was set on a transaction. If one
     * was not set, then the request is retried if possible. This error
     * typically means you are sending a payload, curl encountered a
     * "Connection died, retrying a fresh connect" error, tried to rewind the
     * stream, and then encountered a "necessary data rewind wasn't possible"
     * error, causing the request to be sent through curl_multi_info_read()
     * without an error status.
     */
    private static function retryFailedRewind(
        callable $handler,
        EasyHandle $easy,
        array $ctx
    ) {
        try {
            // Only rewind if the body has been read from.
            $body = $easy->request->getBody();
            if ($body->tell() > 0) {
                $body->rewind();
            }
        } catch (\RuntimeException $e) {
            $ctx['error'] = 'The connection unexpectedly failed without '
                . 'providing an error. The request would have been retried, '
                . 'but attempting to rewind the request body failed. '
                . 'Exception: ' . $e;
            return self::createRejection($easy, $ctx);
        }

        // Retry no more than 3 times before giving up.
        if (!isset($easy->options['_curl_retries'])) {
            $easy->options['_curl_retries'] = 1;
        } elseif ($easy->options['_curl_retries'] == 2) {
            $ctx['error'] = 'The cURL request was retried 3 times '
                . 'and did not succeed. The most likely reason for the failure '
                . 'is that cURL was unable to rewind the body of the request '
                . 'and subsequent retries resulted in the same error. Turn on '
                . 'the debug option to see what went wrong. See '
                . 'https://bugs.php.net/bug.php?id=47204 for more information.';
            return self::createRejection($easy, $ctx);
        } else {
            $easy->options['_curl_retries']++;
        }

        return $handler($easy->request, $easy->options);
    }

    private function createHeaderFn(EasyHandle $easy)
    {
        if (isset($easy->options['on_headers'])) {
            $onHeaders = $easy->options['on_headers'];

            if (!is_callable($onHeaders)) {
                throw new \InvalidArgumentException('on_headers must be callable');
            }
        } else {
            $onHeaders = null;
        }

        return function ($ch, $h) use (
            $onHeaders,
            $easy,
            &$startingResponse
        ) {
            $value = trim($h);
            if ($value === '') {
                $startingResponse = true;
                $easy->createResponse();
                if ($onHeaders !== null) {
                    try {
                        $onHeaders($easy->response);
                    } catch (\Exception $e) {
                        // Associate the exception with the handle and trigger
                        // a curl header write error by returning 0.
                        $easy->onHeadersException = $e;
                        return -1;
                    }
                }
            } elseif ($startingResponse) {
                $startingResponse = false;
                $easy->headers = [$value];
            } else {
                $easy->headers[] = $value;
            }
            return strlen($h);
        };
    }
}
<?php
namespace GuzzleHttp\Handler;

use Psr\Http\Message\RequestInterface;

interface CurlFactoryInterface
{
    /**
     * Creates a cURL handle resource.
     *
     * @param RequestInterface $request Request
     * @param array            $options Transfer options
     *
     * @return EasyHandle
     * @throws \RuntimeException when an option cannot be applied
     */
    public function create(RequestInterface $request, array $options);

    /**
     * Release an easy handle, allowing it to be reused or closed.
     *
     * This function must call unset on the easy handle's "handle" property.
     *
     * @param EasyHandle $easy
     */
    public function release(EasyHandle $easy);
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * HTTP handler that uses cURL easy handles as a transport layer.
 *
 * When using the CurlHandler, custom curl options can be specified as an
 * associative array of curl option constants mapping to values in the
 * **curl** key of the "client" key of the request.
 */
class CurlHandler
{
    /** @var CurlFactoryInterface */
    private $factory;

    /**
     * Accepts an associative array of options:
     *
     * - factory: Optional curl factory used to create cURL handles.
     *
     * @param array $options Array of options to use with the handler
     */
    public function __construct(array $options = [])
    {
        $this->factory = isset($options['handle_factory'])
            ? $options['handle_factory']
            : new CurlFactory(3);
    }

    public function __invoke(RequestInterface $request, array $options)
    {
        if (isset($options['delay'])) {
            usleep($options['delay'] * 1000);
        }

        $easy = $this->factory->create($request, $options);
        curl_exec($easy->handle);
        $easy->errno = curl_errno($easy->handle);

        return CurlFactory::finish($this, $easy, $this->factory);
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Returns an asynchronous response using curl_multi_* functions.
 *
 * When using the CurlMultiHandler, custom curl options can be specified as an
 * associative array of curl option constants mapping to values in the
 * **curl** key of the provided request options.
 *
 * @property resource $_mh Internal use only. Lazy loaded multi-handle.
 */
class CurlMultiHandler
{
    /** @var CurlFactoryInterface */
    private $factory;
    private $selectTimeout;
    private $active;
    private $handles = [];
    private $delays = [];

    /**
     * This handler accepts the following options:
     *
     * - handle_factory: An optional factory  used to create curl handles
     * - select_timeout: Optional timeout (in seconds) to block before timing
     *   out while selecting curl handles. Defaults to 1 second.
     *
     * @param array $options
     */
    public function __construct(array $options = [])
    {
        $this->factory = isset($options['handle_factory'])
            ? $options['handle_factory'] : new CurlFactory(50);
        $this->selectTimeout = isset($options['select_timeout'])
            ? $options['select_timeout'] : 1;
    }

    public function __get($name)
    {
        if ($name === '_mh') {
            return $this->_mh = curl_multi_init();
        }

        throw new \BadMethodCallException();
    }

    public function __destruct()
    {
        if (isset($this->_mh)) {
            curl_multi_close($this->_mh);
            unset($this->_mh);
        }
    }

    public function __invoke(RequestInterface $request, array $options)
    {
        $easy = $this->factory->create($request, $options);
        $id = (int) $easy->handle;

        $promise = new Promise(
            [$this, 'execute'],
            function () use ($id) { return $this->cancel($id); }
        );

        $this->addRequest(['easy' => $easy, 'deferred' => $promise]);

        return $promise;
    }

    /**
     * Ticks the curl event loop.
     */
    public function tick()
    {
        // Add any delayed handles if needed.
        if ($this->delays) {
            $currentTime = microtime(true);
            foreach ($this->delays as $id => $delay) {
                if ($currentTime >= $delay) {
                    unset($this->delays[$id]);
                    curl_multi_add_handle(
                        $this->_mh,
                        $this->handles[$id]['easy']->handle
                    );
                }
            }
        }

        // Step through the task queue which may add additional requests.
        P\queue()->run();

        if ($this->active &&
            curl_multi_select($this->_mh, $this->selectTimeout) === -1
        ) {
            // Perform a usleep if a select returns -1.
            // See: https://bugs.php.net/bug.php?id=61141
            usleep(250);
        }

        while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);

        $this->processMessages();
    }

    /**
     * Runs until all outstanding connections have completed.
     */
    public function execute()
    {
        $queue = P\queue();

        while ($this->handles || !$queue->isEmpty()) {
            // If there are no transfers, then sleep for the next delay
            if (!$this->active && $this->delays) {
                usleep($this->timeToNext());
            }
            $this->tick();
        }
    }

    private function addRequest(array $entry)
    {
        $easy = $entry['easy'];
        $id = (int) $easy->handle;
        $this->handles[$id] = $entry;
        if (empty($easy->options['delay'])) {
            curl_multi_add_handle($this->_mh, $easy->handle);
        } else {
            $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);
        }
    }

    /**
     * Cancels a handle from sending and removes references to it.
     *
     * @param int $id Handle ID to cancel and remove.
     *
     * @return bool True on success, false on failure.
     */
    private function cancel($id)
    {
        // Cannot cancel if it has been processed.
        if (!isset($this->handles[$id])) {
            return false;
        }

        $handle = $this->handles[$id]['easy']->handle;
        unset($this->delays[$id], $this->handles[$id]);
        curl_multi_remove_handle($this->_mh, $handle);
        curl_close($handle);

        return true;
    }

    private function processMessages()
    {
        while ($done = curl_multi_info_read($this->_mh)) {
            $id = (int) $done['handle'];
            curl_multi_remove_handle($this->_mh, $done['handle']);

            if (!isset($this->handles[$id])) {
                // Probably was cancelled.
                continue;
            }

            $entry = $this->handles[$id];
            unset($this->handles[$id], $this->delays[$id]);
            $entry['easy']->errno = $done['result'];
            $entry['deferred']->resolve(
                CurlFactory::finish(
                    $this,
                    $entry['easy'],
                    $this->factory
                )
            );
        }
    }

    private function timeToNext()
    {
        $currentTime = microtime(true);
        $nextTime = PHP_INT_MAX;
        foreach ($this->delays as $time) {
            if ($time < $nextTime) {
                $nextTime = $time;
            }
        }

        return max(0, $nextTime - $currentTime) * 1000000;
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * Represents a cURL easy handle and the data it populates.
 *
 * @internal
 */
final class EasyHandle
{
    /** @var resource cURL resource */
    public $handle;

    /** @var StreamInterface Where data is being written */
    public $sink;

    /** @var array Received HTTP headers so far */
    public $headers = [];

    /** @var ResponseInterface Received response (if any) */
    public $response;

    /** @var RequestInterface Request being sent */
    public $request;

    /** @var array Request options */
    public $options = [];

    /** @var int cURL error number (if any) */
    public $errno = 0;

    /** @var \Exception Exception during on_headers (if any) */
    public $onHeadersException;

    /**
     * Attach a response to the easy handle based on the received headers.
     *
     * @throws \RuntimeException if no headers have been received.
     */
    public function createResponse()
    {
        if (empty($this->headers)) {
            throw new \RuntimeException('No headers have been received');
        }

        // HTTP-version SP status-code SP reason-phrase
        $startLine = explode(' ', array_shift($this->headers), 3);
        $headers = \GuzzleHttp\headers_from_lines($this->headers);
        $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);

        if (!empty($this->options['decode_content'])
            && isset($normalizedKeys['content-encoding'])
        ) {
            $headers['x-encoded-content-encoding']
                = $headers[$normalizedKeys['content-encoding']];
            unset($headers[$normalizedKeys['content-encoding']]);
            if (isset($normalizedKeys['content-length'])) {
                $headers['x-encoded-content-length']
                    = $headers[$normalizedKeys['content-length']];

                $bodyLength = (int) $this->sink->getSize();
                if ($bodyLength) {
                    $headers[$normalizedKeys['content-length']] = $bodyLength;
                } else {
                    unset($headers[$normalizedKeys['content-length']]);
                }
            }
        }

        // Attach a response to the easy handle with the parsed headers.
        $this->response = new Response(
            $startLine[1],
            $headers,
            $this->sink,
            substr($startLine[0], 5),
            isset($startLine[2]) ? (string) $startLine[2] : null
        );
    }

    public function __get($name)
    {
        $msg = $name === 'handle'
            ? 'The EasyHandle has been released'
            : 'Invalid property: ' . $name;
        throw new \BadMethodCallException($msg);
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\HandlerStack;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Handler that returns responses or throw exceptions from a queue.
 */
class MockHandler implements \Countable
{
    private $queue;
    private $lastRequest;
    private $lastOptions;
    private $onFulfilled;
    private $onRejected;

    /**
     * Creates a new MockHandler that uses the default handler stack list of
     * middlewares.
     *
     * @param array $queue Array of responses, callables, or exceptions.
     * @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable $onRejected  Callback to invoke when the return value is rejected.
     *
     * @return HandlerStack
     */
    public static function createWithMiddleware(
        array $queue = null,
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
    }

    /**
     * The passed in value must be an array of
     * {@see Psr7\Http\Message\ResponseInterface} objects, Exceptions,
     * callables, or Promises.
     *
     * @param array $queue
     * @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable $onRejected  Callback to invoke when the return value is rejected.
     */
    public function __construct(
        array $queue = null,
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        $this->onFulfilled = $onFulfilled;
        $this->onRejected = $onRejected;

        if ($queue) {
            call_user_func_array([$this, 'append'], $queue);
        }
    }

    public function __invoke(RequestInterface $request, array $options)
    {
        if (!$this->queue) {
            throw new \OutOfBoundsException('Mock queue is empty');
        }

        if (isset($options['delay'])) {
            usleep($options['delay'] * 1000);
        }

        $this->lastRequest = $request;
        $this->lastOptions = $options;
        $response = array_shift($this->queue);

        if (is_callable($response)) {
            $response = call_user_func($response, $request, $options);
        }

        $response = $response instanceof \Exception
            ? new RejectedPromise($response)
            : \GuzzleHttp\Promise\promise_for($response);

        return $response->then(
            function ($value) use ($request, $options) {
                $this->invokeStats($request, $options, $value);
                if ($this->onFulfilled) {
                    call_user_func($this->onFulfilled, $value);
                }
                if (isset($options['sink'])) {
                    $contents = (string) $value->getBody();
                    $sink = $options['sink'];

                    if (is_resource($sink)) {
                        fwrite($sink, $contents);
                    } elseif (is_string($sink)) {
                        file_put_contents($sink, $contents);
                    } elseif ($sink instanceof \Psr\Http\Message\StreamInterface) {
                        $sink->write($contents);
                    }
                }

                return $value;
            },
            function ($reason) use ($request, $options) {
                $this->invokeStats($request, $options, null, $reason);
                if ($this->onRejected) {
                    call_user_func($this->onRejected, $reason);
                }
                return new RejectedPromise($reason);
            }
        );
    }

    /**
     * Adds one or more variadic requests, exceptions, callables, or promises
     * to the queue.
     */
    public function append()
    {
        foreach (func_get_args() as $value) {
            if ($value instanceof ResponseInterface
                || $value instanceof \Exception
                || $value instanceof PromiseInterface
                || is_callable($value)
            ) {
                $this->queue[] = $value;
            } else {
                throw new \InvalidArgumentException('Expected a response or '
                    . 'exception. Found ' . \GuzzleHttp\describe_type($value));
            }
        }
    }

    /**
     * Get the last received request.
     *
     * @return RequestInterface
     */
    public function getLastRequest()
    {
        return $this->lastRequest;
    }

    /**
     * Get the last received request options.
     *
     * @return RequestInterface
     */
    public function getLastOptions()
    {
        return $this->lastOptions;
    }

    /**
     * Returns the number of remaining items in the queue.
     *
     * @return int
     */
    public function count()
    {
        return count($this->queue);
    }

    private function invokeStats(
        RequestInterface $request,
        array $options,
        ResponseInterface $response = null,
        $reason = null
    ) {
        if (isset($options['on_stats'])) {
            $stats = new TransferStats($request, $response, 0, $reason);
            call_user_func($options['on_stats'], $stats);
        }
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\RequestOptions;
use Psr\Http\Message\RequestInterface;

/**
 * Provides basic proxies for handlers.
 */
class Proxy
{
    /**
     * Sends synchronous requests to a specific handler while sending all other
     * requests to another handler.
     *
     * @param callable $default Handler used for normal responses
     * @param callable $sync    Handler used for synchronous responses.
     *
     * @return callable Returns the composed handler.
     */
    public static function wrapSync(
        callable $default,
        callable $sync
    ) {
        return function (RequestInterface $request, array $options) use ($default, $sync) {
            return empty($options[RequestOptions::SYNCHRONOUS])
                ? $default($request, $options)
                : $sync($request, $options);
        };
    }

    /**
     * Sends streaming requests to a streaming compatible handler while sending
     * all other requests to a default handler.
     *
     * This, for example, could be useful for taking advantage of the
     * performance benefits of curl while still supporting true streaming
     * through the StreamHandler.
     *
     * @param callable $default   Handler used for non-streaming responses
     * @param callable $streaming Handler used for streaming responses
     *
     * @return callable Returns the composed handler.
     */
    public static function wrapStreaming(
        callable $default,
        callable $streaming
    ) {
        return function (RequestInterface $request, array $options) use ($default, $streaming) {
            return empty($options['stream'])
                ? $default($request, $options)
                : $streaming($request, $options);
        };
    }
}
<?php
namespace GuzzleHttp\Handler;

use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

/**
 * HTTP handler that uses PHP's HTTP stream wrapper.
 */
class StreamHandler
{
    private $lastHeaders = [];

    /**
     * Sends an HTTP request.
     *
     * @param RequestInterface $request Request to send.
     * @param array            $options Request transfer options.
     *
     * @return PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        // Sleep if there is a delay specified.
        if (isset($options['delay'])) {
            usleep($options['delay'] * 1000);
        }

        $startTime = isset($options['on_stats']) ? microtime(true) : null;

        try {
            // Does not support the expect header.
            $request = $request->withoutHeader('Expect');

            // Append a content-length header if body size is zero to match
            // cURL's behavior.
            if (0 === $request->getBody()->getSize()) {
                $request = $request->withHeader('Content-Length', 0);
            }

            return $this->createResponse(
                $request,
                $options,
                $this->createStream($request, $options),
                $startTime
            );
        } catch (\InvalidArgumentException $e) {
            throw $e;
        } catch (\Exception $e) {
            // Determine if the error was a networking error.
            $message = $e->getMessage();
            // This list can probably get more comprehensive.
            if (strpos($message, 'getaddrinfo') // DNS lookup failed
                || strpos($message, 'Connection refused')
                || strpos($message, "couldn't connect to host") // error on HHVM
            ) {
                $e = new ConnectException($e->getMessage(), $request, $e);
            }
            $e = RequestException::wrapException($request, $e);
            $this->invokeStats($options, $request, $startTime, null, $e);

            return new RejectedPromise($e);
        }
    }

    private function invokeStats(
        array $options,
        RequestInterface $request,
        $startTime,
        ResponseInterface $response = null,
        $error = null
    ) {
        if (isset($options['on_stats'])) {
            $stats = new TransferStats(
                $request,
                $response,
                microtime(true) - $startTime,
                $error,
                []
            );
            call_user_func($options['on_stats'], $stats);
        }
    }

    private function createResponse(
        RequestInterface $request,
        array $options,
        $stream,
        $startTime
    ) {
        $hdrs = $this->lastHeaders;
        $this->lastHeaders = [];
        $parts = explode(' ', array_shift($hdrs), 3);
        $ver = explode('/', $parts[0])[1];
        $status = $parts[1];
        $reason = isset($parts[2]) ? $parts[2] : null;
        $headers = \GuzzleHttp\headers_from_lines($hdrs);
        list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
        $stream = Psr7\stream_for($stream);
        $sink = $stream;

        if (strcasecmp('HEAD', $request->getMethod())) {
            $sink = $this->createSink($stream, $options);
        }

        $response = new Psr7\Response($status, $headers, $sink, $ver, $reason);

        if (isset($options['on_headers'])) {
            try {
                $options['on_headers']($response);
            } catch (\Exception $e) {
                $msg = 'An error was encountered during the on_headers event';
                $ex = new RequestException($msg, $request, $response, $e);
                return new RejectedPromise($ex);
            }
        }

        // Do not drain when the request is a HEAD request because they have
        // no body.
        if ($sink !== $stream) {
            $this->drain(
                $stream,
                $sink,
                $response->getHeaderLine('Content-Length')
            );
        }

        $this->invokeStats($options, $request, $startTime, $response, null);

        return new FulfilledPromise($response);
    }

    private function createSink(StreamInterface $stream, array $options)
    {
        if (!empty($options['stream'])) {
            return $stream;
        }

        $sink = isset($options['sink'])
            ? $options['sink']
            : fopen('php://temp', 'r+');

        return is_string($sink)
            ? new Psr7\LazyOpenStream($sink, 'w+')
            : Psr7\stream_for($sink);
    }

    private function checkDecode(array $options, array $headers, $stream)
    {
        // Automatically decode responses when instructed.
        if (!empty($options['decode_content'])) {
            $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
            if (isset($normalizedKeys['content-encoding'])) {
                $encoding = $headers[$normalizedKeys['content-encoding']];
                if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
                    $stream = new Psr7\InflateStream(
                        Psr7\stream_for($stream)
                    );
                    $headers['x-encoded-content-encoding']
                        = $headers[$normalizedKeys['content-encoding']];
                    // Remove content-encoding header
                    unset($headers[$normalizedKeys['content-encoding']]);
                    // Fix content-length header
                    if (isset($normalizedKeys['content-length'])) {
                        $headers['x-encoded-content-length']
                            = $headers[$normalizedKeys['content-length']];

                        $length = (int) $stream->getSize();
                        if ($length === 0) {
                            unset($headers[$normalizedKeys['content-length']]);
                        } else {
                            $headers[$normalizedKeys['content-length']] = [$length];
                        }
                    }
                }
            }
        }

        return [$stream, $headers];
    }

    /**
     * Drains the source stream into the "sink" client option.
     *
     * @param StreamInterface $source
     * @param StreamInterface $sink
     * @param string          $contentLength Header specifying the amount of
     *                                       data to read.
     *
     * @return StreamInterface
     * @throws \RuntimeException when the sink option is invalid.
     */
    private function drain(
        StreamInterface $source,
        StreamInterface $sink,
        $contentLength
    ) {
        // If a content-length header is provided, then stop reading once
        // that number of bytes has been read. This can prevent infinitely
        // reading from a stream when dealing with servers that do not honor
        // Connection: Close headers.
        Psr7\copy_to_stream(
            $source,
            $sink,
            (strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
        );

        $sink->seek(0);
        $source->close();

        return $sink;
    }

    /**
     * Create a resource and check to ensure it was created successfully
     *
     * @param callable $callback Callable that returns stream resource
     *
     * @return resource
     * @throws \RuntimeException on error
     */
    private function createResource(callable $callback)
    {
        $errors = null;
        set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
            $errors[] = [
                'message' => $msg,
                'file'    => $file,
                'line'    => $line
            ];
            return true;
        });

        $resource = $callback();
        restore_error_handler();

        if (!$resource) {
            $message = 'Error creating resource: ';
            foreach ($errors as $err) {
                foreach ($err as $key => $value) {
                    $message .= "[$key] $value" . PHP_EOL;
                }
            }
            throw new \RuntimeException(trim($message));
        }

        return $resource;
    }

    private function createStream(RequestInterface $request, array $options)
    {
        static $methods;
        if (!$methods) {
            $methods = array_flip(get_class_methods(__CLASS__));
        }

        // HTTP/1.1 streams using the PHP stream wrapper require a
        // Connection: close header
        if ($request->getProtocolVersion() == '1.1'
            && !$request->hasHeader('Connection')
        ) {
            $request = $request->withHeader('Connection', 'close');
        }

        // Ensure SSL is verified by default
        if (!isset($options['verify'])) {
            $options['verify'] = true;
        }

        $params = [];
        $context = $this->getDefaultContext($request, $options);

        if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
            throw new \InvalidArgumentException('on_headers must be callable');
        }

        if (!empty($options)) {
            foreach ($options as $key => $value) {
                $method = "add_{$key}";
                if (isset($methods[$method])) {
                    $this->{$method}($request, $context, $value, $params);
                }
            }
        }

        if (isset($options['stream_context'])) {
            if (!is_array($options['stream_context'])) {
                throw new \InvalidArgumentException('stream_context must be an array');
            }
            $context = array_replace_recursive(
                $context,
                $options['stream_context']
            );
        }

        $context = $this->createResource(
            function () use ($context, $params) {
                return stream_context_create($context, $params);
            }
        );

        return $this->createResource(
            function () use ($request, &$http_response_header, $context) {
                $resource = fopen((string) $request->getUri()->withFragment(''), 'r', null, $context);
                $this->lastHeaders = $http_response_header;
                return $resource;
            }
        );
    }

    private function getDefaultContext(RequestInterface $request)
    {
        $headers = '';
        foreach ($request->getHeaders() as $name => $value) {
            foreach ($value as $val) {
                $headers .= "$name: $val\r\n";
            }
        }

        $context = [
            'http' => [
                'method'           => $request->getMethod(),
                'header'           => $headers,
                'protocol_version' => $request->getProtocolVersion(),
                'ignore_errors'    => true,
                'follow_location'  => 0,
            ],
        ];

        $body = (string) $request->getBody();

        if (!empty($body)) {
            $context['http']['content'] = $body;
            // Prevent the HTTP handler from adding a Content-Type header.
            if (!$request->hasHeader('Content-Type')) {
                $context['http']['header'] .= "Content-Type:\r\n";
            }
        }

        $context['http']['header'] = rtrim($context['http']['header']);

        return $context;
    }

    private function add_proxy(RequestInterface $request, &$options, $value, &$params)
    {
        if (!is_array($value)) {
            $options['http']['proxy'] = $value;
        } else {
            $scheme = $request->getUri()->getScheme();
            if (isset($value[$scheme])) {
                if (!isset($value['no'])
                    || !\GuzzleHttp\is_host_in_noproxy(
                        $request->getUri()->getHost(),
                        $value['no']
                    )
                ) {
                    $options['http']['proxy'] = $value[$scheme];
                }
            }
        }
    }

    private function add_timeout(RequestInterface $request, &$options, $value, &$params)
    {
        if ($value > 0) {
            $options['http']['timeout'] = $value;
        }
    }

    private function add_verify(RequestInterface $request, &$options, $value, &$params)
    {
        if ($value === true) {
            // PHP 5.6 or greater will find the system cert by default. When
            // < 5.6, use the Guzzle bundled cacert.
            if (PHP_VERSION_ID < 50600) {
                $options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
            }
        } elseif (is_string($value)) {
            $options['ssl']['cafile'] = $value;
            if (!file_exists($value)) {
                throw new \RuntimeException("SSL CA bundle not found: $value");
            }
        } elseif ($value === false) {
            $options['ssl']['verify_peer'] = false;
            $options['ssl']['verify_peer_name'] = false;
            return;
        } else {
            throw new \InvalidArgumentException('Invalid verify request option');
        }

        $options['ssl']['verify_peer'] = true;
        $options['ssl']['verify_peer_name'] = true;
        $options['ssl']['allow_self_signed'] = false;
    }

    private function add_cert(RequestInterface $request, &$options, $value, &$params)
    {
        if (is_array($value)) {
            $options['ssl']['passphrase'] = $value[1];
            $value = $value[0];
        }

        if (!file_exists($value)) {
            throw new \RuntimeException("SSL certificate not found: {$value}");
        }

        $options['ssl']['local_cert'] = $value;
    }

    private function add_progress(RequestInterface $request, &$options, $value, &$params)
    {
        $this->addNotification(
            $params,
            function ($code, $a, $b, $c, $transferred, $total) use ($value) {
                if ($code == STREAM_NOTIFY_PROGRESS) {
                    $value($total, $transferred, null, null);
                }
            }
        );
    }

    private function add_debug(RequestInterface $request, &$options, $value, &$params)
    {
        if ($value === false) {
            return;
        }

        static $map = [
            STREAM_NOTIFY_CONNECT       => 'CONNECT',
            STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
            STREAM_NOTIFY_AUTH_RESULT   => 'AUTH_RESULT',
            STREAM_NOTIFY_MIME_TYPE_IS  => 'MIME_TYPE_IS',
            STREAM_NOTIFY_FILE_SIZE_IS  => 'FILE_SIZE_IS',
            STREAM_NOTIFY_REDIRECTED    => 'REDIRECTED',
            STREAM_NOTIFY_PROGRESS      => 'PROGRESS',
            STREAM_NOTIFY_FAILURE       => 'FAILURE',
            STREAM_NOTIFY_COMPLETED     => 'COMPLETED',
            STREAM_NOTIFY_RESOLVE       => 'RESOLVE',
        ];
        static $args = ['severity', 'message', 'message_code',
            'bytes_transferred', 'bytes_max'];

        $value = \GuzzleHttp\debug_resource($value);
        $ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
        $this->addNotification(
            $params,
            function () use ($ident, $value, $map, $args) {
                $passed = func_get_args();
                $code = array_shift($passed);
                fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
                foreach (array_filter($passed) as $i => $v) {
                    fwrite($value, $args[$i] . ': "' . $v . '" ');
                }
                fwrite($value, "\n");
            }
        );
    }

    private function addNotification(array &$params, callable $notify)
    {
        // Wrap the existing function if needed.
        if (!isset($params['notification'])) {
            $params['notification'] = $notify;
        } else {
            $params['notification'] = $this->callArray([
                $params['notification'],
                $notify
            ]);
        }
    }

    private function callArray(array $functions)
    {
        return function () use ($functions) {
            $args = func_get_args();
            foreach ($functions as $fn) {
                call_user_func_array($fn, $args);
            }
        };
    }
}
<?php
namespace GuzzleHttp;

use Psr\Http\Message\RequestInterface;

/**
 * Creates a composed Guzzle handler function by stacking middlewares on top of
 * an HTTP handler function.
 */
class HandlerStack
{
    /** @var callable */
    private $handler;

    /** @var array */
    private $stack = [];

    /** @var callable|null */
    private $cached;

    /**
     * Creates a default handler stack that can be used by clients.
     *
     * The returned handler will wrap the provided handler or use the most
     * appropriate default handler for you system. The returned HandlerStack has
     * support for cookies, redirects, HTTP error exceptions, and preparing a body
     * before sending.
     *
     * The returned handler stack can be passed to a client in the "handler"
     * option.
     *
     * @param callable $handler HTTP handler function to use with the stack. If no
     *                          handler is provided, the best handler for your
     *                          system will be utilized.
     *
     * @return HandlerStack
     */
    public static function create(callable $handler = null)
    {
        $stack = new self($handler ?: choose_handler());
        $stack->push(Middleware::httpErrors(), 'http_errors');
        $stack->push(Middleware::redirect(), 'allow_redirects');
        $stack->push(Middleware::cookies(), 'cookies');
        $stack->push(Middleware::prepareBody(), 'prepare_body');

        return $stack;
    }

    /**
     * @param callable $handler Underlying HTTP handler.
     */
    public function __construct(callable $handler = null)
    {
        $this->handler = $handler;
    }

    /**
     * Invokes the handler stack as a composed handler
     *
     * @param RequestInterface $request
     * @param array            $options
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        $handler = $this->resolve();

        return $handler($request, $options);
    }

    /**
     * Dumps a string representation of the stack.
     *
     * @return string
     */
    public function __toString()
    {
        $depth = 0;
        $stack = [];
        if ($this->handler) {
            $stack[] = "0) Handler: " . $this->debugCallable($this->handler);
        }

        $result = '';
        foreach (array_reverse($this->stack) as $tuple) {
            $depth++;
            $str = "{$depth}) Name: '{$tuple[1]}', ";
            $str .= "Function: " . $this->debugCallable($tuple[0]);
            $result = "> {$str}\n{$result}";
            $stack[] = $str;
        }

        foreach (array_keys($stack) as $k) {
            $result .= "< {$stack[$k]}\n";
        }

        return $result;
    }

    /**
     * Set the HTTP handler that actually returns a promise.
     *
     * @param callable $handler Accepts a request and array of options and
     *                          returns a Promise.
     */
    public function setHandler(callable $handler)
    {
        $this->handler = $handler;
        $this->cached = null;
    }

    /**
     * Returns true if the builder has a handler.
     *
     * @return bool
     */
    public function hasHandler()
    {
        return (bool) $this->handler;
    }

    /**
     * Unshift a middleware to the bottom of the stack.
     *
     * @param callable $middleware Middleware function
     * @param string   $name       Name to register for this middleware.
     */
    public function unshift(callable $middleware, $name = null)
    {
        array_unshift($this->stack, [$middleware, $name]);
        $this->cached = null;
    }

    /**
     * Push a middleware to the top of the stack.
     *
     * @param callable $middleware Middleware function
     * @param string   $name       Name to register for this middleware.
     */
    public function push(callable $middleware, $name = '')
    {
        $this->stack[] = [$middleware, $name];
        $this->cached = null;
    }

    /**
     * Add a middleware before another middleware by name.
     *
     * @param string   $findName   Middleware to find
     * @param callable $middleware Middleware function
     * @param string   $withName   Name to register for this middleware.
     */
    public function before($findName, callable $middleware, $withName = '')
    {
        $this->splice($findName, $withName, $middleware, true);
    }

    /**
     * Add a middleware after another middleware by name.
     *
     * @param string   $findName   Middleware to find
     * @param callable $middleware Middleware function
     * @param string   $withName   Name to register for this middleware.
     */
    public function after($findName, callable $middleware, $withName = '')
    {
        $this->splice($findName, $withName, $middleware, false);
    }

    /**
     * Remove a middleware by instance or name from the stack.
     *
     * @param callable|string $remove Middleware to remove by instance or name.
     */
    public function remove($remove)
    {
        $this->cached = null;
        $idx = is_callable($remove) ? 0 : 1;
        $this->stack = array_values(array_filter(
            $this->stack,
            function ($tuple) use ($idx, $remove) {
                return $tuple[$idx] !== $remove;
            }
        ));
    }

    /**
     * Compose the middleware and handler into a single callable function.
     *
     * @return callable
     */
    public function resolve()
    {
        if (!$this->cached) {
            if (!($prev = $this->handler)) {
                throw new \LogicException('No handler has been specified');
            }

            foreach (array_reverse($this->stack) as $fn) {
                $prev = $fn[0]($prev);
            }

            $this->cached = $prev;
        }

        return $this->cached;
    }

    /**
     * @param $name
     * @return int
     */
    private function findByName($name)
    {
        foreach ($this->stack as $k => $v) {
            if ($v[1] === $name) {
                return $k;
            }
        }

        throw new \InvalidArgumentException("Middleware not found: $name");
    }

    /**
     * Splices a function into the middleware list at a specific position.
     *
     * @param          $findName
     * @param          $withName
     * @param callable $middleware
     * @param          $before
     */
    private function splice($findName, $withName, callable $middleware, $before)
    {
        $this->cached = null;
        $idx = $this->findByName($findName);
        $tuple = [$middleware, $withName];

        if ($before) {
            if ($idx === 0) {
                array_unshift($this->stack, $tuple);
            } else {
                $replacement = [$tuple, $this->stack[$idx]];
                array_splice($this->stack, $idx, 1, $replacement);
            }
        } elseif ($idx === count($this->stack) - 1) {
            $this->stack[] = $tuple;
        } else {
            $replacement = [$this->stack[$idx], $tuple];
            array_splice($this->stack, $idx, 1, $replacement);
        }
    }

    /**
     * Provides a debug string for a given callable.
     *
     * @param array|callable $fn Function to write as a string.
     *
     * @return string
     */
    private function debugCallable($fn)
    {
        if (is_string($fn)) {
            return "callable({$fn})";
        }

        if (is_array($fn)) {
            return is_string($fn[0])
                ? "callable({$fn[0]}::{$fn[1]})"
                : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
        }

        return 'callable(' . spl_object_hash($fn) . ')';
    }
}
<?php
namespace GuzzleHttp;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Formats log messages using variable substitutions for requests, responses,
 * and other transactional data.
 *
 * The following variable substitutions are supported:
 *
 * - {request}:        Full HTTP request message
 * - {response}:       Full HTTP response message
 * - {ts}:             ISO 8601 date in GMT
 * - {date_iso_8601}   ISO 8601 date in GMT
 * - {date_common_log} Apache common log date using the configured timezone.
 * - {host}:           Host of the request
 * - {method}:         Method of the request
 * - {uri}:            URI of the request
 * - {host}:           Host of the request
 * - {version}:        Protocol version
 * - {target}:         Request target of the request (path + query + fragment)
 * - {hostname}:       Hostname of the machine that sent the request
 * - {code}:           Status code of the response (if available)
 * - {phrase}:         Reason phrase of the response  (if available)
 * - {error}:          Any error messages (if available)
 * - {req_header_*}:   Replace `*` with the lowercased name of a request header to add to the message
 * - {res_header_*}:   Replace `*` with the lowercased name of a response header to add to the message
 * - {req_headers}:    Request headers
 * - {res_headers}:    Response headers
 * - {req_body}:       Request body
 * - {res_body}:       Response body
 */
class MessageFormatter
{
    /**
     * Apache Common Log Format.
     * @link http://httpd.apache.org/docs/2.4/logs.html#common
     * @var string
     */
    const CLF = "{hostname} {req_header_User-Agent} - [{date_common_log}] \"{method} {target} HTTP/{version}\" {code} {res_header_Content-Length}";
    const DEBUG = ">>>>>>>>\n{request}\n<<<<<<<<\n{response}\n--------\n{error}";
    const SHORT = '[{ts}] "{method} {target} HTTP/{version}" {code}';

    /** @var string Template used to format log messages */
    private $template;

    /**
     * @param string $template Log message template
     */
    public function __construct($template = self::CLF)
    {
        $this->template = $template ?: self::CLF;
    }

    /**
     * Returns a formatted message string.
     *
     * @param RequestInterface  $request  Request that was sent
     * @param ResponseInterface $response Response that was received
     * @param \Exception        $error    Exception that was received
     *
     * @return string
     */
    public function format(
        RequestInterface $request,
        ResponseInterface $response = null,
        \Exception $error = null
    ) {
        $cache = [];

        return preg_replace_callback(
            '/{\s*([A-Za-z_\-\.0-9]+)\s*}/',
            function (array $matches) use ($request, $response, $error, &$cache) {

                if (isset($cache[$matches[1]])) {
                    return $cache[$matches[1]];
                }

                $result = '';
                switch ($matches[1]) {
                    case 'request':
                        $result = Psr7\str($request);
                        break;
                    case 'response':
                        $result = $response ? Psr7\str($response) : '';
                        break;
                    case 'req_headers':
                        $result = trim($request->getMethod()
                                . ' ' . $request->getRequestTarget())
                            . ' HTTP/' . $request->getProtocolVersion() . "\r\n"
                            . $this->headers($request);
                        break;
                    case 'res_headers':
                        $result = $response ?
                            sprintf(
                                'HTTP/%s %d %s',
                                $response->getProtocolVersion(),
                                $response->getStatusCode(),
                                $response->getReasonPhrase()
                            ) . "\r\n" . $this->headers($response)
                            : 'NULL';
                        break;
                    case 'req_body':
                        $result = $request->getBody();
                        break;
                    case 'res_body':
                        $result = $response ? $response->getBody() : 'NULL';
                        break;
                    case 'ts':
                    case 'date_iso_8601':
                        $result = gmdate('c');
                        break;
                    case 'date_common_log':
                        $result = date('d/M/Y:H:i:s O');
                        break;
                    case 'method':
                        $result = $request->getMethod();
                        break;
                    case 'version':
                        $result = $request->getProtocolVersion();
                        break;
                    case 'uri':
                    case 'url':
                        $result = $request->getUri();
                        break;
                    case 'target':
                        $result = $request->getRequestTarget();
                        break;
                    case 'req_version':
                        $result = $request->getProtocolVersion();
                        break;
                    case 'res_version':
                        $result = $response
                            ? $response->getProtocolVersion()
                            : 'NULL';
                        break;
                    case 'host':
                        $result = $request->getHeaderLine('Host');
                        break;
                    case 'hostname':
                        $result = gethostname();
                        break;
                    case 'code':
                        $result = $response ? $response->getStatusCode() : 'NULL';
                        break;
                    case 'phrase':
                        $result = $response ? $response->getReasonPhrase() : 'NULL';
                        break;
                    case 'error':
                        $result = $error ? $error->getMessage() : 'NULL';
                        break;
                    default:
                        // handle prefixed dynamic headers
                        if (strpos($matches[1], 'req_header_') === 0) {
                            $result = $request->getHeaderLine(substr($matches[1], 11));
                        } elseif (strpos($matches[1], 'res_header_') === 0) {
                            $result = $response
                                ? $response->getHeaderLine(substr($matches[1], 11))
                                : 'NULL';
                        }
                }

                $cache[$matches[1]] = $result;
                return $result;
            },
            $this->template
        );
    }

    private function headers(MessageInterface $message)
    {
        $result = '';
        foreach ($message->getHeaders() as $name => $values) {
            $result .= $name . ': ' . implode(', ', $values) . "\r\n";
        }

        return trim($result);
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Cookie\CookieJarInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * Functions used to create and wrap handlers with handler middleware.
 */
final class Middleware
{
    /**
     * Middleware that adds cookies to requests.
     *
     * The options array must be set to a CookieJarInterface in order to use
     * cookies. This is typically handled for you by a client.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function cookies()
    {
        return function (callable $handler) {
            return function ($request, array $options) use ($handler) {
                if (empty($options['cookies'])) {
                    return $handler($request, $options);
                } elseif (!($options['cookies'] instanceof CookieJarInterface)) {
                    throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
                }
                $cookieJar = $options['cookies'];
                $request = $cookieJar->withCookieHeader($request);
                return $handler($request, $options)
                    ->then(function ($response) use ($cookieJar, $request) {
                        $cookieJar->extractCookies($request, $response);
                        return $response;
                    }
                );
            };
        };
    }

    /**
     * Middleware that throws exceptions for 4xx or 5xx responses when the
     * "http_error" request option is set to true.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function httpErrors()
    {
        return function (callable $handler) {
            return function ($request, array $options) use ($handler) {
                if (empty($options['http_errors'])) {
                    return $handler($request, $options);
                }
                return $handler($request, $options)->then(
                    function (ResponseInterface $response) use ($request, $handler) {
                        $code = $response->getStatusCode();
                        if ($code < 400) {
                            return $response;
                        }
                        throw RequestException::create($request, $response);
                    }
                );
            };
        };
    }

    /**
     * Middleware that pushes history data to an ArrayAccess container.
     *
     * @param array $container Container to hold the history (by reference).
     *
     * @return callable Returns a function that accepts the next handler.
     * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
     */
    public static function history(&$container)
    {
        if (!is_array($container) && !$container instanceof \ArrayAccess) {
            throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
        }

        return function (callable $handler) use (&$container) {
            return function ($request, array $options) use ($handler, &$container) {
                return $handler($request, $options)->then(
                    function ($value) use ($request, &$container, $options) {
                        $container[] = [
                            'request'  => $request,
                            'response' => $value,
                            'error'    => null,
                            'options'  => $options
                        ];
                        return $value;
                    },
                    function ($reason) use ($request, &$container, $options) {
                        $container[] = [
                            'request'  => $request,
                            'response' => null,
                            'error'    => $reason,
                            'options'  => $options
                        ];
                        return new RejectedPromise($reason);
                    }
                );
            };
        };
    }

    /**
     * Middleware that invokes a callback before and after sending a request.
     *
     * The provided listener cannot modify or alter the response. It simply
     * "taps" into the chain to be notified before returning the promise. The
     * before listener accepts a request and options array, and the after
     * listener accepts a request, options array, and response promise.
     *
     * @param callable $before Function to invoke before forwarding the request.
     * @param callable $after  Function invoked after forwarding.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function tap(callable $before = null, callable $after = null)
    {
        return function (callable $handler) use ($before, $after) {
            return function ($request, array $options) use ($handler, $before, $after) {
                if ($before) {
                    $before($request, $options);
                }
                $response = $handler($request, $options);
                if ($after) {
                    $after($request, $options, $response);
                }
                return $response;
            };
        };
    }

    /**
     * Middleware that handles request redirects.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function redirect()
    {
        return function (callable $handler) {
            return new RedirectMiddleware($handler);
        };
    }

    /**
     * Middleware that retries requests based on the boolean result of
     * invoking the provided "decider" function.
     *
     * If no delay function is provided, a simple implementation of exponential
     * backoff will be utilized.
     *
     * @param callable $decider Function that accepts the number of retries,
     *                          a request, [response], and [exception] and
     *                          returns true if the request is to be retried.
     * @param callable $delay   Function that accepts the number of retries and
     *                          returns the number of milliseconds to delay.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function retry(callable $decider, callable $delay = null)
    {
        return function (callable $handler) use ($decider, $delay) {
            return new RetryMiddleware($decider, $handler, $delay);
        };
    }

    /**
     * Middleware that logs requests, responses, and errors using a message
     * formatter.
     *
     * @param LoggerInterface  $logger Logs messages.
     * @param MessageFormatter $formatter Formatter used to create message strings.
     * @param string           $logLevel Level at which to log requests.
     *
     * @return callable Returns a function that accepts the next handler.
     */
    public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO)
    {
        return function (callable $handler) use ($logger, $formatter, $logLevel) {
            return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) {
                return $handler($request, $options)->then(
                    function ($response) use ($logger, $request, $formatter, $logLevel) {
                        $message = $formatter->format($request, $response);
                        $logger->log($logLevel, $message);
                        return $response;
                    },
                    function ($reason) use ($logger, $request, $formatter) {
                        $response = $reason instanceof RequestException
                            ? $reason->getResponse()
                            : null;
                        $message = $formatter->format($request, $response, $reason);
                        $logger->notice($message);
                        return \GuzzleHttp\Promise\rejection_for($reason);
                    }
                );
            };
        };
    }

    /**
     * This middleware adds a default content-type if possible, a default
     * content-length or transfer-encoding header, and the expect header.
     *
     * @return callable
     */
    public static function prepareBody()
    {
        return function (callable $handler) {
            return new PrepareBodyMiddleware($handler);
        };
    }

    /**
     * Middleware that applies a map function to the request before passing to
     * the next handler.
     *
     * @param callable $fn Function that accepts a RequestInterface and returns
     *                     a RequestInterface.
     * @return callable
     */
    public static function mapRequest(callable $fn)
    {
        return function (callable $handler) use ($fn) {
            return function ($request, array $options) use ($handler, $fn) {
                return $handler($fn($request), $options);
            };
        };
    }

    /**
     * Middleware that applies a map function to the resolved promise's
     * response.
     *
     * @param callable $fn Function that accepts a ResponseInterface and
     *                     returns a ResponseInterface.
     * @return callable
     */
    public static function mapResponse(callable $fn)
    {
        return function (callable $handler) use ($fn) {
            return function ($request, array $options) use ($handler, $fn) {
                return $handler($request, $options)->then($fn);
            };
        };
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Promise\PromisorInterface;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Promise\EachPromise;

/**
 * Sends and iterator of requests concurrently using a capped pool size.
 *
 * The pool will read from an iterator until it is cancelled or until the
 * iterator is consumed. When a request is yielded, the request is sent after
 * applying the "request_options" request options (if provided in the ctor).
 *
 * When a function is yielded by the iterator, the function is provided the
 * "request_options" array that should be merged on top of any existing
 * options, and the function MUST then return a wait-able promise.
 */
class Pool implements PromisorInterface
{
    /** @var EachPromise */
    private $each;

    /**
     * @param ClientInterface $client   Client used to send the requests.
     * @param array|\Iterator $requests Requests or functions that return
     *                                  requests to send concurrently.
     * @param array           $config   Associative array of options
     *     - concurrency: (int) Maximum number of requests to send concurrently
     *     - options: Array of request options to apply to each request.
     *     - fulfilled: (callable) Function to invoke when a request completes.
     *     - rejected: (callable) Function to invoke when a request is rejected.
     */
    public function __construct(
        ClientInterface $client,
        $requests,
        array $config = []
    ) {
        // Backwards compatibility.
        if (isset($config['pool_size'])) {
            $config['concurrency'] = $config['pool_size'];
        } elseif (!isset($config['concurrency'])) {
            $config['concurrency'] = 25;
        }

        if (isset($config['options'])) {
            $opts = $config['options'];
            unset($config['options']);
        } else {
            $opts = [];
        }

        $iterable = \GuzzleHttp\Promise\iter_for($requests);
        $requests = function () use ($iterable, $client, $opts) {
            foreach ($iterable as $key => $rfn) {
                if ($rfn instanceof RequestInterface) {
                    yield $key => $client->sendAsync($rfn, $opts);
                } elseif (is_callable($rfn)) {
                    yield $key => $rfn($opts);
                } else {
                    throw new \InvalidArgumentException('Each value yielded by '
                        . 'the iterator must be a Psr7\Http\Message\RequestInterface '
                        . 'or a callable that returns a promise that fulfills '
                        . 'with a Psr7\Message\Http\ResponseInterface object.');
                }
            }
        };

        $this->each = new EachPromise($requests(), $config);
    }

    public function promise()
    {
        return $this->each->promise();
    }

    /**
     * Sends multiple requests concurrently and returns an array of responses
     * and exceptions that uses the same ordering as the provided requests.
     *
     * IMPORTANT: This method keeps every request and response in memory, and
     * as such, is NOT recommended when sending a large number or an
     * indeterminate number of requests concurrently.
     *
     * @param ClientInterface $client   Client used to send the requests
     * @param array|\Iterator $requests Requests to send concurrently.
     * @param array           $options  Passes through the options available in
     *                                  {@see GuzzleHttp\Pool::__construct}
     *
     * @return array Returns an array containing the response or an exception
     *               in the same order that the requests were sent.
     * @throws \InvalidArgumentException if the event format is incorrect.
     */
    public static function batch(
        ClientInterface $client,
        $requests,
        array $options = []
    ) {
        $res = [];
        self::cmpCallback($options, 'fulfilled', $res);
        self::cmpCallback($options, 'rejected', $res);
        $pool = new static($client, $requests, $options);
        $pool->promise()->wait();
        ksort($res);

        return $res;
    }

    private static function cmpCallback(array &$options, $name, array &$results)
    {
        if (!isset($options[$name])) {
            $options[$name] = function ($v, $k) use (&$results) {
                $results[$k] = $v;
            };
        } else {
            $currentFn = $options[$name];
            $options[$name] = function ($v, $k) use (&$results, $currentFn) {
                $currentFn($v, $k);
                $results[$k] = $v;
            };
        }
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;

/**
 * Prepares requests that contain a body, adding the Content-Length,
 * Content-Type, and Expect headers.
 */
class PrepareBodyMiddleware
{
    /** @var callable  */
    private $nextHandler;

    /** @var array */
    private static $skipMethods = ['GET' => true, 'HEAD' => true];

    /**
     * @param callable $nextHandler Next handler to invoke.
     */
    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    /**
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        $fn = $this->nextHandler;

        // Don't do anything if the request has no body.
        if (isset(self::$skipMethods[$request->getMethod()])
            || $request->getBody()->getSize() === 0
        ) {
            return $fn($request, $options);
        }

        $modify = [];

        // Add a default content-type if possible.
        if (!$request->hasHeader('Content-Type')) {
            if ($uri = $request->getBody()->getMetadata('uri')) {
                if ($type = Psr7\mimetype_from_filename($uri)) {
                    $modify['set_headers']['Content-Type'] = $type;
                }
            }
        }

        // Add a default content-length or transfer-encoding header.
        if (!isset(self::$skipMethods[$request->getMethod()])
            && !$request->hasHeader('Content-Length')
            && !$request->hasHeader('Transfer-Encoding')
        ) {
            $size = $request->getBody()->getSize();
            if ($size !== null) {
                $modify['set_headers']['Content-Length'] = $size;
            } else {
                $modify['set_headers']['Transfer-Encoding'] = 'chunked';
            }
        }

        // Add the expect header if needed.
        $this->addExpectHeader($request, $options, $modify);

        return $fn(Psr7\modify_request($request, $modify), $options);
    }

    private function addExpectHeader(
        RequestInterface $request,
        array $options,
        array &$modify
    ) {
        // Determine if the Expect header should be used
        if ($request->hasHeader('Expect')) {
            return;
        }

        $expect = isset($options['expect']) ? $options['expect'] : null;

        // Return if disabled or if you're not using HTTP/1.1 or HTTP/2.0
        if ($expect === false || $request->getProtocolVersion() < 1.1) {
            return;
        }

        // The expect header is unconditionally enabled
        if ($expect === true) {
            $modify['set_headers']['Expect'] = '100-Continue';
            return;
        }

        // By default, send the expect header when the payload is > 1mb
        if ($expect === null) {
            $expect = 1048576;
        }

        // Always add if the body cannot be rewound, the size cannot be
        // determined, or the size is greater than the cutoff threshold
        $body = $request->getBody();
        $size = $body->getSize();

        if ($size === null || $size >= (int) $expect || !$body->isSeekable()) {
            $modify['set_headers']['Expect'] = '100-Continue';
        }
    }
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\TooManyRedirectsException;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Request redirect middleware.
 *
 * Apply this middleware like other middleware using
 * {@see GuzzleHttp\Middleware::redirect()}.
 */
class RedirectMiddleware
{
    const HISTORY_HEADER = 'X-Guzzle-Redirect-History';

    public static $defaultSettings = [
        'max'             => 5,
        'protocols'       => ['http', 'https'],
        'strict'          => false,
        'referer'         => false,
        'track_redirects' => false,
    ];

    /** @var callable  */
    private $nextHandler;

    /**
     * @param callable $nextHandler Next handler to invoke.
     */
    public function __construct(callable $nextHandler)
    {
        $this->nextHandler = $nextHandler;
    }

    /**
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        $fn = $this->nextHandler;

        if (empty($options['allow_redirects'])) {
            return $fn($request, $options);
        }

        if ($options['allow_redirects'] === true) {
            $options['allow_redirects'] = self::$defaultSettings;
        } elseif (!is_array($options['allow_redirects'])) {
            throw new \InvalidArgumentException('allow_redirects must be true, false, or array');
        } else {
            // Merge the default settings with the provided settings
            $options['allow_redirects'] += self::$defaultSettings;
        }

        if (empty($options['allow_redirects']['max'])) {
            return $fn($request, $options);
        }

        return $fn($request, $options)
            ->then(function (ResponseInterface $response) use ($request, $options) {
                return $this->checkRedirect($request, $options, $response);
            });
    }

    /**
     * @param RequestInterface  $request
     * @param array             $options
     * @param ResponseInterface|PromiseInterface $response
     *
     * @return ResponseInterface|PromiseInterface
     */
    public function checkRedirect(
        RequestInterface $request,
        array $options,
        ResponseInterface $response
    ) {
        if (substr($response->getStatusCode(), 0, 1) != '3'
            || !$response->hasHeader('Location')
        ) {
            return $response;
        }

        $this->guardMax($request, $options);
        $nextRequest = $this->modifyRequest($request, $options, $response);

        if (isset($options['allow_redirects']['on_redirect'])) {
            call_user_func(
                $options['allow_redirects']['on_redirect'],
                $request,
                $response,
                $nextRequest->getUri()
            );
        }

        /** @var PromiseInterface|ResponseInterface $promise */
        $promise = $this($nextRequest, $options);

        // Add headers to be able to track history of redirects.
        if (!empty($options['allow_redirects']['track_redirects'])) {
            return $this->withTracking(
                $promise,
                (string) $nextRequest->getUri()
            );
        }

        return $promise;
    }

    private function withTracking(PromiseInterface $promise, $uri)
    {
        return $promise->then(
            function (ResponseInterface $response) use ($uri) {
                // Note that we are pushing to the front of the list as this
                // would be an earlier response than what is currently present
                // in the history header.
                $header = $response->getHeader(self::HISTORY_HEADER);
                array_unshift($header, $uri);
                return $response->withHeader(self::HISTORY_HEADER, $header);
            }
        );
    }

    private function guardMax(RequestInterface $request, array &$options)
    {
        $current = isset($options['__redirect_count'])
            ? $options['__redirect_count']
            : 0;
        $options['__redirect_count'] = $current + 1;
        $max = $options['allow_redirects']['max'];

        if ($options['__redirect_count'] > $max) {
            throw new TooManyRedirectsException(
                "Will not follow more than {$max} redirects",
                $request
            );
        }
    }

    /**
     * @param RequestInterface  $request
     * @param array             $options
     * @param ResponseInterface $response
     *
     * @return RequestInterface
     */
    public function modifyRequest(
        RequestInterface $request,
        array $options,
        ResponseInterface $response
    ) {
        // Request modifications to apply.
        $modify = [];
        $protocols = $options['allow_redirects']['protocols'];

        // Use a GET request if this is an entity enclosing request and we are
        // not forcing RFC compliance, but rather emulating what all browsers
        // would do.
        $statusCode = $response->getStatusCode();
        if ($statusCode == 303 ||
            ($statusCode <= 302 && $request->getBody() && !$options['allow_redirects']['strict'])
        ) {
            $modify['method'] = 'GET';
            $modify['body'] = '';
        }

        $modify['uri'] = $this->redirectUri($request, $response, $protocols);
        Psr7\rewind_body($request);

        // Add the Referer header if it is told to do so and only
        // add the header if we are not redirecting from https to http.
        if ($options['allow_redirects']['referer']
            && $modify['uri']->getScheme() === $request->getUri()->getScheme()
        ) {
            $uri = $request->getUri()->withUserInfo('', '');
            $modify['set_headers']['Referer'] = (string) $uri;
        } else {
            $modify['remove_headers'][] = 'Referer';
        }

        // Remove Authorization header if host is different.
        if ($request->getUri()->getHost() !== $modify['uri']->getHost()) {
            $modify['remove_headers'][] = 'Authorization';
        }

        return Psr7\modify_request($request, $modify);
    }

    /**
     * Set the appropriate URL on the request based on the location header
     *
     * @param RequestInterface  $request
     * @param ResponseInterface $response
     * @param array             $protocols
     *
     * @return UriInterface
     */
    private function redirectUri(
        RequestInterface $request,
        ResponseInterface $response,
        array $protocols
    ) {
        $location = Psr7\Uri::resolve(
            $request->getUri(),
            $response->getHeaderLine('Location')
        );

        // Ensure that the redirect URI is allowed based on the protocols.
        if (!in_array($location->getScheme(), $protocols)) {
            throw new BadResponseException(
                sprintf(
                    'Redirect URI, %s, does not use one of the allowed redirect protocols: %s',
                    $location,
                    implode(', ', $protocols)
                ),
                $request,
                $response
            );
        }

        return $location;
    }
}
<?php
namespace GuzzleHttp;

/**
 * This class contains a list of built-in Guzzle request options.
 *
 * More documentation for each option can be found at http://guzzlephp.org/.
 *
 * @link http://docs.guzzlephp.org/en/v6/request-options.html
 */
final class RequestOptions
{
    /**
     * allow_redirects: (bool|array) Controls redirect behavior. Pass false
     * to disable redirects, pass true to enable redirects, pass an
     * associative to provide custom redirect settings. Defaults to "false".
     * This option only works if your handler has the RedirectMiddleware. When
     * passing an associative array, you can provide the following key value
     * pairs:
     *
     * - max: (int, default=5) maximum number of allowed redirects.
     * - strict: (bool, default=false) Set to true to use strict redirects
     *   meaning redirect POST requests with POST requests vs. doing what most
     *   browsers do which is redirect POST requests with GET requests
     * - referer: (bool, default=true) Set to false to disable the Referer
     *   header.
     * - protocols: (array, default=['http', 'https']) Allowed redirect
     *   protocols.
     * - on_redirect: (callable) PHP callable that is invoked when a redirect
     *   is encountered. The callable is invoked with the request, the redirect
     *   response that was received, and the effective URI. Any return value
     *   from the on_redirect function is ignored.
     */
    const ALLOW_REDIRECTS = 'allow_redirects';

    /**
     * auth: (array) Pass an array of HTTP authentication parameters to use
     * with the request. The array must contain the username in index [0],
     * the password in index [1], and you can optionally provide a built-in
     * authentication type in index [2]. Pass null to disable authentication
     * for a request.
     */
    const AUTH = 'auth';

    /**
     * body: (string|null|callable|iterator|object) Body to send in the
     * request.
     */
    const BODY = 'body';

    /**
     * cert: (string|array) Set to a string to specify the path to a file
     * containing a PEM formatted SSL client side certificate. If a password
     * is required, then set cert to an array containing the path to the PEM
     * file in the first array element followed by the certificate password
     * in the second array element.
     */
    const CERT = 'cert';

    /**
     * cookies: (bool|GuzzleHttp\Cookie\CookieJarInterface, default=false)
     * Specifies whether or not cookies are used in a request or what cookie
     * jar to use or what cookies to send. This option only works if your
     * handler has the `cookie` middleware. Valid values are `false` and
     * an instance of {@see GuzzleHttp\Cookie\CookieJarInterface}.
     */
    const COOKIES = 'cookies';

    /**
     * connect_timeout: (float, default=0) Float describing the number of
     * seconds to wait while trying to connect to a server. Use 0 to wait
     * indefinitely (the default behavior).
     */
    const CONNECT_TIMEOUT = 'connect_timeout';

    /**
     * debug: (bool|resource) Set to true or set to a PHP stream returned by
     * fopen()  enable debug output with the HTTP handler used to send a
     * request.
     */
    const DEBUG = 'debug';

    /**
     * decode_content: (bool, default=true) Specify whether or not
     * Content-Encoding responses (gzip, deflate, etc.) are automatically
     * decoded.
     */
    const DECODE_CONTENT = 'decode_content';

    /**
     * delay: (int) The amount of time to delay before sending in milliseconds.
     */
    const DELAY = 'delay';

    /**
     * expect: (bool|integer) Controls the behavior of the
     * "Expect: 100-Continue" header.
     *
     * Set to `true` to enable the "Expect: 100-Continue" header for all
     * requests that sends a body. Set to `false` to disable the
     * "Expect: 100-Continue" header for all requests. Set to a number so that
     * the size of the payload must be greater than the number in order to send
     * the Expect header. Setting to a number will send the Expect header for
     * all requests in which the size of the payload cannot be determined or
     * where the body is not rewindable.
     *
     * By default, Guzzle will add the "Expect: 100-Continue" header when the
     * size of the body of a request is greater than 1 MB and a request is
     * using HTTP/1.1.
     */
    const EXPECT = 'expect';

    /**
     * form_params: (array) Associative array of form field names to values
     * where each value is a string or array of strings. Sets the Content-Type
     * header to application/x-www-form-urlencoded when no Content-Type header
     * is already present.
     */
    const FORM_PARAMS = 'form_params';

    /**
     * headers: (array) Associative array of HTTP headers. Each value MUST be
     * a string or array of strings.
     */
    const HEADERS = 'headers';

    /**
     * http_errors: (bool, default=true) Set to false to disable exceptions
     * when a non- successful HTTP response is received. By default,
     * exceptions will be thrown for 4xx and 5xx responses. This option only
     * works if your handler has the `httpErrors` middleware.
     */
    const HTTP_ERRORS = 'http_errors';

    /**
     * json: (mixed) Adds JSON data to a request. The provided value is JSON
     * encoded and a Content-Type header of application/json will be added to
     * the request if no Content-Type header is already present.
     */
    const JSON = 'json';

    /**
     * multipart: (array) Array of associative arrays, each containing a
     * required "name" key mapping to the form field, name, a required
     * "contents" key mapping to a StreamInterface|resource|string, an
     * optional "headers" associative array of custom headers, and an
     * optional "filename" key mapping to a string to send as the filename in
     * the part. If no "filename" key is present, then no "filename" attribute
     * will be added to the part.
     */
    const MULTIPART = 'multipart';

    /**
     * on_headers: (callable) A callable that is invoked when the HTTP headers
     * of the response have been received but the body has not yet begun to
     * download.
     */
    const ON_HEADERS = 'on_headers';

    /**
     * on_stats: (callable) allows you to get access to transfer statistics of
     * a request and access the lower level transfer details of the handler
     * associated with your client. ``on_stats`` is a callable that is invoked
     * when a handler has finished sending a request. The callback is invoked
     * with transfer statistics about the request, the response received, or
     * the error encountered. Included in the data is the total amount of time
     * taken to send the request.
     */
    const ON_STATS = 'on_stats';

    /**
     * progress: (callable) Defines a function to invoke when transfer
     * progress is made. The function accepts the following positional
     * arguments: the total number of bytes expected to be downloaded, the
     * number of bytes downloaded so far, the number of bytes expected to be
     * uploaded, the number of bytes uploaded so far.
     */
    const PROGRESS = 'progress';

    /**
     * proxy: (string|array) Pass a string to specify an HTTP proxy, or an
     * array to specify different proxies for different protocols (where the
     * key is the protocol and the value is a proxy string).
     */
    const PROXY = 'proxy';

    /**
     * query: (array|string) Associative array of query string values to add
     * to the request. This option uses PHP's http_build_query() to create
     * the string representation. Pass a string value if you need more
     * control than what this method provides
     */
    const QUERY = 'query';

    /**
     * sink: (resource|string|StreamInterface) Where the data of the
     * response is written to. Defaults to a PHP temp stream. Providing a
     * string will write data to a file by the given name.
     */
    const SINK = 'sink';

    /**
     * synchronous: (bool) Set to true to inform HTTP handlers that you intend
     * on waiting on the response. This can be useful for optimizations. Note
     * that a promise is still returned if you are using one of the async
     * client methods.
     */
    const SYNCHRONOUS = 'synchronous';

    /**
     * ssl_key: (array|string) Specify the path to a file containing a private
     * SSL key in PEM format. If a password is required, then set to an array
     * containing the path to the SSL key in the first array element followed
     * by the password required for the certificate in the second element.
     */
    const SSL_KEY = 'ssl_key';

    /**
     * stream: Set to true to attempt to stream a response rather than
     * download it all up-front.
     */
    const STREAM = 'stream';

    /**
     * verify: (bool|string, default=true) Describes the SSL certificate
     * verification behavior of a request. Set to true to enable SSL
     * certificate verification using the system CA bundle when available
     * (the default). Set to false to disable certificate verification (this
     * is insecure!). Set to a string to provide the path to a CA bundle on
     * disk to enable verification using a custom certificate.
     */
    const VERIFY = 'verify';

    /**
     * timeout: (float, default=0) Float describing the timeout of the
     * request in seconds. Use 0 to wait indefinitely (the default behavior).
     */
    const TIMEOUT = 'timeout';

    /**
     * version: (float) Specifies the HTTP protocol version to attempt to use.
     */
    const VERSION = 'version';
}
<?php
namespace GuzzleHttp;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Psr7;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Middleware that retries requests based on the boolean result of
 * invoking the provided "decider" function.
 */
class RetryMiddleware
{
    /** @var callable  */
    private $nextHandler;

    /** @var callable */
    private $decider;

    /**
     * @param callable $decider     Function that accepts the number of retries,
     *                              a request, [response], and [exception] and
     *                              returns true if the request is to be
     *                              retried.
     * @param callable $nextHandler Next handler to invoke.
     * @param callable $delay       Function that accepts the number of retries
     *                              and [response] and returns the number of
     *                              milliseconds to delay.
     */
    public function __construct(
        callable $decider,
        callable $nextHandler,
        callable $delay = null
    ) {
        $this->decider = $decider;
        $this->nextHandler = $nextHandler;
        $this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
    }

    /**
     * Default exponential backoff delay function.
     *
     * @param $retries
     *
     * @return int
     */
    public static function exponentialDelay($retries)
    {
        return (int) pow(2, $retries - 1);
    }

    /**
     * @param RequestInterface $request
     * @param array            $options
     *
     * @return PromiseInterface
     */
    public function __invoke(RequestInterface $request, array $options)
    {
        if (!isset($options['retries'])) {
            $options['retries'] = 0;
        }

        $fn = $this->nextHandler;
        return $fn($request, $options)
            ->then(
                $this->onFulfilled($request, $options),
                $this->onRejected($request, $options)
            );
    }

    private function onFulfilled(RequestInterface $req, array $options)
    {
        return function ($value) use ($req, $options) {
            if (!call_user_func(
                $this->decider,
                $options['retries'],
                $req,
                $value,
                null
            )) {
                return $value;
            }
            return $this->doRetry($req, $options, $value);
        };
    }

    private function onRejected(RequestInterface $req, array $options)
    {
        return function ($reason) use ($req, $options) {
            if (!call_user_func(
                $this->decider,
                $options['retries'],
                $req,
                null,
                $reason
            )) {
                return new RejectedPromise($reason);
            }
            return $this->doRetry($req, $options);
        };
    }

    private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null)
    {
        $options['delay'] = call_user_func($this->delay, ++$options['retries'], $response);

        return $this($request, $options);
    }
}
<?php
namespace GuzzleHttp;

use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;

/**
 * Represents data at the point after it was transferred either successfully
 * or after a network error.
 */
final class TransferStats
{
    private $request;
    private $response;
    private $transferTime;
    private $handlerStats;
    private $handlerErrorData;

    /**
     * @param RequestInterface  $request          Request that was sent.
     * @param ResponseInterface $response         Response received (if any)
     * @param null              $transferTime     Total handler transfer time.
     * @param mixed             $handlerErrorData Handler error data.
     * @param array             $handlerStats     Handler specific stats.
     */
    public function __construct(
        RequestInterface $request,
        ResponseInterface $response = null,
        $transferTime = null,
        $handlerErrorData = null,
        $handlerStats = []
    ) {
        $this->request = $request;
        $this->response = $response;
        $this->transferTime = $transferTime;
        $this->handlerErrorData = $handlerErrorData;
        $this->handlerStats = $handlerStats;
    }

    /**
     * @return RequestInterface
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * Returns the response that was received (if any).
     *
     * @return ResponseInterface|null
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Returns true if a response was received.
     *
     * @return bool
     */
    public function hasResponse()
    {
        return $this->response !== null;
    }

    /**
     * Gets handler specific error data.
     *
     * This might be an exception, a integer representing an error code, or
     * anything else. Relying on this value assumes that you know what handler
     * you are using.
     *
     * @return mixed
     */
    public function getHandlerErrorData()
    {
        return $this->handlerErrorData;
    }

    /**
     * Get the effective URI the request was sent to.
     *
     * @return UriInterface
     */
    public function getEffectiveUri()
    {
        return $this->request->getUri();
    }

    /**
     * Get the estimated time the request was being transferred by the handler.
     *
     * @return float Time in seconds.
     */
    public function getTransferTime()
    {
        return $this->transferTime;
    }

    /**
     * Gets an array of all of the handler specific transfer data.
     *
     * @return array
     */
    public function getHandlerStats()
    {
        return $this->handlerStats;
    }

    /**
     * Get a specific handler statistic from the handler by name.
     *
     * @param string $stat Handler specific transfer stat to retrieve.
     *
     * @return mixed|null
     */
    public function getHandlerStat($stat)
    {
        return isset($this->handlerStats[$stat])
            ? $this->handlerStats[$stat]
            : null;
    }
}
<?php
namespace GuzzleHttp;

/**
 * Expands URI templates. Userland implementation of PECL uri_template.
 *
 * @link http://tools.ietf.org/html/rfc6570
 */
class UriTemplate
{
    /** @var string URI template */
    private $template;

    /** @var array Variables to use in the template expansion */
    private $variables;

    /** @var array Hash for quick operator lookups */
    private static $operatorHash = [
        ''  => ['prefix' => '',  'joiner' => ',', 'query' => false],
        '+' => ['prefix' => '',  'joiner' => ',', 'query' => false],
        '#' => ['prefix' => '#', 'joiner' => ',', 'query' => false],
        '.' => ['prefix' => '.', 'joiner' => '.', 'query' => false],
        '/' => ['prefix' => '/', 'joiner' => '/', 'query' => false],
        ';' => ['prefix' => ';', 'joiner' => ';', 'query' => true],
        '?' => ['prefix' => '?', 'joiner' => '&', 'query' => true],
        '&' => ['prefix' => '&', 'joiner' => '&', 'query' => true]
    ];

    /** @var array Delimiters */
    private static $delims = [':', '/', '?', '#', '[', ']', '@', '!', '$',
        '&', '\'', '(', ')', '*', '+', ',', ';', '='];

    /** @var array Percent encoded delimiters */
    private static $delimsPct = ['%3A', '%2F', '%3F', '%23', '%5B', '%5D',
        '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C',
        '%3B', '%3D'];

    public function expand($template, array $variables)
    {
        if (false === strpos($template, '{')) {
            return $template;
        }

        $this->template = $template;
        $this->variables = $variables;

        return preg_replace_callback(
            '/\{([^\}]+)\}/',
            [$this, 'expandMatch'],
            $this->template
        );
    }

    /**
     * Parse an expression into parts
     *
     * @param string $expression Expression to parse
     *
     * @return array Returns an associative array of parts
     */
    private function parseExpression($expression)
    {
        $result = [];

        if (isset(self::$operatorHash[$expression[0]])) {
            $result['operator'] = $expression[0];
            $expression = substr($expression, 1);
        } else {
            $result['operator'] = '';
        }

        foreach (explode(',', $expression) as $value) {
            $value = trim($value);
            $varspec = [];
            if ($colonPos = strpos($value, ':')) {
                $varspec['value'] = substr($value, 0, $colonPos);
                $varspec['modifier'] = ':';
                $varspec['position'] = (int) substr($value, $colonPos + 1);
            } elseif (substr($value, -1) === '*') {
                $varspec['modifier'] = '*';
                $varspec['value'] = substr($value, 0, -1);
            } else {
                $varspec['value'] = (string) $value;
                $varspec['modifier'] = '';
            }
            $result['values'][] = $varspec;
        }

        return $result;
    }

    /**
     * Process an expansion
     *
     * @param array $matches Matches met in the preg_replace_callback
     *
     * @return string Returns the replacement string
     */
    private function expandMatch(array $matches)
    {
        static $rfc1738to3986 = ['+' => '%20', '%7e' => '~'];

        $replacements = [];
        $parsed = self::parseExpression($matches[1]);
        $prefix = self::$operatorHash[$parsed['operator']]['prefix'];
        $joiner = self::$operatorHash[$parsed['operator']]['joiner'];
        $useQuery = self::$operatorHash[$parsed['operator']]['query'];

        foreach ($parsed['values'] as $value) {

            if (!isset($this->variables[$value['value']])) {
                continue;
            }

            $variable = $this->variables[$value['value']];
            $actuallyUseQuery = $useQuery;
            $expanded = '';

            if (is_array($variable)) {

                $isAssoc = $this->isAssoc($variable);
                $kvp = [];
                foreach ($variable as $key => $var) {

                    if ($isAssoc) {
                        $key = rawurlencode($key);
                        $isNestedArray = is_array($var);
                    } else {
                        $isNestedArray = false;
                    }

                    if (!$isNestedArray) {
                        $var = rawurlencode($var);
                        if ($parsed['operator'] === '+' ||
                            $parsed['operator'] === '#'
                        ) {
                            $var = $this->decodeReserved($var);
                        }
                    }

                    if ($value['modifier'] === '*') {
                        if ($isAssoc) {
                            if ($isNestedArray) {
                                // Nested arrays must allow for deeply nested
                                // structures.
                                $var = strtr(
                                    http_build_query([$key => $var]),
                                    $rfc1738to3986
                                );
                            } else {
                                $var = $key . '=' . $var;
                            }
                        } elseif ($key > 0 && $actuallyUseQuery) {
                            $var = $value['value'] . '=' . $var;
                        }
                    }

                    $kvp[$key] = $var;
                }

                if (empty($variable)) {
                    $actuallyUseQuery = false;
                } elseif ($value['modifier'] === '*') {
                    $expanded = implode($joiner, $kvp);
                    if ($isAssoc) {
                        // Don't prepend the value name when using the explode
                        // modifier with an associative array.
                        $actuallyUseQuery = false;
                    }
                } else {
                    if ($isAssoc) {
                        // When an associative array is encountered and the
                        // explode modifier is not set, then the result must be
                        // a comma separated list of keys followed by their
                        // respective values.
                        foreach ($kvp as $k => &$v) {
                            $v = $k . ',' . $v;
                        }
                    }
                    $expanded = implode(',', $kvp);
                }

            } else {
                if ($value['modifier'] === ':') {
                    $variable = substr($variable, 0, $value['position']);
                }
                $expanded = rawurlencode($variable);
                if ($parsed['operator'] === '+' || $parsed['operator'] === '#') {
                    $expanded = $this->decodeReserved($expanded);
                }
            }

            if ($actuallyUseQuery) {
                if (!$expanded && $joiner !== '&') {
                    $expanded = $value['value'];
                } else {
                    $expanded = $value['value'] . '=' . $expanded;
                }
            }

            $replacements[] = $expanded;
        }

        $ret = implode($joiner, $replacements);
        if ($ret && $prefix) {
            return $prefix . $ret;
        }

        return $ret;
    }

    /**
     * Determines if an array is associative.
     *
     * This makes the assumption that input arrays are sequences or hashes.
     * This assumption is a tradeoff for accuracy in favor of speed, but it
     * should work in almost every case where input is supplied for a URI
     * template.
     *
     * @param array $array Array to check
     *
     * @return bool
     */
    private function isAssoc(array $array)
    {
        return $array && array_keys($array)[0] !== 0;
    }

    /**
     * Removes percent encoding on reserved characters (used with + and #
     * modifiers).
     *
     * @param string $string String to fix
     *
     * @return string
     */
    private function decodeReserved($string)
    {
        return str_replace(self::$delimsPct, self::$delims, $string);
    }
}
Guzzle Upgrade Guide
====================

5.0 to 6.0
----------

Guzzle now uses [PSR-7](http://www.php-fig.org/psr/psr-7/) for HTTP messages.
Due to the fact that these messages are immutable, this prompted a refactoring
of Guzzle to use a middleware based system rather than an event system. Any
HTTP message interaction (e.g., `GuzzleHttp\Message\Request`) need to be
updated to work with the new immutable PSR-7 request and response objects. Any
event listeners or subscribers need to be updated to become middleware
functions that wrap handlers (or are injected into a
`GuzzleHttp\HandlerStack`).

- Removed `GuzzleHttp\BatchResults`
- Removed `GuzzleHttp\Collection`
- Removed `GuzzleHttp\HasDataTrait`
- Removed `GuzzleHttp\ToArrayInterface`
- The `guzzlehttp/streams` dependency has been removed. Stream functionality
  is now present in the `GuzzleHttp\Psr7` namespace provided by the
  `guzzlehttp/psr7` package.
- Guzzle no longer uses ReactPHP promises and now uses the
  `guzzlehttp/promises` library. We use a custom promise library for three
  significant reasons:
  1. React promises (at the time of writing this) are recursive. Promise
     chaining and promise resolution will eventually blow the stack. Guzzle
     promises are not recursive as they use a sort of trampolining technique.
     Note: there has been movement in the React project to modify promises to
     no longer utilize recursion.
  2. Guzzle needs to have the ability to synchronously block on a promise to
     wait for a result. Guzzle promises allows this functionality (and does
     not require the use of recursion).
  3. Because we need to be able to wait on a result, doing so using React
     promises requires wrapping react promises with RingPHP futures. This
     overhead is no longer needed, reducing stack sizes, reducing complexity,
     and improving performance.
- `GuzzleHttp\Mimetypes` has been moved to a function in
  `GuzzleHttp\Psr7\mimetype_from_extension` and
  `GuzzleHttp\Psr7\mimetype_from_filename`.
- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
  strings must now be passed into request objects as strings, or provided to
  the `query` request option when creating requests with clients. The `query`
  option uses PHP's `http_build_query` to convert an array to a string. If you
  need a different serialization technique, you will need to pass the query
  string in as a string. There are a couple helper functions that will make
  working with query strings easier: `GuzzleHttp\Psr7\parse_query` and
  `GuzzleHttp\Psr7\build_query`.
- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
  system based on PSR-7, using RingPHP and it's middleware system as well adds
  more complexity than the benefits it provides. All HTTP handlers that were
  present in RingPHP have been modified to work directly with PSR-7 messages
  and placed in the `GuzzleHttp\Handler` namespace. This significantly reduces
  complexity in Guzzle, removes a dependency, and improves performance. RingPHP
  will be maintained for Guzzle 5 support, but will no longer be a part of
  Guzzle 6.
- As Guzzle now uses a middleware based systems the event system and RingPHP
  integration has been removed. Note: while the event system has been removed,
  it is possible to add your own type of event system that is powered by the
  middleware system.
  - Removed the `Event` namespace.
  - Removed the `Subscriber` namespace.
  - Removed `Transaction` class
  - Removed `RequestFsm`
  - Removed `RingBridge`
  - `GuzzleHttp\Subscriber\Cookie` is now provided by
    `GuzzleHttp\Middleware::cookies`
  - `GuzzleHttp\Subscriber\HttpError` is now provided by
    `GuzzleHttp\Middleware::httpError`
  - `GuzzleHttp\Subscriber\History` is now provided by
    `GuzzleHttp\Middleware::history`
  - `GuzzleHttp\Subscriber\Mock` is now provided by
    `GuzzleHttp\Handler\MockHandler`
  - `GuzzleHttp\Subscriber\Prepare` is now provided by
    `GuzzleHttp\PrepareBodyMiddleware`
  - `GuzzleHttp\Subscriber\Redirect` is now provided by
    `GuzzleHttp\RedirectMiddleware`
- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
  `GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
  functions under the `GuzzleHttp` namespace. This requires either a Composer
  based autoloader or you to include functions.php.
- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
  `GuzzleHttp\ClientInterface::getConfig`.
- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
- The `json` and `xml` methods of response objects has been removed. With the
  migration to strictly adhering to PSR-7 as the interface for Guzzle messages,
  adding methods to message interfaces would actually require Guzzle messages
  to extend from PSR-7 messages rather then work with them directly.

## Migrating to middleware

The change to PSR-7 unfortunately required significant refactoring to Guzzle
due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event
system from plugins. The event system relied on mutability of HTTP messages and
side effects in order to work. With immutable messages, you have to change your
workflow to become more about either returning a value (e.g., functional
middlewares) or setting a value on an object. Guzzle v6 has chosen the
functional middleware approach.

Instead of using the event system to listen for things like the `before` event,
you now create a stack based middleware function that intercepts a request on
the way in and the promise of the response on the way out. This is a much
simpler and more predictable approach than the event system and works nicely
with PSR-7 middleware. Due to the use of promises, the middleware system is
also asynchronous.

v5:

```php
use GuzzleHttp\Event\BeforeEvent;
$client = new GuzzleHttp\Client();
// Get the emitter and listen to the before event.
$client->getEmitter()->on('before', function (BeforeEvent $e) {
    // Guzzle v5 events relied on mutation
    $e->getRequest()->setHeader('X-Foo', 'Bar');
});
```

v6:

In v6, you can modify the request before it is sent using the `mapRequest`
middleware. The idiomatic way in v6 to modify the request/response lifecycle is
to setup a handler middleware stack up front and inject the handler into a
client.

```php
use GuzzleHttp\Middleware;
// Create a handler stack that has all of the default middlewares attached
$handler = GuzzleHttp\HandlerStack::create();
// Push the handler onto the handler stack
$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
    // Notice that we have to return a request object
    return $request->withHeader('X-Foo', 'Bar');
}));
// Inject the handler into the client
$client = new GuzzleHttp\Client(['handler' => $handler]);
```

## POST Requests

This version added the [`form_params`](http://guzzle.readthedocs.org/en/latest/request-options.html#form_params)
and `multipart` request options. `form_params` is an associative array of
strings or array of strings and is used to serialize an
`application/x-www-form-urlencoded` POST request. The
[`multipart`](http://guzzle.readthedocs.org/en/latest/request-options.html#multipart)
option is now used to send a multipart/form-data POST request.

`GuzzleHttp\Post\PostFile` has been removed. Use the `multipart` option to add
POST files to a multipart/form-data request.

The `body` option no longer accepts an array to send POST requests. Please use
`multipart` or `form_params` instead.

The `base_url` option has been renamed to `base_uri`.

4.x to 5.0
----------

## Rewritten Adapter Layer

Guzzle now uses [RingPHP](http://ringphp.readthedocs.org/en/latest) to send
HTTP requests. The `adapter` option in a `GuzzleHttp\Client` constructor
is still supported, but it has now been renamed to `handler`. Instead of
passing a `GuzzleHttp\Adapter\AdapterInterface`, you must now pass a PHP
`callable` that follows the RingPHP specification.

## Removed Fluent Interfaces

[Fluent interfaces were removed](http://ocramius.github.io/blog/fluent-interfaces-are-evil)
from the following classes:

- `GuzzleHttp\Collection`
- `GuzzleHttp\Url`
- `GuzzleHttp\Query`
- `GuzzleHttp\Post\PostBody`
- `GuzzleHttp\Cookie\SetCookie`

## Removed functions.php

Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following
functions can be used as replacements.

- `GuzzleHttp\json_decode` -> `GuzzleHttp\Utils::jsonDecode`
- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
  deprecated in favor of using `GuzzleHttp\Pool::batch()`.

The "procedural" global client has been removed with no replacement (e.g.,
`GuzzleHttp\get()`, `GuzzleHttp\post()`, etc.). Use a `GuzzleHttp\Client`
object as a replacement.

## `throwImmediately` has been removed

The concept of "throwImmediately" has been removed from exceptions and error
events. This control mechanism was used to stop a transfer of concurrent
requests from completing. This can now be handled by throwing the exception or
by cancelling a pool of requests or each outstanding future request
individually.

## headers event has been removed

Removed the "headers" event. This event was only useful for changing the
body a response once the headers of the response were known. You can implement
a similar behavior in a number of ways. One example might be to use a
FnStream that has access to the transaction being sent. For example, when the
first byte is written, you could check if the response headers match your
expectations, and if so, change the actual stream body that is being
written to.

## Updates to HTTP Messages

Removed the `asArray` parameter from
`GuzzleHttp\Message\MessageInterface::getHeader`. If you want to get a header
value as an array, then use the newly added `getHeaderAsArray()` method of
`MessageInterface`. This change makes the Guzzle interfaces compatible with
the PSR-7 interfaces.

3.x to 4.0
----------

## Overarching changes:

- Now requires PHP 5.4 or greater.
- No longer requires cURL to send requests.
- Guzzle no longer wraps every exception it throws. Only exceptions that are
  recoverable are now wrapped by Guzzle.
- Various namespaces have been removed or renamed.
- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
  based on the Symfony EventDispatcher is
  now utilized in `GuzzleHttp\Event\EmitterInterface` (resulting in significant
  speed and functionality improvements).

Changes per Guzzle 3.x namespace are described below.

## Batch

The `Guzzle\Batch` namespace has been removed. This is best left to
third-parties to implement on top of Guzzle's core HTTP library.

## Cache

The `Guzzle\Cache` namespace has been removed. (Todo: No suitable replacement
has been implemented yet, but hoping to utilize a PSR cache interface).

## Common

- Removed all of the wrapped exceptions. It's better to use the standard PHP
  library for unrecoverable exceptions.
- `FromConfigInterface` has been removed.
- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
  at `GuzzleHttp\ClientInterface::VERSION`.

### Collection

- `getAll` has been removed. Use `toArray` to convert a collection to an array.
- `inject` has been removed.
- `keySearch` has been removed.
- `getPath` no longer supports wildcard expressions. Use something better like
  JMESPath for this.
- `setPath` now supports appending to an existing array via the `[]` notation.

### Events

Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
`GuzzleHttp\Event\Emitter`.

- `Symfony\Component\EventDispatcher\EventDispatcherInterface` is replaced by
  `GuzzleHttp\Event\EmitterInterface`.
- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
  `GuzzleHttp\Event\Emitter`.
- `Symfony\Component\EventDispatcher\Event` is replaced by
  `GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
  `GuzzleHttp\Event\EventInterface`.
- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
  `HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
  event emitter of a request, client, etc. now uses the `getEmitter` method
  rather than the `getDispatcher` method.

#### Emitter

- Use the `once()` method to add a listener that automatically removes itself
  the first time it is invoked.
- Use the `listeners()` method to retrieve a list of event listeners rather than
  the `getListeners()` method.
- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
  `removeSubscriber()`.

```php
$mock = new Mock();
// 3.x
$request->getEventDispatcher()->addSubscriber($mock);
$request->getEventDispatcher()->removeSubscriber($mock);
// 4.x
$request->getEmitter()->attach($mock);
$request->getEmitter()->detach($mock);
```

Use the `on()` method to add a listener rather than the `addListener()` method.

```php
// 3.x
$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
// 4.x
$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
```

## Http

### General changes

- The cacert.pem certificate has been moved to `src/cacert.pem`.
- Added the concept of adapters that are used to transfer requests over the
  wire.
- Simplified the event system.
- Sending requests in parallel is still possible, but batching is no longer a
  concept of the HTTP layer. Instead, you must use the `complete` and `error`
  events to asynchronously manage parallel request transfers.
- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
- QueryAggregators have been rewritten so that they are simply callable
  functions.
- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
  `functions.php` for an easy to use static client instance.
- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
  `GuzzleHttp\Exception\TransferException`.

### Client

Calling methods like `get()`, `post()`, `head()`, etc. no longer create and
return a request, but rather creates a request, sends the request, and returns
the response.

```php
// 3.0
$request = $client->get('/');
$response = $request->send();

// 4.0
$response = $client->get('/');

// or, to mirror the previous behavior
$request = $client->createRequest('GET', '/');
$response = $client->send($request);
```

`GuzzleHttp\ClientInterface` has changed.

- The `send` method no longer accepts more than one request. Use `sendAll` to
  send multiple requests in parallel.
- `setUserAgent()` has been removed. Use a default request option instead. You
  could, for example, do something like:
  `$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent())`.
- `setSslVerification()` has been removed. Use default request options instead,
  like `$client->setConfig('defaults/verify', true)`.

`GuzzleHttp\Client` has changed.

- The constructor now accepts only an associative array. You can include a
  `base_url` string or array to use a URI template as the base URL of a client.
  You can also specify a `defaults` key that is an associative array of default
  request options. You can pass an `adapter` to use a custom adapter,
  `batch_adapter` to use a custom adapter for sending requests in parallel, or
  a `message_factory` to change the factory used to create HTTP requests and
  responses.
- The client no longer emits a `client.create_request` event.
- Creating requests with a client no longer automatically utilize a URI
  template. You must pass an array into a creational method (e.g.,
  `createRequest`, `get`, `put`, etc.) in order to expand a URI template.

### Messages

Messages no longer have references to their counterparts (i.e., a request no
longer has a reference to it's response, and a response no loger has a
reference to its request). This association is now managed through a
`GuzzleHttp\Adapter\TransactionInterface` object. You can get references to
these transaction objects using request events that are emitted over the
lifecycle of a request.

#### Requests with a body

- `GuzzleHttp\Message\EntityEnclosingRequest` and
  `GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
  separation between requests that contain a body and requests that do not
  contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
  handles both use cases.
- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
  `GuzzleHttp\Message\ResponseInterface`.
- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
  `GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
  both requests and responses and is implemented in
  `GuzzleHttp\Message\MessageFactory`.
- POST field and file methods have been removed from the request object. You
  must now use the methods made available to `GuzzleHttp\Post\PostBodyInterface`
  to control the format of a POST body. Requests that are created using a
  standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
  a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
  the method is POST and no body is provided.

```php
$request = $client->createRequest('POST', '/');
$request->getBody()->setField('foo', 'bar');
$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
```

#### Headers

- `GuzzleHttp\Message\Header` has been removed. Header values are now simply
  represented by an array of values or as a string. Header values are returned
  as a string by default when retrieving a header value from a message. You can
  pass an optional argument of `true` to retrieve a header value as an array
  of strings instead of a single concatenated string.
- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
  `GuzzleHttp\Post`. This interface has been simplified and now allows the
  addition of arbitrary headers.
- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
  of the custom headers are now handled separately in specific
  subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
  been updated to properly handle headers that contain parameters (like the
  `Link` header).

#### Responses

- `GuzzleHttp\Message\Response::getInfo()` and
  `GuzzleHttp\Message\Response::setInfo()` have been removed. Use the event
  system to retrieve this type of information.
- `GuzzleHttp\Message\Response::getRawHeaders()` has been removed.
- `GuzzleHttp\Message\Response::getMessage()` has been removed.
- `GuzzleHttp\Message\Response::calculateAge()` and other cache specific
  methods have moved to the CacheSubscriber.
- Header specific helper functions like `getContentMd5()` have been removed.
  Just use `getHeader('Content-MD5')` instead.
- `GuzzleHttp\Message\Response::setRequest()` and
  `GuzzleHttp\Message\Response::getRequest()` have been removed. Use the event
  system to work with request and response objects as a transaction.
- `GuzzleHttp\Message\Response::getRedirectCount()` has been removed. Use the
  Redirect subscriber instead.
- `GuzzleHttp\Message\Response::isSuccessful()` and other related methods have
  been removed. Use `getStatusCode()` instead.

#### Streaming responses

Streaming requests can now be created by a client directly, returning a
`GuzzleHttp\Message\ResponseInterface` object that contains a body stream
referencing an open PHP HTTP stream.

```php
// 3.0
use Guzzle\Stream\PhpStreamRequestFactory;
$request = $client->get('/');
$factory = new PhpStreamRequestFactory();
$stream = $factory->fromRequest($request);
$data = $stream->read(1024);

// 4.0
$response = $client->get('/', ['stream' => true]);
// Read some data off of the stream in the response body
$data = $response->getBody()->read(1024);
```

#### Redirects

The `configureRedirects()` method has been removed in favor of a
`allow_redirects` request option.

```php
// Standard redirects with a default of a max of 5 redirects
$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);

// Strict redirects with a custom number of redirects
$request = $client->createRequest('GET', '/', [
    'allow_redirects' => ['max' => 5, 'strict' => true]
]);
```

#### EntityBody

EntityBody interfaces and classes have been removed or moved to
`GuzzleHttp\Stream`. All classes and interfaces that once required
`GuzzleHttp\EntityBodyInterface` now require
`GuzzleHttp\Stream\StreamInterface`. Creating a new body for a request no
longer uses `GuzzleHttp\EntityBody::factory` but now uses
`GuzzleHttp\Stream\Stream::factory` or even better:
`GuzzleHttp\Stream\create()`.

- `Guzzle\Http\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
- `Guzzle\Http\IoEmittyinEntityBody` has been removed.

#### Request lifecycle events

Requests previously submitted a large number of requests. The number of events
emitted over the lifecycle of a request has been significantly reduced to make
it easier to understand how to extend the behavior of a request. All events
emitted during the lifecycle of a request now emit a custom
`GuzzleHttp\Event\EventInterface` object that contains context providing
methods and a way in which to modify the transaction at that specific point in
time (e.g., intercept the request and set a response on the transaction).

- `request.before_send` has been renamed to `before` and now emits a
  `GuzzleHttp\Event\BeforeEvent`
- `request.complete` has been renamed to `complete` and now emits a
  `GuzzleHttp\Event\CompleteEvent`.
- `request.sent` has been removed. Use `complete`.
- `request.success` has been removed. Use `complete`.
- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
- `request.exception` has been removed. Use `error`.
- `request.receive.status_line` has been removed.
- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
  maintain a status update.
- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
  intercept writes.
- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
  intercept reads.

`headers` is a new event that is emitted after the response headers of a
request have been received before the body of the response is downloaded. This
event emits a `GuzzleHttp\Event\HeadersEvent`.

You can intercept a request and inject a response using the `intercept()` event
of a `GuzzleHttp\Event\BeforeEvent`, `GuzzleHttp\Event\CompleteEvent`, and
`GuzzleHttp\Event\ErrorEvent` event.

See: http://docs.guzzlephp.org/en/latest/events.html

## Inflection

The `Guzzle\Inflection` namespace has been removed. This is not a core concern
of Guzzle.

## Iterator

The `Guzzle\Iterator` namespace has been removed.

- `Guzzle\Iterator\AppendIterator`, `Guzzle\Iterator\ChunkedIterator`, and
  `Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
  Guzzle itself.
- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
  class is shipped with PHP 5.4.
- `Guzzle\Iterator\MapIterator` is not really needed when using PHP 5.5 because
  it's easier to just wrap an iterator in a generator that maps values.

For a replacement of these iterators, see https://github.com/nikic/iter

## Log

The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
`Guzzle\Log` namespace has been removed. Guzzle now relies on
`Psr\Log\LoggerInterface` for all logging. The MessageFormatter class has been
moved to `GuzzleHttp\Subscriber\Log\Formatter`.

## Parser

The `Guzzle\Parser` namespace has been removed. This was previously used to
make it possible to plug in custom parsers for cookies, messages, URI
templates, and URLs; however, this level of complexity is not needed in Guzzle
so it has been removed.

- Cookie: Cookie parsing logic has been moved to
  `GuzzleHttp\Cookie\SetCookie::fromString`.
- Message: Message parsing logic for both requests and responses has been moved
  to `GuzzleHttp\Message\MessageFactory::fromMessage`. Message parsing is only
  used in debugging or deserializing messages, so it doesn't make sense for
  Guzzle as a library to add this level of complexity to parsing messages.
- UriTemplate: URI template parsing has been moved to
  `GuzzleHttp\UriTemplate`. The Guzzle library will automatically use the PECL
  URI template library if it is installed.
- Url: URL parsing is now performed in `GuzzleHttp\Url::fromString` (previously
  it was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
  then developers are free to subclass `GuzzleHttp\Url`.

## Plugin

The `Guzzle\Plugin` namespace has been renamed to `GuzzleHttp\Subscriber`.
Several plugins are shipping with the core Guzzle library under this namespace.

- `GuzzleHttp\Subscriber\Cookie`: Replaces the old CookiePlugin. Cookie jar
  code has moved to `GuzzleHttp\Cookie`.
- `GuzzleHttp\Subscriber\History`: Replaces the old HistoryPlugin.
- `GuzzleHttp\Subscriber\HttpError`: Throws errors when a bad HTTP response is
  received.
- `GuzzleHttp\Subscriber\Mock`: Replaces the old MockPlugin.
- `GuzzleHttp\Subscriber\Prepare`: Prepares the body of a request just before
  sending. This subscriber is attached to all requests by default.
- `GuzzleHttp\Subscriber\Redirect`: Replaces the RedirectPlugin.

The following plugins have been removed (third-parties are free to re-implement
these if needed):

- `GuzzleHttp\Plugin\Async` has been removed.
- `GuzzleHttp\Plugin\CurlAuth` has been removed.
- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has been removed. This
  functionality should instead be implemented with event listeners that occur
  after normal response parsing occurs in the guzzle/command package.

The following plugins are not part of the core Guzzle package, but are provided
in separate repositories:

- `Guzzle\Http\Plugin\BackoffPlugin` has been rewritten to be much simpler
  to build custom retry policies using simple functions rather than various
  chained classes. See: https://github.com/guzzle/retry-subscriber
- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
  https://github.com/guzzle/cache-subscriber
- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
  https://github.com/guzzle/log-subscriber
- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
  https://github.com/guzzle/message-integrity-subscriber
- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
  `GuzzleHttp\Subscriber\MockSubscriber`.
- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has moved to
  https://github.com/guzzle/oauth-subscriber

## Service

The service description layer of Guzzle has moved into two separate packages:

- http://github.com/guzzle/command Provides a high level abstraction over web
  services by representing web service operations using commands.
- http://github.com/guzzle/guzzle-services Provides an implementation of
  guzzle/command that provides request serialization and response parsing using
  Guzzle service descriptions.

## Stream

Stream have moved to a separate package available at
https://github.com/guzzle/streams.

`Guzzle\Stream\StreamInterface` has been given a large update to cleanly take
on the responsibilities of `Guzzle\Http\EntityBody` and
`Guzzle\Http\EntityBodyInterface` now that they have been removed. The number
of methods implemented by the `StreamInterface` has been drastically reduced to
allow developers to more easily extend and decorate stream behavior.

## Removed methods from StreamInterface

- `getStream` and `setStream` have been removed to better encapsulate streams.
- `getMetadata` and `setMetadata` have been removed in favor of
  `GuzzleHttp\Stream\MetadataStreamInterface`.
- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
  removed. This data is accessible when
  using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
- `rewind` has been removed. Use `seek(0)` for a similar behavior.

## Renamed methods

- `detachStream` has been renamed to `detach`.
- `feof` has been renamed to `eof`.
- `ftell` has been renamed to `tell`.
- `readLine` has moved from an instance method to a static class method of
  `GuzzleHttp\Stream\Stream`.

## Metadata streams

`GuzzleHttp\Stream\MetadataStreamInterface` has been added to denote streams
that contain additional metadata accessible via `getMetadata()`.
`GuzzleHttp\Stream\StreamInterface::getMetadata` and
`GuzzleHttp\Stream\StreamInterface::setMetadata` have been removed.

## StreamRequestFactory

The entire concept of the StreamRequestFactory has been removed. The way this
was used in Guzzle 3 broke the actual interface of sending streaming requests
(instead of getting back a Response, you got a StreamInterface). Streaming
PHP requests are now implemented through the `GuzzleHttp\Adapter\StreamAdapter`.

3.6 to 3.7
----------

### Deprecations

- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:

```php
\Guzzle\Common\Version::$emitWarnings = true;
```

The following APIs and options have been marked as deprecated:

- Marked `Guzzle\Http\Message\Request::isResponseBodyRepeatable()` as deprecated. Use `$request->getResponseBody()->isRepeatable()` instead.
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
- Marked `Guzzle\Http\Message\Request::setIsRedirect()` as deprecated. Use the HistoryPlugin instead.
- Marked `Guzzle\Http\Message\Request::isRedirect()` as deprecated. Use the HistoryPlugin instead.
- Marked `Guzzle\Cache\CacheAdapterFactory::factory()` as deprecated
- Marked `Guzzle\Service\Client::enableMagicMethods()` as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client.
- Marked `Guzzle\Parser\Url\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
- Marked `Guzzle\Common\Collection::inject()` as deprecated.
- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as deprecated. Use
  `$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));` or
  `$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));`

3.7 introduces `request.options` as a parameter for a client configuration and as an optional argument to all creational
request methods. When paired with a client's configuration settings, these options allow you to specify default settings
for various aspects of a request. Because these options make other previous configuration options redundant, several
configuration options and methods of a client and AbstractCommand have been deprecated.

- Marked `Guzzle\Service\Client::getDefaultHeaders()` as deprecated. Use `$client->getDefaultOption('headers')`.
- Marked `Guzzle\Service\Client::setDefaultHeaders()` as deprecated. Use `$client->setDefaultOption('headers/{header_name}', 'value')`.
- Marked 'request.params' for `Guzzle\Http\Client` as deprecated. Use `$client->setDefaultOption('params/{param_name}', 'value')`
- Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0

        $command = $client->getCommand('foo', array(
            'command.headers' => array('Test' => '123'),
            'command.response_body' => '/path/to/file'
        ));

        // Should be changed to:

        $command = $client->getCommand('foo', array(
            'command.request_options' => array(
                'headers' => array('Test' => '123'),
                'save_as' => '/path/to/file'
            )
        ));

### Interface changes

Additions and changes (you will need to update any implementations or subclasses you may have created):

- Added an `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
  createRequest, head, delete, put, patch, post, options, prepareRequest
- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
  `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array())`. You can still pass in a
  resource, string, or EntityBody into the $options parameter to specify the download location of the response.
- Changed `Guzzle\Common\Collection::__construct($data)` to no longer accepts a null value for `$data` but a
  default `array()`
- Added `Guzzle\Stream\StreamInterface::isRepeatable`
- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected methods.

The following methods were removed from interfaces. All of these methods are still available in the concrete classes
that implement them, but you should update your code to use alternative methods:

- Removed `Guzzle\Http\ClientInterface::setDefaultHeaders(). Use
  `$client->getConfig()->setPath('request.options/headers/{header_name}', 'value')`. or
  `$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))` or
  `$client->setDefaultOption('headers/{header_name}', 'value')`. or
  `$client->setDefaultOption('headers', array('header_name' => 'value'))`.
- Removed `Guzzle\Http\ClientInterface::getDefaultHeaders(). Use `$client->getConfig()->getPath('request.options/headers')`.
- Removed `Guzzle\Http\ClientInterface::expandTemplate()`. This is an implementation detail.
- Removed `Guzzle\Http\ClientInterface::setRequestFactory()`. This is an implementation detail.
- Removed `Guzzle\Http\ClientInterface::getCurlMulti()`. This is a very specific implementation detail.
- Removed `Guzzle\Http\Message\RequestInterface::canCache`. Use the CachePlugin.
- Removed `Guzzle\Http\Message\RequestInterface::setIsRedirect`. Use the HistoryPlugin.
- Removed `Guzzle\Http\Message\RequestInterface::isRedirect`. Use the HistoryPlugin.

### Cache plugin breaking changes

- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a
  CacheStorageInterface. These two objects and interface will be removed in a future version.
- Always setting X-cache headers on cached responses
- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
- `CacheStorageInterface::cache($key, Response $response, $ttl = null)` has changed to `cache(RequestInterface
  $request, Response $response);`
- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
- Added `CacheStorageInterface::purge($url)`
- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
  $plugin)` has changed to `DefaultRevalidation::__construct(CacheStorageInterface $cache,
  CanCacheStrategyInterface $canCache = null)`
- Added `RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)`

3.5 to 3.6
----------

* Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
* Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
* Removed the whole changedHeader() function system of messages because all header changes now go through addHeader().
  For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader().
  Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
* Specific header implementations can be created for complex headers. When a message creates a header, it uses a
  HeaderFactory which can map specific headers to specific header classes. There is now a Link header and
  CacheControl header implementation.
* Moved getLinks() from Response to just be used on a Link header object.

If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the
HeaderInterface (e.g. toArray(), getAll(), etc.).

### Interface changes

* Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
* Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
* Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in
  Guzzle\Http\Curl\RequestMediator
* Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
* Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
* Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()

### Removed deprecated functions

* Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
* Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().

### Deprecations

* The ability to case-insensitively search for header values
* Guzzle\Http\Message\Header::hasExactHeader
* Guzzle\Http\Message\Header::raw. Use getAll()
* Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object
  instead.

### Other changes

* All response header helper functions return a string rather than mixing Header objects and strings inconsistently
* Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle
  directly via interfaces
* Removed the injecting of a request object onto a response object. The methods to get and set a request still exist
  but are a no-op until removed.
* Most classes that used to require a `Guzzle\Service\Command\CommandInterface` typehint now request a
  `Guzzle\Service\Command\ArrayCommandInterface`.
* Added `Guzzle\Http\Message\RequestInterface::startResponse()` to the RequestInterface to handle injecting a response
  on a request while the request is still being transferred
* `Guzzle\Service\Command\CommandInterface` now extends from ToArrayInterface and ArrayAccess

3.3 to 3.4
----------

Base URLs of a client now follow the rules of http://tools.ietf.org/html/rfc3986#section-5.2.2 when merging URLs.

3.2 to 3.3
----------

### Response::getEtag() quote stripping removed

`Guzzle\Http\Message\Response::getEtag()` no longer strips quotes around the ETag response header

### Removed `Guzzle\Http\Utils`

The `Guzzle\Http\Utils` class was removed. This class was only used for testing.

### Stream wrapper and type

`Guzzle\Stream\Stream::getWrapper()` and `Guzzle\Stream\Stream::getStreamType()` are no longer converted to lowercase.

### curl.emit_io became emit_io

Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the
'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'

3.1 to 3.2
----------

### CurlMulti is no longer reused globally

Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added
to a single client can pollute requests dispatched from other clients.

If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
ServiceBuilder's `service_builder.create_client` event to inject a custom CurlMulti object into each client as it is
created.

```php
$multi = new Guzzle\Http\Curl\CurlMulti();
$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
    $event['client']->setCurlMulti($multi);
}
});
```

### No default path

URLs no longer have a default path value of '/' if no path was specified.

Before:

```php
$request = $client->get('http://www.foo.com');
echo $request->getUrl();
// >> http://www.foo.com/
```

After:

```php
$request = $client->get('http://www.foo.com');
echo $request->getUrl();
// >> http://www.foo.com
```

### Less verbose BadResponseException

The exception message for `Guzzle\Http\Exception\BadResponseException` no longer contains the full HTTP request and
response information. You can, however, get access to the request and response object by calling `getRequest()` or
`getResponse()` on the exception object.

### Query parameter aggregation

Multi-valued query parameters are no longer aggregated using a callback function. `Guzzle\Http\Query` now has a
setAggregator() method that accepts a `Guzzle\Http\QueryAggregator\QueryAggregatorInterface` object. This object is
responsible for handling the aggregation of multi-valued query string variables into a flattened hash.

2.8 to 3.x
----------

### Guzzle\Service\Inspector

Change `\Guzzle\Service\Inspector::fromConfig` to `\Guzzle\Common\Collection::fromConfig`

**Before**

```php
use Guzzle\Service\Inspector;

class YourClient extends \Guzzle\Service\Client
{
    public static function factory($config = array())
    {
        $default = array();
        $required = array('base_url', 'username', 'api_key');
        $config = Inspector::fromConfig($config, $default, $required);

        $client = new self(
            $config->get('base_url'),
            $config->get('username'),
            $config->get('api_key')
        );
        $client->setConfig($config);

        $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));

        return $client;
    }
```

**After**

```php
use Guzzle\Common\Collection;

class YourClient extends \Guzzle\Service\Client
{
    public static function factory($config = array())
    {
        $default = array();
        $required = array('base_url', 'username', 'api_key');
        $config = Collection::fromConfig($config, $default, $required);

        $client = new self(
            $config->get('base_url'),
            $config->get('username'),
            $config->get('api_key')
        );
        $client->setConfig($config);

        $client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));

        return $client;
    }
```

### Convert XML Service Descriptions to JSON

**Before**

```xml
<?xml version="1.0" encoding="UTF-8"?>
<client>
    <commands>
        <!-- Groups -->
        <command name="list_groups" method="GET" uri="groups.json">
            <doc>Get a list of groups</doc>
        </command>
        <command name="search_groups" method="GET" uri='search.json?query="{{query}} type:group"'>
            <doc>Uses a search query to get a list of groups</doc>
            <param name="query" type="string" required="true" />
        </command>
        <command name="create_group" method="POST" uri="groups.json">
            <doc>Create a group</doc>
            <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
            <param name="Content-Type" location="header" static="application/json"/>
        </command>
        <command name="delete_group" method="DELETE" uri="groups/{{id}}.json">
            <doc>Delete a group by ID</doc>
            <param name="id" type="integer" required="true"/>
        </command>
        <command name="get_group" method="GET" uri="groups/{{id}}.json">
            <param name="id" type="integer" required="true"/>
        </command>
        <command name="update_group" method="PUT" uri="groups/{{id}}.json">
            <doc>Update a group</doc>
            <param name="id" type="integer" required="true"/>
            <param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
            <param name="Content-Type" location="header" static="application/json"/>
        </command>
    </commands>
</client>
```

**After**

```json
{
    "name":       "Zendesk REST API v2",
    "apiVersion": "2012-12-31",
    "description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
    "operations": {
        "list_groups":  {
            "httpMethod":"GET",
            "uri":       "groups.json",
            "summary":   "Get a list of groups"
        },
        "search_groups":{
            "httpMethod":"GET",
            "uri":       "search.json?query=\"{query} type:group\"",
            "summary":   "Uses a search query to get a list of groups",
            "parameters":{
                "query":{
                    "location":   "uri",
                    "description":"Zendesk Search Query",
                    "type":       "string",
                    "required":   true
                }
            }
        },
        "create_group": {
            "httpMethod":"POST",
            "uri":       "groups.json",
            "summary":   "Create a group",
            "parameters":{
                "data":        {
                    "type":       "array",
                    "location":   "body",
                    "description":"Group JSON",
                    "filters":    "json_encode",
                    "required":   true
                },
                "Content-Type":{
                    "type":    "string",
                    "location":"header",
                    "static":  "application/json"
                }
            }
        },
        "delete_group": {
            "httpMethod":"DELETE",
            "uri":       "groups/{id}.json",
            "summary":   "Delete a group",
            "parameters":{
                "id":{
                    "location":   "uri",
                    "description":"Group to delete by ID",
                    "type":       "integer",
                    "required":   true
                }
            }
        },
        "get_group":    {
            "httpMethod":"GET",
            "uri":       "groups/{id}.json",
            "summary":   "Get a ticket",
            "parameters":{
                "id":{
                    "location":   "uri",
                    "description":"Group to get by ID",
                    "type":       "integer",
                    "required":   true
                }
            }
        },
        "update_group": {
            "httpMethod":"PUT",
            "uri":       "groups/{id}.json",
            "summary":   "Update a group",
            "parameters":{
                "id":          {
                    "location":   "uri",
                    "description":"Group to update by ID",
                    "type":       "integer",
                    "required":   true
                },
                "data":        {
                    "type":       "array",
                    "location":   "body",
                    "description":"Group JSON",
                    "filters":    "json_encode",
                    "required":   true
                },
                "Content-Type":{
                    "type":    "string",
                    "location":"header",
                    "static":  "application/json"
                }
            }
        }
}
```

### Guzzle\Service\Description\ServiceDescription

Commands are now called Operations

**Before**

```php
use Guzzle\Service\Description\ServiceDescription;

$sd = new ServiceDescription();
$sd->getCommands();     // @returns ApiCommandInterface[]
$sd->hasCommand($name);
$sd->getCommand($name); // @returns ApiCommandInterface|null
$sd->addCommand($command); // @param ApiCommandInterface $command
```

**After**

```php
use Guzzle\Service\Description\ServiceDescription;

$sd = new ServiceDescription();
$sd->getOperations();           // @returns OperationInterface[]
$sd->hasOperation($name);
$sd->getOperation($name);       // @returns OperationInterface|null
$sd->addOperation($operation);  // @param OperationInterface $operation
```

### Guzzle\Common\Inflection\Inflector

Namespace is now `Guzzle\Inflection\Inflector`

### Guzzle\Http\Plugin

Namespace is now `Guzzle\Plugin`. Many other changes occur within this namespace and are detailed in their own sections below.

### Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log

Now `Guzzle\Plugin\Log\LogPlugin` and `Guzzle\Log` respectively.

**Before**

```php
use Guzzle\Common\Log\ClosureLogAdapter;
use Guzzle\Http\Plugin\LogPlugin;

/** @var \Guzzle\Http\Client */
$client;

// $verbosity is an integer indicating desired message verbosity level
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
```

**After**

```php
use Guzzle\Log\ClosureLogAdapter;
use Guzzle\Log\MessageFormatter;
use Guzzle\Plugin\Log\LogPlugin;

/** @var \Guzzle\Http\Client */
$client;

// $format is a string indicating desired message format -- @see MessageFormatter
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
```

### Guzzle\Http\Plugin\CurlAuthPlugin

Now `Guzzle\Plugin\CurlAuth\CurlAuthPlugin`.

### Guzzle\Http\Plugin\ExponentialBackoffPlugin

Now `Guzzle\Plugin\Backoff\BackoffPlugin`, and other changes.

**Before**

```php
use Guzzle\Http\Plugin\ExponentialBackoffPlugin;

$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
        ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
    ));

$client->addSubscriber($backoffPlugin);
```

**After**

```php
use Guzzle\Plugin\Backoff\BackoffPlugin;
use Guzzle\Plugin\Backoff\HttpBackoffStrategy;

// Use convenient factory method instead -- see implementation for ideas of what
// you can do with chaining backoff strategies
$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
        HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
    ));
$client->addSubscriber($backoffPlugin);
```

### Known Issues

#### [BUG] Accept-Encoding header behavior changed unintentionally.

(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)

In version 2.8 setting the `Accept-Encoding` header would set the CURLOPT_ENCODING option, which permitted cURL to
properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
See issue #217 for a workaround, or use a version containing the fix.
# CHANGELOG


## 1.3.1 - 2016-12-20

### Fixed

- `wait()` foreign promise compatibility


## 1.3.0 - 2016-11-18

### Added

- Adds support for custom task queues.

### Fixed

- Fixed coroutine promise memory leak.


## 1.2.0 - 2016-05-18

### Changed

- Update to now catch `\Throwable` on PHP 7+


## 1.1.0 - 2016-03-07

### Changed

- Update EachPromise to prevent recurring on a iterator when advancing, as this
  could trigger fatal generator errors.
- Update Promise to allow recursive waiting without unwrapping exceptions.


## 1.0.3 - 2015-10-15

### Changed

- Update EachPromise to immediately resolve when the underlying promise iterator
  is empty. Previously, such a promise would throw an exception when its `wait`
  function was called.


## 1.0.2 - 2015-05-15

### Changed

- Conditionally require functions.php.


## 1.0.1 - 2015-06-24

### Changed

- Updating EachPromise to call next on the underlying promise iterator as late
  as possible to ensure that generators that generate new requests based on
  callbacks are not iterated until after callbacks are invoked.


## 1.0.0 - 2015-05-12

- Initial release
{
    "name": "guzzlehttp/promises",
    "description": "Guzzle promises library",
    "keywords": ["promise"],
    "license": "MIT",
    "authors": [
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        }
    ],
    "require": {
        "php": ">=5.5.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^4.0"
    },
    "autoload": {
        "psr-4": {
            "GuzzleHttp\\Promise\\": "src/"
        },
        "files": ["src/functions_include.php"]
    },
    "scripts": {
        "test": "vendor/bin/phpunit",
        "test-ci": "vendor/bin/phpunit --coverage-text"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.4-dev"
        }
    }
}
Copyright (c) 2015-2016 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS 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.
all: clean test

test:
	vendor/bin/phpunit

coverage:
	vendor/bin/phpunit --coverage-html=artifacts/coverage

view-coverage:
	open artifacts/coverage/index.html

clean:
	rm -rf artifacts/*
# Guzzle Promises

[Promises/A+](https://promisesaplus.com/) implementation that handles promise
chaining and resolution iteratively, allowing for "infinite" promise chaining
while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/)
for a general introduction to promises.

- [Features](#features)
- [Quick start](#quick-start)
- [Synchronous wait](#synchronous-wait)
- [Cancellation](#cancellation)
- [API](#api)
  - [Promise](#promise)
  - [FulfilledPromise](#fulfilledpromise)
  - [RejectedPromise](#rejectedpromise)
- [Promise interop](#promise-interop)
- [Implementation notes](#implementation-notes)


# Features

- [Promises/A+](https://promisesaplus.com/) implementation.
- Promise resolution and chaining is handled iteratively, allowing for
  "infinite" promise chaining.
- Promises have a synchronous `wait` method.
- Promises can be cancelled.
- Works with any object that has a `then` function.
- C# style async/await coroutine promises using
  `GuzzleHttp\Promise\coroutine()`.


# Quick start

A *promise* represents the eventual result of an asynchronous operation. The
primary way of interacting with a promise is through its `then` method, which
registers callbacks to receive either a promise's eventual value or the reason
why the promise cannot be fulfilled.


## Callbacks

Callbacks are registered with the `then` method by providing an optional 
`$onFulfilled` followed by an optional `$onRejected` function.


```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(
    // $onFulfilled
    function ($value) {
        echo 'The promise was fulfilled.';
    },
    // $onRejected
    function ($reason) {
        echo 'The promise was rejected.';
    }
);
```

*Resolving* a promise means that you either fulfill a promise with a *value* or
reject a promise with a *reason*. Resolving a promises triggers callbacks
registered with the promises's `then` method. These callbacks are triggered
only once and in the order in which they were added.


## Resolving a promise

Promises are fulfilled using the `resolve($value)` method. Resolving a promise
with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
all of the onFulfilled callbacks (resolving a promise with a rejected promise
will reject the promise and trigger the `$onRejected` callbacks).

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise
    ->then(function ($value) {
        // Return a value and don't break the chain
        return "Hello, " . $value;
    })
    // This then is executed after the first then and receives the value
    // returned from the first then.
    ->then(function ($value) {
        echo $value;
    });

// Resolving the promise triggers the $onFulfilled callbacks and outputs
// "Hello, reader".
$promise->resolve('reader.');
```


## Promise forwarding

Promises can be chained one after the other. Each then in the chain is a new
promise. The return value of a promise is what's forwarded to the next
promise in the chain. Returning a promise in a `then` callback will cause the
subsequent promises in the chain to only be fulfilled when the returned promise
has been fulfilled. The next promise in the chain will be invoked with the
resolved value of the promise.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$nextPromise = new Promise();

$promise
    ->then(function ($value) use ($nextPromise) {
        echo $value;
        return $nextPromise;
    })
    ->then(function ($value) {
        echo $value;
    });

// Triggers the first callback and outputs "A"
$promise->resolve('A');
// Triggers the second callback and outputs "B"
$nextPromise->resolve('B');
```

## Promise rejection

When a promise is rejected, the `$onRejected` callbacks are invoked with the
rejection reason.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    echo $reason;
});

$promise->reject('Error!');
// Outputs "Error!"
```

## Rejection forwarding

If an exception is thrown in an `$onRejected` callback, subsequent
`$onRejected` callbacks are invoked with the thrown exception as the reason.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    throw new \Exception($reason);
})->then(null, function ($reason) {
    assert($reason->getMessage() === 'Error!');
});

$promise->reject('Error!');
```

You can also forward a rejection down the promise chain by returning a
`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or
`$onRejected` callback.

```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    return new RejectedPromise($reason);
})->then(null, function ($reason) {
    assert($reason === 'Error!');
});

$promise->reject('Error!');
```

If an exception is not thrown in a `$onRejected` callback and the callback
does not return a rejected promise, downstream `$onFulfilled` callbacks are
invoked using the value returned from the `$onRejected` callback.

```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;

$promise = new Promise();
$promise
    ->then(null, function ($reason) {
        return "It's ok";
    })
    ->then(function ($value) {
        assert($value === "It's ok");
    });

$promise->reject('Error!');
```

# Synchronous wait

You can synchronously force promises to complete using a promise's `wait`
method. When creating a promise, you can provide a wait function that is used
to synchronously force a promise to complete. When a wait function is invoked
it is expected to deliver a value to the promise or reject the promise. If the
wait function does not deliver a value, then an exception is thrown. The wait
function provided to a promise constructor is invoked when the `wait` function
of the promise is called.

```php
$promise = new Promise(function () use (&$promise) {
    $promise->resolve('foo');
});

// Calling wait will return the value of the promise.
echo $promise->wait(); // outputs "foo"
```

If an exception is encountered while invoking the wait function of a promise,
the promise is rejected with the exception and the exception is thrown.

```php
$promise = new Promise(function () use (&$promise) {
    throw new \Exception('foo');
});

$promise->wait(); // throws the exception.
```

Calling `wait` on a promise that has been fulfilled will not trigger the wait
function. It will simply return the previously resolved value.

```php
$promise = new Promise(function () { die('this is not called!'); });
$promise->resolve('foo');
echo $promise->wait(); // outputs "foo"
```

Calling `wait` on a promise that has been rejected will throw an exception. If
the rejection reason is an instance of `\Exception` the reason is thrown.
Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
can be obtained by calling the `getReason` method of the exception.

```php
$promise = new Promise();
$promise->reject('foo');
$promise->wait();
```

> PHP Fatal error:  Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'


## Unwrapping a promise

When synchronously waiting on a promise, you are joining the state of the
promise into the current state of execution (i.e., return the value of the
promise if it was fulfilled or throw an exception if it was rejected). This is
called "unwrapping" the promise. Waiting on a promise will by default unwrap
the promise state.

You can force a promise to resolve and *not* unwrap the state of the promise
by passing `false` to the first argument of the `wait` function:

```php
$promise = new Promise();
$promise->reject('foo');
// This will not throw an exception. It simply ensures the promise has
// been resolved.
$promise->wait(false);
```

When unwrapping a promise, the resolved value of the promise will be waited
upon until the unwrapped value is not a promise. This means that if you resolve
promise A with a promise B and unwrap promise A, the value returned by the
wait function will be the value delivered to promise B.

**Note**: when you do not unwrap the promise, no value is returned.


# Cancellation

You can cancel a promise that has not yet been fulfilled using the `cancel()`
method of a promise. When creating a promise you can provide an optional
cancel function that when invoked cancels the action of computing a resolution
of the promise.


# API


## Promise

When creating a promise object, you can provide an optional `$waitFn` and
`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
expected to resolve the promise. `$cancelFn` is a function with no arguments
that is expected to cancel the computation of a promise. It is invoked when the
`cancel()` method of a promise is called.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise(
    function () use (&$promise) {
        $promise->resolve('waited');
    },
    function () {
        // do something that will cancel the promise computation (e.g., close
        // a socket, cancel a database query, etc...)
    }
);

assert('waited' === $promise->wait());
```

A promise has the following methods:

- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
  
  Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.

- `otherwise(callable $onRejected) : PromiseInterface`
  
  Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.

- `wait($unwrap = true) : mixed`

  Synchronously waits on the promise to complete.
  
  `$unwrap` controls whether or not the value of the promise is returned for a
  fulfilled promise or if an exception is thrown if the promise is rejected.
  This is set to `true` by default.

- `cancel()`

  Attempts to cancel the promise if possible. The promise being cancelled and
  the parent most ancestor that has not yet been resolved will also be
  cancelled. Any promises waiting on the cancelled promise to resolve will also
  be cancelled.

- `getState() : string`

  Returns the state of the promise. One of `pending`, `fulfilled`, or
  `rejected`.

- `resolve($value)`

  Fulfills the promise with the given `$value`.

- `reject($reason)`

  Rejects the promise with the given `$reason`.


## FulfilledPromise

A fulfilled promise can be created to represent a promise that has been
fulfilled.

```php
use GuzzleHttp\Promise\FulfilledPromise;

$promise = new FulfilledPromise('value');

// Fulfilled callbacks are immediately invoked.
$promise->then(function ($value) {
    echo $value;
});
```


## RejectedPromise

A rejected promise can be created to represent a promise that has been
rejected.

```php
use GuzzleHttp\Promise\RejectedPromise;

$promise = new RejectedPromise('Error');

// Rejected callbacks are immediately invoked.
$promise->then(null, function ($reason) {
    echo $reason;
});
```


# Promise interop

This library works with foreign promises that have a `then` method. This means
you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
for example. When a foreign promise is returned inside of a then method
callback, promise resolution will occur recursively.

```php
// Create a React promise
$deferred = new React\Promise\Deferred();
$reactPromise = $deferred->promise();

// Create a Guzzle promise that is fulfilled with a React promise.
$guzzlePromise = new \GuzzleHttp\Promise\Promise();
$guzzlePromise->then(function ($value) use ($reactPromise) {
    // Do something something with the value...
    // Return the React promise
    return $reactPromise;
});
```

Please note that wait and cancel chaining is no longer possible when forwarding
a foreign promise. You will need to wrap a third-party promise with a Guzzle
promise in order to utilize wait and cancel functions with foreign promises.


## Event Loop Integration

In order to keep the stack size constant, Guzzle promises are resolved
asynchronously using a task queue. When waiting on promises synchronously, the
task queue will be automatically run to ensure that the blocking promise and
any forwarded promises are resolved. When using promises asynchronously in an
event loop, you will need to run the task queue on each tick of the loop. If
you do not run the task queue, then promises will not be resolved.

You can run the task queue using the `run()` method of the global task queue
instance.

```php
// Get the global task queue
$queue = \GuzzleHttp\Promise\queue();
$queue->run();
```

For example, you could use Guzzle promises with React using a periodic timer:

```php
$loop = React\EventLoop\Factory::create();
$loop->addPeriodicTimer(0, [$queue, 'run']);
```

*TODO*: Perhaps adding a `futureTick()` on each tick would be faster?


# Implementation notes


## Promise resolution and chaining is handled iteratively

By shuffling pending handlers from one owner to another, promises are
resolved iteratively, allowing for "infinite" then chaining.

```php
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Promise\Promise;

$parent = new Promise();
$p = $parent;

for ($i = 0; $i < 1000; $i++) {
    $p = $p->then(function ($v) {
        // The stack size remains constant (a good thing)
        echo xdebug_get_stack_depth() . ', ';
        return $v + 1;
    });
}

$parent->resolve(0);
var_dump($p->wait()); // int(1000)

```

When a promise is fulfilled or rejected with a non-promise value, the promise
then takes ownership of the handlers of each child promise and delivers values
down the chain without using recursion.

When a promise is resolved with another promise, the original promise transfers
all of its pending handlers to the new promise. When the new promise is
eventually resolved, all of the pending handlers are delivered the forwarded
value.


## A promise is the deferred.

Some promise libraries implement promises using a deferred object to represent
a computation and a promise object to represent the delivery of the result of
the computation. This is a nice separation of computation and delivery because
consumers of the promise cannot modify the value that will be eventually
delivered.

One side effect of being able to implement promise resolution and chaining
iteratively is that you need to be able for one promise to reach into the state
of another promise to shuffle around ownership of handlers. In order to achieve
this without making the handlers of a promise publicly mutable, a promise is
also the deferred value, allowing promises of the same parent class to reach
into and modify the private properties of promises of the same type. While this
does allow consumers of the value to modify the resolution or rejection of the
deferred, it is a small price to pay for keeping the stack size constant.

```php
$promise = new Promise();
$promise->then(function ($value) { echo $value; });
// The promise is the deferred value, so you can deliver a value to it.
$promise->resolve('foo');
// prints "foo"
```
<?php
namespace GuzzleHttp\Promise;

/**
 * Exception thrown when too many errors occur in the some() or any() methods.
 */
class AggregateException extends RejectionException
{
    public function __construct($msg, array $reasons)
    {
        parent::__construct(
            $reasons,
            sprintf('%s; %d rejected promises', $msg, count($reasons))
        );
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Exception that is set as the reason for a promise that has been cancelled.
 */
class CancellationException extends RejectionException
{
}
<?php
namespace GuzzleHttp\Promise;

use Exception;
use Generator;
use Throwable;

/**
 * Creates a promise that is resolved using a generator that yields values or
 * promises (somewhat similar to C#'s async keyword).
 *
 * When called, the coroutine function will start an instance of the generator
 * and returns a promise that is fulfilled with its final yielded value.
 *
 * Control is returned back to the generator when the yielded promise settles.
 * This can lead to less verbose code when doing lots of sequential async calls
 * with minimal processing in between.
 *
 *     use GuzzleHttp\Promise;
 *
 *     function createPromise($value) {
 *         return new Promise\FulfilledPromise($value);
 *     }
 *
 *     $promise = Promise\coroutine(function () {
 *         $value = (yield createPromise('a'));
 *         try {
 *             $value = (yield createPromise($value . 'b'));
 *         } catch (\Exception $e) {
 *             // The promise was rejected.
 *         }
 *         yield $value . 'c';
 *     });
 *
 *     // Outputs "abc"
 *     $promise->then(function ($v) { echo $v; });
 *
 * @param callable $generatorFn Generator function to wrap into a promise.
 *
 * @return Promise
 * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
 */
final class Coroutine implements PromiseInterface
{
    /**
     * @var PromiseInterface|null
     */
    private $currentPromise;

    /**
     * @var Generator
     */
    private $generator;

    /**
     * @var Promise
     */
    private $result;

    public function __construct(callable $generatorFn)
    {
        $this->generator = $generatorFn();
        $this->result = new Promise(function () {
            while (isset($this->currentPromise)) {
                $this->currentPromise->wait();
            }
        });
        $this->nextCoroutine($this->generator->current());
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        return $this->result->then($onFulfilled, $onRejected);
    }

    public function otherwise(callable $onRejected)
    {
        return $this->result->otherwise($onRejected);
    }

    public function wait($unwrap = true)
    {
        return $this->result->wait($unwrap);
    }

    public function getState()
    {
        return $this->result->getState();
    }

    public function resolve($value)
    {
        $this->result->resolve($value);
    }

    public function reject($reason)
    {
        $this->result->reject($reason);
    }

    public function cancel()
    {
        $this->currentPromise->cancel();
        $this->result->cancel();
    }

    private function nextCoroutine($yielded)
    {
        $this->currentPromise = promise_for($yielded)
            ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
    }

    /**
     * @internal
     */
    public function _handleSuccess($value)
    {
        unset($this->currentPromise);
        try {
            $next = $this->generator->send($value);
            if ($this->generator->valid()) {
                $this->nextCoroutine($next);
            } else {
                $this->result->resolve($value);
            }
        } catch (Exception $exception) {
            $this->result->reject($exception);
        } catch (Throwable $throwable) {
            $this->result->reject($throwable);
        }
    }

    /**
     * @internal
     */
    public function _handleFailure($reason)
    {
        unset($this->currentPromise);
        try {
            $nextYield = $this->generator->throw(exception_for($reason));
            // The throw was caught, so keep iterating on the coroutine
            $this->nextCoroutine($nextYield);
        } catch (Exception $exception) {
            $this->result->reject($exception);
        } catch (Throwable $throwable) {
            $this->result->reject($throwable);
        }
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Represents a promise that iterates over many promises and invokes
 * side-effect functions in the process.
 */
class EachPromise implements PromisorInterface
{
    private $pending = [];

    /** @var \Iterator */
    private $iterable;

    /** @var callable|int */
    private $concurrency;

    /** @var callable */
    private $onFulfilled;

    /** @var callable */
    private $onRejected;

    /** @var Promise */
    private $aggregate;

    /** @var bool */
    private $mutex;

    /**
     * Configuration hash can include the following key value pairs:
     *
     * - fulfilled: (callable) Invoked when a promise fulfills. The function
     *   is invoked with three arguments: the fulfillment value, the index
     *   position from the iterable list of the promise, and the aggregate
     *   promise that manages all of the promises. The aggregate promise may
     *   be resolved from within the callback to short-circuit the promise.
     * - rejected: (callable) Invoked when a promise is rejected. The
     *   function is invoked with three arguments: the rejection reason, the
     *   index position from the iterable list of the promise, and the
     *   aggregate promise that manages all of the promises. The aggregate
     *   promise may be resolved from within the callback to short-circuit
     *   the promise.
     * - concurrency: (integer) Pass this configuration option to limit the
     *   allowed number of outstanding concurrently executing promises,
     *   creating a capped pool of promises. There is no limit by default.
     *
     * @param mixed    $iterable Promises or values to iterate.
     * @param array    $config   Configuration options
     */
    public function __construct($iterable, array $config = [])
    {
        $this->iterable = iter_for($iterable);

        if (isset($config['concurrency'])) {
            $this->concurrency = $config['concurrency'];
        }

        if (isset($config['fulfilled'])) {
            $this->onFulfilled = $config['fulfilled'];
        }

        if (isset($config['rejected'])) {
            $this->onRejected = $config['rejected'];
        }
    }

    public function promise()
    {
        if ($this->aggregate) {
            return $this->aggregate;
        }

        try {
            $this->createPromise();
            $this->iterable->rewind();
            $this->refillPending();
        } catch (\Throwable $e) {
            $this->aggregate->reject($e);
        } catch (\Exception $e) {
            $this->aggregate->reject($e);
        }

        return $this->aggregate;
    }

    private function createPromise()
    {
        $this->mutex = false;
        $this->aggregate = new Promise(function () {
            reset($this->pending);
            if (empty($this->pending) && !$this->iterable->valid()) {
                $this->aggregate->resolve(null);
                return;
            }

            // Consume a potentially fluctuating list of promises while
            // ensuring that indexes are maintained (precluding array_shift).
            while ($promise = current($this->pending)) {
                next($this->pending);
                $promise->wait();
                if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
                    return;
                }
            }
        });

        // Clear the references when the promise is resolved.
        $clearFn = function () {
            $this->iterable = $this->concurrency = $this->pending = null;
            $this->onFulfilled = $this->onRejected = null;
        };

        $this->aggregate->then($clearFn, $clearFn);
    }

    private function refillPending()
    {
        if (!$this->concurrency) {
            // Add all pending promises.
            while ($this->addPending() && $this->advanceIterator());
            return;
        }

        // Add only up to N pending promises.
        $concurrency = is_callable($this->concurrency)
            ? call_user_func($this->concurrency, count($this->pending))
            : $this->concurrency;
        $concurrency = max($concurrency - count($this->pending), 0);
        // Concurrency may be set to 0 to disallow new promises.
        if (!$concurrency) {
            return;
        }
        // Add the first pending promise.
        $this->addPending();
        // Note this is special handling for concurrency=1 so that we do
        // not advance the iterator after adding the first promise. This
        // helps work around issues with generators that might not have the
        // next value to yield until promise callbacks are called.
        while (--$concurrency
            && $this->advanceIterator()
            && $this->addPending());
    }

    private function addPending()
    {
        if (!$this->iterable || !$this->iterable->valid()) {
            return false;
        }

        $promise = promise_for($this->iterable->current());
        $idx = $this->iterable->key();

        $this->pending[$idx] = $promise->then(
            function ($value) use ($idx) {
                if ($this->onFulfilled) {
                    call_user_func(
                        $this->onFulfilled, $value, $idx, $this->aggregate
                    );
                }
                $this->step($idx);
            },
            function ($reason) use ($idx) {
                if ($this->onRejected) {
                    call_user_func(
                        $this->onRejected, $reason, $idx, $this->aggregate
                    );
                }
                $this->step($idx);
            }
        );

        return true;
    }

    private function advanceIterator()
    {
        // Place a lock on the iterator so that we ensure to not recurse,
        // preventing fatal generator errors.
        if ($this->mutex) {
            return false;
        }

        $this->mutex = true;

        try {
            $this->iterable->next();
            $this->mutex = false;
            return true;
        } catch (\Throwable $e) {
            $this->aggregate->reject($e);
            $this->mutex = false;
            return false;
        } catch (\Exception $e) {
            $this->aggregate->reject($e);
            $this->mutex = false;
            return false;
        }
    }

    private function step($idx)
    {
        // If the promise was already resolved, then ignore this step.
        if ($this->aggregate->getState() !== PromiseInterface::PENDING) {
            return;
        }

        unset($this->pending[$idx]);

        // Only refill pending promises if we are not locked, preventing the
        // EachPromise to recursively invoke the provided iterator, which
        // cause a fatal error: "Cannot resume an already running generator"
        if ($this->advanceIterator() && !$this->checkIfFinished()) {
            // Add more pending promises if possible.
            $this->refillPending();
        }
    }

    private function checkIfFinished()
    {
        if (!$this->pending && !$this->iterable->valid()) {
            // Resolve the promise if there's nothing left to do.
            $this->aggregate->resolve(null);
            return true;
        }

        return false;
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A promise that has been fulfilled.
 *
 * Thenning off of this promise will invoke the onFulfilled callback
 * immediately and ignore other callbacks.
 */
class FulfilledPromise implements PromiseInterface
{
    private $value;

    public function __construct($value)
    {
        if (method_exists($value, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a FulfilledPromise with a promise.');
        }

        $this->value = $value;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        // Return itself if there is no onFulfilled function.
        if (!$onFulfilled) {
            return $this;
        }

        $queue = queue();
        $p = new Promise([$queue, 'run']);
        $value = $this->value;
        $queue->add(static function () use ($p, $value, $onFulfilled) {
            if ($p->getState() === self::PENDING) {
                try {
                    $p->resolve($onFulfilled($value));
                } catch (\Throwable $e) {
                    $p->reject($e);
                } catch (\Exception $e) {
                    $p->reject($e);
                }
            }
        });

        return $p;
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then(null, $onRejected);
    }

    public function wait($unwrap = true, $defaultDelivery = null)
    {
        return $unwrap ? $this->value : null;
    }

    public function getState()
    {
        return self::FULFILLED;
    }

    public function resolve($value)
    {
        if ($value !== $this->value) {
            throw new \LogicException("Cannot resolve a fulfilled promise");
        }
    }

    public function reject($reason)
    {
        throw new \LogicException("Cannot reject a fulfilled promise");
    }

    public function cancel()
    {
        // pass
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Get the global task queue used for promise resolution.
 *
 * This task queue MUST be run in an event loop in order for promises to be
 * settled asynchronously. It will be automatically run when synchronously
 * waiting on a promise.
 *
 * <code>
 * while ($eventLoop->isRunning()) {
 *     GuzzleHttp\Promise\queue()->run();
 * }
 * </code>
 *
 * @param TaskQueueInterface $assign Optionally specify a new queue instance.
 *
 * @return TaskQueueInterface
 */
function queue(TaskQueueInterface $assign = null)
{
    static $queue;

    if ($assign) {
        $queue = $assign;
    } elseif (!$queue) {
        $queue = new TaskQueue();
    }

    return $queue;
}

/**
 * Adds a function to run in the task queue when it is next `run()` and returns
 * a promise that is fulfilled or rejected with the result.
 *
 * @param callable $task Task function to run.
 *
 * @return PromiseInterface
 */
function task(callable $task)
{
    $queue = queue();
    $promise = new Promise([$queue, 'run']);
    $queue->add(function () use ($task, $promise) {
        try {
            $promise->resolve($task());
        } catch (\Throwable $e) {
            $promise->reject($e);
        } catch (\Exception $e) {
            $promise->reject($e);
        }
    });

    return $promise;
}

/**
 * Creates a promise for a value if the value is not a promise.
 *
 * @param mixed $value Promise or value.
 *
 * @return PromiseInterface
 */
function promise_for($value)
{
    if ($value instanceof PromiseInterface) {
        return $value;
    }

    // Return a Guzzle promise that shadows the given promise.
    if (method_exists($value, 'then')) {
        $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
        $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
        $promise = new Promise($wfn, $cfn);
        $value->then([$promise, 'resolve'], [$promise, 'reject']);
        return $promise;
    }

    return new FulfilledPromise($value);
}

/**
 * Creates a rejected promise for a reason if the reason is not a promise. If
 * the provided reason is a promise, then it is returned as-is.
 *
 * @param mixed $reason Promise or reason.
 *
 * @return PromiseInterface
 */
function rejection_for($reason)
{
    if ($reason instanceof PromiseInterface) {
        return $reason;
    }

    return new RejectedPromise($reason);
}

/**
 * Create an exception for a rejected promise value.
 *
 * @param mixed $reason
 *
 * @return \Exception|\Throwable
 */
function exception_for($reason)
{
    return $reason instanceof \Exception || $reason instanceof \Throwable
        ? $reason
        : new RejectionException($reason);
}

/**
 * Returns an iterator for the given value.
 *
 * @param mixed $value
 *
 * @return \Iterator
 */
function iter_for($value)
{
    if ($value instanceof \Iterator) {
        return $value;
    } elseif (is_array($value)) {
        return new \ArrayIterator($value);
    } else {
        return new \ArrayIterator([$value]);
    }
}

/**
 * Synchronously waits on a promise to resolve and returns an inspection state
 * array.
 *
 * Returns a state associative array containing a "state" key mapping to a
 * valid promise state. If the state of the promise is "fulfilled", the array
 * will contain a "value" key mapping to the fulfilled value of the promise. If
 * the promise is rejected, the array will contain a "reason" key mapping to
 * the rejection reason of the promise.
 *
 * @param PromiseInterface $promise Promise or value.
 *
 * @return array
 */
function inspect(PromiseInterface $promise)
{
    try {
        return [
            'state' => PromiseInterface::FULFILLED,
            'value' => $promise->wait()
        ];
    } catch (RejectionException $e) {
        return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
    } catch (\Throwable $e) {
        return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
    } catch (\Exception $e) {
        return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
    }
}

/**
 * Waits on all of the provided promises, but does not unwrap rejected promises
 * as thrown exception.
 *
 * Returns an array of inspection state arrays.
 *
 * @param PromiseInterface[] $promises Traversable of promises to wait upon.
 *
 * @return array
 * @see GuzzleHttp\Promise\inspect for the inspection state array format.
 */
function inspect_all($promises)
{
    $results = [];
    foreach ($promises as $key => $promise) {
        $results[$key] = inspect($promise);
    }

    return $results;
}

/**
 * Waits on all of the provided promises and returns the fulfilled values.
 *
 * Returns an array that contains the value of each promise (in the same order
 * the promises were provided). An exception is thrown if any of the promises
 * are rejected.
 *
 * @param mixed $promises Iterable of PromiseInterface objects to wait on.
 *
 * @return array
 * @throws \Exception on error
 * @throws \Throwable on error in PHP >=7
 */
function unwrap($promises)
{
    $results = [];
    foreach ($promises as $key => $promise) {
        $results[$key] = $promise->wait();
    }

    return $results;
}

/**
 * Given an array of promises, return a promise that is fulfilled when all the
 * items in the array are fulfilled.
 *
 * The promise's fulfillment value is an array with fulfillment values at
 * respective positions to the original array. If any promise in the array
 * rejects, the returned promise is rejected with the rejection reason.
 *
 * @param mixed $promises Promises or values.
 *
 * @return PromiseInterface
 */
function all($promises)
{
    $results = [];
    return each(
        $promises,
        function ($value, $idx) use (&$results) {
            $results[$idx] = $value;
        },
        function ($reason, $idx, Promise $aggregate) {
            $aggregate->reject($reason);
        }
    )->then(function () use (&$results) {
        ksort($results);
        return $results;
    });
}

/**
 * Initiate a competitive race between multiple promises or values (values will
 * become immediately fulfilled promises).
 *
 * When count amount of promises have been fulfilled, the returned promise is
 * fulfilled with an array that contains the fulfillment values of the winners
 * in order of resolution.
 *
 * This prommise is rejected with a {@see GuzzleHttp\Promise\AggregateException}
 * if the number of fulfilled promises is less than the desired $count.
 *
 * @param int   $count    Total number of promises.
 * @param mixed $promises Promises or values.
 *
 * @return PromiseInterface
 */
function some($count, $promises)
{
    $results = [];
    $rejections = [];

    return each(
        $promises,
        function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
            if ($p->getState() !== PromiseInterface::PENDING) {
                return;
            }
            $results[$idx] = $value;
            if (count($results) >= $count) {
                $p->resolve(null);
            }
        },
        function ($reason) use (&$rejections) {
            $rejections[] = $reason;
        }
    )->then(
        function () use (&$results, &$rejections, $count) {
            if (count($results) !== $count) {
                throw new AggregateException(
                    'Not enough promises to fulfill count',
                    $rejections
                );
            }
            ksort($results);
            return array_values($results);
        }
    );
}

/**
 * Like some(), with 1 as count. However, if the promise fulfills, the
 * fulfillment value is not an array of 1 but the value directly.
 *
 * @param mixed $promises Promises or values.
 *
 * @return PromiseInterface
 */
function any($promises)
{
    return some(1, $promises)->then(function ($values) { return $values[0]; });
}

/**
 * Returns a promise that is fulfilled when all of the provided promises have
 * been fulfilled or rejected.
 *
 * The returned promise is fulfilled with an array of inspection state arrays.
 *
 * @param mixed $promises Promises or values.
 *
 * @return PromiseInterface
 * @see GuzzleHttp\Promise\inspect for the inspection state array format.
 */
function settle($promises)
{
    $results = [];

    return each(
        $promises,
        function ($value, $idx) use (&$results) {
            $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
        },
        function ($reason, $idx) use (&$results) {
            $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
        }
    )->then(function () use (&$results) {
        ksort($results);
        return $results;
    });
}

/**
 * Given an iterator that yields promises or values, returns a promise that is
 * fulfilled with a null value when the iterator has been consumed or the
 * aggregate promise has been fulfilled or rejected.
 *
 * $onFulfilled is a function that accepts the fulfilled value, iterator
 * index, and the aggregate promise. The callback can invoke any necessary side
 * effects and choose to resolve or reject the aggregate promise if needed.
 *
 * $onRejected is a function that accepts the rejection reason, iterator
 * index, and the aggregate promise. The callback can invoke any necessary side
 * effects and choose to resolve or reject the aggregate promise if needed.
 *
 * @param mixed    $iterable    Iterator or array to iterate over.
 * @param callable $onFulfilled
 * @param callable $onRejected
 *
 * @return PromiseInterface
 */
function each(
    $iterable,
    callable $onFulfilled = null,
    callable $onRejected = null
) {
    return (new EachPromise($iterable, [
        'fulfilled' => $onFulfilled,
        'rejected'  => $onRejected
    ]))->promise();
}

/**
 * Like each, but only allows a certain number of outstanding promises at any
 * given time.
 *
 * $concurrency may be an integer or a function that accepts the number of
 * pending promises and returns a numeric concurrency limit value to allow for
 * dynamic a concurrency size.
 *
 * @param mixed        $iterable
 * @param int|callable $concurrency
 * @param callable     $onFulfilled
 * @param callable     $onRejected
 *
 * @return PromiseInterface
 */
function each_limit(
    $iterable,
    $concurrency,
    callable $onFulfilled = null,
    callable $onRejected = null
) {
    return (new EachPromise($iterable, [
        'fulfilled'   => $onFulfilled,
        'rejected'    => $onRejected,
        'concurrency' => $concurrency
    ]))->promise();
}

/**
 * Like each_limit, but ensures that no promise in the given $iterable argument
 * is rejected. If any promise is rejected, then the aggregate promise is
 * rejected with the encountered rejection.
 *
 * @param mixed        $iterable
 * @param int|callable $concurrency
 * @param callable     $onFulfilled
 *
 * @return PromiseInterface
 */
function each_limit_all(
    $iterable,
    $concurrency,
    callable $onFulfilled = null
) {
    return each_limit(
        $iterable,
        $concurrency,
        $onFulfilled,
        function ($reason, $idx, PromiseInterface $aggregate) {
            $aggregate->reject($reason);
        }
    );
}

/**
 * Returns true if a promise is fulfilled.
 *
 * @param PromiseInterface $promise
 *
 * @return bool
 */
function is_fulfilled(PromiseInterface $promise)
{
    return $promise->getState() === PromiseInterface::FULFILLED;
}

/**
 * Returns true if a promise is rejected.
 *
 * @param PromiseInterface $promise
 *
 * @return bool
 */
function is_rejected(PromiseInterface $promise)
{
    return $promise->getState() === PromiseInterface::REJECTED;
}

/**
 * Returns true if a promise is fulfilled or rejected.
 *
 * @param PromiseInterface $promise
 *
 * @return bool
 */
function is_settled(PromiseInterface $promise)
{
    return $promise->getState() !== PromiseInterface::PENDING;
}

/**
 * @see Coroutine
 *
 * @param callable $generatorFn
 *
 * @return PromiseInterface
 */
function coroutine(callable $generatorFn)
{
    return new Coroutine($generatorFn);
}
<?php

// Don't redefine the functions if included multiple times.
if (!function_exists('GuzzleHttp\Promise\promise_for')) {
    require __DIR__ . '/functions.php';
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Promises/A+ implementation that avoids recursion when possible.
 *
 * @link https://promisesaplus.com/
 */
class Promise implements PromiseInterface
{
    private $state = self::PENDING;
    private $result;
    private $cancelFn;
    private $waitFn;
    private $waitList;
    private $handlers = [];

    /**
     * @param callable $waitFn   Fn that when invoked resolves the promise.
     * @param callable $cancelFn Fn that when invoked cancels the promise.
     */
    public function __construct(
        callable $waitFn = null,
        callable $cancelFn = null
    ) {
        $this->waitFn = $waitFn;
        $this->cancelFn = $cancelFn;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        if ($this->state === self::PENDING) {
            $p = new Promise(null, [$this, 'cancel']);
            $this->handlers[] = [$p, $onFulfilled, $onRejected];
            $p->waitList = $this->waitList;
            $p->waitList[] = $this;
            return $p;
        }

        // Return a fulfilled promise and immediately invoke any callbacks.
        if ($this->state === self::FULFILLED) {
            return $onFulfilled
                ? promise_for($this->result)->then($onFulfilled)
                : promise_for($this->result);
        }

        // It's either cancelled or rejected, so return a rejected promise
        // and immediately invoke any callbacks.
        $rejection = rejection_for($this->result);
        return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then(null, $onRejected);
    }

    public function wait($unwrap = true)
    {
        $this->waitIfPending();

        $inner = $this->result instanceof PromiseInterface
            ? $this->result->wait($unwrap)
            : $this->result;

        if ($unwrap) {
            if ($this->result instanceof PromiseInterface
                || $this->state === self::FULFILLED
            ) {
                return $inner;
            } else {
                // It's rejected so "unwrap" and throw an exception.
                throw exception_for($inner);
            }
        }
    }

    public function getState()
    {
        return $this->state;
    }

    public function cancel()
    {
        if ($this->state !== self::PENDING) {
            return;
        }

        $this->waitFn = $this->waitList = null;

        if ($this->cancelFn) {
            $fn = $this->cancelFn;
            $this->cancelFn = null;
            try {
                $fn();
            } catch (\Throwable $e) {
                $this->reject($e);
            } catch (\Exception $e) {
                $this->reject($e);
            }
        }

        // Reject the promise only if it wasn't rejected in a then callback.
        if ($this->state === self::PENDING) {
            $this->reject(new CancellationException('Promise has been cancelled'));
        }
    }

    public function resolve($value)
    {
        $this->settle(self::FULFILLED, $value);
    }

    public function reject($reason)
    {
        $this->settle(self::REJECTED, $reason);
    }

    private function settle($state, $value)
    {
        if ($this->state !== self::PENDING) {
            // Ignore calls with the same resolution.
            if ($state === $this->state && $value === $this->result) {
                return;
            }
            throw $this->state === $state
                ? new \LogicException("The promise is already {$state}.")
                : new \LogicException("Cannot change a {$this->state} promise to {$state}");
        }

        if ($value === $this) {
            throw new \LogicException('Cannot fulfill or reject a promise with itself');
        }

        // Clear out the state of the promise but stash the handlers.
        $this->state = $state;
        $this->result = $value;
        $handlers = $this->handlers;
        $this->handlers = null;
        $this->waitList = $this->waitFn = null;
        $this->cancelFn = null;

        if (!$handlers) {
            return;
        }

        // If the value was not a settled promise or a thenable, then resolve
        // it in the task queue using the correct ID.
        if (!method_exists($value, 'then')) {
            $id = $state === self::FULFILLED ? 1 : 2;
            // It's a success, so resolve the handlers in the queue.
            queue()->add(static function () use ($id, $value, $handlers) {
                foreach ($handlers as $handler) {
                    self::callHandler($id, $value, $handler);
                }
            });
        } elseif ($value instanceof Promise
            && $value->getState() === self::PENDING
        ) {
            // We can just merge our handlers onto the next promise.
            $value->handlers = array_merge($value->handlers, $handlers);
        } else {
            // Resolve the handlers when the forwarded promise is resolved.
            $value->then(
                static function ($value) use ($handlers) {
                    foreach ($handlers as $handler) {
                        self::callHandler(1, $value, $handler);
                    }
                },
                static function ($reason) use ($handlers) {
                    foreach ($handlers as $handler) {
                        self::callHandler(2, $reason, $handler);
                    }
                }
            );
        }
    }

    /**
     * Call a stack of handlers using a specific callback index and value.
     *
     * @param int   $index   1 (resolve) or 2 (reject).
     * @param mixed $value   Value to pass to the callback.
     * @param array $handler Array of handler data (promise and callbacks).
     *
     * @return array Returns the next group to resolve.
     */
    private static function callHandler($index, $value, array $handler)
    {
        /** @var PromiseInterface $promise */
        $promise = $handler[0];

        // The promise may have been cancelled or resolved before placing
        // this thunk in the queue.
        if ($promise->getState() !== self::PENDING) {
            return;
        }

        try {
            if (isset($handler[$index])) {
                $promise->resolve($handler[$index]($value));
            } elseif ($index === 1) {
                // Forward resolution values as-is.
                $promise->resolve($value);
            } else {
                // Forward rejections down the chain.
                $promise->reject($value);
            }
        } catch (\Throwable $reason) {
            $promise->reject($reason);
        } catch (\Exception $reason) {
            $promise->reject($reason);
        }
    }

    private function waitIfPending()
    {
        if ($this->state !== self::PENDING) {
            return;
        } elseif ($this->waitFn) {
            $this->invokeWaitFn();
        } elseif ($this->waitList) {
            $this->invokeWaitList();
        } else {
            // If there's not wait function, then reject the promise.
            $this->reject('Cannot wait on a promise that has '
                . 'no internal wait function. You must provide a wait '
                . 'function when constructing the promise to be able to '
                . 'wait on a promise.');
        }

        queue()->run();

        if ($this->state === self::PENDING) {
            $this->reject('Invoking the wait callback did not resolve the promise');
        }
    }

    private function invokeWaitFn()
    {
        try {
            $wfn = $this->waitFn;
            $this->waitFn = null;
            $wfn(true);
        } catch (\Exception $reason) {
            if ($this->state === self::PENDING) {
                // The promise has not been resolved yet, so reject the promise
                // with the exception.
                $this->reject($reason);
            } else {
                // The promise was already resolved, so there's a problem in
                // the application.
                throw $reason;
            }
        }
    }

    private function invokeWaitList()
    {
        $waitList = $this->waitList;
        $this->waitList = null;

        foreach ($waitList as $result) {
            while (true) {
                $result->waitIfPending();

                if ($result->result instanceof Promise) {
                    $result = $result->result;
                } else {
                    if ($result->result instanceof PromiseInterface) {
                        $result->result->wait(false);
                    }
                    break;
                }
            }
        }
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A promise represents the eventual result of an asynchronous operation.
 *
 * The primary way of interacting with a promise is through its then method,
 * which registers callbacks to receive either a promise’s eventual value or
 * the reason why the promise cannot be fulfilled.
 *
 * @link https://promisesaplus.com/
 */
interface PromiseInterface
{
    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';

    /**
     * Appends fulfillment and rejection handlers to the promise, and returns
     * a new promise resolving to the return value of the called handler.
     *
     * @param callable $onFulfilled Invoked when the promise fulfills.
     * @param callable $onRejected  Invoked when the promise is rejected.
     *
     * @return PromiseInterface
     */
    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    );

    /**
     * Appends a rejection handler callback to the promise, and returns a new
     * promise resolving to the return value of the callback if it is called,
     * or to its original fulfillment value if the promise is instead
     * fulfilled.
     *
     * @param callable $onRejected Invoked when the promise is rejected.
     *
     * @return PromiseInterface
     */
    public function otherwise(callable $onRejected);

    /**
     * Get the state of the promise ("pending", "rejected", or "fulfilled").
     *
     * The three states can be checked against the constants defined on
     * PromiseInterface: PENDING, FULFILLED, and REJECTED.
     *
     * @return string
     */
    public function getState();

    /**
     * Resolve the promise with the given value.
     *
     * @param mixed $value
     * @throws \RuntimeException if the promise is already resolved.
     */
    public function resolve($value);

    /**
     * Reject the promise with the given reason.
     *
     * @param mixed $reason
     * @throws \RuntimeException if the promise is already resolved.
     */
    public function reject($reason);

    /**
     * Cancels the promise if possible.
     *
     * @link https://github.com/promises-aplus/cancellation-spec/issues/7
     */
    public function cancel();

    /**
     * Waits until the promise completes if possible.
     *
     * Pass $unwrap as true to unwrap the result of the promise, either
     * returning the resolved value or throwing the rejected exception.
     *
     * If the promise cannot be waited on, then the promise will be rejected.
     *
     * @param bool $unwrap
     *
     * @return mixed
     * @throws \LogicException if the promise has no wait function or if the
     *                         promise does not settle after waiting.
     */
    public function wait($unwrap = true);
}
<?php
namespace GuzzleHttp\Promise;

/**
 * Interface used with classes that return a promise.
 */
interface PromisorInterface
{
    /**
     * Returns a promise.
     *
     * @return PromiseInterface
     */
    public function promise();
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A promise that has been rejected.
 *
 * Thenning off of this promise will invoke the onRejected callback
 * immediately and ignore other callbacks.
 */
class RejectedPromise implements PromiseInterface
{
    private $reason;

    public function __construct($reason)
    {
        if (method_exists($reason, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a RejectedPromise with a promise.');
        }

        $this->reason = $reason;
    }

    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        // If there's no onRejected callback then just return self.
        if (!$onRejected) {
            return $this;
        }

        $queue = queue();
        $reason = $this->reason;
        $p = new Promise([$queue, 'run']);
        $queue->add(static function () use ($p, $reason, $onRejected) {
            if ($p->getState() === self::PENDING) {
                try {
                    // Return a resolved promise if onRejected does not throw.
                    $p->resolve($onRejected($reason));
                } catch (\Throwable $e) {
                    // onRejected threw, so return a rejected promise.
                    $p->reject($e);
                } catch (\Exception $e) {
                    // onRejected threw, so return a rejected promise.
                    $p->reject($e);
                }
            }
        });

        return $p;
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then(null, $onRejected);
    }

    public function wait($unwrap = true, $defaultDelivery = null)
    {
        if ($unwrap) {
            throw exception_for($this->reason);
        }
    }

    public function getState()
    {
        return self::REJECTED;
    }

    public function resolve($value)
    {
        throw new \LogicException("Cannot resolve a rejected promise");
    }

    public function reject($reason)
    {
        if ($reason !== $this->reason) {
            throw new \LogicException("Cannot reject a rejected promise");
        }
    }

    public function cancel()
    {
        // pass
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A special exception that is thrown when waiting on a rejected promise.
 *
 * The reason value is available via the getReason() method.
 */
class RejectionException extends \RuntimeException
{
    /** @var mixed Rejection reason. */
    private $reason;

    /**
     * @param mixed $reason       Rejection reason.
     * @param string $description Optional description
     */
    public function __construct($reason, $description = null)
    {
        $this->reason = $reason;

        $message = 'The promise was rejected';

        if ($description) {
            $message .= ' with reason: ' . $description;
        } elseif (is_string($reason)
            || (is_object($reason) && method_exists($reason, '__toString'))
        ) {
            $message .= ' with reason: ' . $this->reason;
        } elseif ($reason instanceof \JsonSerializable) {
            $message .= ' with reason: '
                . json_encode($this->reason, JSON_PRETTY_PRINT);
        }

        parent::__construct($message);
    }

    /**
     * Returns the rejection reason.
     *
     * @return mixed
     */
    public function getReason()
    {
        return $this->reason;
    }
}
<?php
namespace GuzzleHttp\Promise;

/**
 * A task queue that executes tasks in a FIFO order.
 *
 * This task queue class is used to settle promises asynchronously and
 * maintains a constant stack size. You can use the task queue asynchronously
 * by calling the `run()` function of the global task queue in an event loop.
 *
 *     GuzzleHttp\Promise\queue()->run();
 */
class TaskQueue implements TaskQueueInterface
{
    private $enableShutdown = true;
    private $queue = [];

    public function __construct($withShutdown = true)
    {
        if ($withShutdown) {
            register_shutdown_function(function () {
                if ($this->enableShutdown) {
                    // Only run the tasks if an E_ERROR didn't occur.
                    $err = error_get_last();
                    if (!$err || ($err['type'] ^ E_ERROR)) {
                        $this->run();
                    }
                }
            });
        }
    }

    public function isEmpty()
    {
        return !$this->queue;
    }

    public function add(callable $task)
    {
        $this->queue[] = $task;
    }

    public function run()
    {
        /** @var callable $task */
        while ($task = array_shift($this->queue)) {
            $task();
        }
    }

    /**
     * The task queue will be run and exhausted by default when the process
     * exits IFF the exit is not the result of a PHP E_ERROR error.
     *
     * You can disable running the automatic shutdown of the queue by calling
     * this function. If you disable the task queue shutdown process, then you
     * MUST either run the task queue (as a result of running your event loop
     * or manually using the run() method) or wait on each outstanding promise.
     *
     * Note: This shutdown will occur before any destructors are triggered.
     */
    public function disableShutdown()
    {
        $this->enableShutdown = false;
    }
}
<?php
namespace GuzzleHttp\Promise;

interface TaskQueueInterface
{
    /**
     * Returns true if the queue is empty.
     *
     * @return bool
     */
    public function isEmpty();

    /**
     * Adds a task to the queue that will be executed the next time run is
     * called.
     *
     * @param callable $task
     */
    public function add(callable $task);

    /**
     * Execute all of the pending task in the queue.
     */
    public function run();
}
phpunit.xml
composer.phar
composer.lock
composer-test.lock
vendor/
build/artifacts/
artifacts/
docs/_build
docs/*.pyc
.idea
.DS_STORE
language: php

php:
  - 5.4
  - 5.5
  - 5.6
  - 7.0
  - hhvm

sudo: false

install:
  - travis_retry composer install --no-interaction --prefer-source

script: make test

matrix:
  allow_failures:
    - php: hhvm
  fast_finish: true
# CHANGELOG

## 1.3.1 - 2016-06-25

* Fix `Uri::__toString` for network path references, e.g. `//example.org`.
* Fix missing lowercase normalization for host.
* Fix handling of URI components in case they are `'0'` in a lot of places,
  e.g. as a user info password.
* Fix `Uri::withAddedHeader` to correctly merge headers with different case.
* Fix trimming of header values in `Uri::withAddedHeader`. Header values may
  be surrounded by whitespace which should be ignored according to RFC 7230
  Section 3.2.4. This does not apply to header names.
* Fix `Uri::withAddedHeader` with an array of header values.
* Fix `Uri::resolve` when base path has no slash and handling of fragment.
* Fix handling of encoding in `Uri::with(out)QueryValue` so one can pass the
  key/value both in encoded as well as decoded form to those methods. This is
  consistent with withPath, withQuery etc.
* Fix `ServerRequest::withoutAttribute` when attribute value is null.

## 1.3.0 - 2016-04-13

* Added remaining interfaces needed for full PSR7 compatibility
  (ServerRequestInterface, UploadedFileInterface, etc.).
* Added support for stream_for from scalars.
* Can now extend Uri.
* Fixed a bug in validating request methods by making it more permissive.

## 1.2.3 - 2016-02-18

* Fixed support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
  streams, which can sometimes return fewer bytes than requested with `fread`.
* Fixed handling of gzipped responses with FNAME headers.

## 1.2.2 - 2016-01-22

* Added support for URIs without any authority.
* Added support for HTTP 451 'Unavailable For Legal Reasons.'
* Added support for using '0' as a filename.
* Added support for including non-standard ports in Host headers.

## 1.2.1 - 2015-11-02

* Now supporting negative offsets when seeking to SEEK_END.

## 1.2.0 - 2015-08-15

* Body as `"0"` is now properly added to a response.
* Now allowing forward seeking in CachingStream.
* Now properly parsing HTTP requests that contain proxy targets in
  `parse_request`.
* functions.php is now conditionally required.
* user-info is no longer dropped when resolving URIs.

## 1.1.0 - 2015-06-24

* URIs can now be relative.
* `multipart/form-data` headers are now overridden case-insensitively.
* URI paths no longer encode the following characters because they are allowed
  in URIs: "(", ")", "*", "!", "'"
* A port is no longer added to a URI when the scheme is missing and no port is
  present.

## 1.0.0 - 2015-05-19

Initial release.

Currently unsupported:

- `Psr\Http\Message\ServerRequestInterface`
- `Psr\Http\Message\UploadedFileInterface`
{
    "name": "guzzlehttp/psr7",
    "type": "library",
    "description": "PSR-7 message implementation",
    "keywords": ["message", "stream", "http", "uri"],
    "license": "MIT",
    "authors": [
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        }
    ],
    "require": {
        "php": ">=5.4.0",
        "psr/http-message": "~1.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0"
    },
    "provide": {
        "psr/http-message-implementation": "1.0"
    },
    "autoload": {
        "psr-4": {
            "GuzzleHttp\\Psr7\\": "src/"
        },
        "files": ["src/functions_include.php"]
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.4-dev"
        }
    }
}
Copyright (c) 2015 Michael Dowling, https://github.com/mtdowling <mtdowling@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS 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.
all: clean test

test:
	vendor/bin/phpunit $(TEST)

coverage:
	vendor/bin/phpunit --coverage-html=artifacts/coverage $(TEST)

view-coverage:
	open artifacts/coverage/index.html

check-tag:
	$(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1"))

tag: check-tag
	@echo Tagging $(TAG)
	chag update $(TAG)
	git commit -a -m '$(TAG) release'
	chag tag
	@echo "Release has been created. Push using 'make release'"
	@echo "Changes made in the release commit"
	git diff HEAD~1 HEAD

release: check-tag
	git push origin master
	git push origin $(TAG)

clean:
	rm -rf artifacts/*
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
         colors="true">
  <testsuites>
    <testsuite>
      <directory>tests</directory>
    </testsuite>
  </testsuites>
  <filter>
    <whitelist>
      <directory suffix=".php">src</directory>
      <exclude>
        <directory suffix="Interface.php">src/</directory>
      </exclude>
    </whitelist>
  </filter>
</phpunit>
# PSR-7 Message Implementation

This repository contains a full [PSR-7](http://www.php-fig.org/psr/psr-7/)
message implementation, several stream decorators, and some helpful
functionality like query string parsing.


[![Build Status](https://travis-ci.org/guzzle/psr7.svg?branch=master)](https://travis-ci.org/guzzle/psr7)


# Stream implementation

This package comes with a number of stream implementations and stream
decorators.


## AppendStream

`GuzzleHttp\Psr7\AppendStream`

Reads from multiple streams, one after the other.

```php
use GuzzleHttp\Psr7;

$a = Psr7\stream_for('abc, ');
$b = Psr7\stream_for('123.');
$composed = new Psr7\AppendStream([$a, $b]);

$composed->addStream(Psr7\stream_for(' Above all listen to me'));

echo $composed; // abc, 123. Above all listen to me.
```


## BufferStream

`GuzzleHttp\Psr7\BufferStream`

Provides a buffer stream that can be written to fill a buffer, and read
from to remove bytes from the buffer.

This stream returns a "hwm" metadata value that tells upstream consumers
what the configured high water mark of the stream is, or the maximum
preferred size of the buffer.

```php
use GuzzleHttp\Psr7;

// When more than 1024 bytes are in the buffer, it will begin returning
// false to writes. This is an indication that writers should slow down.
$buffer = new Psr7\BufferStream(1024);
```


## CachingStream

The CachingStream is used to allow seeking over previously read bytes on
non-seekable streams. This can be useful when transferring a non-seekable
entity body fails due to needing to rewind the stream (for example, resulting
from a redirect). Data that is read from the remote stream will be buffered in
a PHP temp stream so that previously read bytes are cached first in memory,
then on disk.

```php
use GuzzleHttp\Psr7;

$original = Psr7\stream_for(fopen('http://www.google.com', 'r'));
$stream = new Psr7\CachingStream($original);

$stream->read(1024);
echo $stream->tell();
// 1024

$stream->seek(0);
echo $stream->tell();
// 0
```


## DroppingStream

`GuzzleHttp\Psr7\DroppingStream`

Stream decorator that begins dropping data once the size of the underlying
stream becomes too full.

```php
use GuzzleHttp\Psr7;

// Create an empty stream
$stream = Psr7\stream_for();

// Start dropping data when the stream has more than 10 bytes
$dropping = new Psr7\DroppingStream($stream, 10);

$dropping->write('01234567890123456789');
echo $stream; // 0123456789
```


## FnStream

`GuzzleHttp\Psr7\FnStream`

Compose stream implementations based on a hash of functions.

Allows for easy testing and extension of a provided stream without needing
to create a concrete class for a simple extension point.

```php

use GuzzleHttp\Psr7;

$stream = Psr7\stream_for('hi');
$fnStream = Psr7\FnStream::decorate($stream, [
    'rewind' => function () use ($stream) {
        echo 'About to rewind - ';
        $stream->rewind();
        echo 'rewound!';
    }
]);

$fnStream->rewind();
// Outputs: About to rewind - rewound!
```


## InflateStream

`GuzzleHttp\Psr7\InflateStream`

Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.

This stream decorator skips the first 10 bytes of the given stream to remove
the gzip header, converts the provided stream to a PHP stream resource,
then appends the zlib.inflate filter. The stream is then converted back
to a Guzzle stream resource to be used as a Guzzle stream.


## LazyOpenStream

`GuzzleHttp\Psr7\LazyOpenStream`

Lazily reads or writes to a file that is opened only after an IO operation
take place on the stream.

```php
use GuzzleHttp\Psr7;

$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
// The file has not yet been opened...

echo $stream->read(10);
// The file is opened and read from only when needed.
```


## LimitStream

`GuzzleHttp\Psr7\LimitStream`

LimitStream can be used to read a subset or slice of an existing stream object.
This can be useful for breaking a large file into smaller pieces to be sent in
chunks (e.g. Amazon S3's multipart upload API).

```php
use GuzzleHttp\Psr7;

$original = Psr7\stream_for(fopen('/tmp/test.txt', 'r+'));
echo $original->getSize();
// >>> 1048576

// Limit the size of the body to 1024 bytes and start reading from byte 2048
$stream = new Psr7\LimitStream($original, 1024, 2048);
echo $stream->getSize();
// >>> 1024
echo $stream->tell();
// >>> 0
```


## MultipartStream

`GuzzleHttp\Psr7\MultipartStream`

Stream that when read returns bytes for a streaming multipart or
multipart/form-data stream.


## NoSeekStream

`GuzzleHttp\Psr7\NoSeekStream`

NoSeekStream wraps a stream and does not allow seeking.

```php
use GuzzleHttp\Psr7;

$original = Psr7\stream_for('foo');
$noSeek = new Psr7\NoSeekStream($original);

echo $noSeek->read(3);
// foo
var_export($noSeek->isSeekable());
// false
$noSeek->seek(0);
var_export($noSeek->read(3));
// NULL
```


## PumpStream

`GuzzleHttp\Psr7\PumpStream`

Provides a read only stream that pumps data from a PHP callable.

When invoking the provided callable, the PumpStream will pass the amount of
data requested to read to the callable. The callable can choose to ignore
this value and return fewer or more bytes than requested. Any extra data
returned by the provided callable is buffered internally until drained using
the read() function of the PumpStream. The provided callable MUST return
false when there is no more data to read.


## Implementing stream decorators

Creating a stream decorator is very easy thanks to the
`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
stream. Just `use` the `StreamDecoratorTrait` and implement your custom
methods.

For example, let's say we wanted to call a specific function each time the last
byte is read from a stream. This could be implemented by overriding the
`read()` method.

```php
use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7\StreamDecoratorTrait;

class EofCallbackStream implements StreamInterface
{
    use StreamDecoratorTrait;

    private $callback;

    public function __construct(StreamInterface $stream, callable $cb)
    {
        $this->stream = $stream;
        $this->callback = $cb;
    }

    public function read($length)
    {
        $result = $this->stream->read($length);

        // Invoke the callback when EOF is hit.
        if ($this->eof()) {
            call_user_func($this->callback);
        }

        return $result;
    }
}
```

This decorator could be added to any existing stream and used like so:

```php
use GuzzleHttp\Psr7;

$original = Psr7\stream_for('foo');

$eofStream = new EofCallbackStream($original, function () {
    echo 'EOF!';
});

$eofStream->read(2);
$eofStream->read(1);
// echoes "EOF!"
$eofStream->seek(0);
$eofStream->read(3);
// echoes "EOF!"
```


## PHP StreamWrapper

You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
PSR-7 stream as a PHP stream resource.

Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
stream from a PSR-7 stream.

```php
use GuzzleHttp\Psr7\StreamWrapper;

$stream = GuzzleHttp\Psr7\stream_for('hello!');
$resource = StreamWrapper::getResource($stream);
echo fread($resource, 6); // outputs hello!
```


# Function API

There are various functions available under the `GuzzleHttp\Psr7` namespace.


## `function str`

`function str(MessageInterface $message)`

Returns the string representation of an HTTP message.

```php
$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
echo GuzzleHttp\Psr7\str($request);
```


## `function uri_for`

`function uri_for($uri)`

This function accepts a string or `Psr\Http\Message\UriInterface` and returns a
UriInterface for the given value. If the value is already a `UriInterface`, it
is returned as-is.

```php
$uri = GuzzleHttp\Psr7\uri_for('http://example.com');
assert($uri === GuzzleHttp\Psr7\uri_for($uri));
```


## `function stream_for`

`function stream_for($resource = '', array $options = [])`

Create a new stream based on the input type.

Options is an associative array that can contain the following keys:

* - metadata: Array of custom metadata.
* - size: Size of the stream.

This method accepts the following `$resource` types:

- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
- `string`: Creates a stream object that uses the given string as the contents.
- `resource`: Creates a stream object that wraps the given PHP stream resource.
- `Iterator`: If the provided value implements `Iterator`, then a read-only
  stream object will be created that wraps the given iterable. Each time the
  stream is read from, data from the iterator will fill a buffer and will be
  continuously called until the buffer is equal to the requested read size.
  Subsequent read calls will first read from the buffer and then call `next`
  on the underlying iterator until it is exhausted.
- `object` with `__toString()`: If the object has the `__toString()` method,
  the object will be cast to a string and then a stream will be returned that
  uses the string value.
- `NULL`: When `null` is passed, an empty stream object is returned.
- `callable` When a callable is passed, a read-only stream object will be
  created that invokes the given callable. The callable is invoked with the
  number of suggested bytes to read. The callable can return any number of
  bytes, but MUST return `false` when there is no more data to return. The
  stream object that wraps the callable will invoke the callable until the
  number of requested bytes are available. Any additional bytes will be
  buffered and used in subsequent reads.

```php
$stream = GuzzleHttp\Psr7\stream_for('foo');
$stream = GuzzleHttp\Psr7\stream_for(fopen('/path/to/file', 'r'));

$generator function ($bytes) {
    for ($i = 0; $i < $bytes; $i++) {
        yield ' ';
    }
}

$stream = GuzzleHttp\Psr7\stream_for($generator(100));
```


## `function parse_header`

`function parse_header($header)`

Parse an array of header values containing ";" separated data into an array of
associative arrays representing the header key value pair data of the header.
When a parameter does not contain a value, but just contains a key, this
function will inject a key with a '' string value.


## `function normalize_header`

`function normalize_header($header)`

Converts an array of header values that may contain comma separated headers
into an array of headers with no comma separated values.


## `function modify_request`

`function modify_request(RequestInterface $request, array $changes)`

Clone and modify a request with the given changes. This method is useful for
reducing the number of clones needed to mutate a message.

The changes can be one of:

- method: (string) Changes the HTTP method.
- set_headers: (array) Sets the given headers.
- remove_headers: (array) Remove the given headers.
- body: (mixed) Sets the given body.
- uri: (UriInterface) Set the URI.
- query: (string) Set the query string value of the URI.
- version: (string) Set the protocol version.


## `function rewind_body`

`function rewind_body(MessageInterface $message)`

Attempts to rewind a message body and throws an exception on failure. The body
of the message will only be rewound if a call to `tell()` returns a value other
than `0`.


## `function try_fopen`

`function try_fopen($filename, $mode)`

Safely opens a PHP stream resource using a filename.

When fopen fails, PHP normally raises a warning. This function adds an error
handler that checks for errors and throws an exception instead.


## `function copy_to_string`

`function copy_to_string(StreamInterface $stream, $maxLen = -1)`

Copy the contents of a stream into a string until the given number of bytes
have been read.


## `function copy_to_stream`

`function copy_to_stream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)`

Copy the contents of a stream into another stream until the given number of
bytes have been read.


## `function hash`

`function hash(StreamInterface $stream, $algo, $rawOutput = false)`

Calculate a hash of a Stream. This method reads the entire stream to calculate
a rolling hash (based on PHP's hash_init functions).


## `function readline`

`function readline(StreamInterface $stream, $maxLength = null)`

Read a line from the stream up to the maximum allowed buffer length.


## `function parse_request`

`function parse_request($message)`

Parses a request message string into a request object.


## `function parse_response`

`function parse_response($message)`

Parses a response message string into a response object.


## `function parse_query`

`function parse_query($str, $urlEncoding = true)`

Parse a query string into an associative array.

If multiple values are found for the same key, the value of that key value pair
will become an array. This function does not parse nested PHP style arrays into
an associative array (e.g., `foo[a]=1&foo[b]=2` will be parsed into
`['foo[a]' => '1', 'foo[b]' => '2']`).


## `function build_query`

`function build_query(array $params, $encoding = PHP_QUERY_RFC3986)`

Build a query string from an array of key value pairs.

This function can use the return value of parse_query() to build a query string.
This function does not modify the provided keys when an array is encountered
(like http_build_query would).


## `function mimetype_from_filename`

`function mimetype_from_filename($filename)`

Determines the mimetype of a file by looking at its extension.


## `function mimetype_from_extension`

`function mimetype_from_extension($extension)`

Maps a file extensions to a mimetype.


# Static URI methods

The `GuzzleHttp\Psr7\Uri` class has several static methods to manipulate URIs.


## `GuzzleHttp\Psr7\Uri::removeDotSegments`

`public static function removeDotSegments(string $path): string`

Removes dot segments from a path and returns the new path.

See http://tools.ietf.org/html/rfc3986#section-5.2.4


## `GuzzleHttp\Psr7\Uri::resolve`

`public static function resolve(UriInterface $base, $rel): UriInterface`

Resolve a base URI with a relative URI and return a new URI.

See http://tools.ietf.org/html/rfc3986#section-5


## `GuzzleHttp\Psr7\Uri::withQueryValue`

`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`

Create a new URI with a specific query string value.

Any existing query string values that exactly match the provided key are
removed and replaced with the given key value pair.


## `GuzzleHttp\Psr7\Uri::withoutQueryValue`

`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`

Create a new URI with a specific query string value removed.

Any existing query string values that exactly match the provided key are
removed.


## `GuzzleHttp\Psr7\Uri::fromParts`

`public static function fromParts(array $parts): UriInterface`

Create a `GuzzleHttp\Psr7\Uri` object from a hash of `parse_url` parts.
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Reads from multiple streams, one after the other.
 *
 * This is a read-only stream decorator.
 */
class AppendStream implements StreamInterface
{
    /** @var StreamInterface[] Streams being decorated */
    private $streams = [];

    private $seekable = true;
    private $current = 0;
    private $pos = 0;
    private $detached = false;

    /**
     * @param StreamInterface[] $streams Streams to decorate. Each stream must
     *                                   be readable.
     */
    public function __construct(array $streams = [])
    {
        foreach ($streams as $stream) {
            $this->addStream($stream);
        }
    }

    public function __toString()
    {
        try {
            $this->rewind();
            return $this->getContents();
        } catch (\Exception $e) {
            return '';
        }
    }

    /**
     * Add a stream to the AppendStream
     *
     * @param StreamInterface $stream Stream to append. Must be readable.
     *
     * @throws \InvalidArgumentException if the stream is not readable
     */
    public function addStream(StreamInterface $stream)
    {
        if (!$stream->isReadable()) {
            throw new \InvalidArgumentException('Each stream must be readable');
        }

        // The stream is only seekable if all streams are seekable
        if (!$stream->isSeekable()) {
            $this->seekable = false;
        }

        $this->streams[] = $stream;
    }

    public function getContents()
    {
        return copy_to_string($this);
    }

    /**
     * Closes each attached stream.
     *
     * {@inheritdoc}
     */
    public function close()
    {
        $this->pos = $this->current = 0;

        foreach ($this->streams as $stream) {
            $stream->close();
        }

        $this->streams = [];
    }

    /**
     * Detaches each attached stream
     *
     * {@inheritdoc}
     */
    public function detach()
    {
        $this->close();
        $this->detached = true;
    }

    public function tell()
    {
        return $this->pos;
    }

    /**
     * Tries to calculate the size by adding the size of each stream.
     *
     * If any of the streams do not return a valid number, then the size of the
     * append stream cannot be determined and null is returned.
     *
     * {@inheritdoc}
     */
    public function getSize()
    {
        $size = 0;

        foreach ($this->streams as $stream) {
            $s = $stream->getSize();
            if ($s === null) {
                return null;
            }
            $size += $s;
        }

        return $size;
    }

    public function eof()
    {
        return !$this->streams ||
            ($this->current >= count($this->streams) - 1 &&
             $this->streams[$this->current]->eof());
    }

    public function rewind()
    {
        $this->seek(0);
    }

    /**
     * Attempts to seek to the given position. Only supports SEEK_SET.
     *
     * {@inheritdoc}
     */
    public function seek($offset, $whence = SEEK_SET)
    {
        if (!$this->seekable) {
            throw new \RuntimeException('This AppendStream is not seekable');
        } elseif ($whence !== SEEK_SET) {
            throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
        }

        $this->pos = $this->current = 0;

        // Rewind each stream
        foreach ($this->streams as $i => $stream) {
            try {
                $stream->rewind();
            } catch (\Exception $e) {
                throw new \RuntimeException('Unable to seek stream '
                    . $i . ' of the AppendStream', 0, $e);
            }
        }

        // Seek to the actual position by reading from each stream
        while ($this->pos < $offset && !$this->eof()) {
            $result = $this->read(min(8096, $offset - $this->pos));
            if ($result === '') {
                break;
            }
        }
    }

    /**
     * Reads from all of the appended streams until the length is met or EOF.
     *
     * {@inheritdoc}
     */
    public function read($length)
    {
        $buffer = '';
        $total = count($this->streams) - 1;
        $remaining = $length;
        $progressToNext = false;

        while ($remaining > 0) {

            // Progress to the next stream if needed.
            if ($progressToNext || $this->streams[$this->current]->eof()) {
                $progressToNext = false;
                if ($this->current === $total) {
                    break;
                }
                $this->current++;
            }

            $result = $this->streams[$this->current]->read($remaining);

            // Using a loose comparison here to match on '', false, and null
            if ($result == null) {
                $progressToNext = true;
                continue;
            }

            $buffer .= $result;
            $remaining = $length - strlen($buffer);
        }

        $this->pos += strlen($buffer);

        return $buffer;
    }

    public function isReadable()
    {
        return true;
    }

    public function isWritable()
    {
        return false;
    }

    public function isSeekable()
    {
        return $this->seekable;
    }

    public function write($string)
    {
        throw new \RuntimeException('Cannot write to an AppendStream');
    }

    public function getMetadata($key = null)
    {
        return $key ? null : [];
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Provides a buffer stream that can be written to to fill a buffer, and read
 * from to remove bytes from the buffer.
 *
 * This stream returns a "hwm" metadata value that tells upstream consumers
 * what the configured high water mark of the stream is, or the maximum
 * preferred size of the buffer.
 */
class BufferStream implements StreamInterface
{
    private $hwm;
    private $buffer = '';

    /**
     * @param int $hwm High water mark, representing the preferred maximum
     *                 buffer size. If the size of the buffer exceeds the high
     *                 water mark, then calls to write will continue to succeed
     *                 but will return false to inform writers to slow down
     *                 until the buffer has been drained by reading from it.
     */
    public function __construct($hwm = 16384)
    {
        $this->hwm = $hwm;
    }

    public function __toString()
    {
        return $this->getContents();
    }

    public function getContents()
    {
        $buffer = $this->buffer;
        $this->buffer = '';

        return $buffer;
    }

    public function close()
    {
        $this->buffer = '';
    }

    public function detach()
    {
        $this->close();
    }

    public function getSize()
    {
        return strlen($this->buffer);
    }

    public function isReadable()
    {
        return true;
    }

    public function isWritable()
    {
        return true;
    }

    public function isSeekable()
    {
        return false;
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        throw new \RuntimeException('Cannot seek a BufferStream');
    }

    public function eof()
    {
        return strlen($this->buffer) === 0;
    }

    public function tell()
    {
        throw new \RuntimeException('Cannot determine the position of a BufferStream');
    }

    /**
     * Reads data from the buffer.
     */
    public function read($length)
    {
        $currentLength = strlen($this->buffer);

        if ($length >= $currentLength) {
            // No need to slice the buffer because we don't have enough data.
            $result = $this->buffer;
            $this->buffer = '';
        } else {
            // Slice up the result to provide a subset of the buffer.
            $result = substr($this->buffer, 0, $length);
            $this->buffer = substr($this->buffer, $length);
        }

        return $result;
    }

    /**
     * Writes data to the buffer.
     */
    public function write($string)
    {
        $this->buffer .= $string;

        // TODO: What should happen here?
        if (strlen($this->buffer) >= $this->hwm) {
            return false;
        }

        return strlen($string);
    }

    public function getMetadata($key = null)
    {
        if ($key == 'hwm') {
            return $this->hwm;
        }

        return $key ? null : [];
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that can cache previously read bytes from a sequentially
 * read stream.
 */
class CachingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var StreamInterface Stream being wrapped */
    private $remoteStream;

    /** @var int Number of bytes to skip reading due to a write on the buffer */
    private $skipReadBytes = 0;

    /**
     * We will treat the buffer object as the body of the stream
     *
     * @param StreamInterface $stream Stream to cache
     * @param StreamInterface $target Optionally specify where data is cached
     */
    public function __construct(
        StreamInterface $stream,
        StreamInterface $target = null
    ) {
        $this->remoteStream = $stream;
        $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
    }

    public function getSize()
    {
        return max($this->stream->getSize(), $this->remoteStream->getSize());
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        if ($whence == SEEK_SET) {
            $byte = $offset;
        } elseif ($whence == SEEK_CUR) {
            $byte = $offset + $this->tell();
        } elseif ($whence == SEEK_END) {
            $size = $this->remoteStream->getSize();
            if ($size === null) {
                $size = $this->cacheEntireStream();
            }
            $byte = $size + $offset;
        } else {
            throw new \InvalidArgumentException('Invalid whence');
        }

        $diff = $byte - $this->stream->getSize();

        if ($diff > 0) {
            // Read the remoteStream until we have read in at least the amount
            // of bytes requested, or we reach the end of the file.
            while ($diff > 0 && !$this->remoteStream->eof()) {
                $this->read($diff);
                $diff = $byte - $this->stream->getSize();
            }
        } else {
            // We can just do a normal seek since we've already seen this byte.
            $this->stream->seek($byte);
        }
    }

    public function read($length)
    {
        // Perform a regular read on any previously read data from the buffer
        $data = $this->stream->read($length);
        $remaining = $length - strlen($data);

        // More data was requested so read from the remote stream
        if ($remaining) {
            // If data was written to the buffer in a position that would have
            // been filled from the remote stream, then we must skip bytes on
            // the remote stream to emulate overwriting bytes from that
            // position. This mimics the behavior of other PHP stream wrappers.
            $remoteData = $this->remoteStream->read(
                $remaining + $this->skipReadBytes
            );

            if ($this->skipReadBytes) {
                $len = strlen($remoteData);
                $remoteData = substr($remoteData, $this->skipReadBytes);
                $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
            }

            $data .= $remoteData;
            $this->stream->write($remoteData);
        }

        return $data;
    }

    public function write($string)
    {
        // When appending to the end of the currently read stream, you'll want
        // to skip bytes from being read from the remote stream to emulate
        // other stream wrappers. Basically replacing bytes of data of a fixed
        // length.
        $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
        if ($overflow > 0) {
            $this->skipReadBytes += $overflow;
        }

        return $this->stream->write($string);
    }

    public function eof()
    {
        return $this->stream->eof() && $this->remoteStream->eof();
    }

    /**
     * Close both the remote stream and buffer stream
     */
    public function close()
    {
        $this->remoteStream->close() && $this->stream->close();
    }

    private function cacheEntireStream()
    {
        $target = new FnStream(['write' => 'strlen']);
        copy_to_stream($this, $target);

        return $this->tell();
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that begins dropping data once the size of the underlying
 * stream becomes too full.
 */
class DroppingStream implements StreamInterface
{
    use StreamDecoratorTrait;

    private $maxLength;

    /**
     * @param StreamInterface $stream    Underlying stream to decorate.
     * @param int             $maxLength Maximum size before dropping data.
     */
    public function __construct(StreamInterface $stream, $maxLength)
    {
        $this->stream = $stream;
        $this->maxLength = $maxLength;
    }

    public function write($string)
    {
        $diff = $this->maxLength - $this->stream->getSize();

        // Begin returning 0 when the underlying stream is too large.
        if ($diff <= 0) {
            return 0;
        }

        // Write the stream or a subset of the stream if needed.
        if (strlen($string) < $diff) {
            return $this->stream->write($string);
        }

        return $this->stream->write(substr($string, 0, $diff));
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Compose stream implementations based on a hash of functions.
 *
 * Allows for easy testing and extension of a provided stream without needing
 * to create a concrete class for a simple extension point.
 */
class FnStream implements StreamInterface
{
    /** @var array */
    private $methods;

    /** @var array Methods that must be implemented in the given array */
    private static $slots = ['__toString', 'close', 'detach', 'rewind',
        'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
        'isReadable', 'read', 'getContents', 'getMetadata'];

    /**
     * @param array $methods Hash of method name to a callable.
     */
    public function __construct(array $methods)
    {
        $this->methods = $methods;

        // Create the functions on the class
        foreach ($methods as $name => $fn) {
            $this->{'_fn_' . $name} = $fn;
        }
    }

    /**
     * Lazily determine which methods are not implemented.
     * @throws \BadMethodCallException
     */
    public function __get($name)
    {
        throw new \BadMethodCallException(str_replace('_fn_', '', $name)
            . '() is not implemented in the FnStream');
    }

    /**
     * The close method is called on the underlying stream only if possible.
     */
    public function __destruct()
    {
        if (isset($this->_fn_close)) {
            call_user_func($this->_fn_close);
        }
    }

    /**
     * Adds custom functionality to an underlying stream by intercepting
     * specific method calls.
     *
     * @param StreamInterface $stream  Stream to decorate
     * @param array           $methods Hash of method name to a closure
     *
     * @return FnStream
     */
    public static function decorate(StreamInterface $stream, array $methods)
    {
        // If any of the required methods were not provided, then simply
        // proxy to the decorated stream.
        foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
            $methods[$diff] = [$stream, $diff];
        }

        return new self($methods);
    }

    public function __toString()
    {
        return call_user_func($this->_fn___toString);
    }

    public function close()
    {
        return call_user_func($this->_fn_close);
    }

    public function detach()
    {
        return call_user_func($this->_fn_detach);
    }

    public function getSize()
    {
        return call_user_func($this->_fn_getSize);
    }

    public function tell()
    {
        return call_user_func($this->_fn_tell);
    }

    public function eof()
    {
        return call_user_func($this->_fn_eof);
    }

    public function isSeekable()
    {
        return call_user_func($this->_fn_isSeekable);
    }

    public function rewind()
    {
        call_user_func($this->_fn_rewind);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        call_user_func($this->_fn_seek, $offset, $whence);
    }

    public function isWritable()
    {
        return call_user_func($this->_fn_isWritable);
    }

    public function write($string)
    {
        return call_user_func($this->_fn_write, $string);
    }

    public function isReadable()
    {
        return call_user_func($this->_fn_isReadable);
    }

    public function read($length)
    {
        return call_user_func($this->_fn_read, $length);
    }

    public function getContents()
    {
        return call_user_func($this->_fn_getContents);
    }

    public function getMetadata($key = null)
    {
        return call_user_func($this->_fn_getMetadata, $key);
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

/**
 * Returns the string representation of an HTTP message.
 *
 * @param MessageInterface $message Message to convert to a string.
 *
 * @return string
 */
function str(MessageInterface $message)
{
    if ($message instanceof RequestInterface) {
        $msg = trim($message->getMethod() . ' '
                . $message->getRequestTarget())
            . ' HTTP/' . $message->getProtocolVersion();
        if (!$message->hasHeader('host')) {
            $msg .= "\r\nHost: " . $message->getUri()->getHost();
        }
    } elseif ($message instanceof ResponseInterface) {
        $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
            . $message->getStatusCode() . ' '
            . $message->getReasonPhrase();
    } else {
        throw new \InvalidArgumentException('Unknown message type');
    }

    foreach ($message->getHeaders() as $name => $values) {
        $msg .= "\r\n{$name}: " . implode(', ', $values);
    }

    return "{$msg}\r\n\r\n" . $message->getBody();
}

/**
 * Returns a UriInterface for the given value.
 *
 * This function accepts a string or {@see Psr\Http\Message\UriInterface} and
 * returns a UriInterface for the given value. If the value is already a
 * `UriInterface`, it is returned as-is.
 *
 * @param string|UriInterface $uri
 *
 * @return UriInterface
 * @throws \InvalidArgumentException
 */
function uri_for($uri)
{
    if ($uri instanceof UriInterface) {
        return $uri;
    } elseif (is_string($uri)) {
        return new Uri($uri);
    }

    throw new \InvalidArgumentException('URI must be a string or UriInterface');
}

/**
 * Create a new stream based on the input type.
 *
 * Options is an associative array that can contain the following keys:
 * - metadata: Array of custom metadata.
 * - size: Size of the stream.
 *
 * @param resource|string|null|int|float|bool|StreamInterface|callable $resource Entity body data
 * @param array                                                        $options  Additional options
 *
 * @return Stream
 * @throws \InvalidArgumentException if the $resource arg is not valid.
 */
function stream_for($resource = '', array $options = [])
{
    if (is_scalar($resource)) {
        $stream = fopen('php://temp', 'r+');
        if ($resource !== '') {
            fwrite($stream, $resource);
            fseek($stream, 0);
        }
        return new Stream($stream, $options);
    }

    switch (gettype($resource)) {
        case 'resource':
            return new Stream($resource, $options);
        case 'object':
            if ($resource instanceof StreamInterface) {
                return $resource;
            } elseif ($resource instanceof \Iterator) {
                return new PumpStream(function () use ($resource) {
                    if (!$resource->valid()) {
                        return false;
                    }
                    $result = $resource->current();
                    $resource->next();
                    return $result;
                }, $options);
            } elseif (method_exists($resource, '__toString')) {
                return stream_for((string) $resource, $options);
            }
            break;
        case 'NULL':
            return new Stream(fopen('php://temp', 'r+'), $options);
    }

    if (is_callable($resource)) {
        return new PumpStream($resource, $options);
    }

    throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
}

/**
 * Parse an array of header values containing ";" separated data into an
 * array of associative arrays representing the header key value pair
 * data of the header. When a parameter does not contain a value, but just
 * contains a key, this function will inject a key with a '' string value.
 *
 * @param string|array $header Header to parse into components.
 *
 * @return array Returns the parsed header values.
 */
function parse_header($header)
{
    static $trimmed = "\"'  \n\t\r";
    $params = $matches = [];

    foreach (normalize_header($header) as $val) {
        $part = [];
        foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) {
            if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
                $m = $matches[0];
                if (isset($m[1])) {
                    $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
                } else {
                    $part[] = trim($m[0], $trimmed);
                }
            }
        }
        if ($part) {
            $params[] = $part;
        }
    }

    return $params;
}

/**
 * Converts an array of header values that may contain comma separated
 * headers into an array of headers with no comma separated values.
 *
 * @param string|array $header Header to normalize.
 *
 * @return array Returns the normalized header field values.
 */
function normalize_header($header)
{
    if (!is_array($header)) {
        return array_map('trim', explode(',', $header));
    }

    $result = [];
    foreach ($header as $value) {
        foreach ((array) $value as $v) {
            if (strpos($v, ',') === false) {
                $result[] = $v;
                continue;
            }
            foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
                $result[] = trim($vv);
            }
        }
    }

    return $result;
}

/**
 * Clone and modify a request with the given changes.
 *
 * The changes can be one of:
 * - method: (string) Changes the HTTP method.
 * - set_headers: (array) Sets the given headers.
 * - remove_headers: (array) Remove the given headers.
 * - body: (mixed) Sets the given body.
 * - uri: (UriInterface) Set the URI.
 * - query: (string) Set the query string value of the URI.
 * - version: (string) Set the protocol version.
 *
 * @param RequestInterface $request Request to clone and modify.
 * @param array            $changes Changes to apply.
 *
 * @return RequestInterface
 */
function modify_request(RequestInterface $request, array $changes)
{
    if (!$changes) {
        return $request;
    }

    $headers = $request->getHeaders();

    if (!isset($changes['uri'])) {
        $uri = $request->getUri();
    } else {
        // Remove the host header if one is on the URI
        if ($host = $changes['uri']->getHost()) {
            $changes['set_headers']['Host'] = $host;

            if ($port = $changes['uri']->getPort()) {
                $standardPorts = ['http' => 80, 'https' => 443];
                $scheme = $changes['uri']->getScheme();
                if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
                    $changes['set_headers']['Host'] .= ':'.$port;
                }
            }
        }
        $uri = $changes['uri'];
    }

    if (!empty($changes['remove_headers'])) {
        $headers = _caseless_remove($changes['remove_headers'], $headers);
    }

    if (!empty($changes['set_headers'])) {
        $headers = _caseless_remove(array_keys($changes['set_headers']), $headers);
        $headers = $changes['set_headers'] + $headers;
    }

    if (isset($changes['query'])) {
        $uri = $uri->withQuery($changes['query']);
    }

    if ($request instanceof ServerRequestInterface) {
        return new ServerRequest(
            isset($changes['method']) ? $changes['method'] : $request->getMethod(),
            $uri,
            $headers,
            isset($changes['body']) ? $changes['body'] : $request->getBody(),
            isset($changes['version'])
                ? $changes['version']
                : $request->getProtocolVersion(),
            $request->getServerParams()
        );
    }

    return new Request(
        isset($changes['method']) ? $changes['method'] : $request->getMethod(),
        $uri,
        $headers,
        isset($changes['body']) ? $changes['body'] : $request->getBody(),
        isset($changes['version'])
            ? $changes['version']
            : $request->getProtocolVersion()
    );
}

/**
 * Attempts to rewind a message body and throws an exception on failure.
 *
 * The body of the message will only be rewound if a call to `tell()` returns a
 * value other than `0`.
 *
 * @param MessageInterface $message Message to rewind
 *
 * @throws \RuntimeException
 */
function rewind_body(MessageInterface $message)
{
    $body = $message->getBody();

    if ($body->tell()) {
        $body->rewind();
    }
}

/**
 * Safely opens a PHP stream resource using a filename.
 *
 * When fopen fails, PHP normally raises a warning. This function adds an
 * error handler that checks for errors and throws an exception instead.
 *
 * @param string $filename File to open
 * @param string $mode     Mode used to open the file
 *
 * @return resource
 * @throws \RuntimeException if the file cannot be opened
 */
function try_fopen($filename, $mode)
{
    $ex = null;
    set_error_handler(function () use ($filename, $mode, &$ex) {
        $ex = new \RuntimeException(sprintf(
            'Unable to open %s using mode %s: %s',
            $filename,
            $mode,
            func_get_args()[1]
        ));
    });

    $handle = fopen($filename, $mode);
    restore_error_handler();

    if ($ex) {
        /** @var $ex \RuntimeException */
        throw $ex;
    }

    return $handle;
}

/**
 * Copy the contents of a stream into a string until the given number of
 * bytes have been read.
 *
 * @param StreamInterface $stream Stream to read
 * @param int             $maxLen Maximum number of bytes to read. Pass -1
 *                                to read the entire stream.
 * @return string
 * @throws \RuntimeException on error.
 */
function copy_to_string(StreamInterface $stream, $maxLen = -1)
{
    $buffer = '';

    if ($maxLen === -1) {
        while (!$stream->eof()) {
            $buf = $stream->read(1048576);
            // Using a loose equality here to match on '' and false.
            if ($buf == null) {
                break;
            }
            $buffer .= $buf;
        }
        return $buffer;
    }

    $len = 0;
    while (!$stream->eof() && $len < $maxLen) {
        $buf = $stream->read($maxLen - $len);
        // Using a loose equality here to match on '' and false.
        if ($buf == null) {
            break;
        }
        $buffer .= $buf;
        $len = strlen($buffer);
    }

    return $buffer;
}

/**
 * Copy the contents of a stream into another stream until the given number
 * of bytes have been read.
 *
 * @param StreamInterface $source Stream to read from
 * @param StreamInterface $dest   Stream to write to
 * @param int             $maxLen Maximum number of bytes to read. Pass -1
 *                                to read the entire stream.
 *
 * @throws \RuntimeException on error.
 */
function copy_to_stream(
    StreamInterface $source,
    StreamInterface $dest,
    $maxLen = -1
) {
    if ($maxLen === -1) {
        while (!$source->eof()) {
            if (!$dest->write($source->read(1048576))) {
                break;
            }
        }
        return;
    }

    $bytes = 0;
    while (!$source->eof()) {
        $buf = $source->read($maxLen - $bytes);
        if (!($len = strlen($buf))) {
            break;
        }
        $bytes += $len;
        $dest->write($buf);
        if ($bytes == $maxLen) {
            break;
        }
    }
}

/**
 * Calculate a hash of a Stream
 *
 * @param StreamInterface $stream    Stream to calculate the hash for
 * @param string          $algo      Hash algorithm (e.g. md5, crc32, etc)
 * @param bool            $rawOutput Whether or not to use raw output
 *
 * @return string Returns the hash of the stream
 * @throws \RuntimeException on error.
 */
function hash(
    StreamInterface $stream,
    $algo,
    $rawOutput = false
) {
    $pos = $stream->tell();

    if ($pos > 0) {
        $stream->rewind();
    }

    $ctx = hash_init($algo);
    while (!$stream->eof()) {
        hash_update($ctx, $stream->read(1048576));
    }

    $out = hash_final($ctx, (bool) $rawOutput);
    $stream->seek($pos);

    return $out;
}

/**
 * Read a line from the stream up to the maximum allowed buffer length
 *
 * @param StreamInterface $stream    Stream to read from
 * @param int             $maxLength Maximum buffer length
 *
 * @return string|bool
 */
function readline(StreamInterface $stream, $maxLength = null)
{
    $buffer = '';
    $size = 0;

    while (!$stream->eof()) {
        // Using a loose equality here to match on '' and false.
        if (null == ($byte = $stream->read(1))) {
            return $buffer;
        }
        $buffer .= $byte;
        // Break when a new line is found or the max length - 1 is reached
        if ($byte === "\n" || ++$size === $maxLength - 1) {
            break;
        }
    }

    return $buffer;
}

/**
 * Parses a request message string into a request object.
 *
 * @param string $message Request message string.
 *
 * @return Request
 */
function parse_request($message)
{
    $data = _parse_message($message);
    $matches = [];
    if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
        throw new \InvalidArgumentException('Invalid request string');
    }
    $parts = explode(' ', $data['start-line'], 3);
    $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';

    $request = new Request(
        $parts[0],
        $matches[1] === '/' ? _parse_request_uri($parts[1], $data['headers']) : $parts[1],
        $data['headers'],
        $data['body'],
        $version
    );

    return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
}

/**
 * Parses a response message string into a response object.
 *
 * @param string $message Response message string.
 *
 * @return Response
 */
function parse_response($message)
{
    $data = _parse_message($message);
    if (!preg_match('/^HTTP\/.* [0-9]{3} .*/', $data['start-line'])) {
        throw new \InvalidArgumentException('Invalid response string');
    }
    $parts = explode(' ', $data['start-line'], 3);

    return new Response(
        $parts[1],
        $data['headers'],
        $data['body'],
        explode('/', $parts[0])[1],
        isset($parts[2]) ? $parts[2] : null
    );
}

/**
 * Parse a query string into an associative array.
 *
 * If multiple values are found for the same key, the value of that key
 * value pair will become an array. This function does not parse nested
 * PHP style arrays into an associative array (e.g., foo[a]=1&foo[b]=2 will
 * be parsed into ['foo[a]' => '1', 'foo[b]' => '2']).
 *
 * @param string      $str         Query string to parse
 * @param bool|string $urlEncoding How the query string is encoded
 *
 * @return array
 */
function parse_query($str, $urlEncoding = true)
{
    $result = [];

    if ($str === '') {
        return $result;
    }

    if ($urlEncoding === true) {
        $decoder = function ($value) {
            return rawurldecode(str_replace('+', ' ', $value));
        };
    } elseif ($urlEncoding == PHP_QUERY_RFC3986) {
        $decoder = 'rawurldecode';
    } elseif ($urlEncoding == PHP_QUERY_RFC1738) {
        $decoder = 'urldecode';
    } else {
        $decoder = function ($str) { return $str; };
    }

    foreach (explode('&', $str) as $kvp) {
        $parts = explode('=', $kvp, 2);
        $key = $decoder($parts[0]);
        $value = isset($parts[1]) ? $decoder($parts[1]) : null;
        if (!isset($result[$key])) {
            $result[$key] = $value;
        } else {
            if (!is_array($result[$key])) {
                $result[$key] = [$result[$key]];
            }
            $result[$key][] = $value;
        }
    }

    return $result;
}

/**
 * Build a query string from an array of key value pairs.
 *
 * This function can use the return value of parse_query() to build a query
 * string. This function does not modify the provided keys when an array is
 * encountered (like http_build_query would).
 *
 * @param array     $params   Query string parameters.
 * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
 *                            to encode using RFC3986, or PHP_QUERY_RFC1738
 *                            to encode using RFC1738.
 * @return string
 */
function build_query(array $params, $encoding = PHP_QUERY_RFC3986)
{
    if (!$params) {
        return '';
    }

    if ($encoding === false) {
        $encoder = function ($str) { return $str; };
    } elseif ($encoding === PHP_QUERY_RFC3986) {
        $encoder = 'rawurlencode';
    } elseif ($encoding === PHP_QUERY_RFC1738) {
        $encoder = 'urlencode';
    } else {
        throw new \InvalidArgumentException('Invalid type');
    }

    $qs = '';
    foreach ($params as $k => $v) {
        $k = $encoder($k);
        if (!is_array($v)) {
            $qs .= $k;
            if ($v !== null) {
                $qs .= '=' . $encoder($v);
            }
            $qs .= '&';
        } else {
            foreach ($v as $vv) {
                $qs .= $k;
                if ($vv !== null) {
                    $qs .= '=' . $encoder($vv);
                }
                $qs .= '&';
            }
        }
    }

    return $qs ? (string) substr($qs, 0, -1) : '';
}

/**
 * Determines the mimetype of a file by looking at its extension.
 *
 * @param $filename
 *
 * @return null|string
 */
function mimetype_from_filename($filename)
{
    return mimetype_from_extension(pathinfo($filename, PATHINFO_EXTENSION));
}

/**
 * Maps a file extensions to a mimetype.
 *
 * @param $extension string The file extension.
 *
 * @return string|null
 * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
 */
function mimetype_from_extension($extension)
{
    static $mimetypes = [
        '7z' => 'application/x-7z-compressed',
        'aac' => 'audio/x-aac',
        'ai' => 'application/postscript',
        'aif' => 'audio/x-aiff',
        'asc' => 'text/plain',
        'asf' => 'video/x-ms-asf',
        'atom' => 'application/atom+xml',
        'avi' => 'video/x-msvideo',
        'bmp' => 'image/bmp',
        'bz2' => 'application/x-bzip2',
        'cer' => 'application/pkix-cert',
        'crl' => 'application/pkix-crl',
        'crt' => 'application/x-x509-ca-cert',
        'css' => 'text/css',
        'csv' => 'text/csv',
        'cu' => 'application/cu-seeme',
        'deb' => 'application/x-debian-package',
        'doc' => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'dvi' => 'application/x-dvi',
        'eot' => 'application/vnd.ms-fontobject',
        'eps' => 'application/postscript',
        'epub' => 'application/epub+zip',
        'etx' => 'text/x-setext',
        'flac' => 'audio/flac',
        'flv' => 'video/x-flv',
        'gif' => 'image/gif',
        'gz' => 'application/gzip',
        'htm' => 'text/html',
        'html' => 'text/html',
        'ico' => 'image/x-icon',
        'ics' => 'text/calendar',
        'ini' => 'text/plain',
        'iso' => 'application/x-iso9660-image',
        'jar' => 'application/java-archive',
        'jpe' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'jpg' => 'image/jpeg',
        'js' => 'text/javascript',
        'json' => 'application/json',
        'latex' => 'application/x-latex',
        'log' => 'text/plain',
        'm4a' => 'audio/mp4',
        'm4v' => 'video/mp4',
        'mid' => 'audio/midi',
        'midi' => 'audio/midi',
        'mov' => 'video/quicktime',
        'mp3' => 'audio/mpeg',
        'mp4' => 'video/mp4',
        'mp4a' => 'audio/mp4',
        'mp4v' => 'video/mp4',
        'mpe' => 'video/mpeg',
        'mpeg' => 'video/mpeg',
        'mpg' => 'video/mpeg',
        'mpg4' => 'video/mp4',
        'oga' => 'audio/ogg',
        'ogg' => 'audio/ogg',
        'ogv' => 'video/ogg',
        'ogx' => 'application/ogg',
        'pbm' => 'image/x-portable-bitmap',
        'pdf' => 'application/pdf',
        'pgm' => 'image/x-portable-graymap',
        'png' => 'image/png',
        'pnm' => 'image/x-portable-anymap',
        'ppm' => 'image/x-portable-pixmap',
        'ppt' => 'application/vnd.ms-powerpoint',
        'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'ps' => 'application/postscript',
        'qt' => 'video/quicktime',
        'rar' => 'application/x-rar-compressed',
        'ras' => 'image/x-cmu-raster',
        'rss' => 'application/rss+xml',
        'rtf' => 'application/rtf',
        'sgm' => 'text/sgml',
        'sgml' => 'text/sgml',
        'svg' => 'image/svg+xml',
        'swf' => 'application/x-shockwave-flash',
        'tar' => 'application/x-tar',
        'tif' => 'image/tiff',
        'tiff' => 'image/tiff',
        'torrent' => 'application/x-bittorrent',
        'ttf' => 'application/x-font-ttf',
        'txt' => 'text/plain',
        'wav' => 'audio/x-wav',
        'webm' => 'video/webm',
        'wma' => 'audio/x-ms-wma',
        'wmv' => 'video/x-ms-wmv',
        'woff' => 'application/x-font-woff',
        'wsdl' => 'application/wsdl+xml',
        'xbm' => 'image/x-xbitmap',
        'xls' => 'application/vnd.ms-excel',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'xml' => 'application/xml',
        'xpm' => 'image/x-xpixmap',
        'xwd' => 'image/x-xwindowdump',
        'yaml' => 'text/yaml',
        'yml' => 'text/yaml',
        'zip' => 'application/zip',
    ];

    $extension = strtolower($extension);

    return isset($mimetypes[$extension])
        ? $mimetypes[$extension]
        : null;
}

/**
 * Parses an HTTP message into an associative array.
 *
 * The array contains the "start-line" key containing the start line of
 * the message, "headers" key containing an associative array of header
 * array values, and a "body" key containing the body of the message.
 *
 * @param string $message HTTP request or response to parse.
 *
 * @return array
 * @internal
 */
function _parse_message($message)
{
    if (!$message) {
        throw new \InvalidArgumentException('Invalid message');
    }

    // Iterate over each line in the message, accounting for line endings
    $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
    $result = ['start-line' => array_shift($lines), 'headers' => [], 'body' => ''];
    array_shift($lines);

    for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) {
        $line = $lines[$i];
        // If two line breaks were encountered, then this is the end of body
        if (empty($line)) {
            if ($i < $totalLines - 1) {
                $result['body'] = implode('', array_slice($lines, $i + 2));
            }
            break;
        }
        if (strpos($line, ':')) {
            $parts = explode(':', $line, 2);
            $key = trim($parts[0]);
            $value = isset($parts[1]) ? trim($parts[1]) : '';
            $result['headers'][$key][] = $value;
        }
    }

    return $result;
}

/**
 * Constructs a URI for an HTTP request message.
 *
 * @param string $path    Path from the start-line
 * @param array  $headers Array of headers (each value an array).
 *
 * @return string
 * @internal
 */
function _parse_request_uri($path, array $headers)
{
    $hostKey = array_filter(array_keys($headers), function ($k) {
        return strtolower($k) === 'host';
    });

    // If no host is found, then a full URI cannot be constructed.
    if (!$hostKey) {
        return $path;
    }

    $host = $headers[reset($hostKey)][0];
    $scheme = substr($host, -4) === ':443' ? 'https' : 'http';

    return $scheme . '://' . $host . '/' . ltrim($path, '/');
}

/** @internal */
function _caseless_remove($keys, array $data)
{
    $result = [];

    foreach ($keys as &$key) {
        $key = strtolower($key);
    }

    foreach ($data as $k => $v) {
        if (!in_array(strtolower($k), $keys)) {
            $result[$k] = $v;
        }
    }

    return $result;
}
<?php

// Don't redefine the functions if included multiple times.
if (!function_exists('GuzzleHttp\Psr7\str')) {
    require __DIR__ . '/functions.php';
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Uses PHP's zlib.inflate filter to inflate deflate or gzipped content.
 *
 * This stream decorator skips the first 10 bytes of the given stream to remove
 * the gzip header, converts the provided stream to a PHP stream resource,
 * then appends the zlib.inflate filter. The stream is then converted back
 * to a Guzzle stream resource to be used as a Guzzle stream.
 *
 * @link http://tools.ietf.org/html/rfc1952
 * @link http://php.net/manual/en/filters.compression.php
 */
class InflateStream implements StreamInterface
{
    use StreamDecoratorTrait;

    public function __construct(StreamInterface $stream)
    {
        // read the first 10 bytes, ie. gzip header
        $header = $stream->read(10);
        $filenameHeaderLength = $this->getLengthOfPossibleFilenameHeader($stream, $header);
        // Skip the header, that is 10 + length of filename + 1 (nil) bytes
        $stream = new LimitStream($stream, -1, 10 + $filenameHeaderLength);
        $resource = StreamWrapper::getResource($stream);
        stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ);
        $this->stream = new Stream($resource);
    }

    /**
     * @param StreamInterface $stream
     * @param $header
     * @return int
     */
    private function getLengthOfPossibleFilenameHeader(StreamInterface $stream, $header)
    {
        $filename_header_length = 0;

        if (substr(bin2hex($header), 6, 2) === '08') {
            // we have a filename, read until nil
            $filename_header_length = 1;
            while ($stream->read(1) !== chr(0)) {
                $filename_header_length++;
            }
        }

        return $filename_header_length;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Lazily reads or writes to a file that is opened only after an IO operation
 * take place on the stream.
 */
class LazyOpenStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var string File to open */
    private $filename;

    /** @var string $mode */
    private $mode;

    /**
     * @param string $filename File to lazily open
     * @param string $mode     fopen mode to use when opening the stream
     */
    public function __construct($filename, $mode)
    {
        $this->filename = $filename;
        $this->mode = $mode;
    }

    /**
     * Creates the underlying stream lazily when required.
     *
     * @return StreamInterface
     */
    protected function createStream()
    {
        return stream_for(try_fopen($this->filename, $this->mode));
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;


/**
 * Decorator used to return only a subset of a stream
 */
class LimitStream implements StreamInterface
{
    use StreamDecoratorTrait;

    /** @var int Offset to start reading from */
    private $offset;

    /** @var int Limit the number of bytes that can be read */
    private $limit;

    /**
     * @param StreamInterface $stream Stream to wrap
     * @param int             $limit  Total number of bytes to allow to be read
     *                                from the stream. Pass -1 for no limit.
     * @param int|null        $offset Position to seek to before reading (only
     *                                works on seekable streams).
     */
    public function __construct(
        StreamInterface $stream,
        $limit = -1,
        $offset = 0
    ) {
        $this->stream = $stream;
        $this->setLimit($limit);
        $this->setOffset($offset);
    }

    public function eof()
    {
        // Always return true if the underlying stream is EOF
        if ($this->stream->eof()) {
            return true;
        }

        // No limit and the underlying stream is not at EOF
        if ($this->limit == -1) {
            return false;
        }

        return $this->stream->tell() >= $this->offset + $this->limit;
    }

    /**
     * Returns the size of the limited subset of data
     * {@inheritdoc}
     */
    public function getSize()
    {
        if (null === ($length = $this->stream->getSize())) {
            return null;
        } elseif ($this->limit == -1) {
            return $length - $this->offset;
        } else {
            return min($this->limit, $length - $this->offset);
        }
    }

    /**
     * Allow for a bounded seek on the read limited stream
     * {@inheritdoc}
     */
    public function seek($offset, $whence = SEEK_SET)
    {
        if ($whence !== SEEK_SET || $offset < 0) {
            throw new \RuntimeException(sprintf(
                'Cannot seek to offset % with whence %s',
                $offset,
                $whence
            ));
        }

        $offset += $this->offset;

        if ($this->limit !== -1) {
            if ($offset > $this->offset + $this->limit) {
                $offset = $this->offset + $this->limit;
            }
        }

        $this->stream->seek($offset);
    }

    /**
     * Give a relative tell()
     * {@inheritdoc}
     */
    public function tell()
    {
        return $this->stream->tell() - $this->offset;
    }

    /**
     * Set the offset to start limiting from
     *
     * @param int $offset Offset to seek to and begin byte limiting from
     *
     * @throws \RuntimeException if the stream cannot be seeked.
     */
    public function setOffset($offset)
    {
        $current = $this->stream->tell();

        if ($current !== $offset) {
            // If the stream cannot seek to the offset position, then read to it
            if ($this->stream->isSeekable()) {
                $this->stream->seek($offset);
            } elseif ($current > $offset) {
                throw new \RuntimeException("Could not seek to stream offset $offset");
            } else {
                $this->stream->read($offset - $current);
            }
        }

        $this->offset = $offset;
    }

    /**
     * Set the limit of bytes that the decorator allows to be read from the
     * stream.
     *
     * @param int $limit Number of bytes to allow to be read from the stream.
     *                   Use -1 for no limit.
     */
    public function setLimit($limit)
    {
        $this->limit = $limit;
    }

    public function read($length)
    {
        if ($this->limit == -1) {
            return $this->stream->read($length);
        }

        // Check if the current position is less than the total allowed
        // bytes + original offset
        $remaining = ($this->offset + $this->limit) - $this->stream->tell();
        if ($remaining > 0) {
            // Only return the amount of requested data, ensuring that the byte
            // limit is not exceeded
            return $this->stream->read(min($remaining, $length));
        }

        return '';
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Trait implementing functionality common to requests and responses.
 */
trait MessageTrait
{
    /** @var array Map of all registered headers, as original name => array of values */
    private $headers = [];

    /** @var array Map of lowercase header name => original name at registration */
    private $headerNames  = [];

    /** @var string */
    private $protocol = '1.1';

    /** @var StreamInterface */
    private $stream;

    public function getProtocolVersion()
    {
        return $this->protocol;
    }

    public function withProtocolVersion($version)
    {
        if ($this->protocol === $version) {
            return $this;
        }

        $new = clone $this;
        $new->protocol = $version;
        return $new;
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    public function hasHeader($header)
    {
        return isset($this->headerNames[strtolower($header)]);
    }

    public function getHeader($header)
    {
        $header = strtolower($header);

        if (!isset($this->headerNames[$header])) {
            return [];
        }

        $header = $this->headerNames[$header];

        return $this->headers[$header];
    }

    public function getHeaderLine($header)
    {
        return implode(', ', $this->getHeader($header));
    }

    public function withHeader($header, $value)
    {
        if (!is_array($value)) {
            $value = [$value];
        }

        $value = $this->trimHeaderValues($value);
        $normalized = strtolower($header);

        $new = clone $this;
        if (isset($new->headerNames[$normalized])) {
            unset($new->headers[$new->headerNames[$normalized]]);
        }
        $new->headerNames[$normalized] = $header;
        $new->headers[$header] = $value;

        return $new;
    }

    public function withAddedHeader($header, $value)
    {
        if (!is_array($value)) {
            $value = [$value];
        }

        $value = $this->trimHeaderValues($value);
        $normalized = strtolower($header);

        $new = clone $this;
        if (isset($new->headerNames[$normalized])) {
            $header = $this->headerNames[$normalized];
            $new->headers[$header] = array_merge($this->headers[$header], $value);
        } else {
            $new->headerNames[$normalized] = $header;
            $new->headers[$header] = $value;
        }

        return $new;
    }

    public function withoutHeader($header)
    {
        $normalized = strtolower($header);

        if (!isset($this->headerNames[$normalized])) {
            return $this;
        }

        $header = $this->headerNames[$normalized];

        $new = clone $this;
        unset($new->headers[$header], $new->headerNames[$normalized]);

        return $new;
    }

    public function getBody()
    {
        if (!$this->stream) {
            $this->stream = stream_for('');
        }

        return $this->stream;
    }

    public function withBody(StreamInterface $body)
    {
        if ($body === $this->stream) {
            return $this;
        }

        $new = clone $this;
        $new->stream = $body;
        return $new;
    }

    private function setHeaders(array $headers)
    {
        $this->headerNames = $this->headers = [];
        foreach ($headers as $header => $value) {
            if (!is_array($value)) {
                $value = [$value];
            }

            $value = $this->trimHeaderValues($value);
            $normalized = strtolower($header);
            if (isset($this->headerNames[$normalized])) {
                $header = $this->headerNames[$normalized];
                $this->headers[$header] = array_merge($this->headers[$header], $value);
            } else {
                $this->headerNames[$normalized] = $header;
                $this->headers[$header] = $value;
            }
        }
    }

    /**
     * Trims whitespace from the header values.
     *
     * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
     *
     * header-field = field-name ":" OWS field-value OWS
     * OWS          = *( SP / HTAB )
     *
     * @param string[] $values Header values
     *
     * @return string[] Trimmed header values
     *
     * @see https://tools.ietf.org/html/rfc7230#section-3.2.4
     */
    private function trimHeaderValues(array $values)
    {
        return array_map(function ($value) {
            return trim($value, " \t");
        }, $values);
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream that when read returns bytes for a streaming multipart or
 * multipart/form-data stream.
 */
class MultipartStream implements StreamInterface
{
    use StreamDecoratorTrait;

    private $boundary;

    /**
     * @param array  $elements Array of associative arrays, each containing a
     *                         required "name" key mapping to the form field,
     *                         name, a required "contents" key mapping to a
     *                         StreamInterface/resource/string, an optional
     *                         "headers" associative array of custom headers,
     *                         and an optional "filename" key mapping to a
     *                         string to send as the filename in the part.
     * @param string $boundary You can optionally provide a specific boundary
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(array $elements = [], $boundary = null)
    {
        $this->boundary = $boundary ?: uniqid();
        $this->stream = $this->createStream($elements);
    }

    /**
     * Get the boundary
     *
     * @return string
     */
    public function getBoundary()
    {
        return $this->boundary;
    }

    public function isWritable()
    {
        return false;
    }

    /**
     * Get the headers needed before transferring the content of a POST file
     */
    private function getHeaders(array $headers)
    {
        $str = '';
        foreach ($headers as $key => $value) {
            $str .= "{$key}: {$value}\r\n";
        }

        return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
    }

    /**
     * Create the aggregate stream that will be used to upload the POST data
     */
    protected function createStream(array $elements)
    {
        $stream = new AppendStream();

        foreach ($elements as $element) {
            $this->addElement($stream, $element);
        }

        // Add the trailing boundary with CRLF
        $stream->addStream(stream_for("--{$this->boundary}--\r\n"));

        return $stream;
    }

    private function addElement(AppendStream $stream, array $element)
    {
        foreach (['contents', 'name'] as $key) {
            if (!array_key_exists($key, $element)) {
                throw new \InvalidArgumentException("A '{$key}' key is required");
            }
        }

        $element['contents'] = stream_for($element['contents']);

        if (empty($element['filename'])) {
            $uri = $element['contents']->getMetadata('uri');
            if (substr($uri, 0, 6) !== 'php://') {
                $element['filename'] = $uri;
            }
        }

        list($body, $headers) = $this->createElement(
            $element['name'],
            $element['contents'],
            isset($element['filename']) ? $element['filename'] : null,
            isset($element['headers']) ? $element['headers'] : []
        );

        $stream->addStream(stream_for($this->getHeaders($headers)));
        $stream->addStream($body);
        $stream->addStream(stream_for("\r\n"));
    }

    /**
     * @return array
     */
    private function createElement($name, $stream, $filename, array $headers)
    {
        // Set a default content-disposition header if one was no provided
        $disposition = $this->getHeader($headers, 'content-disposition');
        if (!$disposition) {
            $headers['Content-Disposition'] = ($filename === '0' || $filename)
                ? sprintf('form-data; name="%s"; filename="%s"',
                    $name,
                    basename($filename))
                : "form-data; name=\"{$name}\"";
        }

        // Set a default content-length header if one was no provided
        $length = $this->getHeader($headers, 'content-length');
        if (!$length) {
            if ($length = $stream->getSize()) {
                $headers['Content-Length'] = (string) $length;
            }
        }

        // Set a default Content-Type if one was not supplied
        $type = $this->getHeader($headers, 'content-type');
        if (!$type && ($filename === '0' || $filename)) {
            if ($type = mimetype_from_filename($filename)) {
                $headers['Content-Type'] = $type;
            }
        }

        return [$stream, $headers];
    }

    private function getHeader(array $headers, $key)
    {
        $lowercaseHeader = strtolower($key);
        foreach ($headers as $k => $v) {
            if (strtolower($k) === $lowercaseHeader) {
                return $v;
            }
        }

        return null;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator that prevents a stream from being seeked
 */
class NoSeekStream implements StreamInterface
{
    use StreamDecoratorTrait;

    public function seek($offset, $whence = SEEK_SET)
    {
        throw new \RuntimeException('Cannot seek a NoSeekStream');
    }

    public function isSeekable()
    {
        return false;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Provides a read only stream that pumps data from a PHP callable.
 *
 * When invoking the provided callable, the PumpStream will pass the amount of
 * data requested to read to the callable. The callable can choose to ignore
 * this value and return fewer or more bytes than requested. Any extra data
 * returned by the provided callable is buffered internally until drained using
 * the read() function of the PumpStream. The provided callable MUST return
 * false when there is no more data to read.
 */
class PumpStream implements StreamInterface
{
    /** @var callable */
    private $source;

    /** @var int */
    private $size;

    /** @var int */
    private $tellPos = 0;

    /** @var array */
    private $metadata;

    /** @var BufferStream */
    private $buffer;

    /**
     * @param callable $source Source of the stream data. The callable MAY
     *                         accept an integer argument used to control the
     *                         amount of data to return. The callable MUST
     *                         return a string when called, or false on error
     *                         or EOF.
     * @param array $options   Stream options:
     *                         - metadata: Hash of metadata to use with stream.
     *                         - size: Size of the stream, if known.
     */
    public function __construct(callable $source, array $options = [])
    {
        $this->source = $source;
        $this->size = isset($options['size']) ? $options['size'] : null;
        $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
        $this->buffer = new BufferStream();
    }

    public function __toString()
    {
        try {
            return copy_to_string($this);
        } catch (\Exception $e) {
            return '';
        }
    }

    public function close()
    {
        $this->detach();
    }

    public function detach()
    {
        $this->tellPos = false;
        $this->source = null;
    }

    public function getSize()
    {
        return $this->size;
    }

    public function tell()
    {
        return $this->tellPos;
    }

    public function eof()
    {
        return !$this->source;
    }

    public function isSeekable()
    {
        return false;
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        throw new \RuntimeException('Cannot seek a PumpStream');
    }

    public function isWritable()
    {
        return false;
    }

    public function write($string)
    {
        throw new \RuntimeException('Cannot write to a PumpStream');
    }

    public function isReadable()
    {
        return true;
    }

    public function read($length)
    {
        $data = $this->buffer->read($length);
        $readLen = strlen($data);
        $this->tellPos += $readLen;
        $remaining = $length - $readLen;

        if ($remaining) {
            $this->pump($remaining);
            $data .= $this->buffer->read($remaining);
            $this->tellPos += strlen($data) - $readLen;
        }

        return $data;
    }

    public function getContents()
    {
        $result = '';
        while (!$this->eof()) {
            $result .= $this->read(1000000);
        }

        return $result;
    }

    public function getMetadata($key = null)
    {
        if (!$key) {
            return $this->metadata;
        }

        return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
    }

    private function pump($length)
    {
        if ($this->source) {
            do {
                $data = call_user_func($this->source, $length);
                if ($data === false || $data === null) {
                    $this->source = null;
                    return;
                }
                $this->buffer->write($data);
                $length -= strlen($data);
            } while ($length > 0);
        }
    }
}
<?php
namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriInterface;

/**
 * PSR-7 request implementation.
 */
class Request implements RequestInterface
{
    use MessageTrait;

    /** @var string */
    private $method;

    /** @var null|string */
    private $requestTarget;

    /** @var null|UriInterface */
    private $uri;

    /**
     * @param string                               $method  HTTP method
     * @param string|UriInterface                  $uri     URI
     * @param array                                $headers Request headers
     * @param string|null|resource|StreamInterface $body    Request body
     * @param string                               $version Protocol version
     */
    public function __construct(
        $method,
        $uri,
        array $headers = [],
        $body = null,
        $version = '1.1'
    ) {
        if (!($uri instanceof UriInterface)) {
            $uri = new Uri($uri);
        }

        $this->method = strtoupper($method);
        $this->uri = $uri;
        $this->setHeaders($headers);
        $this->protocol = $version;

        if (!$this->hasHeader('Host')) {
            $this->updateHostFromUri();
        }

        if ($body !== '' && $body !== null) {
            $this->stream = stream_for($body);
        }
    }

    public function getRequestTarget()
    {
        if ($this->requestTarget !== null) {
            return $this->requestTarget;
        }

        $target = $this->uri->getPath();
        if ($target == '') {
            $target = '/';
        }
        if ($this->uri->getQuery() != '') {
            $target .= '?' . $this->uri->getQuery();
        }

        return $target;
    }

    public function withRequestTarget($requestTarget)
    {
        if (preg_match('#\s#', $requestTarget)) {
            throw new InvalidArgumentException(
                'Invalid request target provided; cannot contain whitespace'
            );
        }

        $new = clone $this;
        $new->requestTarget = $requestTarget;
        return $new;
    }

    public function getMethod()
    {
        return $this->method;
    }

    public function withMethod($method)
    {
        $new = clone $this;
        $new->method = strtoupper($method);
        return $new;
    }

    public function getUri()
    {
        return $this->uri;
    }

    public function withUri(UriInterface $uri, $preserveHost = false)
    {
        if ($uri === $this->uri) {
            return $this;
        }

        $new = clone $this;
        $new->uri = $uri;

        if (!$preserveHost) {
            $new->updateHostFromUri();
        }

        return $new;
    }

    private function updateHostFromUri()
    {
        $host = $this->uri->getHost();

        if ($host == '') {
            return;
        }

        if (($port = $this->uri->getPort()) !== null) {
            $host .= ':' . $port;
        }

        if (isset($this->headerNames['host'])) {
            $header = $this->headerNames['host'];
        } else {
            $header = 'Host';
            $this->headerNames['host'] = 'Host';
        }
        // Ensure Host is the first header.
        // See: http://tools.ietf.org/html/rfc7230#section-5.4
        $this->headers = [$header => [$host]] + $this->headers;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\ResponseInterface;

/**
 * PSR-7 response implementation.
 */
class Response implements ResponseInterface
{
    use MessageTrait;

    /** @var array Map of standard HTTP status code/reason phrases */
    private static $phrases = [
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-status',
        208 => 'Already Reported',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => 'Switch Proxy',
        307 => 'Temporary Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Time-out',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Large',
        415 => 'Unsupported Media Type',
        416 => 'Requested range not satisfiable',
        417 => 'Expectation Failed',
        418 => 'I\'m a teapot',
        422 => 'Unprocessable Entity',
        423 => 'Locked',
        424 => 'Failed Dependency',
        425 => 'Unordered Collection',
        426 => 'Upgrade Required',
        428 => 'Precondition Required',
        429 => 'Too Many Requests',
        431 => 'Request Header Fields Too Large',
        451 => 'Unavailable For Legal Reasons',
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Time-out',
        505 => 'HTTP Version not supported',
        506 => 'Variant Also Negotiates',
        507 => 'Insufficient Storage',
        508 => 'Loop Detected',
        511 => 'Network Authentication Required',
    ];

    /** @var string */
    private $reasonPhrase = '';

    /** @var int */
    private $statusCode = 200;

    /**
     * @param int                                  $status  Status code
     * @param array                                $headers Response headers
     * @param string|null|resource|StreamInterface $body    Response body
     * @param string                               $version Protocol version
     * @param string|null                          $reason  Reason phrase (when empty a default will be used based on the status code)
     */
    public function __construct(
        $status = 200,
        array $headers = [],
        $body = null,
        $version = '1.1',
        $reason = null
    ) {
        $this->statusCode = (int) $status;

        if ($body !== '' && $body !== null) {
            $this->stream = stream_for($body);
        }

        $this->setHeaders($headers);
        if ($reason == '' && isset(self::$phrases[$this->statusCode])) {
            $this->reasonPhrase = self::$phrases[$status];
        } else {
            $this->reasonPhrase = (string) $reason;
        }

        $this->protocol = $version;
    }

    public function getStatusCode()
    {
        return $this->statusCode;
    }

    public function getReasonPhrase()
    {
        return $this->reasonPhrase;
    }

    public function withStatus($code, $reasonPhrase = '')
    {
        $new = clone $this;
        $new->statusCode = (int) $code;
        if ($reasonPhrase == '' && isset(self::$phrases[$new->statusCode])) {
            $reasonPhrase = self::$phrases[$new->statusCode];
        }
        $new->reasonPhrase = $reasonPhrase;
        return $new;
    }
}
<?php

namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;

/**
 * Server-side HTTP request
 *
 * Extends the Request definition to add methods for accessing incoming data,
 * specifically server parameters, cookies, matched path parameters, query
 * string arguments, body parameters, and upload file information.
 *
 * "Attributes" are discovered via decomposing the request (and usually
 * specifically the URI path), and typically will be injected by the application.
 *
 * Requests are considered immutable; all methods that might change state are
 * implemented such that they retain the internal state of the current
 * message and return a new instance that contains the changed state.
 */
class ServerRequest extends Request implements ServerRequestInterface
{
    /**
     * @var array
     */
    private $attributes = [];

    /**
     * @var array
     */
    private $cookieParams = [];

    /**
     * @var null|array|object
     */
    private $parsedBody;

    /**
     * @var array
     */
    private $queryParams = [];

    /**
     * @var array
     */
    private $serverParams;

    /**
     * @var array
     */
    private $uploadedFiles = [];

    /**
     * @param string                               $method       HTTP method
     * @param string|UriInterface                  $uri          URI
     * @param array                                $headers      Request headers
     * @param string|null|resource|StreamInterface $body         Request body
     * @param string                               $version      Protocol version
     * @param array                                $serverParams Typically the $_SERVER superglobal
     */
    public function __construct(
        $method,
        $uri,
        array $headers = [],
        $body = null,
        $version = '1.1',
        array $serverParams = []
    ) {
        $this->serverParams = $serverParams;

        parent::__construct($method, $uri, $headers, $body, $version);
    }

    /**
     * Return an UploadedFile instance array.
     *
     * @param array $files A array which respect $_FILES structure
     * @throws InvalidArgumentException for unrecognized values
     * @return array
     */
    public static function normalizeFiles(array $files)
    {
        $normalized = [];

        foreach ($files as $key => $value) {
            if ($value instanceof UploadedFileInterface) {
                $normalized[$key] = $value;
            } elseif (is_array($value) && isset($value['tmp_name'])) {
                $normalized[$key] = self::createUploadedFileFromSpec($value);
            } elseif (is_array($value)) {
                $normalized[$key] = self::normalizeFiles($value);
                continue;
            } else {
                throw new InvalidArgumentException('Invalid value in files specification');
            }
        }

        return $normalized;
    }

    /**
     * Create and return an UploadedFile instance from a $_FILES specification.
     *
     * If the specification represents an array of values, this method will
     * delegate to normalizeNestedFileSpec() and return that return value.
     *
     * @param array $value $_FILES struct
     * @return array|UploadedFileInterface
     */
    private static function createUploadedFileFromSpec(array $value)
    {
        if (is_array($value['tmp_name'])) {
            return self::normalizeNestedFileSpec($value);
        }

        return new UploadedFile(
            $value['tmp_name'],
            (int) $value['size'],
            (int) $value['error'],
            $value['name'],
            $value['type']
        );
    }

    /**
     * Normalize an array of file specifications.
     *
     * Loops through all nested files and returns a normalized array of
     * UploadedFileInterface instances.
     *
     * @param array $files
     * @return UploadedFileInterface[]
     */
    private static function normalizeNestedFileSpec(array $files = [])
    {
        $normalizedFiles = [];

        foreach (array_keys($files['tmp_name']) as $key) {
            $spec = [
                'tmp_name' => $files['tmp_name'][$key],
                'size'     => $files['size'][$key],
                'error'    => $files['error'][$key],
                'name'     => $files['name'][$key],
                'type'     => $files['type'][$key],
            ];
            $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
        }

        return $normalizedFiles;
    }

    /**
     * Return a ServerRequest populated with superglobals:
     * $_GET
     * $_POST
     * $_COOKIE
     * $_FILES
     * $_SERVER
     *
     * @return ServerRequestInterface
     */
    public static function fromGlobals()
    {
        $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
        $headers = function_exists('getallheaders') ? getallheaders() : [];
        $uri = self::getUriFromGlobals();
        $body = new LazyOpenStream('php://input', 'r+');
        $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';

        $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);

        return $serverRequest
            ->withCookieParams($_COOKIE)
            ->withQueryParams($_GET)
            ->withParsedBody($_POST)
            ->withUploadedFiles(self::normalizeFiles($_FILES));
    }

    /**
     * Get a Uri populated with values from $_SERVER.
     *
     * @return UriInterface
     */
    public static function getUriFromGlobals() {
        $uri = new Uri('');

        if (isset($_SERVER['HTTPS'])) {
            $uri = $uri->withScheme($_SERVER['HTTPS'] == 'on' ? 'https' : 'http');
        }

        if (isset($_SERVER['HTTP_HOST'])) {
            $uri = $uri->withHost($_SERVER['HTTP_HOST']);
        } elseif (isset($_SERVER['SERVER_NAME'])) {
            $uri = $uri->withHost($_SERVER['SERVER_NAME']);
        }

        if (isset($_SERVER['SERVER_PORT'])) {
            $uri = $uri->withPort($_SERVER['SERVER_PORT']);
        }

        if (isset($_SERVER['REQUEST_URI'])) {
            $uri = $uri->withPath(current(explode('?', $_SERVER['REQUEST_URI'])));
        }

        if (isset($_SERVER['QUERY_STRING'])) {
            $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
        }

        return $uri;
    }


    /**
     * {@inheritdoc}
     */
    public function getServerParams()
    {
        return $this->serverParams;
    }

    /**
     * {@inheritdoc}
     */
    public function getUploadedFiles()
    {
        return $this->uploadedFiles;
    }

    /**
     * {@inheritdoc}
     */
    public function withUploadedFiles(array $uploadedFiles)
    {
        $new = clone $this;
        $new->uploadedFiles = $uploadedFiles;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getCookieParams()
    {
        return $this->cookieParams;
    }

    /**
     * {@inheritdoc}
     */
    public function withCookieParams(array $cookies)
    {
        $new = clone $this;
        $new->cookieParams = $cookies;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getQueryParams()
    {
        return $this->queryParams;
    }

    /**
     * {@inheritdoc}
     */
    public function withQueryParams(array $query)
    {
        $new = clone $this;
        $new->queryParams = $query;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getParsedBody()
    {
        return $this->parsedBody;
    }

    /**
     * {@inheritdoc}
     */
    public function withParsedBody($data)
    {
        $new = clone $this;
        $new->parsedBody = $data;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function getAttributes()
    {
        return $this->attributes;
    }

    /**
     * {@inheritdoc}
     */
    public function getAttribute($attribute, $default = null)
    {
        if (false === array_key_exists($attribute, $this->attributes)) {
            return $default;
        }

        return $this->attributes[$attribute];
    }

    /**
     * {@inheritdoc}
     */
    public function withAttribute($attribute, $value)
    {
        $new = clone $this;
        $new->attributes[$attribute] = $value;

        return $new;
    }

    /**
     * {@inheritdoc}
     */
    public function withoutAttribute($attribute)
    {
        if (false === array_key_exists($attribute, $this->attributes)) {
            return $this;
        }

        $new = clone $this;
        unset($new->attributes[$attribute]);

        return $new;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * PHP stream implementation.
 *
 * @var $stream
 */
class Stream implements StreamInterface
{
    private $stream;
    private $size;
    private $seekable;
    private $readable;
    private $writable;
    private $uri;
    private $customMetadata;

    /** @var array Hash of readable and writable stream types */
    private static $readWriteHash = [
        'read' => [
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
            'x+t' => true, 'c+t' => true, 'a+' => true
        ],
        'write' => [
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
        ]
    ];

    /**
     * This constructor accepts an associative array of options.
     *
     * - size: (int) If a read stream would otherwise have an indeterminate
     *   size, but the size is known due to foreknowledge, then you can
     *   provide that size, in bytes.
     * - metadata: (array) Any additional metadata to return when the metadata
     *   of the stream is accessed.
     *
     * @param resource $stream  Stream resource to wrap.
     * @param array    $options Associative array of options.
     *
     * @throws \InvalidArgumentException if the stream is not a stream resource
     */
    public function __construct($stream, $options = [])
    {
        if (!is_resource($stream)) {
            throw new \InvalidArgumentException('Stream must be a resource');
        }

        if (isset($options['size'])) {
            $this->size = $options['size'];
        }

        $this->customMetadata = isset($options['metadata'])
            ? $options['metadata']
            : [];

        $this->stream = $stream;
        $meta = stream_get_meta_data($this->stream);
        $this->seekable = $meta['seekable'];
        $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
        $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
        $this->uri = $this->getMetadata('uri');
    }

    public function __get($name)
    {
        if ($name == 'stream') {
            throw new \RuntimeException('The stream is detached');
        }

        throw new \BadMethodCallException('No value for ' . $name);
    }

    /**
     * Closes the stream when the destructed
     */
    public function __destruct()
    {
        $this->close();
    }

    public function __toString()
    {
        try {
            $this->seek(0);
            return (string) stream_get_contents($this->stream);
        } catch (\Exception $e) {
            return '';
        }
    }

    public function getContents()
    {
        $contents = stream_get_contents($this->stream);

        if ($contents === false) {
            throw new \RuntimeException('Unable to read stream contents');
        }

        return $contents;
    }

    public function close()
    {
        if (isset($this->stream)) {
            if (is_resource($this->stream)) {
                fclose($this->stream);
            }
            $this->detach();
        }
    }

    public function detach()
    {
        if (!isset($this->stream)) {
            return null;
        }

        $result = $this->stream;
        unset($this->stream);
        $this->size = $this->uri = null;
        $this->readable = $this->writable = $this->seekable = false;

        return $result;
    }

    public function getSize()
    {
        if ($this->size !== null) {
            return $this->size;
        }

        if (!isset($this->stream)) {
            return null;
        }

        // Clear the stat cache if the stream has a URI
        if ($this->uri) {
            clearstatcache(true, $this->uri);
        }

        $stats = fstat($this->stream);
        if (isset($stats['size'])) {
            $this->size = $stats['size'];
            return $this->size;
        }

        return null;
    }

    public function isReadable()
    {
        return $this->readable;
    }

    public function isWritable()
    {
        return $this->writable;
    }

    public function isSeekable()
    {
        return $this->seekable;
    }

    public function eof()
    {
        return !$this->stream || feof($this->stream);
    }

    public function tell()
    {
        $result = ftell($this->stream);

        if ($result === false) {
            throw new \RuntimeException('Unable to determine stream position');
        }

        return $result;
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        if (!$this->seekable) {
            throw new \RuntimeException('Stream is not seekable');
        } elseif (fseek($this->stream, $offset, $whence) === -1) {
            throw new \RuntimeException('Unable to seek to stream position '
                . $offset . ' with whence ' . var_export($whence, true));
        }
    }

    public function read($length)
    {
        if (!$this->readable) {
            throw new \RuntimeException('Cannot read from non-readable stream');
        }

        return fread($this->stream, $length);
    }

    public function write($string)
    {
        if (!$this->writable) {
            throw new \RuntimeException('Cannot write to a non-writable stream');
        }

        // We can't know the size after writing anything
        $this->size = null;
        $result = fwrite($this->stream, $string);

        if ($result === false) {
            throw new \RuntimeException('Unable to write to stream');
        }

        return $result;
    }

    public function getMetadata($key = null)
    {
        if (!isset($this->stream)) {
            return $key ? null : [];
        } elseif (!$key) {
            return $this->customMetadata + stream_get_meta_data($this->stream);
        } elseif (isset($this->customMetadata[$key])) {
            return $this->customMetadata[$key];
        }

        $meta = stream_get_meta_data($this->stream);

        return isset($meta[$key]) ? $meta[$key] : null;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Stream decorator trait
 * @property StreamInterface stream
 */
trait StreamDecoratorTrait
{
    /**
     * @param StreamInterface $stream Stream to decorate
     */
    public function __construct(StreamInterface $stream)
    {
        $this->stream = $stream;
    }

    /**
     * Magic method used to create a new stream if streams are not added in
     * the constructor of a decorator (e.g., LazyOpenStream).
     *
     * @param string $name Name of the property (allows "stream" only).
     *
     * @return StreamInterface
     */
    public function __get($name)
    {
        if ($name == 'stream') {
            $this->stream = $this->createStream();
            return $this->stream;
        }

        throw new \UnexpectedValueException("$name not found on class");
    }

    public function __toString()
    {
        try {
            if ($this->isSeekable()) {
                $this->seek(0);
            }
            return $this->getContents();
        } catch (\Exception $e) {
            // Really, PHP? https://bugs.php.net/bug.php?id=53648
            trigger_error('StreamDecorator::__toString exception: '
                . (string) $e, E_USER_ERROR);
            return '';
        }
    }

    public function getContents()
    {
        return copy_to_string($this);
    }

    /**
     * Allow decorators to implement custom methods
     *
     * @param string $method Missing method name
     * @param array  $args   Method arguments
     *
     * @return mixed
     */
    public function __call($method, array $args)
    {
        $result = call_user_func_array([$this->stream, $method], $args);

        // Always return the wrapped object if the result is a return $this
        return $result === $this->stream ? $this : $result;
    }

    public function close()
    {
        $this->stream->close();
    }

    public function getMetadata($key = null)
    {
        return $this->stream->getMetadata($key);
    }

    public function detach()
    {
        return $this->stream->detach();
    }

    public function getSize()
    {
        return $this->stream->getSize();
    }

    public function eof()
    {
        return $this->stream->eof();
    }

    public function tell()
    {
        return $this->stream->tell();
    }

    public function isReadable()
    {
        return $this->stream->isReadable();
    }

    public function isWritable()
    {
        return $this->stream->isWritable();
    }

    public function isSeekable()
    {
        return $this->stream->isSeekable();
    }

    public function rewind()
    {
        $this->seek(0);
    }

    public function seek($offset, $whence = SEEK_SET)
    {
        $this->stream->seek($offset, $whence);
    }

    public function read($length)
    {
        return $this->stream->read($length);
    }

    public function write($string)
    {
        return $this->stream->write($string);
    }

    /**
     * Implement in subclasses to dynamically create streams when requested.
     *
     * @return StreamInterface
     * @throws \BadMethodCallException
     */
    protected function createStream()
    {
        throw new \BadMethodCallException('Not implemented');
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\StreamInterface;

/**
 * Converts Guzzle streams into PHP stream resources.
 */
class StreamWrapper
{
    /** @var resource */
    public $context;

    /** @var StreamInterface */
    private $stream;

    /** @var string r, r+, or w */
    private $mode;

    /**
     * Returns a resource representing the stream.
     *
     * @param StreamInterface $stream The stream to get a resource for
     *
     * @return resource
     * @throws \InvalidArgumentException if stream is not readable or writable
     */
    public static function getResource(StreamInterface $stream)
    {
        self::register();

        if ($stream->isReadable()) {
            $mode = $stream->isWritable() ? 'r+' : 'r';
        } elseif ($stream->isWritable()) {
            $mode = 'w';
        } else {
            throw new \InvalidArgumentException('The stream must be readable, '
                . 'writable, or both.');
        }

        return fopen('guzzle://stream', $mode, null, stream_context_create([
            'guzzle' => ['stream' => $stream]
        ]));
    }

    /**
     * Registers the stream wrapper if needed
     */
    public static function register()
    {
        if (!in_array('guzzle', stream_get_wrappers())) {
            stream_wrapper_register('guzzle', __CLASS__);
        }
    }

    public function stream_open($path, $mode, $options, &$opened_path)
    {
        $options = stream_context_get_options($this->context);

        if (!isset($options['guzzle']['stream'])) {
            return false;
        }

        $this->mode = $mode;
        $this->stream = $options['guzzle']['stream'];

        return true;
    }

    public function stream_read($count)
    {
        return $this->stream->read($count);
    }

    public function stream_write($data)
    {
        return (int) $this->stream->write($data);
    }

    public function stream_tell()
    {
        return $this->stream->tell();
    }

    public function stream_eof()
    {
        return $this->stream->eof();
    }

    public function stream_seek($offset, $whence)
    {
        $this->stream->seek($offset, $whence);

        return true;
    }

    public function stream_stat()
    {
        static $modeMap = [
            'r'  => 33060,
            'r+' => 33206,
            'w'  => 33188
        ];

        return [
            'dev'     => 0,
            'ino'     => 0,
            'mode'    => $modeMap[$this->mode],
            'nlink'   => 0,
            'uid'     => 0,
            'gid'     => 0,
            'rdev'    => 0,
            'size'    => $this->stream->getSize() ?: 0,
            'atime'   => 0,
            'mtime'   => 0,
            'ctime'   => 0,
            'blksize' => 0,
            'blocks'  => 0
        ];
    }
}
<?php
namespace GuzzleHttp\Psr7;

use InvalidArgumentException;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use RuntimeException;

class UploadedFile implements UploadedFileInterface
{
    /**
     * @var int[]
     */
    private static $errors = [
        UPLOAD_ERR_OK,
        UPLOAD_ERR_INI_SIZE,
        UPLOAD_ERR_FORM_SIZE,
        UPLOAD_ERR_PARTIAL,
        UPLOAD_ERR_NO_FILE,
        UPLOAD_ERR_NO_TMP_DIR,
        UPLOAD_ERR_CANT_WRITE,
        UPLOAD_ERR_EXTENSION,
    ];

    /**
     * @var string
     */
    private $clientFilename;

    /**
     * @var string
     */
    private $clientMediaType;

    /**
     * @var int
     */
    private $error;

    /**
     * @var null|string
     */
    private $file;

    /**
     * @var bool
     */
    private $moved = false;

    /**
     * @var int
     */
    private $size;

    /**
     * @var StreamInterface|null
     */
    private $stream;

    /**
     * @param StreamInterface|string|resource $streamOrFile
     * @param int $size
     * @param int $errorStatus
     * @param string|null $clientFilename
     * @param string|null $clientMediaType
     */
    public function __construct(
        $streamOrFile,
        $size,
        $errorStatus,
        $clientFilename = null,
        $clientMediaType = null
    ) {
        $this->setError($errorStatus);
        $this->setSize($size);
        $this->setClientFilename($clientFilename);
        $this->setClientMediaType($clientMediaType);

        if ($this->isOk()) {
            $this->setStreamOrFile($streamOrFile);
        }
    }

    /**
     * Depending on the value set file or stream variable
     *
     * @param mixed $streamOrFile
     * @throws InvalidArgumentException
     */
    private function setStreamOrFile($streamOrFile)
    {
        if (is_string($streamOrFile)) {
            $this->file = $streamOrFile;
        } elseif (is_resource($streamOrFile)) {
            $this->stream = new Stream($streamOrFile);
        } elseif ($streamOrFile instanceof StreamInterface) {
            $this->stream = $streamOrFile;
        } else {
            throw new InvalidArgumentException(
                'Invalid stream or file provided for UploadedFile'
            );
        }
    }

    /**
     * @param int $error
     * @throws InvalidArgumentException
     */
    private function setError($error)
    {
        if (false === is_int($error)) {
            throw new InvalidArgumentException(
                'Upload file error status must be an integer'
            );
        }

        if (false === in_array($error, UploadedFile::$errors)) {
            throw new InvalidArgumentException(
                'Invalid error status for UploadedFile'
            );
        }

        $this->error = $error;
    }

    /**
     * @param int $size
     * @throws InvalidArgumentException
     */
    private function setSize($size)
    {
        if (false === is_int($size)) {
            throw new InvalidArgumentException(
                'Upload file size must be an integer'
            );
        }

        $this->size = $size;
    }

    /**
     * @param mixed $param
     * @return boolean
     */
    private function isStringOrNull($param)
    {
        return in_array(gettype($param), ['string', 'NULL']);
    }

    /**
     * @param mixed $param
     * @return boolean
     */
    private function isStringNotEmpty($param)
    {
        return is_string($param) && false === empty($param);
    }

    /**
     * @param string|null $clientFilename
     * @throws InvalidArgumentException
     */
    private function setClientFilename($clientFilename)
    {
        if (false === $this->isStringOrNull($clientFilename)) {
            throw new InvalidArgumentException(
                'Upload file client filename must be a string or null'
            );
        }

        $this->clientFilename = $clientFilename;
    }

    /**
     * @param string|null $clientMediaType
     * @throws InvalidArgumentException
     */
    private function setClientMediaType($clientMediaType)
    {
        if (false === $this->isStringOrNull($clientMediaType)) {
            throw new InvalidArgumentException(
                'Upload file client media type must be a string or null'
            );
        }

        $this->clientMediaType = $clientMediaType;
    }

    /**
     * Return true if there is no upload error
     *
     * @return boolean
     */
    private function isOk()
    {
        return $this->error === UPLOAD_ERR_OK;
    }

    /**
     * @return boolean
     */
    public function isMoved()
    {
        return $this->moved;
    }

    /**
     * @throws RuntimeException if is moved or not ok
     */
    private function validateActive()
    {
        if (false === $this->isOk()) {
            throw new RuntimeException('Cannot retrieve stream due to upload error');
        }

        if ($this->isMoved()) {
            throw new RuntimeException('Cannot retrieve stream after it has already been moved');
        }
    }

    /**
     * {@inheritdoc}
     * @throws RuntimeException if the upload was not successful.
     */
    public function getStream()
    {
        $this->validateActive();

        if ($this->stream instanceof StreamInterface) {
            return $this->stream;
        }

        return new LazyOpenStream($this->file, 'r+');
    }

    /**
     * {@inheritdoc}
     *
     * @see http://php.net/is_uploaded_file
     * @see http://php.net/move_uploaded_file
     * @param string $targetPath Path to which to move the uploaded file.
     * @throws RuntimeException if the upload was not successful.
     * @throws InvalidArgumentException if the $path specified is invalid.
     * @throws RuntimeException on any error during the move operation, or on
     *     the second or subsequent call to the method.
     */
    public function moveTo($targetPath)
    {
        $this->validateActive();

        if (false === $this->isStringNotEmpty($targetPath)) {
            throw new InvalidArgumentException(
                'Invalid path provided for move operation; must be a non-empty string'
            );
        }

        if ($this->file) {
            $this->moved = php_sapi_name() == 'cli'
                ? rename($this->file, $targetPath)
                : move_uploaded_file($this->file, $targetPath);
        } else {
            copy_to_stream(
                $this->getStream(),
                new LazyOpenStream($targetPath, 'w')
            );

            $this->moved = true;
        }

        if (false === $this->moved) {
            throw new RuntimeException(
                sprintf('Uploaded file could not be moved to %s', $targetPath)
            );
        }
    }

    /**
     * {@inheritdoc}
     *
     * @return int|null The file size in bytes or null if unknown.
     */
    public function getSize()
    {
        return $this->size;
    }

    /**
     * {@inheritdoc}
     *
     * @see http://php.net/manual/en/features.file-upload.errors.php
     * @return int One of PHP's UPLOAD_ERR_XXX constants.
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * {@inheritdoc}
     *
     * @return string|null The filename sent by the client or null if none
     *     was provided.
     */
    public function getClientFilename()
    {
        return $this->clientFilename;
    }

    /**
     * {@inheritdoc}
     */
    public function getClientMediaType()
    {
        return $this->clientMediaType;
    }
}
<?php
namespace GuzzleHttp\Psr7;

use Psr\Http\Message\UriInterface;

/**
 * PSR-7 URI implementation.
 *
 * @author Michael Dowling
 * @author Tobias Schultze
 * @author Matthew Weier O'Phinney
 */
class Uri implements UriInterface
{
    private static $schemes = [
        'http'  => 80,
        'https' => 443,
    ];

    private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
    private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
    private static $replaceQuery = ['=' => '%3D', '&' => '%26'];

    /** @var string Uri scheme. */
    private $scheme = '';

    /** @var string Uri user info. */
    private $userInfo = '';

    /** @var string Uri host. */
    private $host = '';

    /** @var int|null Uri port. */
    private $port;

    /** @var string Uri path. */
    private $path = '';

    /** @var string Uri query string. */
    private $query = '';

    /** @var string Uri fragment. */
    private $fragment = '';

    /**
     * @param string $uri URI to parse
     */
    public function __construct($uri = '')
    {
        if ($uri != '') {
            $parts = parse_url($uri);
            if ($parts === false) {
                throw new \InvalidArgumentException("Unable to parse URI: $uri");
            }
            $this->applyParts($parts);
        }
    }

    public function __toString()
    {
        return self::createUriString(
            $this->scheme,
            $this->getAuthority(),
            $this->path,
            $this->query,
            $this->fragment
        );
    }

    /**
     * Removes dot segments from a path and returns the new path.
     *
     * @param string $path
     *
     * @return string
     * @link http://tools.ietf.org/html/rfc3986#section-5.2.4
     */
    public static function removeDotSegments($path)
    {
        static $noopPaths = ['' => true, '/' => true, '*' => true];
        static $ignoreSegments = ['.' => true, '..' => true];

        if (isset($noopPaths[$path])) {
            return $path;
        }

        $results = [];
        $segments = explode('/', $path);
        foreach ($segments as $segment) {
            if ($segment === '..') {
                array_pop($results);
            } elseif (!isset($ignoreSegments[$segment])) {
                $results[] = $segment;
            }
        }

        $newPath = implode('/', $results);
        // Add the leading slash if necessary
        if (substr($path, 0, 1) === '/' &&
            substr($newPath, 0, 1) !== '/'
        ) {
            $newPath = '/' . $newPath;
        }

        // Add the trailing slash if necessary
        if ($newPath !== '/' && isset($ignoreSegments[end($segments)])) {
            $newPath .= '/';
        }

        return $newPath;
    }

    /**
     * Resolve a base URI with a relative URI and return a new URI.
     *
     * @param UriInterface        $base Base URI
     * @param string|UriInterface $rel  Relative URI
     *
     * @return UriInterface
     * @link http://tools.ietf.org/html/rfc3986#section-5.2
     */
    public static function resolve(UriInterface $base, $rel)
    {
        if (!($rel instanceof UriInterface)) {
            $rel = new self($rel);
        }

        if ((string) $rel === '') {
            // we can simply return the same base URI instance for this same-document reference
            return $base;
        }

        if ($rel->getScheme() != '') {
            return $rel->withPath(self::removeDotSegments($rel->getPath()));
        }

        if ($rel->getAuthority() != '') {
            $targetAuthority = $rel->getAuthority();
            $targetPath = self::removeDotSegments($rel->getPath());
            $targetQuery = $rel->getQuery();
        } else {
            $targetAuthority = $base->getAuthority();
            if ($rel->getPath() === '') {
                $targetPath = $base->getPath();
                $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
            } else {
                if ($rel->getPath()[0] === '/') {
                    $targetPath = $rel->getPath();
                } else {
                    if ($targetAuthority != '' && $base->getPath() === '') {
                        $targetPath = '/' . $rel->getPath();
                    } else {
                        $lastSlashPos = strrpos($base->getPath(), '/');
                        if ($lastSlashPos === false) {
                            $targetPath = $rel->getPath();
                        } else {
                            $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1) . $rel->getPath();
                        }
                    }
                }
                $targetPath = self::removeDotSegments($targetPath);
                $targetQuery = $rel->getQuery();
            }
        }

        return new self(self::createUriString(
            $base->getScheme(),
            $targetAuthority,
            $targetPath,
            $targetQuery,
            $rel->getFragment()
        ));
    }

    /**
     * Create a new URI with a specific query string value removed.
     *
     * Any existing query string values that exactly match the provided key are
     * removed.
     *
     * @param UriInterface $uri URI to use as a base.
     * @param string       $key Query string key to remove.
     *
     * @return UriInterface
     */
    public static function withoutQueryValue(UriInterface $uri, $key)
    {
        $current = $uri->getQuery();
        if ($current == '') {
            return $uri;
        }

        $decodedKey = rawurldecode($key);
        $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
            return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
        });

        return $uri->withQuery(implode('&', $result));
    }

    /**
     * Create a new URI with a specific query string value.
     *
     * Any existing query string values that exactly match the provided key are
     * removed and replaced with the given key value pair.
     *
     * A value of null will set the query string key without a value, e.g. "key"
     * instead of "key=value".
     *
     * @param UriInterface $uri   URI to use as a base.
     * @param string       $key   Key to set.
     * @param string|null  $value Value to set
     *
     * @return UriInterface
     */
    public static function withQueryValue(UriInterface $uri, $key, $value)
    {
        $current = $uri->getQuery();

        if ($current == '') {
            $result = [];
        } else {
            $decodedKey = rawurldecode($key);
            $result = array_filter(explode('&', $current), function ($part) use ($decodedKey) {
                return rawurldecode(explode('=', $part)[0]) !== $decodedKey;
            });
        }

        // Query string separators ("=", "&") within the key or value need to be encoded
        // (while preventing double-encoding) before setting the query string. All other
        // chars that need percent-encoding will be encoded by withQuery().
        $key = strtr($key, self::$replaceQuery);

        if ($value !== null) {
            $result[] = $key . '=' . strtr($value, self::$replaceQuery);
        } else {
            $result[] = $key;
        }

        return $uri->withQuery(implode('&', $result));
    }

    /**
     * Create a URI from a hash of parse_url parts.
     *
     * @param array $parts
     *
     * @return self
     */
    public static function fromParts(array $parts)
    {
        $uri = new self();
        $uri->applyParts($parts);
        return $uri;
    }

    public function getScheme()
    {
        return $this->scheme;
    }

    public function getAuthority()
    {
        if ($this->host == '') {
            return '';
        }

        $authority = $this->host;
        if ($this->userInfo != '') {
            $authority = $this->userInfo . '@' . $authority;
        }

        if ($this->port !== null) {
            $authority .= ':' . $this->port;
        }

        return $authority;
    }

    public function getUserInfo()
    {
        return $this->userInfo;
    }

    public function getHost()
    {
        return $this->host;
    }

    public function getPort()
    {
        return $this->port;
    }

    public function getPath()
    {
        return $this->path;
    }

    public function getQuery()
    {
        return $this->query;
    }

    public function getFragment()
    {
        return $this->fragment;
    }

    public function withScheme($scheme)
    {
        $scheme = $this->filterScheme($scheme);

        if ($this->scheme === $scheme) {
            return $this;
        }

        $new = clone $this;
        $new->scheme = $scheme;
        $new->port = $new->filterPort($new->port);
        return $new;
    }

    public function withUserInfo($user, $password = null)
    {
        $info = $user;
        if ($password != '') {
            $info .= ':' . $password;
        }

        if ($this->userInfo === $info) {
            return $this;
        }

        $new = clone $this;
        $new->userInfo = $info;
        return $new;
    }

    public function withHost($host)
    {
        $host = $this->filterHost($host);

        if ($this->host === $host) {
            return $this;
        }

        $new = clone $this;
        $new->host = $host;
        return $new;
    }

    public function withPort($port)
    {
        $port = $this->filterPort($port);

        if ($this->port === $port) {
            return $this;
        }

        $new = clone $this;
        $new->port = $port;
        return $new;
    }

    public function withPath($path)
    {
        $path = $this->filterPath($path);

        if ($this->path === $path) {
            return $this;
        }

        $new = clone $this;
        $new->path = $path;
        return $new;
    }

    public function withQuery($query)
    {
        $query = $this->filterQueryAndFragment($query);

        if ($this->query === $query) {
            return $this;
        }

        $new = clone $this;
        $new->query = $query;
        return $new;
    }

    public function withFragment($fragment)
    {
        $fragment = $this->filterQueryAndFragment($fragment);

        if ($this->fragment === $fragment) {
            return $this;
        }

        $new = clone $this;
        $new->fragment = $fragment;
        return $new;
    }

    /**
     * Apply parse_url parts to a URI.
     *
     * @param array $parts Array of parse_url parts to apply.
     */
    private function applyParts(array $parts)
    {
        $this->scheme = isset($parts['scheme'])
            ? $this->filterScheme($parts['scheme'])
            : '';
        $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
        $this->host = isset($parts['host'])
            ? $this->filterHost($parts['host'])
            : '';
        $this->port = isset($parts['port'])
            ? $this->filterPort($parts['port'])
            : null;
        $this->path = isset($parts['path'])
            ? $this->filterPath($parts['path'])
            : '';
        $this->query = isset($parts['query'])
            ? $this->filterQueryAndFragment($parts['query'])
            : '';
        $this->fragment = isset($parts['fragment'])
            ? $this->filterQueryAndFragment($parts['fragment'])
            : '';
        if (isset($parts['pass'])) {
            $this->userInfo .= ':' . $parts['pass'];
        }
    }

    /**
     * Create a URI string from its various parts
     *
     * @param string $scheme
     * @param string $authority
     * @param string $path
     * @param string $query
     * @param string $fragment
     * @return string
     */
    private static function createUriString($scheme, $authority, $path, $query, $fragment)
    {
        $uri = '';

        if ($scheme != '') {
            $uri .= $scheme . ':';
        }

        if ($authority != '') {
            $uri .= '//' . $authority;
        }

        if ($path != '') {
            if ($path[0] !== '/') {
                if ($authority != '') {
                    // If the path is rootless and an authority is present, the path MUST be prefixed by "/"
                    $path = '/' . $path;
                }
            } elseif (isset($path[1]) && $path[1] === '/') {
                if ($authority == '') {
                    // If the path is starting with more than one "/" and no authority is present, the
                    // starting slashes MUST be reduced to one.
                    $path = '/' . ltrim($path, '/');
                }
            }

            $uri .= $path;
        }

        if ($query != '') {
            $uri .= '?' . $query;
        }

        if ($fragment != '') {
            $uri .= '#' . $fragment;
        }

        return $uri;
    }

    /**
     * Is a given port non-standard for the current scheme?
     *
     * @param string $scheme
     * @param int    $port
     *
     * @return bool
     */
    private static function isNonStandardPort($scheme, $port)
    {
        return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
    }

    /**
     * @param string $scheme
     *
     * @return string
     *
     * @throws \InvalidArgumentException If the scheme is invalid.
     */
    private function filterScheme($scheme)
    {
        if (!is_string($scheme)) {
            throw new \InvalidArgumentException('Scheme must be a string');
        }

        return strtolower($scheme);
    }

    /**
     * @param string $host
     *
     * @return string
     *
     * @throws \InvalidArgumentException If the host is invalid.
     */
    private function filterHost($host)
    {
        if (!is_string($host)) {
            throw new \InvalidArgumentException('Host must be a string');
        }

        return strtolower($host);
    }

    /**
     * @param int|null $port
     *
     * @return int|null
     *
     * @throws \InvalidArgumentException If the port is invalid.
     */
    private function filterPort($port)
    {
        if ($port === null) {
            return null;
        }

        $port = (int) $port;
        if (1 > $port || 0xffff < $port) {
            throw new \InvalidArgumentException(
                sprintf('Invalid port: %d. Must be between 1 and 65535', $port)
            );
        }

        return self::isNonStandardPort($this->scheme, $port) ? $port : null;
    }

    /**
     * Filters the path of a URI
     *
     * @param string $path
     *
     * @return string
     *
     * @throws \InvalidArgumentException If the path is invalid.
     */
    private function filterPath($path)
    {
        if (!is_string($path)) {
            throw new \InvalidArgumentException('Path must be a string');
        }

        return preg_replace_callback(
            '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
            [$this, 'rawurlencodeMatchZero'],
            $path
        );
    }

    /**
     * Filters the query string or fragment of a URI.
     *
     * @param string $str
     *
     * @return string
     *
     * @throws \InvalidArgumentException If the query or fragment is invalid.
     */
    private function filterQueryAndFragment($str)
    {
        if (!is_string($str)) {
            throw new \InvalidArgumentException('Query and fragment must be a string');
        }

        return preg_replace_callback(
            '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
            [$this, 'rawurlencodeMatchZero'],
            $str
        );
    }

    private function rawurlencodeMatchZero(array $match)
    {
        return rawurlencode($match[0]);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\AppendStream;
use GuzzleHttp\Psr7;

class AppendStreamTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Each stream must be readable
     */
    public function testValidatesStreamsAreReadable()
    {
        $a = new AppendStream();
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isReadable'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(false));
        $a->addStream($s);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage The AppendStream can only seek with SEEK_SET
     */
    public function testValidatesSeekType()
    {
        $a = new AppendStream();
        $a->seek(100, SEEK_CUR);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Unable to seek stream 0 of the AppendStream
     */
    public function testTriesToRewindOnSeek()
    {
        $a = new AppendStream();
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isReadable', 'rewind', 'isSeekable'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(true));
        $s->expects($this->once())
            ->method('isSeekable')
            ->will($this->returnValue(true));
        $s->expects($this->once())
            ->method('rewind')
            ->will($this->throwException(new \RuntimeException()));
        $a->addStream($s);
        $a->seek(10);
    }

    public function testSeeksToPositionByReading()
    {
        $a = new AppendStream([
            Psr7\stream_for('foo'),
            Psr7\stream_for('bar'),
            Psr7\stream_for('baz'),
        ]);

        $a->seek(3);
        $this->assertEquals(3, $a->tell());
        $this->assertEquals('bar', $a->read(3));

        $a->seek(6);
        $this->assertEquals(6, $a->tell());
        $this->assertEquals('baz', $a->read(3));
    }

    public function testDetachesEachStream()
    {
        $s1 = Psr7\stream_for('foo');
        $s2 = Psr7\stream_for('bar');
        $a = new AppendStream([$s1, $s2]);
        $this->assertSame('foobar', (string) $a);
        $a->detach();
        $this->assertSame('', (string) $a);
        $this->assertSame(0, $a->getSize());
    }

    public function testClosesEachStream()
    {
        $s1 = Psr7\stream_for('foo');
        $a = new AppendStream([$s1]);
        $a->close();
        $this->assertSame('', (string) $a);
    }

    /**
     * @expectedExceptionMessage Cannot write to an AppendStream
     * @expectedException \RuntimeException
     */
    public function testIsNotWritable()
    {
        $a = new AppendStream([Psr7\stream_for('foo')]);
        $this->assertFalse($a->isWritable());
        $this->assertTrue($a->isSeekable());
        $this->assertTrue($a->isReadable());
        $a->write('foo');
    }

    public function testDoesNotNeedStreams()
    {
        $a = new AppendStream();
        $this->assertEquals('', (string) $a);
    }

    public function testCanReadFromMultipleStreams()
    {
        $a = new AppendStream([
            Psr7\stream_for('foo'),
            Psr7\stream_for('bar'),
            Psr7\stream_for('baz'),
        ]);
        $this->assertFalse($a->eof());
        $this->assertSame(0, $a->tell());
        $this->assertEquals('foo', $a->read(3));
        $this->assertEquals('bar', $a->read(3));
        $this->assertEquals('baz', $a->read(3));
        $this->assertSame('', $a->read(1));
        $this->assertTrue($a->eof());
        $this->assertSame(9, $a->tell());
        $this->assertEquals('foobarbaz', (string) $a);
    }

    public function testCanDetermineSizeFromMultipleStreams()
    {
        $a = new AppendStream([
            Psr7\stream_for('foo'),
            Psr7\stream_for('bar')
        ]);
        $this->assertEquals(6, $a->getSize());

        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isSeekable', 'isReadable'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('isSeekable')
            ->will($this->returnValue(null));
        $s->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(true));
        $a->addStream($s);
        $this->assertNull($a->getSize());
    }

    public function testCatchesExceptionsWhenCastingToString()
    {
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isSeekable', 'read', 'isReadable', 'eof'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('isSeekable')
            ->will($this->returnValue(true));
        $s->expects($this->once())
            ->method('read')
            ->will($this->throwException(new \RuntimeException('foo')));
        $s->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(true));
        $s->expects($this->any())
            ->method('eof')
            ->will($this->returnValue(false));
        $a = new AppendStream([$s]);
        $this->assertFalse($a->eof());
        $this->assertSame('', (string) $a);
    }

    public function testCanDetach()
    {
        $s = new AppendStream();
        $s->detach();
    }

    public function testReturnsEmptyMetadata()
    {
        $s = new AppendStream();
        $this->assertEquals([], $s->getMetadata());
        $this->assertNull($s->getMetadata('foo'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

require __DIR__ . '/../vendor/autoload.php';

class HasToString
{
    public function __toString() {
        return 'foo';
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\BufferStream;

class BufferStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testHasMetadata()
    {
        $b = new BufferStream(10);
        $this->assertTrue($b->isReadable());
        $this->assertTrue($b->isWritable());
        $this->assertFalse($b->isSeekable());
        $this->assertEquals(null, $b->getMetadata('foo'));
        $this->assertEquals(10, $b->getMetadata('hwm'));
        $this->assertEquals([], $b->getMetadata());
    }

    public function testRemovesReadDataFromBuffer()
    {
        $b = new BufferStream();
        $this->assertEquals(3, $b->write('foo'));
        $this->assertEquals(3, $b->getSize());
        $this->assertFalse($b->eof());
        $this->assertEquals('foo', $b->read(10));
        $this->assertTrue($b->eof());
        $this->assertEquals('', $b->read(10));
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Cannot determine the position of a BufferStream
     */
    public function testCanCastToStringOrGetContents()
    {
        $b = new BufferStream();
        $b->write('foo');
        $b->write('baz');
        $this->assertEquals('foo', $b->read(3));
        $b->write('bar');
        $this->assertEquals('bazbar', (string) $b);
        $b->tell();
    }

    public function testDetachClearsBuffer()
    {
        $b = new BufferStream();
        $b->write('foo');
        $b->detach();
        $this->assertTrue($b->eof());
        $this->assertEquals(3, $b->write('abc'));
        $this->assertEquals('abc', $b->read(10));
    }

    public function testExceedingHighwaterMarkReturnsFalseButStillBuffers()
    {
        $b = new BufferStream(5);
        $this->assertEquals(3, $b->write('hi '));
        $this->assertFalse($b->write('hello'));
        $this->assertEquals('hi hello', (string) $b);
        $this->assertEquals(4, $b->write('test'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\CachingStream;

/**
 * @covers GuzzleHttp\Psr7\CachingStream
 */
class CachingStreamTest extends \PHPUnit_Framework_TestCase
{
    /** @var CachingStream */
    protected $body;
    protected $decorated;

    public function setUp()
    {
        $this->decorated = Psr7\stream_for('testing');
        $this->body = new CachingStream($this->decorated);
    }

    public function tearDown()
    {
        $this->decorated->close();
        $this->body->close();
    }

    public function testUsesRemoteSizeIfPossible()
    {
        $body = Psr7\stream_for('test');
        $caching = new CachingStream($body);
        $this->assertEquals(4, $caching->getSize());
    }

    public function testReadsUntilCachedToByte()
    {
        $this->body->seek(5);
        $this->assertEquals('n', $this->body->read(1));
        $this->body->seek(0);
        $this->assertEquals('t', $this->body->read(1));
    }

    public function testCanSeekNearEndWithSeekEnd()
    {
        $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
        $cached = new CachingStream($baseStream);
        $cached->seek(-1, SEEK_END);
        $this->assertEquals(25, $baseStream->tell());
        $this->assertEquals('z', $cached->read(1));
        $this->assertEquals(26, $cached->getSize());
    }

    public function testCanSeekToEndWithSeekEnd()
    {
        $baseStream = Psr7\stream_for(implode('', range('a', 'z')));
        $cached = new CachingStream($baseStream);
        $cached->seek(0, SEEK_END);
        $this->assertEquals(26, $baseStream->tell());
        $this->assertEquals('', $cached->read(1));
        $this->assertEquals(26, $cached->getSize());
    }

    public function testCanUseSeekEndWithUnknownSize()
    {
        $baseStream = Psr7\stream_for('testing');
        $decorated = Psr7\FnStream::decorate($baseStream, [
            'getSize' => function () { return null; }
        ]);
        $cached = new CachingStream($decorated);
        $cached->seek(-1, SEEK_END);
        $this->assertEquals('g', $cached->read(1));
    }

    public function testRewindUsesSeek()
    {
        $a = Psr7\stream_for('foo');
        $d = $this->getMockBuilder('GuzzleHttp\Psr7\CachingStream')
            ->setMethods(array('seek'))
            ->setConstructorArgs(array($a))
            ->getMock();
        $d->expects($this->once())
            ->method('seek')
            ->with(0)
            ->will($this->returnValue(true));
        $d->seek(0);
    }

    public function testCanSeekToReadBytes()
    {
        $this->assertEquals('te', $this->body->read(2));
        $this->body->seek(0);
        $this->assertEquals('test', $this->body->read(4));
        $this->assertEquals(4, $this->body->tell());
        $this->body->seek(2);
        $this->assertEquals(2, $this->body->tell());
        $this->body->seek(2, SEEK_CUR);
        $this->assertEquals(4, $this->body->tell());
        $this->assertEquals('ing', $this->body->read(3));
    }

    public function testCanSeekToReadBytesWithPartialBodyReturned()
    {
        $stream = fopen('php://temp', 'r+');
        fwrite($stream, 'testing');
        fseek($stream, 0);

        $this->decorated = $this->getMockBuilder('\GuzzleHttp\Psr7\Stream')
            ->setConstructorArgs([$stream])
            ->setMethods(['read'])
            ->getMock();

        $this->decorated->expects($this->exactly(2))
            ->method('read')
            ->willReturnCallback(function($length) use ($stream){
                return fread($stream, 2);
            });

        $this->body = new CachingStream($this->decorated);

        $this->assertEquals(0, $this->body->tell());
        $this->body->seek(4, SEEK_SET);
        $this->assertEquals(4, $this->body->tell());

        $this->body->seek(0);
        $this->assertEquals('test', $this->body->read(4));
    }

    public function testWritesToBufferStream()
    {
        $this->body->read(2);
        $this->body->write('hi');
        $this->body->seek(0);
        $this->assertEquals('tehiing', (string) $this->body);
    }

    public function testSkipsOverwrittenBytes()
    {
        $decorated = Psr7\stream_for(
            implode("\n", array_map(function ($n) {
                return str_pad($n, 4, '0', STR_PAD_LEFT);
            }, range(0, 25)))
        );

        $body = new CachingStream($decorated);

        $this->assertEquals("0000\n", Psr7\readline($body));
        $this->assertEquals("0001\n", Psr7\readline($body));
        // Write over part of the body yet to be read, so skip some bytes
        $this->assertEquals(5, $body->write("TEST\n"));
        $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
        // Read, which skips bytes, then reads
        $this->assertEquals("0003\n", Psr7\readline($body));
        $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
        $this->assertEquals("0004\n", Psr7\readline($body));
        $this->assertEquals("0005\n", Psr7\readline($body));

        // Overwrite part of the cached body (so don't skip any bytes)
        $body->seek(5);
        $this->assertEquals(5, $body->write("ABCD\n"));
        $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
        $this->assertEquals("TEST\n", Psr7\readline($body));
        $this->assertEquals("0003\n", Psr7\readline($body));
        $this->assertEquals("0004\n", Psr7\readline($body));
        $this->assertEquals("0005\n", Psr7\readline($body));
        $this->assertEquals("0006\n", Psr7\readline($body));
        $this->assertEquals(5, $body->write("1234\n"));
        $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));

        // Seek to 0 and ensure the overwritten bit is replaced
        $body->seek(0);
        $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));

        // Ensure that casting it to a string does not include the bit that was overwritten
        $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
    }

    public function testClosesBothStreams()
    {
        $s = fopen('php://temp', 'r');
        $a = Psr7\stream_for($s);
        $d = new CachingStream($a);
        $d->close();
        $this->assertFalse(is_resource($s));
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testEnsuresValidWhence()
    {
        $this->body->seek(10, -123456);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\BufferStream;
use GuzzleHttp\Psr7\DroppingStream;

class DroppingStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testBeginsDroppingWhenSizeExceeded()
    {
        $stream = new BufferStream();
        $drop = new DroppingStream($stream, 5);
        $this->assertEquals(3, $drop->write('hel'));
        $this->assertEquals(2, $drop->write('lo'));
        $this->assertEquals(5, $drop->getSize());
        $this->assertEquals('hello', $drop->read(5));
        $this->assertEquals(0, $drop->getSize());
        $drop->write('12345678910');
        $this->assertEquals(5, $stream->getSize());
        $this->assertEquals(5, $drop->getSize());
        $this->assertEquals('12345', (string) $drop);
        $this->assertEquals(0, $drop->getSize());
        $drop->write('hello');
        $this->assertSame(0, $drop->write('test'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;

/**
 * @covers GuzzleHttp\Psr7\FnStream
 */
class FnStreamTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \BadMethodCallException
     * @expectedExceptionMessage seek() is not implemented in the FnStream
     */
    public function testThrowsWhenNotImplemented()
    {
        (new FnStream([]))->seek(1);
    }

    public function testProxiesToFunction()
    {
        $s = new FnStream([
            'read' => function ($len) {
                $this->assertEquals(3, $len);
                return 'foo';
            }
        ]);

        $this->assertEquals('foo', $s->read(3));
    }

    public function testCanCloseOnDestruct()
    {
        $called = false;
        $s = new FnStream([
            'close' => function () use (&$called) {
                $called = true;
            }
        ]);
        unset($s);
        $this->assertTrue($called);
    }

    public function testDoesNotRequireClose()
    {
        $s = new FnStream([]);
        unset($s);
    }

    public function testDecoratesStream()
    {
        $a = Psr7\stream_for('foo');
        $b = FnStream::decorate($a, []);
        $this->assertEquals(3, $b->getSize());
        $this->assertEquals($b->isWritable(), true);
        $this->assertEquals($b->isReadable(), true);
        $this->assertEquals($b->isSeekable(), true);
        $this->assertEquals($b->read(3), 'foo');
        $this->assertEquals($b->tell(), 3);
        $this->assertEquals($a->tell(), 3);
        $this->assertSame('', $a->read(1));
        $this->assertEquals($b->eof(), true);
        $this->assertEquals($a->eof(), true);
        $b->seek(0);
        $this->assertEquals('foo', (string) $b);
        $b->seek(0);
        $this->assertEquals('foo', $b->getContents());
        $this->assertEquals($a->getMetadata(), $b->getMetadata());
        $b->seek(0, SEEK_END);
        $b->write('bar');
        $this->assertEquals('foobar', (string) $b);
        $this->assertInternalType('resource', $b->detach());
        $b->close();
    }

    public function testDecoratesWithCustomizations()
    {
        $called = false;
        $a = Psr7\stream_for('foo');
        $b = FnStream::decorate($a, [
            'read' => function ($len) use (&$called, $a) {
                $called = true;
                return $a->read($len);
            }
        ]);
        $this->assertEquals('foo', $b->read(3));
        $this->assertTrue($called);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\NoSeekStream;

class FunctionsTest extends \PHPUnit_Framework_TestCase
{
    public function testCopiesToString()
    {
        $s = Psr7\stream_for('foobaz');
        $this->assertEquals('foobaz', Psr7\copy_to_string($s));
        $s->seek(0);
        $this->assertEquals('foo', Psr7\copy_to_string($s, 3));
        $this->assertEquals('baz', Psr7\copy_to_string($s, 3));
        $this->assertEquals('', Psr7\copy_to_string($s));
    }

    public function testCopiesToStringStopsWhenReadFails()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s1 = FnStream::decorate($s1, [
            'read' => function () { return ''; }
        ]);
        $result = Psr7\copy_to_string($s1);
        $this->assertEquals('', $result);
    }

    public function testCopiesToStream()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s2 = Psr7\stream_for('');
        Psr7\copy_to_stream($s1, $s2);
        $this->assertEquals('foobaz', (string) $s2);
        $s2 = Psr7\stream_for('');
        $s1->seek(0);
        Psr7\copy_to_stream($s1, $s2, 3);
        $this->assertEquals('foo', (string) $s2);
        Psr7\copy_to_stream($s1, $s2, 3);
        $this->assertEquals('foobaz', (string) $s2);
    }

    public function testStopsCopyToStreamWhenWriteFails()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s2 = Psr7\stream_for('');
        $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
        Psr7\copy_to_stream($s1, $s2);
        $this->assertEquals('', (string) $s2);
    }

    public function testStopsCopyToSteamWhenWriteFailsWithMaxLen()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s2 = Psr7\stream_for('');
        $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
        Psr7\copy_to_stream($s1, $s2, 10);
        $this->assertEquals('', (string) $s2);
    }

    public function testStopsCopyToSteamWhenReadFailsWithMaxLen()
    {
        $s1 = Psr7\stream_for('foobaz');
        $s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]);
        $s2 = Psr7\stream_for('');
        Psr7\copy_to_stream($s1, $s2, 10);
        $this->assertEquals('', (string) $s2);
    }

    public function testReadsLines()
    {
        $s = Psr7\stream_for("foo\nbaz\nbar");
        $this->assertEquals("foo\n", Psr7\readline($s));
        $this->assertEquals("baz\n", Psr7\readline($s));
        $this->assertEquals("bar", Psr7\readline($s));
    }

    public function testReadsLinesUpToMaxLength()
    {
        $s = Psr7\stream_for("12345\n");
        $this->assertEquals("123", Psr7\readline($s, 4));
        $this->assertEquals("45\n", Psr7\readline($s));
    }

    public function testReadsLineUntilFalseReturnedFromRead()
    {
        $s = $this->getMockBuilder('GuzzleHttp\Psr7\Stream')
            ->setMethods(['read', 'eof'])
            ->disableOriginalConstructor()
            ->getMock();
        $s->expects($this->exactly(2))
            ->method('read')
            ->will($this->returnCallback(function () {
                static $c = false;
                if ($c) {
                    return false;
                }
                $c = true;
                return 'h';
            }));
        $s->expects($this->exactly(2))
            ->method('eof')
            ->will($this->returnValue(false));
        $this->assertEquals("h", Psr7\readline($s));
    }

    public function testCalculatesHash()
    {
        $s = Psr7\stream_for('foobazbar');
        $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
    }

    /**
     * @expectedException \RuntimeException
     */
    public function testCalculatesHashThrowsWhenSeekFails()
    {
        $s = new NoSeekStream(Psr7\stream_for('foobazbar'));
        $s->read(2);
        Psr7\hash($s, 'md5');
    }

    public function testCalculatesHashSeeksToOriginalPosition()
    {
        $s = Psr7\stream_for('foobazbar');
        $s->seek(4);
        $this->assertEquals(md5('foobazbar'), Psr7\hash($s, 'md5'));
        $this->assertEquals(4, $s->tell());
    }

    public function testOpensFilesSuccessfully()
    {
        $r = Psr7\try_fopen(__FILE__, 'r');
        $this->assertInternalType('resource', $r);
        fclose($r);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
     */
    public function testThrowsExceptionNotWarning()
    {
        Psr7\try_fopen('/path/to/does/not/exist', 'r');
    }

    public function parseQueryProvider()
    {
        return [
            // Does not need to parse when the string is empty
            ['', []],
            // Can parse mult-values items
            ['q=a&q=b', ['q' => ['a', 'b']]],
            // Can parse multi-valued items that use numeric indices
            ['q[0]=a&q[1]=b', ['q[0]' => 'a', 'q[1]' => 'b']],
            // Can parse duplicates and does not include numeric indices
            ['q[]=a&q[]=b', ['q[]' => ['a', 'b']]],
            // Ensures that the value of "q" is an array even though one value
            ['q[]=a', ['q[]' => 'a']],
            // Does not modify "." to "_" like PHP's parse_str()
            ['q.a=a&q.b=b', ['q.a' => 'a', 'q.b' => 'b']],
            // Can decode %20 to " "
            ['q%20a=a%20b', ['q a' => 'a b']],
            // Can parse funky strings with no values by assigning each to null
            ['q&a', ['q' => null, 'a' => null]],
            // Does not strip trailing equal signs
            ['data=abc=', ['data' => 'abc=']],
            // Can store duplicates without affecting other values
            ['foo=a&foo=b&?µ=c', ['foo' => ['a', 'b'], '?µ' => 'c']],
            // Sets value to null when no "=" is present
            ['foo', ['foo' => null]],
            // Preserves "0" keys.
            ['0', ['0' => null]],
            // Sets the value to an empty string when "=" is present
            ['0=', ['0' => '']],
            // Preserves falsey keys
            ['var=0', ['var' => '0']],
            ['a[b][c]=1&a[b][c]=2', ['a[b][c]' => ['1', '2']]],
            ['a[b]=c&a[d]=e', ['a[b]' => 'c', 'a[d]' => 'e']],
            // Ensure it doesn't leave things behind with repeated values
            // Can parse mult-values items
            ['q=a&q=b&q=c', ['q' => ['a', 'b', 'c']]],
        ];
    }

    /**
     * @dataProvider parseQueryProvider
     */
    public function testParsesQueries($input, $output)
    {
        $result = Psr7\parse_query($input);
        $this->assertSame($output, $result);
    }

    public function testDoesNotDecode()
    {
        $str = 'foo%20=bar';
        $data = Psr7\parse_query($str, false);
        $this->assertEquals(['foo%20' => 'bar'], $data);
    }

    /**
     * @dataProvider parseQueryProvider
     */
    public function testParsesAndBuildsQueries($input, $output)
    {
        $result = Psr7\parse_query($input, false);
        $this->assertSame($input, Psr7\build_query($result, false));
    }

    public function testEncodesWithRfc1738()
    {
        $str = Psr7\build_query(['foo bar' => 'baz+'], PHP_QUERY_RFC1738);
        $this->assertEquals('foo+bar=baz%2B', $str);
    }

    public function testEncodesWithRfc3986()
    {
        $str = Psr7\build_query(['foo bar' => 'baz+'], PHP_QUERY_RFC3986);
        $this->assertEquals('foo%20bar=baz%2B', $str);
    }

    public function testDoesNotEncode()
    {
        $str = Psr7\build_query(['foo bar' => 'baz+'], false);
        $this->assertEquals('foo bar=baz+', $str);
    }

    public function testCanControlDecodingType()
    {
        $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC3986);
        $this->assertEquals('foo+bar', $result['var']);
        $result = Psr7\parse_query('var=foo+bar', PHP_QUERY_RFC1738);
        $this->assertEquals('foo bar', $result['var']);
    }

    public function testParsesRequestMessages()
    {
        $req = "GET /abc HTTP/1.0\r\nHost: foo.com\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
        $request = Psr7\parse_request($req);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('/abc', $request->getRequestTarget());
        $this->assertEquals('1.0', $request->getProtocolVersion());
        $this->assertEquals('foo.com', $request->getHeaderLine('Host'));
        $this->assertEquals('Bar', $request->getHeaderLine('Foo'));
        $this->assertEquals('Bam, Qux', $request->getHeaderLine('Baz'));
        $this->assertEquals('Test', (string) $request->getBody());
        $this->assertEquals('http://foo.com/abc', (string) $request->getUri());
    }

    public function testParsesRequestMessagesWithHttpsScheme()
    {
        $req = "PUT /abc?baz=bar HTTP/1.1\r\nHost: foo.com:443\r\n\r\n";
        $request = Psr7\parse_request($req);
        $this->assertEquals('PUT', $request->getMethod());
        $this->assertEquals('/abc?baz=bar', $request->getRequestTarget());
        $this->assertEquals('1.1', $request->getProtocolVersion());
        $this->assertEquals('foo.com:443', $request->getHeaderLine('Host'));
        $this->assertEquals('', (string) $request->getBody());
        $this->assertEquals('https://foo.com/abc?baz=bar', (string) $request->getUri());
    }

    public function testParsesRequestMessagesWithUriWhenHostIsNotFirst()
    {
        $req = "PUT / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
        $request = Psr7\parse_request($req);
        $this->assertEquals('PUT', $request->getMethod());
        $this->assertEquals('/', $request->getRequestTarget());
        $this->assertEquals('http://foo.com/', (string) $request->getUri());
    }

    public function testParsesRequestMessagesWithFullUri()
    {
        $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
        $request = Psr7\parse_request($req);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('https://www.google.com:443/search?q=foobar', $request->getRequestTarget());
        $this->assertEquals('1.1', $request->getProtocolVersion());
        $this->assertEquals('www.google.com', $request->getHeaderLine('Host'));
        $this->assertEquals('', (string) $request->getBody());
        $this->assertEquals('https://www.google.com/search?q=foobar', (string) $request->getUri());
    }

    public function testParsesRequestMessagesWithCustomMethod()
    {
        $req = "GET_DATA / HTTP/1.1\r\nFoo: Bar\r\nHost: foo.com\r\n\r\n";
        $request = Psr7\parse_request($req);
        $this->assertEquals('GET_DATA', $request->getMethod());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesRequestMessages()
    {
        Psr7\parse_request("HTTP/1.1 200 OK\r\n\r\n");
    }

    public function testParsesResponseMessages()
    {
        $res = "HTTP/1.0 200 OK\r\nFoo: Bar\r\nBaz: Bam\r\nBaz: Qux\r\n\r\nTest";
        $response = Psr7\parse_response($res);
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertEquals('OK', $response->getReasonPhrase());
        $this->assertEquals('1.0', $response->getProtocolVersion());
        $this->assertEquals('Bar', $response->getHeaderLine('Foo'));
        $this->assertEquals('Bam, Qux', $response->getHeaderLine('Baz'));
        $this->assertEquals('Test', (string) $response->getBody());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesResponseMessages()
    {
        Psr7\parse_response("GET / HTTP/1.1\r\n\r\n");
    }

    public function testDetermineMimetype()
    {
        $this->assertNull(Psr7\mimetype_from_extension('not-a-real-extension'));
        $this->assertEquals(
            'application/json',
            Psr7\mimetype_from_extension('json')
        );
        $this->assertEquals(
            'image/jpeg',
            Psr7\mimetype_from_filename('/tmp/images/IMG034821.JPEG')
        );
    }

    public function testCreatesUriForValue()
    {
        $this->assertInstanceOf('GuzzleHttp\Psr7\Uri', Psr7\uri_for('/foo'));
        $this->assertInstanceOf(
            'GuzzleHttp\Psr7\Uri',
            Psr7\uri_for(new Psr7\Uri('/foo'))
        );
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesUri()
    {
        Psr7\uri_for([]);
    }

    public function testKeepsPositionOfResource()
    {
        $h = fopen(__FILE__, 'r');
        fseek($h, 10);
        $stream = Psr7\stream_for($h);
        $this->assertEquals(10, $stream->tell());
        $stream->close();
    }

    public function testCreatesWithFactory()
    {
        $stream = Psr7\stream_for('foo');
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $stream);
        $this->assertEquals('foo', $stream->getContents());
        $stream->close();
    }

    public function testFactoryCreatesFromEmptyString()
    {
        $s = Psr7\stream_for();
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
    }

    public function testFactoryCreatesFromNull()
    {
        $s = Psr7\stream_for(null);
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
    }

    public function testFactoryCreatesFromResource()
    {
        $r = fopen(__FILE__, 'r');
        $s = Psr7\stream_for($r);
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
        $this->assertSame(file_get_contents(__FILE__), (string) $s);
    }

    public function testFactoryCreatesFromObjectWithToString()
    {
        $r = new HasToString();
        $s = Psr7\stream_for($r);
        $this->assertInstanceOf('GuzzleHttp\Psr7\Stream', $s);
        $this->assertEquals('foo', (string) $s);
    }

    public function testCreatePassesThrough()
    {
        $s = Psr7\stream_for('foo');
        $this->assertSame($s, Psr7\stream_for($s));
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testThrowsExceptionForUnknown()
    {
        Psr7\stream_for(new \stdClass());
    }

    public function testReturnsCustomMetadata()
    {
        $s = Psr7\stream_for('foo', ['metadata' => ['hwm' => 3]]);
        $this->assertEquals(3, $s->getMetadata('hwm'));
        $this->assertArrayHasKey('hwm', $s->getMetadata());
    }

    public function testCanSetSize()
    {
        $s = Psr7\stream_for('', ['size' => 10]);
        $this->assertEquals(10, $s->getSize());
    }

    public function testCanCreateIteratorBasedStream()
    {
        $a = new \ArrayIterator(['foo', 'bar', '123']);
        $p = Psr7\stream_for($a);
        $this->assertInstanceOf('GuzzleHttp\Psr7\PumpStream', $p);
        $this->assertEquals('foo', $p->read(3));
        $this->assertFalse($p->eof());
        $this->assertEquals('b', $p->read(1));
        $this->assertEquals('a', $p->read(1));
        $this->assertEquals('r12', $p->read(3));
        $this->assertFalse($p->eof());
        $this->assertEquals('3', $p->getContents());
        $this->assertTrue($p->eof());
        $this->assertEquals(9, $p->tell());
    }

    public function testConvertsRequestsToStrings()
    {
        $request = new Psr7\Request('PUT', 'http://foo.com/hi?123', [
            'Baz' => 'bar',
            'Qux' => 'ipsum'
        ], 'hello', '1.0');
        $this->assertEquals(
            "PUT /hi?123 HTTP/1.0\r\nHost: foo.com\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
            Psr7\str($request)
        );
    }

    public function testConvertsResponsesToStrings()
    {
        $response = new Psr7\Response(200, [
            'Baz' => 'bar',
            'Qux' => 'ipsum'
        ], 'hello', '1.0', 'FOO');
        $this->assertEquals(
            "HTTP/1.0 200 FOO\r\nBaz: bar\r\nQux: ipsum\r\n\r\nhello",
            Psr7\str($response)
        );
    }

    public function parseParamsProvider()
    {
        $res1 = array(
            array(
                '<http:/.../front.jpeg>',
                'rel' => 'front',
                'type' => 'image/jpeg',
            ),
            array(
                '<http://.../back.jpeg>',
                'rel' => 'back',
                'type' => 'image/jpeg',
            ),
        );
        return array(
            array(
                '<http:/.../front.jpeg>; rel="front"; type="image/jpeg", <http://.../back.jpeg>; rel=back; type="image/jpeg"',
                $res1
            ),
            array(
                '<http:/.../front.jpeg>; rel="front"; type="image/jpeg",<http://.../back.jpeg>; rel=back; type="image/jpeg"',
                $res1
            ),
            array(
                'foo="baz"; bar=123, boo, test="123", foobar="foo;bar"',
                array(
                    array('foo' => 'baz', 'bar' => '123'),
                    array('boo'),
                    array('test' => '123'),
                    array('foobar' => 'foo;bar')
                )
            ),
            array(
                '<http://.../side.jpeg?test=1>; rel="side"; type="image/jpeg",<http://.../side.jpeg?test=2>; rel=side; type="image/jpeg"',
                array(
                    array('<http://.../side.jpeg?test=1>', 'rel' => 'side', 'type' => 'image/jpeg'),
                    array('<http://.../side.jpeg?test=2>', 'rel' => 'side', 'type' => 'image/jpeg')
                )
            ),
            array(
                '',
                array()
            )
        );
    }
    /**
     * @dataProvider parseParamsProvider
     */
    public function testParseParams($header, $result)
    {
        $this->assertEquals($result, Psr7\parse_header($header));
    }

    public function testParsesArrayHeaders()
    {
        $header = ['a, b', 'c', 'd, e'];
        $this->assertEquals(['a', 'b', 'c', 'd', 'e'], Psr7\normalize_header($header));
    }

    public function testRewindsBody()
    {
        $body = Psr7\stream_for('abc');
        $res = new Psr7\Response(200, [], $body);
        Psr7\rewind_body($res);
        $this->assertEquals(0, $body->tell());
        $body->rewind(1);
        Psr7\rewind_body($res);
        $this->assertEquals(0, $body->tell());
    }

    /**
     * @expectedException \RuntimeException
     */
    public function testThrowsWhenBodyCannotBeRewound()
    {
        $body = Psr7\stream_for('abc');
        $body->read(1);
        $body = FnStream::decorate($body, [
            'rewind' => function () { throw new \RuntimeException('a'); }
        ]);
        $res = new Psr7\Response(200, [], $body);
        Psr7\rewind_body($res);
    }

    public function testCanModifyRequestWithUri()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, [
            'uri' => new Psr7\Uri('http://www.foo.com')
        ]);
        $this->assertEquals('http://www.foo.com', (string) $r2->getUri());
        $this->assertEquals('www.foo.com', (string) $r2->getHeaderLine('host'));
    }

    public function testCanModifyRequestWithUriAndPort()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com:8000');
        $r2 = Psr7\modify_request($r1, [
            'uri' => new Psr7\Uri('http://www.foo.com:8000')
        ]);
        $this->assertEquals('http://www.foo.com:8000', (string) $r2->getUri());
        $this->assertEquals('www.foo.com:8000', (string) $r2->getHeaderLine('host'));
    }

    public function testCanModifyRequestWithCaseInsensitiveHeader()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com', ['User-Agent' => 'foo']);
        $r2 = Psr7\modify_request($r1, ['set_headers' => ['User-agent' => 'bar']]);
        $this->assertEquals('bar', $r2->getHeaderLine('User-Agent'));
        $this->assertEquals('bar', $r2->getHeaderLine('User-agent'));
    }

    public function testReturnsAsIsWhenNoChanges()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, []);
        $this->assertTrue($r2 instanceof Psr7\Request);

        $r1 = new Psr7\ServerRequest('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, []);
        $this->assertTrue($r2 instanceof \Psr\Http\Message\ServerRequestInterface);
    }

    public function testReturnsUriAsIsWhenNoChanges()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, ['set_headers' => ['foo' => 'bar']]);
        $this->assertNotSame($r1, $r2);
        $this->assertEquals('bar', $r2->getHeaderLine('foo'));
    }

    public function testRemovesHeadersFromMessage()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com', ['foo' => 'bar']);
        $r2 = Psr7\modify_request($r1, ['remove_headers' => ['foo']]);
        $this->assertNotSame($r1, $r2);
        $this->assertFalse($r2->hasHeader('foo'));
    }

    public function testAddsQueryToUri()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, ['query' => 'foo=bar']);
        $this->assertNotSame($r1, $r2);
        $this->assertEquals('foo=bar', $r2->getUri()->getQuery());
    }

    public function testModifyRequestKeepInstanceOfRequest()
    {
        $r1 = new Psr7\Request('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, ['remove_headers' => ['non-existent']]);
        $this->assertTrue($r2 instanceof Psr7\Request);

        $r1 = new Psr7\ServerRequest('GET', 'http://foo.com');
        $r2 = Psr7\modify_request($r1, ['remove_headers' => ['non-existent']]);
        $this->assertTrue($r2 instanceof \Psr\Http\Message\ServerRequestInterface);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\InflateStream;

class InflateStreamtest extends \PHPUnit_Framework_TestCase
{
    public function testInflatesStreams()
    {
        $content = gzencode('test');
        $a = Psr7\stream_for($content);
        $b = new InflateStream($a);
        $this->assertEquals('test', (string) $b);
    }

    public function testInflatesStreamsWithFilename()
    {
        $content = $this->getGzipStringWithFilename('test');
        $a = Psr7\stream_for($content);
        $b = new InflateStream($a);
        $this->assertEquals('test', (string) $b);
    }

    private function getGzipStringWithFilename($original_string)
    {
        $gzipped = bin2hex(gzencode($original_string));

        $header = substr($gzipped, 0, 20);
        // set FNAME flag
        $header[6]=0;
        $header[7]=8;
        // make a dummy filename
        $filename = "64756d6d7900";
        $rest = substr($gzipped, 20);

        return hex2bin($header . $filename . $rest);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\LazyOpenStream;

class LazyOpenStreamTest extends \PHPUnit_Framework_TestCase
{
    private $fname;

    public function setup()
    {
        $this->fname = tempnam('/tmp', 'tfile');

        if (file_exists($this->fname)) {
            unlink($this->fname);
        }
    }

    public function tearDown()
    {
        if (file_exists($this->fname)) {
            unlink($this->fname);
        }
    }

    public function testOpensLazily()
    {
        $l = new LazyOpenStream($this->fname, 'w+');
        $l->write('foo');
        $this->assertInternalType('array', $l->getMetadata());
        $this->assertFileExists($this->fname);
        $this->assertEquals('foo', file_get_contents($this->fname));
        $this->assertEquals('foo', (string) $l);
    }

    public function testProxiesToFile()
    {
        file_put_contents($this->fname, 'foo');
        $l = new LazyOpenStream($this->fname, 'r');
        $this->assertEquals('foo', $l->read(4));
        $this->assertTrue($l->eof());
        $this->assertEquals(3, $l->tell());
        $this->assertTrue($l->isReadable());
        $this->assertTrue($l->isSeekable());
        $this->assertFalse($l->isWritable());
        $l->seek(1);
        $this->assertEquals('oo', $l->getContents());
        $this->assertEquals('foo', (string) $l);
        $this->assertEquals(3, $l->getSize());
        $this->assertInternalType('array', $l->getMetadata());
        $l->close();
    }

    public function testDetachesUnderlyingStream()
    {
        file_put_contents($this->fname, 'foo');
        $l = new LazyOpenStream($this->fname, 'r');
        $r = $l->detach();
        $this->assertInternalType('resource', $r);
        fseek($r, 0);
        $this->assertEquals('foo', stream_get_contents($r));
        fclose($r);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\NoSeekStream;

/**
 * @covers GuzzleHttp\Psr7\LimitStream
 */
class LimitStreamTest extends \PHPUnit_Framework_TestCase
{
    /** @var LimitStream */
    protected $body;

    /** @var Stream */
    protected $decorated;

    public function setUp()
    {
        $this->decorated = Psr7\stream_for(fopen(__FILE__, 'r'));
        $this->body = new LimitStream($this->decorated, 10, 3);
    }

    public function testReturnsSubset()
    {
        $body = new LimitStream(Psr7\stream_for('foo'), -1, 1);
        $this->assertEquals('oo', (string) $body);
        $this->assertTrue($body->eof());
        $body->seek(0);
        $this->assertFalse($body->eof());
        $this->assertEquals('oo', $body->read(100));
        $this->assertSame('', $body->read(1));
        $this->assertTrue($body->eof());
    }

    public function testReturnsSubsetWhenCastToString()
    {
        $body = Psr7\stream_for('foo_baz_bar');
        $limited = new LimitStream($body, 3, 4);
        $this->assertEquals('baz', (string) $limited);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Unable to seek to stream position 10 with whence 0
     */
    public function testEnsuresPositionCanBeekSeekedTo()
    {
        new LimitStream(Psr7\stream_for(''), 0, 10);
    }

    public function testReturnsSubsetOfEmptyBodyWhenCastToString()
    {
        $body = Psr7\stream_for('01234567891234');
        $limited = new LimitStream($body, 0, 10);
        $this->assertEquals('', (string) $limited);
    }

    public function testReturnsSpecificSubsetOBodyWhenCastToString()
    {
        $body = Psr7\stream_for('0123456789abcdef');
        $limited = new LimitStream($body, 3, 10);
        $this->assertEquals('abc', (string) $limited);
    }

    public function testSeeksWhenConstructed()
    {
        $this->assertEquals(0, $this->body->tell());
        $this->assertEquals(3, $this->decorated->tell());
    }

    public function testAllowsBoundedSeek()
    {
        $this->body->seek(100);
        $this->assertEquals(10, $this->body->tell());
        $this->assertEquals(13, $this->decorated->tell());
        $this->body->seek(0);
        $this->assertEquals(0, $this->body->tell());
        $this->assertEquals(3, $this->decorated->tell());
        try {
            $this->body->seek(-10);
            $this->fail();
        } catch (\RuntimeException $e) {}
        $this->assertEquals(0, $this->body->tell());
        $this->assertEquals(3, $this->decorated->tell());
        $this->body->seek(5);
        $this->assertEquals(5, $this->body->tell());
        $this->assertEquals(8, $this->decorated->tell());
        // Fail
        try {
            $this->body->seek(1000, SEEK_END);
            $this->fail();
        } catch (\RuntimeException $e) {}
    }

    public function testReadsOnlySubsetOfData()
    {
        $data = $this->body->read(100);
        $this->assertEquals(10, strlen($data));
        $this->assertSame('', $this->body->read(1000));

        $this->body->setOffset(10);
        $newData = $this->body->read(100);
        $this->assertEquals(10, strlen($newData));
        $this->assertNotSame($data, $newData);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Could not seek to stream offset 2
     */
    public function testThrowsWhenCurrentGreaterThanOffsetSeek()
    {
        $a = Psr7\stream_for('foo_bar');
        $b = new NoSeekStream($a);
        $c = new LimitStream($b);
        $a->getContents();
        $c->setOffset(2);
    }

    public function testCanGetContentsWithoutSeeking()
    {
        $a = Psr7\stream_for('foo_bar');
        $b = new NoSeekStream($a);
        $c = new LimitStream($b);
        $this->assertEquals('foo_bar', $c->getContents());
    }

    public function testClaimsConsumedWhenReadLimitIsReached()
    {
        $this->assertFalse($this->body->eof());
        $this->body->read(1000);
        $this->assertTrue($this->body->eof());
    }

    public function testContentLengthIsBounded()
    {
        $this->assertEquals(10, $this->body->getSize());
    }

    public function testGetContentsIsBasedOnSubset()
    {
        $body = new LimitStream(Psr7\stream_for('foobazbar'), 3, 3);
        $this->assertEquals('baz', $body->getContents());
    }

    public function testReturnsNullIfSizeCannotBeDetermined()
    {
        $a = new FnStream([
            'getSize' => function () { return null; },
            'tell'    => function () { return 0; },
        ]);
        $b = new LimitStream($a);
        $this->assertNull($b->getSize());
    }

    public function testLengthLessOffsetWhenNoLimitSize()
    {
        $a = Psr7\stream_for('foo_bar');
        $b = new LimitStream($a, -1, 4);
        $this->assertEquals(3, $b->getSize());
    }
}
<?php
namespace GuzzleHttp\Tests;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\MultipartStream;

class MultipartStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testCreatesDefaultBoundary()
    {
        $b = new MultipartStream();
        $this->assertNotEmpty($b->getBoundary());
    }

    public function testCanProvideBoundary()
    {
        $b = new MultipartStream([], 'foo');
        $this->assertEquals('foo', $b->getBoundary());
    }

    public function testIsNotWritable()
    {
        $b = new MultipartStream();
        $this->assertFalse($b->isWritable());
    }

    public function testCanCreateEmptyStream()
    {
        $b = new MultipartStream();
        $boundary = $b->getBoundary();
        $this->assertSame("--{$boundary}--\r\n", $b->getContents());
        $this->assertSame(strlen($boundary) + 6, $b->getSize());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesFilesArrayElement()
    {
        new MultipartStream([['foo' => 'bar']]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testEnsuresFileHasName()
    {
        new MultipartStream([['contents' => 'bar']]);
    }

    public function testSerializesFields()
    {
        $b = new MultipartStream([
            [
                'name'     => 'foo',
                'contents' => 'bar'
            ],
            [
                'name' => 'baz',
                'contents' => 'bam'
            ]
        ], 'boundary');
        $this->assertEquals(
            "--boundary\r\nContent-Disposition: form-data; name=\"foo\"\r\nContent-Length: 3\r\n\r\n"
            . "bar\r\n--boundary\r\nContent-Disposition: form-data; name=\"baz\"\r\nContent-Length: 3"
            . "\r\n\r\nbam\r\n--boundary--\r\n", (string) $b);
    }

    public function testSerializesNonStringFields()
    {
        $b = new MultipartStream([
            [
                'name'     => 'int',
                'contents' => (int) 1
            ],
            [
                'name' => 'bool',
                'contents' => (boolean) false
            ],
            [
                'name' => 'bool2',
                'contents' => (boolean) true
            ],
            [
                'name' => 'float',
                'contents' => (float) 1.1
            ]
        ], 'boundary');
        $this->assertEquals(
            "--boundary\r\nContent-Disposition: form-data; name=\"int\"\r\nContent-Length: 1\r\n\r\n"
            . "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"bool\"\r\n\r\n\r\n--boundary"
            . "\r\nContent-Disposition: form-data; name=\"bool2\"\r\nContent-Length: 1\r\n\r\n"
            . "1\r\n--boundary\r\nContent-Disposition: form-data; name=\"float\"\r\nContent-Length: 3"
            . "\r\n\r\n1.1\r\n--boundary--\r\n", (string) $b);
    }

    public function testSerializesFiles()
    {
        $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
            'getMetadata' => function () {
                return '/foo/bar.txt';
            }
        ]);

        $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), [
            'getMetadata' => function () {
                return '/foo/baz.jpg';
            }
        ]);

        $f3 = Psr7\FnStream::decorate(Psr7\stream_for('bar'), [
            'getMetadata' => function () {
                return '/foo/bar.gif';
            }
        ]);

        $b = new MultipartStream([
            [
                'name'     => 'foo',
                'contents' => $f1
            ],
            [
                'name' => 'qux',
                'contents' => $f2
            ],
            [
                'name'     => 'qux',
                'contents' => $f3
            ],
        ], 'boundary');

        $expected = <<<EOT
--boundary
Content-Disposition: form-data; name="foo"; filename="bar.txt"
Content-Length: 3
Content-Type: text/plain

foo
--boundary
Content-Disposition: form-data; name="qux"; filename="baz.jpg"
Content-Length: 3
Content-Type: image/jpeg

baz
--boundary
Content-Disposition: form-data; name="qux"; filename="bar.gif"
Content-Length: 3
Content-Type: image/gif

bar
--boundary--

EOT;

        $this->assertEquals($expected, str_replace("\r", '', $b));
    }

    public function testSerializesFilesWithCustomHeaders()
    {
        $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
            'getMetadata' => function () {
                return '/foo/bar.txt';
            }
        ]);

        $b = new MultipartStream([
            [
                'name' => 'foo',
                'contents' => $f1,
                'headers'  => [
                    'x-foo' => 'bar',
                    'content-disposition' => 'custom'
                ]
            ]
        ], 'boundary');

        $expected = <<<EOT
--boundary
x-foo: bar
content-disposition: custom
Content-Length: 3
Content-Type: text/plain

foo
--boundary--

EOT;

        $this->assertEquals($expected, str_replace("\r", '', $b));
    }

    public function testSerializesFilesWithCustomHeadersAndMultipleValues()
    {
        $f1 = Psr7\FnStream::decorate(Psr7\stream_for('foo'), [
            'getMetadata' => function () {
                return '/foo/bar.txt';
            }
        ]);

        $f2 = Psr7\FnStream::decorate(Psr7\stream_for('baz'), [
            'getMetadata' => function () {
                return '/foo/baz.jpg';
            }
        ]);

        $b = new MultipartStream([
            [
                'name'     => 'foo',
                'contents' => $f1,
                'headers'  => [
                    'x-foo' => 'bar',
                    'content-disposition' => 'custom'
                ]
            ],
            [
                'name'     => 'foo',
                'contents' => $f2,
                'headers'  => ['cOntenT-Type' => 'custom'],
            ]
        ], 'boundary');

        $expected = <<<EOT
--boundary
x-foo: bar
content-disposition: custom
Content-Length: 3
Content-Type: text/plain

foo
--boundary
cOntenT-Type: custom
Content-Disposition: form-data; name="foo"; filename="baz.jpg"
Content-Length: 3

baz
--boundary--

EOT;

        $this->assertEquals($expected, str_replace("\r", '', $b));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\NoSeekStream;

/**
 * @covers GuzzleHttp\Psr7\NoSeekStream
 * @covers GuzzleHttp\Psr7\StreamDecoratorTrait
 */
class NoSeekStreamTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Cannot seek a NoSeekStream
     */
    public function testCannotSeek()
    {
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isSeekable', 'seek'])
            ->getMockForAbstractClass();
        $s->expects($this->never())->method('seek');
        $s->expects($this->never())->method('isSeekable');
        $wrapped = new NoSeekStream($s);
        $this->assertFalse($wrapped->isSeekable());
        $wrapped->seek(2);
    }

    /**
     * @expectedException \RuntimeException
     * @expectedExceptionMessage Cannot write to a non-writable stream
     */
    public function testHandlesClose()
    {
        $s = Psr7\stream_for('foo');
        $wrapped = new NoSeekStream($s);
        $wrapped->close();
        $wrapped->write('foo');
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\PumpStream;
use GuzzleHttp\Psr7;

class PumpStreamTest extends \PHPUnit_Framework_TestCase
{
    public function testHasMetadataAndSize()
    {
        $p = new PumpStream(function () {}, [
            'metadata' => ['foo' => 'bar'],
            'size'     => 100
        ]);

        $this->assertEquals('bar', $p->getMetadata('foo'));
        $this->assertEquals(['foo' => 'bar'], $p->getMetadata());
        $this->assertEquals(100, $p->getSize());
    }

    public function testCanReadFromCallable()
    {
        $p = Psr7\stream_for(function ($size) {
            return 'a';
        });
        $this->assertEquals('a', $p->read(1));
        $this->assertEquals(1, $p->tell());
        $this->assertEquals('aaaaa', $p->read(5));
        $this->assertEquals(6, $p->tell());
    }

    public function testStoresExcessDataInBuffer()
    {
        $called = [];
        $p = Psr7\stream_for(function ($size) use (&$called) {
            $called[] = $size;
            return 'abcdef';
        });
        $this->assertEquals('a', $p->read(1));
        $this->assertEquals('b', $p->read(1));
        $this->assertEquals('cdef', $p->read(4));
        $this->assertEquals('abcdefabc', $p->read(9));
        $this->assertEquals([1, 9, 3], $called);
    }

    public function testInifiniteStreamWrappedInLimitStream()
    {
        $p = Psr7\stream_for(function () { return 'a'; });
        $s = new LimitStream($p, 5);
        $this->assertEquals('aaaaa', (string) $s);
    }

    public function testDescribesCapabilities()
    {
        $p = Psr7\stream_for(function () {});
        $this->assertTrue($p->isReadable());
        $this->assertFalse($p->isSeekable());
        $this->assertFalse($p->isWritable());
        $this->assertNull($p->getSize());
        $this->assertEquals('', $p->getContents());
        $this->assertEquals('', (string) $p);
        $p->close();
        $this->assertEquals('', $p->read(10));
        $this->assertTrue($p->eof());

        try {
            $this->assertFalse($p->write('aa'));
            $this->fail();
        } catch (\RuntimeException $e) {}
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;

/**
 * @covers GuzzleHttp\Psr7\Request
 */
class RequestTest extends \PHPUnit_Framework_TestCase
{
    public function testRequestUriMayBeString()
    {
        $r = new Request('GET', '/');
        $this->assertEquals('/', (string) $r->getUri());
    }

    public function testRequestUriMayBeUri()
    {
        $uri = new Uri('/');
        $r = new Request('GET', $uri);
        $this->assertSame($uri, $r->getUri());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidateRequestUri()
    {
        new Request('GET', '///');
    }

    public function testCanConstructWithBody()
    {
        $r = new Request('GET', '/', [], 'baz');
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertEquals('baz', (string) $r->getBody());
    }

    public function testNullBody()
    {
        $r = new Request('GET', '/', [], null);
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('', (string) $r->getBody());
    }

    public function testFalseyBody()
    {
        $r = new Request('GET', '/', [], '0');
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('0', (string) $r->getBody());
    }

    public function testConstructorDoesNotReadStreamBody()
    {
        $streamIsRead = false;
        $body = Psr7\FnStream::decorate(Psr7\stream_for(''), [
            '__toString' => function () use (&$streamIsRead) {
                $streamIsRead = true;
                return '';
            }
        ]);

        $r = new Request('GET', '/', [], $body);
        $this->assertFalse($streamIsRead);
        $this->assertSame($body, $r->getBody());
    }

    public function testCapitalizesMethod()
    {
        $r = new Request('get', '/');
        $this->assertEquals('GET', $r->getMethod());
    }

    public function testCapitalizesWithMethod()
    {
        $r = new Request('GET', '/');
        $this->assertEquals('PUT', $r->withMethod('put')->getMethod());
    }

    public function testWithUri()
    {
        $r1 = new Request('GET', '/');
        $u1 = $r1->getUri();
        $u2 = new Uri('http://www.example.com');
        $r2 = $r1->withUri($u2);
        $this->assertNotSame($r1, $r2);
        $this->assertSame($u2, $r2->getUri());
        $this->assertSame($u1, $r1->getUri());
    }

    public function testSameInstanceWhenSameUri()
    {
        $r1 = new Request('GET', 'http://foo.com');
        $r2 = $r1->withUri($r1->getUri());
        $this->assertSame($r1, $r2);
    }

    public function testWithRequestTarget()
    {
        $r1 = new Request('GET', '/');
        $r2 = $r1->withRequestTarget('*');
        $this->assertEquals('*', $r2->getRequestTarget());
        $this->assertEquals('/', $r1->getRequestTarget());
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testRequestTargetDoesNotAllowSpaces()
    {
        $r1 = new Request('GET', '/');
        $r1->withRequestTarget('/foo bar');
    }

    public function testRequestTargetDefaultsToSlash()
    {
        $r1 = new Request('GET', '');
        $this->assertEquals('/', $r1->getRequestTarget());
        $r2 = new Request('GET', '*');
        $this->assertEquals('*', $r2->getRequestTarget());
        $r3 = new Request('GET', 'http://foo.com/bar baz/');
        $this->assertEquals('/bar%20baz/', $r3->getRequestTarget());
    }

    public function testBuildsRequestTarget()
    {
        $r1 = new Request('GET', 'http://foo.com/baz?bar=bam');
        $this->assertEquals('/baz?bar=bam', $r1->getRequestTarget());
    }

    public function testBuildsRequestTargetWithFalseyQuery()
    {
        $r1 = new Request('GET', 'http://foo.com/baz?0');
        $this->assertEquals('/baz?0', $r1->getRequestTarget());
    }

    public function testHostIsAddedFirst()
    {
        $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Foo' => 'Bar']);
        $this->assertEquals([
            'Host' => ['foo.com'],
            'Foo'  => ['Bar']
        ], $r->getHeaders());
    }

    public function testCanGetHeaderAsCsv()
    {
        $r = new Request('GET', 'http://foo.com/baz?bar=bam', [
            'Foo' => ['a', 'b', 'c']
        ]);
        $this->assertEquals('a, b, c', $r->getHeaderLine('Foo'));
        $this->assertEquals('', $r->getHeaderLine('Bar'));
    }

    public function testHostIsNotOverwrittenWhenPreservingHost()
    {
        $r = new Request('GET', 'http://foo.com/baz?bar=bam', ['Host' => 'a.com']);
        $this->assertEquals(['Host' => ['a.com']], $r->getHeaders());
        $r2 = $r->withUri(new Uri('http://www.foo.com/bar'), true);
        $this->assertEquals('a.com', $r2->getHeaderLine('Host'));
    }

    public function testOverridesHostWithUri()
    {
        $r = new Request('GET', 'http://foo.com/baz?bar=bam');
        $this->assertEquals(['Host' => ['foo.com']], $r->getHeaders());
        $r2 = $r->withUri(new Uri('http://www.baz.com/bar'));
        $this->assertEquals('www.baz.com', $r2->getHeaderLine('Host'));
    }

    public function testAggregatesHeaders()
    {
        $r = new Request('GET', '', [
            'ZOO' => 'zoobar',
            'zoo' => ['foobar', 'zoobar']
        ]);
        $this->assertEquals(['ZOO' => ['zoobar', 'foobar', 'zoobar']], $r->getHeaders());
        $this->assertEquals('zoobar, foobar, zoobar', $r->getHeaderLine('zoo'));
    }

    public function testAddsPortToHeader()
    {
        $r = new Request('GET', 'http://foo.com:8124/bar');
        $this->assertEquals('foo.com:8124', $r->getHeaderLine('host'));
    }

    public function testAddsPortToHeaderAndReplacePreviousPort()
    {
        $r = new Request('GET', 'http://foo.com:8124/bar');
        $r = $r->withUri(new Uri('http://foo.com:8125/bar'));
        $this->assertEquals('foo.com:8125', $r->getHeaderLine('host'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Response;

/**
 * @covers GuzzleHttp\Psr7\MessageTrait
 * @covers GuzzleHttp\Psr7\Response
 */
class ResponseTest extends \PHPUnit_Framework_TestCase
{
    public function testDefaultConstructor()
    {
        $r = new Response();
        $this->assertSame(200, $r->getStatusCode());
        $this->assertSame('1.1', $r->getProtocolVersion());
        $this->assertSame('OK', $r->getReasonPhrase());
        $this->assertSame([], $r->getHeaders());
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('', (string) $r->getBody());
    }

    public function testCanConstructWithStatusCode()
    {
        $r = new Response(404);
        $this->assertSame(404, $r->getStatusCode());
        $this->assertSame('Not Found', $r->getReasonPhrase());
    }

    public function testConstructorDoesNotReadStreamBody()
    {
        $streamIsRead = false;
        $body = Psr7\FnStream::decorate(Psr7\stream_for(''), [
            '__toString' => function () use (&$streamIsRead) {
                $streamIsRead = true;
                return '';
            }
        ]);

        $r = new Response(200, [], $body);
        $this->assertFalse($streamIsRead);
        $this->assertSame($body, $r->getBody());
    }

    public function testStatusCanBeNumericString()
    {
        $r = new Response('404');
        $r2 = $r->withStatus('201');
        $this->assertSame(404, $r->getStatusCode());
        $this->assertSame('Not Found', $r->getReasonPhrase());
        $this->assertSame(201, $r2->getStatusCode());
        $this->assertSame('Created', $r2->getReasonPhrase());
    }

    public function testCanConstructWithHeaders()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame('Bar', $r->getHeaderLine('Foo'));
        $this->assertSame(['Bar'], $r->getHeader('Foo'));
    }

    public function testCanConstructWithHeadersAsArray()
    {
        $r = new Response(200, [
            'Foo' => ['baz', 'bar']
        ]);
        $this->assertSame(['Foo' => ['baz', 'bar']], $r->getHeaders());
        $this->assertSame('baz, bar', $r->getHeaderLine('Foo'));
        $this->assertSame(['baz', 'bar'], $r->getHeader('Foo'));
    }

    public function testCanConstructWithBody()
    {
        $r = new Response(200, [], 'baz');
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('baz', (string) $r->getBody());
    }

    public function testNullBody()
    {
        $r = new Response(200, [], null);
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('', (string) $r->getBody());
    }

    public function testFalseyBody()
    {
        $r = new Response(200, [], '0');
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('0', (string) $r->getBody());
    }

    public function testCanConstructWithReason()
    {
        $r = new Response(200, [], null, '1.1', 'bar');
        $this->assertSame('bar', $r->getReasonPhrase());

        $r = new Response(200, [], null, '1.1', '0');
        $this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works');
    }

    public function testCanConstructWithProtocolVersion()
    {
        $r = new Response(200, [], null, '1000');
        $this->assertSame('1000', $r->getProtocolVersion());
    }

    public function testWithStatusCodeAndNoReason()
    {
        $r = (new Response())->withStatus(201);
        $this->assertSame(201, $r->getStatusCode());
        $this->assertSame('Created', $r->getReasonPhrase());
    }

    public function testWithStatusCodeAndReason()
    {
        $r = (new Response())->withStatus(201, 'Foo');
        $this->assertSame(201, $r->getStatusCode());
        $this->assertSame('Foo', $r->getReasonPhrase());

        $r = (new Response())->withStatus(201, '0');
        $this->assertSame(201, $r->getStatusCode());
        $this->assertSame('0', $r->getReasonPhrase(), 'Falsey reason works');
    }

    public function testWithProtocolVersion()
    {
        $r = (new Response())->withProtocolVersion('1000');
        $this->assertSame('1000', $r->getProtocolVersion());
    }

    public function testSameInstanceWhenSameProtocol()
    {
        $r = new Response();
        $this->assertSame($r, $r->withProtocolVersion('1.1'));
    }

    public function testWithBody()
    {
        $b = Psr7\stream_for('0');
        $r = (new Response())->withBody($b);
        $this->assertInstanceOf('Psr\Http\Message\StreamInterface', $r->getBody());
        $this->assertSame('0', (string) $r->getBody());
    }

    public function testSameInstanceWhenSameBody()
    {
        $r = new Response();
        $b = $r->getBody();
        $this->assertSame($r, $r->withBody($b));
    }

    public function testWithHeader()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withHeader('baZ', 'Bam');
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam']], $r2->getHeaders());
        $this->assertSame('Bam', $r2->getHeaderLine('baz'));
        $this->assertSame(['Bam'], $r2->getHeader('baz'));
    }

    public function testWithHeaderAsArray()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withHeader('baZ', ['Bam', 'Bar']);
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar'], 'baZ' => ['Bam', 'Bar']], $r2->getHeaders());
        $this->assertSame('Bam, Bar', $r2->getHeaderLine('baz'));
        $this->assertSame(['Bam', 'Bar'], $r2->getHeader('baz'));
    }

    public function testWithHeaderReplacesDifferentCase()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withHeader('foO', 'Bam');
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['foO' => ['Bam']], $r2->getHeaders());
        $this->assertSame('Bam', $r2->getHeaderLine('foo'));
        $this->assertSame(['Bam'], $r2->getHeader('foo'));
    }

    public function testWithAddedHeader()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withAddedHeader('foO', 'Baz');
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar', 'Baz']], $r2->getHeaders());
        $this->assertSame('Bar, Baz', $r2->getHeaderLine('foo'));
        $this->assertSame(['Bar', 'Baz'], $r2->getHeader('foo'));
    }

    public function testWithAddedHeaderAsArray()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withAddedHeader('foO', ['Baz', 'Bam']);
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar', 'Baz', 'Bam']], $r2->getHeaders());
        $this->assertSame('Bar, Baz, Bam', $r2->getHeaderLine('foo'));
        $this->assertSame(['Bar', 'Baz', 'Bam'], $r2->getHeader('foo'));
    }

    public function testWithAddedHeaderThatDoesNotExist()
    {
        $r = new Response(200, ['Foo' => 'Bar']);
        $r2 = $r->withAddedHeader('nEw', 'Baz');
        $this->assertSame(['Foo' => ['Bar']], $r->getHeaders());
        $this->assertSame(['Foo' => ['Bar'], 'nEw' => ['Baz']], $r2->getHeaders());
        $this->assertSame('Baz', $r2->getHeaderLine('new'));
        $this->assertSame(['Baz'], $r2->getHeader('new'));
    }

    public function testWithoutHeaderThatExists()
    {
        $r = new Response(200, ['Foo' => 'Bar', 'Baz' => 'Bam']);
        $r2 = $r->withoutHeader('foO');
        $this->assertTrue($r->hasHeader('foo'));
        $this->assertSame(['Foo' => ['Bar'], 'Baz' => ['Bam']], $r->getHeaders());
        $this->assertFalse($r2->hasHeader('foo'));
        $this->assertSame(['Baz' => ['Bam']], $r2->getHeaders());
    }

    public function testWithoutHeaderThatDoesNotExist()
    {
        $r = new Response(200, ['Baz' => 'Bam']);
        $r2 = $r->withoutHeader('foO');
        $this->assertSame($r, $r2);
        $this->assertFalse($r2->hasHeader('foo'));
        $this->assertSame(['Baz' => ['Bam']], $r2->getHeaders());
    }

    public function testSameInstanceWhenRemovingMissingHeader()
    {
        $r = new Response();
        $this->assertSame($r, $r->withoutHeader('foo'));
    }

    public function testHeaderValuesAreTrimmed()
    {
        $r1 = new Response(200, ['OWS' => " \t \tFoo\t \t "]);
        $r2 = (new Response())->withHeader('OWS', " \t \tFoo\t \t ");
        $r3 = (new Response())->withAddedHeader('OWS', " \t \tFoo\t \t ");;

        foreach ([$r1, $r2, $r3] as $r) {
            $this->assertSame(['OWS' => ['Foo']], $r->getHeaders());
            $this->assertSame('Foo', $r->getHeaderLine('OWS'));
            $this->assertSame(['Foo'], $r->getHeader('OWS'));
        }
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\ServerRequest;
use GuzzleHttp\Psr7\UploadedFile;
use GuzzleHttp\Psr7\Uri;

/**
 * @covers GuzzleHttp\Psr7\ServerRequest
 */
class ServerRequestTest extends \PHPUnit_Framework_TestCase
{
    public function dataNormalizeFiles()
    {
        return [
            'Single file' => [
                [
                    'file' => [
                        'name' => 'MyFile.txt',
                        'type' => 'text/plain',
                        'tmp_name' => '/tmp/php/php1h4j1o',
                        'error' => '0',
                        'size' => '123'
                    ]
                ],
                [
                    'file' => new UploadedFile(
                        '/tmp/php/php1h4j1o',
                        123,
                        UPLOAD_ERR_OK,
                        'MyFile.txt',
                        'text/plain'
                    )
                ]
            ],
            'Empty file' => [
                [
                    'image_file' => [
                        'name' => '',
                        'type' => '',
                        'tmp_name' => '',
                        'error' => '4',
                        'size' => '0'
                    ]
                ],
                [
                    'image_file' => new UploadedFile(
                        '',
                        0,
                        UPLOAD_ERR_NO_FILE,
                        '',
                        ''
                    )
                ]
            ],
            'Already Converted' => [
                [
                    'file' => new UploadedFile(
                        '/tmp/php/php1h4j1o',
                        123,
                        UPLOAD_ERR_OK,
                        'MyFile.txt',
                        'text/plain'
                    )
                ],
                [
                    'file' => new UploadedFile(
                        '/tmp/php/php1h4j1o',
                        123,
                        UPLOAD_ERR_OK,
                        'MyFile.txt',
                        'text/plain'
                    )
                ]
            ],
            'Already Converted array' => [
                [
                    'file' => [
                        new UploadedFile(
                            '/tmp/php/php1h4j1o',
                            123,
                            UPLOAD_ERR_OK,
                            'MyFile.txt',
                            'text/plain'
                        ),
                        new UploadedFile(
                            '',
                            0,
                            UPLOAD_ERR_NO_FILE,
                            '',
                            ''
                        )
                    ],
                ],
                [
                    'file' => [
                        new UploadedFile(
                            '/tmp/php/php1h4j1o',
                            123,
                            UPLOAD_ERR_OK,
                            'MyFile.txt',
                            'text/plain'
                        ),
                        new UploadedFile(
                            '',
                            0,
                            UPLOAD_ERR_NO_FILE,
                            '',
                            ''
                        )
                    ],
                ]
            ],
            'Multiple files' => [
                [
                    'text_file' => [
                        'name' => 'MyFile.txt',
                        'type' => 'text/plain',
                        'tmp_name' => '/tmp/php/php1h4j1o',
                        'error' => '0',
                        'size' => '123'
                    ],
                    'image_file' => [
                        'name' => '',
                        'type' => '',
                        'tmp_name' => '',
                        'error' => '4',
                        'size' => '0'
                    ]
                ],
                [
                    'text_file' => new UploadedFile(
                        '/tmp/php/php1h4j1o',
                        123,
                        UPLOAD_ERR_OK,
                        'MyFile.txt',
                        'text/plain'
                    ),
                    'image_file' => new UploadedFile(
                        '',
                        0,
                        UPLOAD_ERR_NO_FILE,
                        '',
                        ''
                    )
                ]
            ],
            'Nested files' => [
                [
                    'file' => [
                        'name' => [
                            0 => 'MyFile.txt',
                            1 => 'Image.png',
                        ],
                        'type' => [
                            0 => 'text/plain',
                            1 => 'image/png',
                        ],
                        'tmp_name' => [
                            0 => '/tmp/php/hp9hskjhf',
                            1 => '/tmp/php/php1h4j1o',
                        ],
                        'error' => [
                            0 => '0',
                            1 => '0',
                        ],
                        'size' => [
                            0 => '123',
                            1 => '7349',
                        ],
                    ],
                    'nested' => [
                        'name' => [
                            'other' => 'Flag.txt',
                            'test' => [
                                0 => 'Stuff.txt',
                                1 => '',
                            ],
                        ],
                        'type' => [
                            'other' => 'text/plain',
                            'test' => [
                                0 => 'text/plain',
                                1 => '',
                            ],
                        ],
                        'tmp_name' => [
                            'other' => '/tmp/php/hp9hskjhf',
                            'test' => [
                                0 => '/tmp/php/asifu2gp3',
                                1 => '',
                            ],
                        ],
                        'error' => [
                            'other' => '0',
                            'test' => [
                                0 => '0',
                                1 => '4',
                            ],
                        ],
                        'size' => [
                            'other' => '421',
                            'test' => [
                                0 => '32',
                                1 => '0',
                            ]
                        ]
                    ],
                ],
                [
                    'file' => [
                        0 => new UploadedFile(
                            '/tmp/php/hp9hskjhf',
                            123,
                            UPLOAD_ERR_OK,
                            'MyFile.txt',
                            'text/plain'
                        ),
                        1 => new UploadedFile(
                            '/tmp/php/php1h4j1o',
                            7349,
                            UPLOAD_ERR_OK,
                            'Image.png',
                            'image/png'
                        ),
                    ],
                    'nested' => [
                        'other' => new UploadedFile(
                            '/tmp/php/hp9hskjhf',
                            421,
                            UPLOAD_ERR_OK,
                            'Flag.txt',
                            'text/plain'
                        ),
                        'test' => [
                            0 => new UploadedFile(
                                '/tmp/php/asifu2gp3',
                                32,
                                UPLOAD_ERR_OK,
                                'Stuff.txt',
                                'text/plain'
                            ),
                            1 => new UploadedFile(
                                '',
                                0,
                                UPLOAD_ERR_NO_FILE,
                                '',
                                ''
                            ),
                        ]
                    ]
                ]
            ]
        ];
    }

    /**
     * @dataProvider dataNormalizeFiles
     */
    public function testNormalizeFiles($files, $expected)
    {
        $result = ServerRequest::normalizeFiles($files);

        $this->assertEquals($expected, $result);
    }

    public function testNormalizeFilesRaisesException()
    {
        $this->setExpectedException('InvalidArgumentException', 'Invalid value in files specification');

        ServerRequest::normalizeFiles(['test' => 'something']);
    }

    public function dataGetUriFromGlobals()
    {
        $server = [
            'PHP_SELF' => '/blog/article.php',
            'GATEWAY_INTERFACE' => 'CGI/1.1',
            'SERVER_ADDR' => 'Server IP: 217.112.82.20',
            'SERVER_NAME' => 'www.blakesimpson.co.uk',
            'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/5.2.13',
            'SERVER_PROTOCOL' => 'HTTP/1.0',
            'REQUEST_METHOD' => 'POST',
            'REQUEST_TIME' => 'Request start time: 1280149029',
            'QUERY_STRING' => 'id=10&user=foo',
            'DOCUMENT_ROOT' => '/path/to/your/server/root/',
            'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
            'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
            'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5',
            'HTTP_CONNECTION' => 'keep-alive',
            'HTTP_HOST' => 'www.blakesimpson.co.uk',
            'HTTP_REFERER' => 'http://previous.url.com',
            'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)',
            'HTTPS' => '1',
            'REMOTE_ADDR' => '193.60.168.69',
            'REMOTE_HOST' => 'Client server\'s host name',
            'REMOTE_PORT' => '5390',
            'SCRIPT_FILENAME' => '/path/to/this/script.php',
            'SERVER_ADMIN' => 'webmaster@blakesimpson.co.uk',
            'SERVER_PORT' => '80',
            'SERVER_SIGNATURE' => 'Version signature: 5.123',
            'SCRIPT_NAME' => '/blog/article.php',
            'REQUEST_URI' => '/blog/article.php?id=10&user=foo',
        ];

        return [
            'Normal request' => [
                'http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
                $server,
            ],
            'Secure request' => [
                'https://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
                array_merge($server, ['HTTPS' => 'on', 'SERVER_PORT' => '443']),
            ],
            'HTTP_HOST missing' => [
                'http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo',
                array_merge($server, ['HTTP_HOST' => null]),
            ],
            'No query String' => [
                'http://www.blakesimpson.co.uk/blog/article.php',
                array_merge($server, ['REQUEST_URI' => '/blog/article.php', 'QUERY_STRING' => '']),
            ],
            'Different port' => [
                'http://www.blakesimpson.co.uk:8324/blog/article.php?id=10&user=foo',
                array_merge($server, ['SERVER_PORT' => '8324']),
            ],
            'Empty server variable' => [
                '',
                [],
            ],
        ];
    }

    /**
     * @dataProvider dataGetUriFromGlobals
     */
    public function testGetUriFromGlobals($expected, $serverParams)
    {
        $_SERVER = $serverParams;

        $this->assertEquals(new Uri($expected), ServerRequest::getUriFromGlobals());
    }

    public function testFromGlobals()
    {
        $_SERVER = [
            'PHP_SELF' => '/blog/article.php',
            'GATEWAY_INTERFACE' => 'CGI/1.1',
            'SERVER_ADDR' => 'Server IP: 217.112.82.20',
            'SERVER_NAME' => 'www.blakesimpson.co.uk',
            'SERVER_SOFTWARE' => 'Apache/2.2.15 (Win32) JRun/4.0 PHP/5.2.13',
            'SERVER_PROTOCOL' => 'HTTP/1.0',
            'REQUEST_METHOD' => 'POST',
            'REQUEST_TIME' => 'Request start time: 1280149029',
            'QUERY_STRING' => 'id=10&user=foo',
            'DOCUMENT_ROOT' => '/path/to/your/server/root/',
            'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
            'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
            'HTTP_ACCEPT_LANGUAGE' => 'en-gb,en;q=0.5',
            'HTTP_CONNECTION' => 'keep-alive',
            'HTTP_HOST' => 'www.blakesimpson.co.uk',
            'HTTP_REFERER' => 'http://previous.url.com',
            'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)',
            'HTTPS' => '1',
            'REMOTE_ADDR' => '193.60.168.69',
            'REMOTE_HOST' => 'Client server\'s host name',
            'REMOTE_PORT' => '5390',
            'SCRIPT_FILENAME' => '/path/to/this/script.php',
            'SERVER_ADMIN' => 'webmaster@blakesimpson.co.uk',
            'SERVER_PORT' => '80',
            'SERVER_SIGNATURE' => 'Version signature: 5.123',
            'SCRIPT_NAME' => '/blog/article.php',
            'REQUEST_URI' => '/blog/article.php?id=10&user=foo',
        ];

        $_COOKIE = [
            'logged-in' => 'yes!'
        ];

        $_POST = [
            'name' => 'Pesho',
            'email' => 'pesho@example.com',
        ];

        $_GET = [
            'id' => 10,
            'user' => 'foo',
        ];

        $_FILES = [
            'file' => [
                'name' => 'MyFile.txt',
                'type' => 'text/plain',
                'tmp_name' => '/tmp/php/php1h4j1o',
                'error' => UPLOAD_ERR_OK,
                'size' => 123,
            ]
        ];

        $server = ServerRequest::fromGlobals();

        $this->assertEquals('POST', $server->getMethod());
        $this->assertEquals(['Host' => ['www.blakesimpson.co.uk']], $server->getHeaders());
        $this->assertEquals('', (string) $server->getBody());
        $this->assertEquals('1.0', $server->getProtocolVersion());
        $this->assertEquals($_COOKIE, $server->getCookieParams());
        $this->assertEquals($_POST, $server->getParsedBody());
        $this->assertEquals($_GET, $server->getQueryParams());

        $this->assertEquals(
            new Uri('http://www.blakesimpson.co.uk/blog/article.php?id=10&user=foo'),
            $server->getUri()
        );

        $expectedFiles = [
            'file' => new UploadedFile(
                '/tmp/php/php1h4j1o',
                123,
                UPLOAD_ERR_OK,
                'MyFile.txt',
                'text/plain'
            ),
        ];

        $this->assertEquals($expectedFiles, $server->getUploadedFiles());
    }

    public function testUploadedFiles()
    {
        $request1 = new ServerRequest('GET', '/');

        $files = [
            'file' => new UploadedFile('test', 123, UPLOAD_ERR_OK)
        ];

        $request2 = $request1->withUploadedFiles($files);

        $this->assertNotSame($request2, $request1);
        $this->assertSame([], $request1->getUploadedFiles());
        $this->assertSame($files, $request2->getUploadedFiles());
    }

    public function testServerParams()
    {
        $params = ['name' => 'value'];

        $request = new ServerRequest('GET', '/', [], null, '1.1', $params);
        $this->assertSame($params, $request->getServerParams());
    }

    public function testCookieParams()
    {
        $request1 = new ServerRequest('GET', '/');

        $params = ['name' => 'value'];

        $request2 = $request1->withCookieParams($params);

        $this->assertNotSame($request2, $request1);
        $this->assertEmpty($request1->getCookieParams());
        $this->assertSame($params, $request2->getCookieParams());
    }

    public function testQueryParams()
    {
        $request1 = new ServerRequest('GET', '/');

        $params = ['name' => 'value'];

        $request2 = $request1->withQueryParams($params);

        $this->assertNotSame($request2, $request1);
        $this->assertEmpty($request1->getQueryParams());
        $this->assertSame($params, $request2->getQueryParams());
    }

    public function testParsedBody()
    {
        $request1 = new ServerRequest('GET', '/');

        $params = ['name' => 'value'];

        $request2 = $request1->withParsedBody($params);

        $this->assertNotSame($request2, $request1);
        $this->assertEmpty($request1->getParsedBody());
        $this->assertSame($params, $request2->getParsedBody());
    }

    public function testAttributes()
    {
        $request1 = new ServerRequest('GET', '/');

        $request2 = $request1->withAttribute('name', 'value');
        $request3 = $request2->withAttribute('other', 'otherValue');
        $request4 = $request3->withoutAttribute('other');
        $request5 = $request3->withoutAttribute('unknown');

        $this->assertNotSame($request2, $request1);
        $this->assertNotSame($request3, $request2);
        $this->assertNotSame($request4, $request3);
        $this->assertNotSame($request5, $request4);

        $this->assertEmpty($request1->getAttributes());
        $this->assertEmpty($request1->getAttribute('name'));
        $this->assertEquals(
            'something',
            $request1->getAttribute('name', 'something'),
            'Should return the default value'
        );

        $this->assertEquals('value', $request2->getAttribute('name'));
        $this->assertEquals(['name' => 'value'], $request2->getAttributes());
        $this->assertEquals(['name' => 'value', 'other' => 'otherValue'], $request3->getAttributes());
        $this->assertEquals(['name' => 'value'], $request4->getAttributes());
    }

    public function testNullAttribute()
    {
        $request = (new ServerRequest('GET', '/'))->withAttribute('name', null);

        $this->assertSame(['name' => null], $request->getAttributes());
        $this->assertNull($request->getAttribute('name', 'different-default'));

        $requestWithoutAttribute = $request->withoutAttribute('name');

        $this->assertSame([], $requestWithoutAttribute->getAttributes());
        $this->assertSame('different-default', $requestWithoutAttribute->getAttribute('name', 'different-default'));
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use Psr\Http\Message\StreamInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\StreamDecoratorTrait;

class Str implements StreamInterface
{
    use StreamDecoratorTrait;
}

/**
 * @covers GuzzleHttp\Psr7\StreamDecoratorTrait
 */
class StreamDecoratorTraitTest extends \PHPUnit_Framework_TestCase
{
    private $a;
    private $b;
    private $c;

    public function setUp()
    {
        $this->c = fopen('php://temp', 'r+');
        fwrite($this->c, 'foo');
        fseek($this->c, 0);
        $this->a = Psr7\stream_for($this->c);
        $this->b = new Str($this->a);
    }

    public function testCatchesExceptionsWhenCastingToString()
    {
        $s = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['read'])
            ->getMockForAbstractClass();
        $s->expects($this->once())
            ->method('read')
            ->will($this->throwException(new \Exception('foo')));
        $msg = '';
        set_error_handler(function ($errNo, $str) use (&$msg) { $msg = $str; });
        echo new Str($s);
        restore_error_handler();
        $this->assertContains('foo', $msg);
    }

    public function testToString()
    {
        $this->assertEquals('foo', (string) $this->b);
    }

    public function testHasSize()
    {
        $this->assertEquals(3, $this->b->getSize());
    }

    public function testReads()
    {
        $this->assertEquals('foo', $this->b->read(10));
    }

    public function testCheckMethods()
    {
        $this->assertEquals($this->a->isReadable(), $this->b->isReadable());
        $this->assertEquals($this->a->isWritable(), $this->b->isWritable());
        $this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
    }

    public function testSeeksAndTells()
    {
        $this->b->seek(1);
        $this->assertEquals(1, $this->a->tell());
        $this->assertEquals(1, $this->b->tell());
        $this->b->seek(0);
        $this->assertEquals(0, $this->a->tell());
        $this->assertEquals(0, $this->b->tell());
        $this->b->seek(0, SEEK_END);
        $this->assertEquals(3, $this->a->tell());
        $this->assertEquals(3, $this->b->tell());
    }

    public function testGetsContents()
    {
        $this->assertEquals('foo', $this->b->getContents());
        $this->assertEquals('', $this->b->getContents());
        $this->b->seek(1);
        $this->assertEquals('oo', $this->b->getContents(1));
    }

    public function testCloses()
    {
        $this->b->close();
        $this->assertFalse(is_resource($this->c));
    }

    public function testDetaches()
    {
        $this->b->detach();
        $this->assertFalse($this->b->isReadable());
    }

    public function testWrapsMetadata()
    {
        $this->assertSame($this->b->getMetadata(), $this->a->getMetadata());
        $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri'));
    }

    public function testWrapsWrites()
    {
        $this->b->seek(0, SEEK_END);
        $this->b->write('foo');
        $this->assertEquals('foofoo', (string) $this->a);
    }

    /**
     * @expectedException \UnexpectedValueException
     */
    public function testThrowsWithInvalidGetter()
    {
        $this->b->foo;
    }

    /**
     * @expectedException \BadMethodCallException
     */
    public function testThrowsWhenGetterNotImplemented()
    {
        $s = new BadStream();
        $s->stream;
    }
}

class BadStream
{
    use StreamDecoratorTrait;

    public function __construct() {}
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\NoSeekStream;
use GuzzleHttp\Psr7\Stream;

/**
 * @covers GuzzleHttp\Psr7\Stream
 */
class StreamTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \InvalidArgumentException
     */
    public function testConstructorThrowsExceptionOnInvalidArgument()
    {
        new Stream(true);
    }

    public function testConstructorInitializesProperties()
    {
        $handle = fopen('php://temp', 'r+');
        fwrite($handle, 'data');
        $stream = new Stream($handle);
        $this->assertTrue($stream->isReadable());
        $this->assertTrue($stream->isWritable());
        $this->assertTrue($stream->isSeekable());
        $this->assertEquals('php://temp', $stream->getMetadata('uri'));
        $this->assertInternalType('array', $stream->getMetadata());
        $this->assertEquals(4, $stream->getSize());
        $this->assertFalse($stream->eof());
        $stream->close();
    }

    public function testStreamClosesHandleOnDestruct()
    {
        $handle = fopen('php://temp', 'r');
        $stream = new Stream($handle);
        unset($stream);
        $this->assertFalse(is_resource($handle));
    }

    public function testConvertsToString()
    {
        $handle = fopen('php://temp', 'w+');
        fwrite($handle, 'data');
        $stream = new Stream($handle);
        $this->assertEquals('data', (string) $stream);
        $this->assertEquals('data', (string) $stream);
        $stream->close();
    }

    public function testGetsContents()
    {
        $handle = fopen('php://temp', 'w+');
        fwrite($handle, 'data');
        $stream = new Stream($handle);
        $this->assertEquals('', $stream->getContents());
        $stream->seek(0);
        $this->assertEquals('data', $stream->getContents());
        $this->assertEquals('', $stream->getContents());
    }

    public function testChecksEof()
    {
        $handle = fopen('php://temp', 'w+');
        fwrite($handle, 'data');
        $stream = new Stream($handle);
        $this->assertFalse($stream->eof());
        $stream->read(4);
        $this->assertTrue($stream->eof());
        $stream->close();
    }

    public function testGetSize()
    {
        $size = filesize(__FILE__);
        $handle = fopen(__FILE__, 'r');
        $stream = new Stream($handle);
        $this->assertEquals($size, $stream->getSize());
        // Load from cache
        $this->assertEquals($size, $stream->getSize());
        $stream->close();
    }

    public function testEnsuresSizeIsConsistent()
    {
        $h = fopen('php://temp', 'w+');
        $this->assertEquals(3, fwrite($h, 'foo'));
        $stream = new Stream($h);
        $this->assertEquals(3, $stream->getSize());
        $this->assertEquals(4, $stream->write('test'));
        $this->assertEquals(7, $stream->getSize());
        $this->assertEquals(7, $stream->getSize());
        $stream->close();
    }

    public function testProvidesStreamPosition()
    {
        $handle = fopen('php://temp', 'w+');
        $stream = new Stream($handle);
        $this->assertEquals(0, $stream->tell());
        $stream->write('foo');
        $this->assertEquals(3, $stream->tell());
        $stream->seek(1);
        $this->assertEquals(1, $stream->tell());
        $this->assertSame(ftell($handle), $stream->tell());
        $stream->close();
    }

    public function testCanDetachStream()
    {
        $r = fopen('php://temp', 'w+');
        $stream = new Stream($r);
        $stream->write('foo');
        $this->assertTrue($stream->isReadable());
        $this->assertSame($r, $stream->detach());
        $stream->detach();

        $this->assertFalse($stream->isReadable());
        $this->assertFalse($stream->isWritable());
        $this->assertFalse($stream->isSeekable());

        $throws = function (callable $fn) use ($stream) {
            try {
                $fn($stream);
                $this->fail();
            } catch (\Exception $e) {}
        };

        $throws(function ($stream) { $stream->read(10); });
        $throws(function ($stream) { $stream->write('bar'); });
        $throws(function ($stream) { $stream->seek(10); });
        $throws(function ($stream) { $stream->tell(); });
        $throws(function ($stream) { $stream->eof(); });
        $throws(function ($stream) { $stream->getSize(); });
        $throws(function ($stream) { $stream->getContents(); });
        $this->assertSame('', (string) $stream);
        $stream->close();
    }

    public function testCloseClearProperties()
    {
        $handle = fopen('php://temp', 'r+');
        $stream = new Stream($handle);
        $stream->close();

        $this->assertFalse($stream->isSeekable());
        $this->assertFalse($stream->isReadable());
        $this->assertFalse($stream->isWritable());
        $this->assertNull($stream->getSize());
        $this->assertEmpty($stream->getMetadata());
    }

    public function testDoesNotThrowInToString()
    {
        $s = \GuzzleHttp\Psr7\stream_for('foo');
        $s = new NoSeekStream($s);
        $this->assertEquals('foo', (string) $s);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\StreamWrapper;
use GuzzleHttp\Psr7;

/**
 * @covers GuzzleHttp\Psr7\StreamWrapper
 */
class StreamWrapperTest extends \PHPUnit_Framework_TestCase
{
    public function testResource()
    {
        $stream = Psr7\stream_for('foo');
        $handle = StreamWrapper::getResource($stream);
        $this->assertSame('foo', fread($handle, 3));
        $this->assertSame(3, ftell($handle));
        $this->assertSame(3, fwrite($handle, 'bar'));
        $this->assertSame(0, fseek($handle, 0));
        $this->assertSame('foobar', fread($handle, 6));
        $this->assertSame('', fread($handle, 1));
        $this->assertTrue(feof($handle));

        $stBlksize  = defined('PHP_WINDOWS_VERSION_BUILD') ? -1 : 0;

        // This fails on HHVM for some reason
        if (!defined('HHVM_VERSION')) {
            $this->assertEquals([
                'dev'     => 0,
                'ino'     => 0,
                'mode'    => 33206,
                'nlink'   => 0,
                'uid'     => 0,
                'gid'     => 0,
                'rdev'    => 0,
                'size'    => 6,
                'atime'   => 0,
                'mtime'   => 0,
                'ctime'   => 0,
                'blksize' => $stBlksize,
                'blocks'  => $stBlksize,
                0         => 0,
                1         => 0,
                2         => 33206,
                3         => 0,
                4         => 0,
                5         => 0,
                6         => 0,
                7         => 6,
                8         => 0,
                9         => 0,
                10        => 0,
                11        => $stBlksize,
                12        => $stBlksize,
            ], fstat($handle));
        }

        $this->assertTrue(fclose($handle));
        $this->assertSame('foobar', (string) $stream);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testValidatesStream()
    {
        $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isReadable', 'isWritable'])
            ->getMockForAbstractClass();
        $stream->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(false));
        $stream->expects($this->once())
            ->method('isWritable')
            ->will($this->returnValue(false));
        StreamWrapper::getResource($stream);
    }

    /**
     * @expectedException \PHPUnit_Framework_Error_Warning
     */
    public function testReturnsFalseWhenStreamDoesNotExist()
    {
        fopen('guzzle://foo', 'r');
    }

    public function testCanOpenReadonlyStream()
    {
        $stream = $this->getMockBuilder('Psr\Http\Message\StreamInterface')
            ->setMethods(['isReadable', 'isWritable'])
            ->getMockForAbstractClass();
        $stream->expects($this->once())
            ->method('isReadable')
            ->will($this->returnValue(false));
        $stream->expects($this->once())
            ->method('isWritable')
            ->will($this->returnValue(true));
        $r = StreamWrapper::getResource($stream);
        $this->assertInternalType('resource', $r);
        fclose($r);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use ReflectionProperty;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\UploadedFile;

/**
 * @covers GuzzleHttp\Psr7\UploadedFile
 */
class UploadedFileTest extends \PHPUnit_Framework_TestCase
{
    protected $cleanup;

    public function setUp()
    {
        $this->cleanup = [];
    }

    public function tearDown()
    {
        foreach ($this->cleanup as $file) {
            if (is_scalar($file) && file_exists($file)) {
                unlink($file);
            }
        }
    }

    public function invalidStreams()
    {
        return [
            'null'         => [null],
            'true'         => [true],
            'false'        => [false],
            'int'          => [1],
            'float'        => [1.1],
            'array'        => [['filename']],
            'object'       => [(object) ['filename']],
        ];
    }

    /**
     * @dataProvider invalidStreams
     */
    public function testRaisesExceptionOnInvalidStreamOrFile($streamOrFile)
    {
        $this->setExpectedException('InvalidArgumentException');

        new UploadedFile($streamOrFile, 0, UPLOAD_ERR_OK);
    }

    public function invalidSizes()
    {
        return [
            'null'   => [null],
            'float'  => [1.1],
            'array'  => [[1]],
            'object' => [(object) [1]],
        ];
    }

    /**
     * @dataProvider invalidSizes
     */
    public function testRaisesExceptionOnInvalidSize($size)
    {
        $this->setExpectedException('InvalidArgumentException', 'size');

        new UploadedFile(fopen('php://temp', 'wb+'), $size, UPLOAD_ERR_OK);
    }

    public function invalidErrorStatuses()
    {
        return [
            'null'     => [null],
            'true'     => [true],
            'false'    => [false],
            'float'    => [1.1],
            'string'   => ['1'],
            'array'    => [[1]],
            'object'   => [(object) [1]],
            'negative' => [-1],
            'too-big'  => [9],
        ];
    }

    /**
     * @dataProvider invalidErrorStatuses
     */
    public function testRaisesExceptionOnInvalidErrorStatus($status)
    {
        $this->setExpectedException('InvalidArgumentException', 'status');

        new UploadedFile(fopen('php://temp', 'wb+'), 0, $status);
    }

    public function invalidFilenamesAndMediaTypes()
    {
        return [
            'true'   => [true],
            'false'  => [false],
            'int'    => [1],
            'float'  => [1.1],
            'array'  => [['string']],
            'object' => [(object) ['string']],
        ];
    }

    /**
     * @dataProvider invalidFilenamesAndMediaTypes
     */
    public function testRaisesExceptionOnInvalidClientFilename($filename)
    {
        $this->setExpectedException('InvalidArgumentException', 'filename');

        new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, $filename);
    }

    /**
     * @dataProvider invalidFilenamesAndMediaTypes
     */
    public function testRaisesExceptionOnInvalidClientMediaType($mediaType)
    {
        $this->setExpectedException('InvalidArgumentException', 'media type');

        new UploadedFile(fopen('php://temp', 'wb+'), 0, UPLOAD_ERR_OK, 'foobar.baz', $mediaType);
    }

    public function testGetStreamReturnsOriginalStreamObject()
    {
        $stream = new Stream(fopen('php://temp', 'r'));
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);

        $this->assertSame($stream, $upload->getStream());
    }

    public function testGetStreamReturnsWrappedPhpStream()
    {
        $stream = fopen('php://temp', 'wb+');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
        $uploadStream = $upload->getStream()->detach();

        $this->assertSame($stream, $uploadStream);
    }

    public function testGetStreamReturnsStreamForFile()
    {
        $this->cleanup[] = $stream = tempnam(sys_get_temp_dir(), 'stream_file');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);
        $uploadStream = $upload->getStream();
        $r = new ReflectionProperty($uploadStream, 'filename');
        $r->setAccessible(true);

        $this->assertSame($stream, $r->getValue($uploadStream));
    }

    public function testSuccessful()
    {
        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
        $upload = new UploadedFile($stream, $stream->getSize(), UPLOAD_ERR_OK, 'filename.txt', 'text/plain');

        $this->assertEquals($stream->getSize(), $upload->getSize());
        $this->assertEquals('filename.txt', $upload->getClientFilename());
        $this->assertEquals('text/plain', $upload->getClientMediaType());

        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'successful');
        $upload->moveTo($to);
        $this->assertFileExists($to);
        $this->assertEquals($stream->__toString(), file_get_contents($to));
    }

    public function invalidMovePaths()
    {
        return [
            'null'   => [null],
            'true'   => [true],
            'false'  => [false],
            'int'    => [1],
            'float'  => [1.1],
            'empty'  => [''],
            'array'  => [['filename']],
            'object' => [(object) ['filename']],
        ];
    }

    /**
     * @dataProvider invalidMovePaths
     */
    public function testMoveRaisesExceptionForInvalidPath($path)
    {
        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);

        $this->cleanup[] = $path;

        $this->setExpectedException('InvalidArgumentException', 'path');
        $upload->moveTo($path);
    }

    public function testMoveCannotBeCalledMoreThanOnce()
    {
        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);

        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
        $upload->moveTo($to);
        $this->assertTrue(file_exists($to));

        $this->setExpectedException('RuntimeException', 'moved');
        $upload->moveTo($to);
    }

    public function testCannotRetrieveStreamAfterMove()
    {
        $stream = \GuzzleHttp\Psr7\stream_for('Foo bar!');
        $upload = new UploadedFile($stream, 0, UPLOAD_ERR_OK);

        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'diac');
        $upload->moveTo($to);
        $this->assertFileExists($to);

        $this->setExpectedException('RuntimeException', 'moved');
        $upload->getStream();
    }

    public function nonOkErrorStatus()
    {
        return [
            'UPLOAD_ERR_INI_SIZE'   => [ UPLOAD_ERR_INI_SIZE ],
            'UPLOAD_ERR_FORM_SIZE'  => [ UPLOAD_ERR_FORM_SIZE ],
            'UPLOAD_ERR_PARTIAL'    => [ UPLOAD_ERR_PARTIAL ],
            'UPLOAD_ERR_NO_FILE'    => [ UPLOAD_ERR_NO_FILE ],
            'UPLOAD_ERR_NO_TMP_DIR' => [ UPLOAD_ERR_NO_TMP_DIR ],
            'UPLOAD_ERR_CANT_WRITE' => [ UPLOAD_ERR_CANT_WRITE ],
            'UPLOAD_ERR_EXTENSION'  => [ UPLOAD_ERR_EXTENSION ],
        ];
    }

    /**
     * @dataProvider nonOkErrorStatus
     */
    public function testConstructorDoesNotRaiseExceptionForInvalidStreamWhenErrorStatusPresent($status)
    {
        $uploadedFile = new UploadedFile('not ok', 0, $status);
        $this->assertSame($status, $uploadedFile->getError());
    }

    /**
     * @dataProvider nonOkErrorStatus
     */
    public function testMoveToRaisesExceptionWhenErrorStatusPresent($status)
    {
        $uploadedFile = new UploadedFile('not ok', 0, $status);
        $this->setExpectedException('RuntimeException', 'upload error');
        $uploadedFile->moveTo(__DIR__ . '/' . uniqid());
    }

    /**
     * @dataProvider nonOkErrorStatus
     */
    public function testGetStreamRaisesExceptionWhenErrorStatusPresent($status)
    {
        $uploadedFile = new UploadedFile('not ok', 0, $status);
        $this->setExpectedException('RuntimeException', 'upload error');
        $stream = $uploadedFile->getStream();
    }

    public function testMoveToCreatesStreamIfOnlyAFilenameWasProvided()
    {
        $this->cleanup[] = $from = tempnam(sys_get_temp_dir(), 'copy_from');
        $this->cleanup[] = $to = tempnam(sys_get_temp_dir(), 'copy_to');

        copy(__FILE__, $from);

        $uploadedFile = new UploadedFile($from, 100, UPLOAD_ERR_OK, basename($from), 'text/plain');
        $uploadedFile->moveTo($to);

        $this->assertFileEquals(__FILE__, $to);
    }
}
<?php
namespace GuzzleHttp\Tests\Psr7;

use GuzzleHttp\Psr7\Uri;

/**
 * @covers GuzzleHttp\Psr7\Uri
 */
class UriTest extends \PHPUnit_Framework_TestCase
{
    const RFC3986_BASE = 'http://a/b/c/d;p?q';

    public function testParsesProvidedUri()
    {
        $uri = new Uri('https://user:pass@example.com:8080/path/123?q=abc#test');

        $this->assertSame('https', $uri->getScheme());
        $this->assertSame('user:pass@example.com:8080', $uri->getAuthority());
        $this->assertSame('user:pass', $uri->getUserInfo());
        $this->assertSame('example.com', $uri->getHost());
        $this->assertSame(8080, $uri->getPort());
        $this->assertSame('/path/123', $uri->getPath());
        $this->assertSame('q=abc', $uri->getQuery());
        $this->assertSame('test', $uri->getFragment());
        $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri);
    }

    public function testCanTransformAndRetrievePartsIndividually()
    {
        $uri = (new Uri())
            ->withScheme('https')
            ->withUserInfo('user', 'pass')
            ->withHost('example.com')
            ->withPort(8080)
            ->withPath('/path/123')
            ->withQuery('q=abc')
            ->withFragment('test');

        $this->assertSame('https', $uri->getScheme());
        $this->assertSame('user:pass@example.com:8080', $uri->getAuthority());
        $this->assertSame('user:pass', $uri->getUserInfo());
        $this->assertSame('example.com', $uri->getHost());
        $this->assertSame(8080, $uri->getPort());
        $this->assertSame('/path/123', $uri->getPath());
        $this->assertSame('q=abc', $uri->getQuery());
        $this->assertSame('test', $uri->getFragment());
        $this->assertSame('https://user:pass@example.com:8080/path/123?q=abc#test', (string) $uri);
    }

    /**
     * @dataProvider getValidUris
     */
    public function testValidUrisStayValid($input)
    {
        $uri = new Uri($input);

        $this->assertSame($input, (string) $uri);
    }

    /**
     * @dataProvider getValidUris
     */
    public function testFromParts($input)
    {
        $uri = Uri::fromParts(parse_url($input));

        $this->assertSame($input, (string) $uri);
    }

    public function getValidUris()
    {
        return [
            ['urn:path-rootless'],
            ['urn:path:with:colon'],
            ['urn:/path-absolute'],
            ['urn:/'],
            // only scheme with empty path
            ['urn:'],
            // only path
            ['/'],
            ['relative/'],
            ['0'],
            // same document reference
            [''],
            // network path without scheme
            ['//example.org'],
            ['//example.org/'],
            ['//example.org?q#h'],
            // only query
            ['?q'],
            ['?q=abc&foo=bar'],
            // only fragment
            ['#fragment'],
            // dot segments are not removed automatically
            ['./foo/../bar'],
        ];
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Unable to parse URI
     * @dataProvider getInvalidUris
     */
    public function testInvalidUrisThrowException($invalidUri)
    {
        new Uri($invalidUri);
    }

    public function getInvalidUris()
    {
        return [
            // parse_url() requires the host component which makes sense for http(s)
            // but not when the scheme is not known or different. So '//' or '///' is
            // currently invalid as well but should not according to RFC 3986.
            ['http://'],
            ['urn://host:with:colon'], // host cannot contain ":"
        ];
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Invalid port: 100000. Must be between 1 and 65535
     */
    public function testPortMustBeValid()
    {
        (new Uri())->withPort(100000);
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Invalid port: 0. Must be between 1 and 65535
     */
    public function testWithPortCannotBeZero()
    {
        (new Uri())->withPort(0);
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Unable to parse URI
     */
    public function testParseUriPortCannotBeZero()
    {
        new Uri('//example.com:0');
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testSchemeMustHaveCorrectType()
    {
        (new Uri())->withScheme([]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testHostMustHaveCorrectType()
    {
        (new Uri())->withHost([]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testPathMustHaveCorrectType()
    {
        (new Uri())->withPath([]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testQueryMustHaveCorrectType()
    {
        (new Uri())->withQuery([]);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testFragmentMustHaveCorrectType()
    {
        (new Uri())->withFragment([]);
    }

    public function testCanParseFalseyUriParts()
    {
        $uri = new Uri('0://0:0@0/0?0#0');

        $this->assertSame('0', $uri->getScheme());
        $this->assertSame('0:0@0', $uri->getAuthority());
        $this->assertSame('0:0', $uri->getUserInfo());
        $this->assertSame('0', $uri->getHost());
        $this->assertSame('/0', $uri->getPath());
        $this->assertSame('0', $uri->getQuery());
        $this->assertSame('0', $uri->getFragment());
        $this->assertSame('0://0:0@0/0?0#0', (string) $uri);
    }

    public function testCanConstructFalseyUriParts()
    {
        $uri = (new Uri())
            ->withScheme('0')
            ->withUserInfo('0', '0')
            ->withHost('0')
            ->withPath('/0')
            ->withQuery('0')
            ->withFragment('0');

        $this->assertSame('0', $uri->getScheme());
        $this->assertSame('0:0@0', $uri->getAuthority());
        $this->assertSame('0:0', $uri->getUserInfo());
        $this->assertSame('0', $uri->getHost());
        $this->assertSame('/0', $uri->getPath());
        $this->assertSame('0', $uri->getQuery());
        $this->assertSame('0', $uri->getFragment());
        $this->assertSame('0://0:0@0/0?0#0', (string) $uri);
    }

    /**
     * @dataProvider getResolveTestCases
     */
    public function testResolvesUris($base, $rel, $expected)
    {
        $uri = new Uri($base);
        $actual = Uri::resolve($uri, $rel);
        $this->assertSame($expected, (string) $actual);
    }

    public function getResolveTestCases()
    {
        return [
            [self::RFC3986_BASE, 'g:h',           'g:h'],
            [self::RFC3986_BASE, 'g',             'http://a/b/c/g'],
            [self::RFC3986_BASE, './g',           'http://a/b/c/g'],
            [self::RFC3986_BASE, 'g/',            'http://a/b/c/g/'],
            [self::RFC3986_BASE, '/g',            'http://a/g'],
            [self::RFC3986_BASE, '//g',           'http://g'],
            [self::RFC3986_BASE, '?y',            'http://a/b/c/d;p?y'],
            [self::RFC3986_BASE, 'g?y',           'http://a/b/c/g?y'],
            [self::RFC3986_BASE, '#s',            'http://a/b/c/d;p?q#s'],
            [self::RFC3986_BASE, 'g#s',           'http://a/b/c/g#s'],
            [self::RFC3986_BASE, 'g?y#s',         'http://a/b/c/g?y#s'],
            [self::RFC3986_BASE, ';x',            'http://a/b/c/;x'],
            [self::RFC3986_BASE, 'g;x',           'http://a/b/c/g;x'],
            [self::RFC3986_BASE, 'g;x?y#s',       'http://a/b/c/g;x?y#s'],
            [self::RFC3986_BASE, '',              self::RFC3986_BASE],
            [self::RFC3986_BASE, '.',             'http://a/b/c/'],
            [self::RFC3986_BASE, './',            'http://a/b/c/'],
            [self::RFC3986_BASE, '..',            'http://a/b/'],
            [self::RFC3986_BASE, '../',           'http://a/b/'],
            [self::RFC3986_BASE, '../g',          'http://a/b/g'],
            [self::RFC3986_BASE, '../..',         'http://a/'],
            [self::RFC3986_BASE, '../../',        'http://a/'],
            [self::RFC3986_BASE, '../../g',       'http://a/g'],
            [self::RFC3986_BASE, '../../../g',    'http://a/g'],
            [self::RFC3986_BASE, '../../../../g', 'http://a/g'],
            [self::RFC3986_BASE, '/./g',          'http://a/g'],
            [self::RFC3986_BASE, '/../g',         'http://a/g'],
            [self::RFC3986_BASE, 'g.',            'http://a/b/c/g.'],
            [self::RFC3986_BASE, '.g',            'http://a/b/c/.g'],
            [self::RFC3986_BASE, 'g..',           'http://a/b/c/g..'],
            [self::RFC3986_BASE, '..g',           'http://a/b/c/..g'],
            [self::RFC3986_BASE, './../g',        'http://a/b/g'],
            [self::RFC3986_BASE, 'foo////g',      'http://a/b/c/foo////g'],
            [self::RFC3986_BASE, './g/.',         'http://a/b/c/g/'],
            [self::RFC3986_BASE, 'g/./h',         'http://a/b/c/g/h'],
            [self::RFC3986_BASE, 'g/../h',        'http://a/b/c/h'],
            [self::RFC3986_BASE, 'g;x=1/./y',     'http://a/b/c/g;x=1/y'],
            [self::RFC3986_BASE, 'g;x=1/../y',    'http://a/b/c/y'],
            // dot-segments in the query or fragment
            [self::RFC3986_BASE, 'g?y/./x',       'http://a/b/c/g?y/./x'],
            [self::RFC3986_BASE, 'g?y/../x',      'http://a/b/c/g?y/../x'],
            [self::RFC3986_BASE, 'g#s/./x',       'http://a/b/c/g#s/./x'],
            [self::RFC3986_BASE, 'g#s/../x',      'http://a/b/c/g#s/../x'],
            [self::RFC3986_BASE, 'g#s/../x',      'http://a/b/c/g#s/../x'],
            [self::RFC3986_BASE, '?y#s',          'http://a/b/c/d;p?y#s'],
            ['http://a/b/c/d;p?q#s', '?y',        'http://a/b/c/d;p?y'],
            ['http://u@a/b/c/d;p?q', '.',         'http://u@a/b/c/'],
            ['http://u:p@a/b/c/d;p?q', '.',       'http://u:p@a/b/c/'],
            ['http://a/b/c/d/', 'e',              'http://a/b/c/d/e'],
            ['urn:no-slash', 'e',                 'urn:e'],
            // falsey relative parts
            [self::RFC3986_BASE, '//0',           'http://0'],
            [self::RFC3986_BASE, '0',             'http://a/b/c/0'],
            [self::RFC3986_BASE, '?0',            'http://a/b/c/d;p?0'],
            [self::RFC3986_BASE, '#0',            'http://a/b/c/d;p?q#0'],
        ];
    }

    public function testAddAndRemoveQueryValues()
    {
        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'a', 'b');
        $uri = Uri::withQueryValue($uri, 'c', 'd');
        $uri = Uri::withQueryValue($uri, 'e', null);
        $this->assertSame('a=b&c=d&e', $uri->getQuery());

        $uri = Uri::withoutQueryValue($uri, 'c');
        $this->assertSame('a=b&e', $uri->getQuery());
        $uri = Uri::withoutQueryValue($uri, 'e');
        $this->assertSame('a=b', $uri->getQuery());
        $uri = Uri::withoutQueryValue($uri, 'a');
        $this->assertSame('', $uri->getQuery());
    }

    public function testWithQueryValueReplacesSameKeys()
    {
        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'a', 'b');
        $uri = Uri::withQueryValue($uri, 'c', 'd');
        $uri = Uri::withQueryValue($uri, 'a', 'e');
        $this->assertSame('c=d&a=e', $uri->getQuery());
    }

    public function testWithoutQueryValueRemovesAllSameKeys()
    {
        $uri = (new Uri())->withQuery('a=b&c=d&a=e');
        $uri = Uri::withoutQueryValue($uri, 'a');
        $this->assertSame('c=d', $uri->getQuery());
    }

    public function testRemoveNonExistingQueryValue()
    {
        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'a', 'b');
        $uri = Uri::withoutQueryValue($uri, 'c');
        $this->assertSame('a=b', $uri->getQuery());
    }

    public function testWithQueryValueHandlesEncoding()
    {
        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'E=mc^2', 'ein&stein');
        $this->assertSame('E%3Dmc%5E2=ein%26stein', $uri->getQuery(), 'Decoded key/value get encoded');

        $uri = new Uri();
        $uri = Uri::withQueryValue($uri, 'E%3Dmc%5e2', 'ein%26stein');
        $this->assertSame('E%3Dmc%5e2=ein%26stein', $uri->getQuery(), 'Encoded key/value do not get double-encoded');
    }

    public function testWithoutQueryValueHandlesEncoding()
    {
        // It also tests that the case of the percent-encoding does not matter,
        // i.e. both lowercase "%3d" and uppercase "%5E" can be removed.
        $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
        $uri = Uri::withoutQueryValue($uri, 'E=mc^2');
        $this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in decoded form');

        $uri = (new Uri())->withQuery('E%3dmc%5E2=einstein&foo=bar');
        $uri = Uri::withoutQueryValue($uri, 'E%3Dmc%5e2');
        $this->assertSame('foo=bar', $uri->getQuery(), 'Handles key in encoded form');
    }

    public function testSchemeIsNormalizedToLowercase()
    {
        $uri = new Uri('HTTP://example.com');

        $this->assertSame('http', $uri->getScheme());
        $this->assertSame('http://example.com', (string) $uri);

        $uri = (new Uri('//example.com'))->withScheme('HTTP');

        $this->assertSame('http', $uri->getScheme());
        $this->assertSame('http://example.com', (string) $uri);
    }

    public function testHostIsNormalizedToLowercase()
    {
        $uri = new Uri('//eXaMpLe.CoM');

        $this->assertSame('example.com', $uri->getHost());
        $this->assertSame('//example.com', (string) $uri);

        $uri = (new Uri())->withHost('eXaMpLe.CoM');

        $this->assertSame('example.com', $uri->getHost());
        $this->assertSame('//example.com', (string) $uri);
    }

    public function testPortIsNullIfStandardPortForScheme()
    {
        // HTTPS standard port
        $uri = new Uri('https://example.com:443');
        $this->assertNull($uri->getPort());
        $this->assertSame('example.com', $uri->getAuthority());

        $uri = (new Uri('https://example.com'))->withPort(443);
        $this->assertNull($uri->getPort());
        $this->assertSame('example.com', $uri->getAuthority());

        // HTTP standard port
        $uri = new Uri('http://example.com:80');
        $this->assertNull($uri->getPort());
        $this->assertSame('example.com', $uri->getAuthority());

        $uri = (new Uri('http://example.com'))->withPort(80);
        $this->assertNull($uri->getPort());
        $this->assertSame('example.com', $uri->getAuthority());
    }

    public function testPortIsReturnedIfSchemeUnknown()
    {
        $uri = (new Uri('//example.com'))->withPort(80);

        $this->assertSame(80, $uri->getPort());
        $this->assertSame('example.com:80', $uri->getAuthority());
    }

    public function testStandardPortIsNullIfSchemeChanges()
    {
        $uri = new Uri('http://example.com:443');
        $this->assertSame('http', $uri->getScheme());
        $this->assertSame(443, $uri->getPort());

        $uri = $uri->withScheme('https');
        $this->assertNull($uri->getPort());
    }

    public function testPortPassedAsStringIsCastedToInt()
    {
        $uri = (new Uri('//example.com'))->withPort('8080');

        $this->assertSame(8080, $uri->getPort(), 'Port is returned as integer');
        $this->assertSame('example.com:8080', $uri->getAuthority());
    }

    public function testPortCanBeRemoved()
    {
        $uri = (new Uri('http://example.com:8080'))->withPort(null);

        $this->assertNull($uri->getPort());
        $this->assertSame('http://example.com', (string) $uri);
    }

    public function testAuthorityWithUserInfoButWithoutHost()
    {
        $uri = (new Uri())->withUserInfo('user', 'pass');

        $this->assertSame('user:pass', $uri->getUserInfo());
        $this->assertSame('', $uri->getAuthority());
    }

    public function uriComponentsEncodingProvider()
    {
        $unreserved = 'a-zA-Z0-9.-_~!$&\'()*+,;=:@';

        return [
            // Percent encode spaces
            ['/pa th?q=va lue#frag ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'],
            // Percent encode multibyte
            ['/€?€#€', '/%E2%82%AC', '%E2%82%AC', '%E2%82%AC', '/%E2%82%AC?%E2%82%AC#%E2%82%AC'],
            // Don't encode something that's already encoded
            ['/pa%20th?q=va%20lue#frag%20ment', '/pa%20th', 'q=va%20lue', 'frag%20ment', '/pa%20th?q=va%20lue#frag%20ment'],
            // Percent encode invalid percent encodings
            ['/pa%2-th?q=va%2-lue#frag%2-ment', '/pa%252-th', 'q=va%252-lue', 'frag%252-ment', '/pa%252-th?q=va%252-lue#frag%252-ment'],
            // Don't encode path segments
            ['/pa/th//two?q=va/lue#frag/ment', '/pa/th//two', 'q=va/lue', 'frag/ment', '/pa/th//two?q=va/lue#frag/ment'],
            // Don't encode unreserved chars or sub-delimiters
            ["/$unreserved?$unreserved#$unreserved", "/$unreserved", $unreserved, $unreserved, "/$unreserved?$unreserved#$unreserved"],
            // Encoded unreserved chars are not decoded
            ['/p%61th?q=v%61lue#fr%61gment', '/p%61th', 'q=v%61lue', 'fr%61gment', '/p%61th?q=v%61lue#fr%61gment'],
        ];
    }

    /**
     * @dataProvider uriComponentsEncodingProvider
     */
    public function testUriComponentsGetEncodedProperly($input, $path, $query, $fragment, $output)
    {
        $uri = new Uri($input);
        $this->assertSame($path, $uri->getPath());
        $this->assertSame($query, $uri->getQuery());
        $this->assertSame($fragment, $uri->getFragment());
        $this->assertSame($output, (string) $uri);
    }

    public function testWithPathEncodesProperly()
    {
        $uri = (new Uri())->withPath('/baz?#€/b%61r');
        // Query and fragment delimiters and multibyte chars are encoded.
        $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', $uri->getPath());
        $this->assertSame('/baz%3F%23%E2%82%AC/b%61r', (string) $uri);
    }

    public function testWithQueryEncodesProperly()
    {
        $uri = (new Uri())->withQuery('?=#&€=/&b%61r');
        // A query starting with a "?" is valid and must not be magically removed. Otherwise it would be impossible to
        // construct such an URI. Also the "?" and "/" does not need to be encoded in the query.
        $this->assertSame('?=%23&%E2%82%AC=/&b%61r', $uri->getQuery());
        $this->assertSame('??=%23&%E2%82%AC=/&b%61r', (string) $uri);
    }

    public function testWithFragmentEncodesProperly()
    {
        $uri = (new Uri())->withFragment('#€?/b%61r');
        // A fragment starting with a "#" is valid and must not be magically removed. Otherwise it would be impossible to
        // construct such an URI. Also the "?" and "/" does not need to be encoded in the fragment.
        $this->assertSame('%23%E2%82%AC?/b%61r', $uri->getFragment());
        $this->assertSame('#%23%E2%82%AC?/b%61r', (string) $uri);
    }

    public function testAllowsForRelativeUri()
    {
        $uri = (new Uri)->withPath('foo');
        $this->assertSame('foo', $uri->getPath());
        $this->assertSame('foo', (string) $uri);
    }

    public function testAddsSlashForRelativeUriStringWithHost()
    {
        // If the path is rootless and an authority is present, the path MUST
        // be prefixed by "/".
        $uri = (new Uri)->withPath('foo')->withHost('example.com');
        $this->assertSame('foo', $uri->getPath());
        // concatenating a relative path with a host doesn't work: "//example.comfoo" would be wrong
        $this->assertSame('//example.com/foo', (string) $uri);
    }

    public function testRemoveExtraSlashesWihoutHost()
    {
        // If the path is starting with more than one "/" and no authority is
        // present, the starting slashes MUST be reduced to one.
        $uri = (new Uri)->withPath('//foo');
        $this->assertSame('//foo', $uri->getPath());
        // URI "//foo" would be interpreted as network reference and thus change the original path to the host
        $this->assertSame('/foo', (string) $uri);
    }

    public function testDefaultReturnValuesOfGetters()
    {
        $uri = new Uri();

        $this->assertSame('', $uri->getScheme());
        $this->assertSame('', $uri->getAuthority());
        $this->assertSame('', $uri->getUserInfo());
        $this->assertSame('', $uri->getHost());
        $this->assertNull($uri->getPort());
        $this->assertSame('', $uri->getPath());
        $this->assertSame('', $uri->getQuery());
        $this->assertSame('', $uri->getFragment());
    }

    public function testImmutability()
    {
        $uri = new Uri();

        $this->assertNotSame($uri, $uri->withScheme('https'));
        $this->assertNotSame($uri, $uri->withUserInfo('user', 'pass'));
        $this->assertNotSame($uri, $uri->withHost('example.com'));
        $this->assertNotSame($uri, $uri->withPort(8080));
        $this->assertNotSame($uri, $uri->withPath('/path/123'));
        $this->assertNotSame($uri, $uri->withQuery('q=abc'));
        $this->assertNotSame($uri, $uri->withFragment('test'));
    }

    public function testExtendingClassesInstantiates()
    {
        // The non-standard port triggers a cascade of private methods which
        //  should not use late static binding to access private static members.
        // If they do, this will fatal.
        $this->assertInstanceOf(
            '\GuzzleHttp\Tests\Psr7\ExtendingClassTest',
            new ExtendingClassTest('http://h:9/')
        );
    }
}

class ExtendingClassTest extends \GuzzleHttp\Psr7\Uri
{
}
#!/usr/bin/env bash

if [[ "$#" -eq 1 ]]; then
    echo "Handling \"$1\" brew package..."
else
    echo "Brew failed - invalid $0 call"
    exit 1;
fi

if [[ $(brew ls --versions "$1") ]]; then
    if brew outdated "$1"; then
        echo "Package upgrade is not required, skipping"
    else
        echo "Updating package...";
        brew upgrade "$1"
        if [ $? -ne 0 ]; then
            echo "Upgrade failed"
            exit 1
        fi
    fi
else
    echo "Package not available - installing..."
    brew install "$1"
    if [ $? -ne 0 ]; then
        echo "Install failed"
        exit 1
    fi
fi

echo "Linking installed package..."
brew link "$1"
#!/usr/bin/env bash
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
sudo php composer-setup.php --install-dir=/usr/local/bin/ --filename=composer
rm composer-setup.php
#!/usr/bin/env bash

echo "Here's the OSX environment:"
sw_vers
brew --version

echo "Updating brew..."
brew update

if [[ "${_PHP}" == "hhvm" ]]; then
    echo "Adding brew HHVM dependencies..."
    brew tap hhvm/hhvm

else
    echo "Adding brew PHP dependencies..."
    brew tap homebrew/dupes
    brew tap homebrew/versions
    brew tap homebrew/homebrew-php
fi
{
    "name": "league/flysystem",
    "description": "Filesystem abstraction: Many filesystems, one API.",
    "keywords": [
        "filesystem", "filesystems", "files", "storage", "dropbox", "aws",
        "abstraction", "s3", "ftp", "sftp", "remote", "webdav",
        "file systems", "cloud", "cloud files", "rackspace", "copy.com"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "Frank de Jonge",
            "email": "info@frenky.net"
        }
    ],
    "require": {
        "php": ">=5.5.9"
    },
    "require-dev": {
        "ext-fileinfo": "*",
        "phpunit/phpunit": "~4.8",
        "mockery/mockery": "~0.9",
        "phpspec/phpspec": "^2.2"
    },
    "autoload": {
        "psr-4": {
            "League\\Flysystem\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "League\\Flysystem\\Stub\\": "stub/"
        }
    },
    "suggest": {
        "ext-fileinfo": "Required for MimeType",
        "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
        "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
        "league/flysystem-copy": "Allows you to use Copy.com storage",
        "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
        "league/flysystem-webdav": "Allows you to use WebDAV storage",
        "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
        "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
        "league/flysystem-dropbox": "Allows you to use Dropbox storage",
        "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
        "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
        "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
    },
    "conflict": {
        "league/flysystem-sftp": "<1.0.6"
    },
    "config": {
        "bin-dir": "bin"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.1-dev"
        }
    }
}
Copyright (c) 2013-2016 Frank de Jonge

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.
<?php

namespace League\Flysystem\Adapter;

use League\Flysystem\AdapterInterface;

abstract class AbstractAdapter implements AdapterInterface
{
    /**
     * @var string path prefix
     */
    protected $pathPrefix;

    /**
     * @var string
     */
    protected $pathSeparator = '/';

    /**
     * Set the path prefix.
     *
     * @param string $prefix
     *
     * @return self
     */
    public function setPathPrefix($prefix)
    {
        $is_empty = empty($prefix);

        if ( ! $is_empty) {
            $prefix = rtrim($prefix, $this->pathSeparator) . $this->pathSeparator;
        }

        $this->pathPrefix = $is_empty ? null : $prefix;
    }

    /**
     * Get the path prefix.
     *
     * @return string path prefix
     */
    public function getPathPrefix()
    {
        return $this->pathPrefix;
    }

    /**
     * Prefix a path.
     *
     * @param string $path
     *
     * @return string prefixed path
     */
    public function applyPathPrefix($path)
    {
        $path = ltrim($path, '\\/');

        if (strlen($path) === 0) {
            return $this->getPathPrefix() ?: '';
        }

        if ($prefix = $this->getPathPrefix()) {
            $path = $prefix . $path;
        }

        return $path;
    }

    /**
     * Remove a path prefix.
     *
     * @param string $path
     *
     * @return string path without the prefix
     */
    public function removePathPrefix($path)
    {
        $pathPrefix = $this->getPathPrefix();

        if ($pathPrefix === null) {
            return $path;
        }

        return substr($path, strlen($pathPrefix));
    }
}
<?php

namespace League\Flysystem\Adapter;

use DateTime;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
use League\Flysystem\NotSupportedException;
use League\Flysystem\SafeStorage;
use RuntimeException;

abstract class AbstractFtpAdapter extends AbstractAdapter
{
    /**
     * @var mixed
     */
    protected $connection;

    /**
     * @var string
     */
    protected $host;

    /**
     * @var int
     */
    protected $port = 21;

    /**
     * @var bool
     */
    protected $ssl = false;

    /**
     * @var int
     */
    protected $timeout = 90;

    /**
     * @var bool
     */
    protected $passive = true;

    /**
     * @var string
     */
    protected $separator = '/';

    /**
     * @var string|null
     */
    protected $root;

    /**
     * @var int
     */
    protected $permPublic = 0744;

    /**
     * @var int
     */
    protected $permPrivate = 0700;

    /**
     * @var array
     */
    protected $configurable = [];

    /**
     * @var string
     */
    protected $systemType;

    /**
     * @var bool
     */
    protected $alternativeRecursion = false;

    /**
     * @var SafeStorage
     */
    protected $safeStorage;

    /**
     * Constructor.
     *
     * @param array $config
     */
    public function __construct(array $config)
    {
        $this->safeStorage = new SafeStorage();
        $this->setConfig($config);
    }

    /**
     * Set the config.
     *
     * @param array $config
     *
     * @return $this
     */
    public function setConfig(array $config)
    {
        foreach ($this->configurable as $setting) {
            if ( ! isset($config[$setting])) {
                continue;
            }

            $method = 'set' . ucfirst($setting);

            if (method_exists($this, $method)) {
                $this->$method($config[$setting]);
            }
        }

        return $this;
    }

    /**
     * Returns the host.
     *
     * @return string
     */
    public function getHost()
    {
        return $this->host;
    }

    /**
     * Set the host.
     *
     * @param string $host
     *
     * @return $this
     */
    public function setHost($host)
    {
        $this->host = $host;

        return $this;
    }

    /**
     * Set the public permission value.
     *
     * @param int $permPublic
     *
     * @return $this
     */
    public function setPermPublic($permPublic)
    {
        $this->permPublic = $permPublic;

        return $this;
    }

    /**
     * Set the private permission value.
     *
     * @param int $permPrivate
     *
     * @return $this
     */
    public function setPermPrivate($permPrivate)
    {
        $this->permPrivate = $permPrivate;

        return $this;
    }

    /**
     * Returns the ftp port.
     *
     * @return int
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * Returns the root folder to work from.
     *
     * @return string
     */
    public function getRoot()
    {
        return $this->root;
    }

    /**
     * Set the ftp port.
     *
     * @param int|string $port
     *
     * @return $this
     */
    public function setPort($port)
    {
        $this->port = (int) $port;

        return $this;
    }

    /**
     * Set the root folder to work from.
     *
     * @param string $root
     *
     * @return $this
     */
    public function setRoot($root)
    {
        $this->root = rtrim($root, '\\/') . $this->separator;

        return $this;
    }

    /**
     * Returns the ftp username.
     *
     * @return string username
     */
    public function getUsername()
    {
        return $this->safeStorage->retrieveSafely('username') ?: 'anonymous';
    }

    /**
     * Set ftp username.
     *
     * @param string $username
     *
     * @return $this
     */
    public function setUsername($username)
    {
        $this->safeStorage->storeSafely('username', $username);

        return $this;
    }

    /**
     * Returns the password.
     *
     * @return string password
     */
    public function getPassword()
    {
        return $this->safeStorage->retrieveSafely('password');
    }

    /**
     * Set the ftp password.
     *
     * @param string $password
     *
     * @return $this
     */
    public function setPassword($password)
    {
        $this->safeStorage->storeSafely('password', $password);

        return $this;
    }

    /**
     * Returns the amount of seconds before the connection will timeout.
     *
     * @return int
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Set the amount of seconds before the connection should timeout.
     *
     * @param int $timeout
     *
     * @return $this
     */
    public function setTimeout($timeout)
    {
        $this->timeout = (int) $timeout;

        return $this;
    }

    /**
     * Return the FTP system type.
     *
     * @return string
     */
    public function getSystemType()
    {
        return $this->systemType;
    }

    /**
     * Set the FTP system type (windows or unix).
     *
     * @param string $systemType
     *
     * @return $this
     */
    public function setSystemType($systemType)
    {
        $this->systemType = strtolower($systemType);

        return $this;
    }

    /**
     * @inheritdoc
     */
    public function listContents($directory = '', $recursive = false)
    {
        return $this->listDirectoryContents($directory, $recursive);
    }

    abstract protected function listDirectoryContents($directory, $recursive = false);

    /**
     * Normalize a directory listing.
     *
     * @param array  $listing
     * @param string $prefix
     *
     * @return array directory listing
     */
    protected function normalizeListing(array $listing, $prefix = '')
    {
        $base = $prefix;
        $result = [];
        $listing = $this->removeDotDirectories($listing);

        while ($item = array_shift($listing)) {
            if (preg_match('#^.*:$#', $item)) {
                $base = trim($item, ':');
                continue;
            }

            $result[] = $this->normalizeObject($item, $base);
        }

        return $this->sortListing($result);
    }

    /**
     * Sort a directory listing.
     *
     * @param array $result
     *
     * @return array sorted listing
     */
    protected function sortListing(array $result)
    {
        $compare = function ($one, $two) {
            return strnatcmp($one['path'], $two['path']);
        };

        usort($result, $compare);

        return $result;
    }

    /**
     * Normalize a file entry.
     *
     * @param string $item
     * @param string $base
     *
     * @return array normalized file array
     *
     * @throws NotSupportedException
     */
    protected function normalizeObject($item, $base)
    {
        $systemType = $this->systemType ?: $this->detectSystemType($item);

        if ($systemType === 'unix') {
            return $this->normalizeUnixObject($item, $base);
        } elseif ($systemType === 'windows') {
            return $this->normalizeWindowsObject($item, $base);
        }

        throw NotSupportedException::forFtpSystemType($systemType);
    }

    /**
     * Normalize a Unix file entry.
     *
     * @param string $item
     * @param string $base
     *
     * @return array normalized file array
     */
    protected function normalizeUnixObject($item, $base)
    {
        $item = preg_replace('#\s+#', ' ', trim($item), 7);

        if (count(explode(' ', $item, 9)) !== 9) {
            throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
        }

        list($permissions, /* $number */, /* $owner */, /* $group */, $size, /* $month */, /* $day */, /* $time*/, $name) = explode(' ', $item, 9);
        $type = $this->detectType($permissions);
        $path = empty($base) ? $name : $base . $this->separator . $name;

        if ($type === 'dir') {
            return compact('type', 'path');
        }

        $permissions = $this->normalizePermissions($permissions);
        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
        $size = (int) $size;

        return compact('type', 'path', 'visibility', 'size');
    }

    /**
     * Normalize a Windows/DOS file entry.
     *
     * @param string $item
     * @param string $base
     *
     * @return array normalized file array
     */
    protected function normalizeWindowsObject($item, $base)
    {
        $item = preg_replace('#\s+#', ' ', trim($item), 3);

        if (count(explode(' ', $item, 4)) !== 4) {
            throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
        }

        list($date, $time, $size, $name) = explode(' ', $item, 4);
        $path = empty($base) ? $name : $base . $this->separator . $name;

        // Check for the correct date/time format
        $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
        $dt = DateTime::createFromFormat($format, $date . $time);
        $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time");

        if ($size === '<DIR>') {
            $type = 'dir';

            return compact('type', 'path', 'timestamp');
        }

        $type = 'file';
        $visibility = AdapterInterface::VISIBILITY_PUBLIC;
        $size = (int) $size;

        return compact('type', 'path', 'visibility', 'size', 'timestamp');
    }

    /**
     * Get the system type from a listing item.
     *
     * @param string $item
     *
     * @return string the system type
     */
    protected function detectSystemType($item)
    {
        return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix';
    }

    /**
     * Get the file type from the permissions.
     *
     * @param string $permissions
     *
     * @return string file type
     */
    protected function detectType($permissions)
    {
        return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file';
    }

    /**
     * Normalize a permissions string.
     *
     * @param string $permissions
     *
     * @return int
     */
    protected function normalizePermissions($permissions)
    {
        // remove the type identifier
        $permissions = substr($permissions, 1);

        // map the string rights to the numeric counterparts
        $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'];
        $permissions = strtr($permissions, $map);

        // split up the permission groups
        $parts = str_split($permissions, 3);

        // convert the groups
        $mapper = function ($part) {
            return array_sum(str_split($part));
        };

        // get the sum of the groups
        return array_sum(array_map($mapper, $parts));
    }

    /**
     * Filter out dot-directories.
     *
     * @param array $list
     *
     * @return array
     */
    public function removeDotDirectories(array $list)
    {
        $filter = function ($line) {
            if ( ! empty($line) && ! preg_match('#.* \.(\.)?$|^total#', $line)) {
                return true;
            }

            return false;
        };

        return array_filter($list, $filter);
    }

    /**
     * @inheritdoc
     */
    public function has($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Ensure a directory exists.
     *
     * @param string $dirname
     */
    public function ensureDirectory($dirname)
    {
        if ( ! empty($dirname) && ! $this->has($dirname)) {
            $this->createDir($dirname, new Config());
        }
    }

    /**
     * @return mixed
     */
    public function getConnection()
    {
        if ( ! $this->isConnected()) {
            $this->disconnect();
            $this->connect();
        }

        return $this->connection;
    }

    /**
     * Get the public permission value.
     *
     * @return int
     */
    public function getPermPublic()
    {
        return $this->permPublic;
    }

    /**
     * Get the private permission value.
     *
     * @return int
     */
    public function getPermPrivate()
    {
        return $this->permPrivate;
    }

    /**
     * Disconnect on destruction.
     */
    public function __destruct()
    {
        $this->disconnect();
    }

    /**
     * Establish a connection.
     */
    abstract public function connect();

    /**
     * Close the connection.
     */
    abstract public function disconnect();

    /**
     * Check if a connection is active.
     *
     * @return bool
     */
    abstract public function isConnected();
}
<?php

namespace League\Flysystem\Adapter;

use ErrorException;
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
use League\Flysystem\Util;
use League\Flysystem\Util\MimeType;
use RuntimeException;

class Ftp extends AbstractFtpAdapter
{
    use StreamedCopyTrait;

    /**
     * @var int
     */
    protected $transferMode = FTP_BINARY;

    /**
     * @var null|bool
     */
    protected $ignorePassiveAddress = null;

    /**
     * @var bool
     */
    protected $recurseManually = false;

    /**
     * @var array
     */
    protected $configurable = [
        'host',
        'port',
        'username',
        'password',
        'ssl',
        'timeout',
        'root',
        'permPrivate',
        'permPublic',
        'passive',
        'transferMode',
        'systemType',
        'ignorePassiveAddress',
        'recurseManually',
    ];

    /**
     * Set the transfer mode.
     *
     * @param int $mode
     *
     * @return $this
     */
    public function setTransferMode($mode)
    {
        $this->transferMode = $mode;

        return $this;
    }

    /**
     * Set if Ssl is enabled.
     *
     * @param bool $ssl
     *
     * @return $this
     */
    public function setSsl($ssl)
    {
        $this->ssl = (bool) $ssl;

        return $this;
    }

    /**
     * Set if passive mode should be used.
     *
     * @param bool $passive
     */
    public function setPassive($passive = true)
    {
        $this->passive = $passive;
    }

    /**
     * @param bool $ignorePassiveAddress
     */
    public function setIgnorePassiveAddress($ignorePassiveAddress)
    {
        $this->ignorePassiveAddress = $ignorePassiveAddress;
    }

    /**
     * @param bool $recurseManually
     */
    public function setRecurseManually($recurseManually)
    {
        $this->recurseManually = $recurseManually;
    }

    /**
     * Connect to the FTP server.
     */
    public function connect()
    {
        if ($this->ssl) {
            $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout());
        } else {
            $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout());
        }

        if ( ! $this->connection) {
            throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort());
        }

        $this->login();
        $this->setConnectionPassiveMode();
        $this->setConnectionRoot();
    }

    /**
     * Set the connections to passive mode.
     *
     * @throws RuntimeException
     */
    protected function setConnectionPassiveMode()
    {
        if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) {
            ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress);
        }

        if ( ! ftp_pasv($this->connection, $this->passive)) {
            throw new RuntimeException(
                'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort()
            );
        }
    }

    /**
     * Set the connection root.
     */
    protected function setConnectionRoot()
    {
        $root = $this->getRoot();
        $connection = $this->connection;

        if (empty($root) === false && ! ftp_chdir($connection, $root)) {
            throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot());
        }

        // Store absolute path for further reference.
        // This is needed when creating directories and
        // initial root was a relative path, else the root
        // would be relative to the chdir'd path.
        $this->root = ftp_pwd($connection);
    }

    /**
     * Login.
     *
     * @throws RuntimeException
     */
    protected function login()
    {
        set_error_handler(
            function () {
            }
        );
        $isLoggedIn = ftp_login($this->connection, $this->getUsername(), $this->getPassword());
        restore_error_handler();

        if ( ! $isLoggedIn) {
            $this->disconnect();
            throw new RuntimeException(
                'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort(
                ) . ', username: ' . $this->getUsername()
            );
        }
    }

    /**
     * Disconnect from the FTP server.
     */
    public function disconnect()
    {
        if ($this->isConnected()) {
            ftp_close($this->connection);
        }

        $this->connection = null;
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, Config $config)
    {
        $stream = fopen('php://temp', 'w+b');
        fwrite($stream, $contents);
        rewind($stream);
        $result = $this->writeStream($path, $stream, $config);
        fclose($stream);

        if ($result === false) {
            return false;
        }

        $result['contents'] = $contents;
        $result['mimetype'] = Util::guessMimeType($path, $contents);

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function writeStream($path, $resource, Config $config)
    {
        $this->ensureDirectory(Util::dirname($path));

        if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) {
            return false;
        }

        if ($visibility = $config->get('visibility')) {
            $this->setVisibility($path, $visibility);
        }

        return compact('path', 'visibility');
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, Config $config)
    {
        return $this->write($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->writeStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        return ftp_rename($this->getConnection(), $path, $newpath);
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        return ftp_delete($this->getConnection(), $path);
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        $connection = $this->getConnection();
        $contents = array_reverse($this->listDirectoryContents($dirname));

        foreach ($contents as $object) {
            if ($object['type'] === 'file') {
                if ( ! ftp_delete($connection, $object['path'])) {
                    return false;
                }
            } elseif ( ! ftp_rmdir($connection, $object['path'])) {
                return false;
            }
        }

        return ftp_rmdir($connection, $dirname);
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, Config $config)
    {
        $connection = $this->getConnection();
        $directories = explode('/', $dirname);

        foreach ($directories as $directory) {
            if (false === $this->createActualDirectory($directory, $connection)) {
                $this->setConnectionRoot();

                return false;
            }

            ftp_chdir($connection, $directory);
        }

        $this->setConnectionRoot();

        return ['path' => $dirname];
    }

    /**
     * Create a directory.
     *
     * @param string   $directory
     * @param resource $connection
     *
     * @return bool
     */
    protected function createActualDirectory($directory, $connection)
    {
        // List the current directory
        $listing = ftp_nlist($connection, '.') ?: [];

        foreach ($listing as $key => $item) {
            if (preg_match('~^\./.*~', $item)) {
                $listing[$key] = substr($item, 2);
            }
        }

        if (in_array($directory, $listing)) {
            return true;
        }

        return (boolean) ftp_mkdir($connection, $directory);
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        $connection = $this->getConnection();

        if ($path === '') {
            return ['type' => 'dir', 'path' => ''];
        }

        if (@ftp_chdir($connection, $path) === true) {
            $this->setConnectionRoot();

            return ['type' => 'dir', 'path' => $path];
        }

        $listing = ftp_rawlist($connection, '-A ' . str_replace('*', '\\*', $path));

        if (empty($listing)) {
            return false;
        }

        if (preg_match('/.* not found/', $listing[0])) {
            return false;
        }

        if (preg_match('/^total [0-9]*$/', $listing[0])) {
            array_shift($listing);
        }

        return $this->normalizeObject($listing[0], '');
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        if ( ! $metadata = $this->getMetadata($path)) {
            return false;
        }

        $metadata['mimetype'] = MimeType::detectByFilename($path);

        return $metadata;
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        $timestamp = ftp_mdtm($this->getConnection(), $path);

        return ($timestamp !== -1) ? ['timestamp' => $timestamp] : false;
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        if ( ! $object = $this->readStream($path)) {
            return false;
        }

        $object['contents'] = stream_get_contents($object['stream']);
        fclose($object['stream']);
        unset($object['stream']);

        return $object;
    }

    /**
     * @inheritdoc
     */
    public function readStream($path)
    {
        $stream = fopen('php://temp', 'w+b');
        $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
        rewind($stream);

        if ( ! $result) {
            fclose($stream);

            return false;
        }

        return compact('stream');
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate();

        if ( ! ftp_chmod($this->getConnection(), $mode, $path)) {
            return false;
        }

        return compact('visibility');
    }

    /**
     * @inheritdoc
     *
     * @param string $directory
     */
    protected function listDirectoryContents($directory, $recursive = true)
    {
        $directory = str_replace('*', '\\*', $directory);

        if ($recursive && $this->recurseManually) {
            return $this->listDirectoryContentsRecursive($directory);
        }

        $options = $recursive ? '-alnR' : '-aln';
        $listing = ftp_rawlist($this->getConnection(), $options . ' ' . $directory);

        return $listing ? $this->normalizeListing($listing, $directory) : [];
    }

    /**
     * @inheritdoc
     *
     * @param string $directory
     */
    protected function listDirectoryContentsRecursive($directory)
    {
        $listing = $this->normalizeListing(ftp_rawlist($this->getConnection(), '-aln' . ' ' . $directory) ?: []);
        $output = [];

        foreach ($listing as $directory) {
            $output[] = $directory;
            if ($directory['type'] !== 'dir') continue;

            $output = array_merge($output, $this->listDirectoryContentsRecursive($directory['path']));
        }

        return $output;
    }

    /**
     * Check if the connection is open.
     *
     * @return bool
     * @throws ErrorException
     */
    public function isConnected()
    {
        try {
            return is_resource($this->connection) && ftp_rawlist($this->connection, '/') !== false;
        } catch (ErrorException $e) {
            fclose($this->connection);
            $this->connection = null;

            if (strpos($e->getMessage(), 'ftp_rawlist') === false) {
                throw $e;
            }

            return false;
        }
    }
}
<?php

namespace League\Flysystem\Adapter;

class Ftpd extends Ftp
{
    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        if (empty($path) || ! ($object = ftp_raw($this->getConnection(), 'STAT ' . $path)) || count($object) < 3) {
            return false;
        }

        if (substr($object[1], 0, 5) === "ftpd:") {
            return false;
        }

        return $this->normalizeObject($object[1], '');
    }

    /**
     * @inheritdoc
     */
    protected function listDirectoryContents($directory, $recursive = true)
    {
        $listing = ftp_rawlist($this->getConnection(), $directory, $recursive);

        if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) {
            return [];
        }

        return $this->normalizeListing($listing, $directory);
    }
}
<?php

namespace League\Flysystem\Adapter;

use DirectoryIterator;
use FilesystemIterator;
use finfo as Finfo;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
use League\Flysystem\Exception;
use League\Flysystem\NotSupportedException;
use League\Flysystem\UnreadableFileException;
use League\Flysystem\Util;
use LogicException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;

class Local extends AbstractAdapter
{
    /**
     * @var int
     */
    const SKIP_LINKS = 0001;

    /**
     * @var int
     */
    const DISALLOW_LINKS = 0002;

    /**
     * @var array
     */
    protected static $permissions = [
        'file' => [
            'public' => 0644,
            'private' => 0600,
        ],
        'dir' => [
            'public' => 0755,
            'private' => 0700,
        ]
    ];

    /**
     * @var string
     */
    protected $pathSeparator = DIRECTORY_SEPARATOR;

    /**
     * @var array
     */
    protected $permissionMap;

    /**
     * @var int
     */
    protected $writeFlags;
    /**
     * @var int
     */
    private $linkHandling;

    /**
     * Constructor.
     *
     * @param string $root
     * @param int    $writeFlags
     * @param int    $linkHandling
     * @param array  $permissions
     */
    public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = [])
    {
        $root = is_link($root) ? realpath($root) : $root;
        $this->permissionMap = array_replace_recursive(static::$permissions, $permissions);
        $this->ensureDirectory($root);

        if ( ! is_dir($root) || ! is_readable($root)) {
            throw new LogicException('The root path ' . $root . ' is not readable.');
        }

        $this->setPathPrefix($root);
        $this->writeFlags = $writeFlags;
        $this->linkHandling = $linkHandling;
    }

    /**
     * Ensure the root directory exists.
     *
     * @param string $root root directory path
     *
     * @return void
     *
     * @throws Exception in case the root directory can not be created
     */
    protected function ensureDirectory($root)
    {
        if ( ! is_dir($root)) {
            $umask = umask(0);
            @mkdir($root, $this->permissionMap['dir']['public'], true);
            umask($umask);

            if ( ! is_dir($root)) {
                throw new Exception(sprintf('Impossible to create the root directory "%s".', $root));
            }
        }
    }

    /**
     * @inheritdoc
     */
    public function has($path)
    {
        $location = $this->applyPathPrefix($path);

        return file_exists($location);
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $this->ensureDirectory(dirname($location));
		
        if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
            return false;
        }
		
        $type = 'file';
        $result = compact('contents', 'type', 'size', 'path');

        if ($visibility = $config->get('visibility')) {
            $result['visibility'] = $visibility;
            $this->setVisibility($path, $visibility);
        }

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function writeStream($path, $resource, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $this->ensureDirectory(dirname($location));
        $stream = fopen($location, 'w+b');

        if ( ! $stream) {
            return false;
        }

        stream_copy_to_stream($resource, $stream);

        if ( ! fclose($stream)) {
            return false;
        }

        if ($visibility = $config->get('visibility')) {
            $this->setVisibility($path, $visibility);
        }

        return compact('path', 'visibility');
    }

    /**
     * @inheritdoc
     */
    public function readStream($path)
    {
        $location = $this->applyPathPrefix($path);
        $stream = fopen($location, 'rb');

        return compact('stream', 'path');
    }

    /**
     * @inheritdoc
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->writeStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $mimetype = Util::guessMimeType($path, $contents);
        $size = file_put_contents($location, $contents, $this->writeFlags);

        if ($size === false) {
            return false;
        }

        return compact('path', 'size', 'contents', 'mimetype');
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        $location = $this->applyPathPrefix($path);
        $contents = file_get_contents($location);

        if ($contents === false) {
            return false;
        }

        return compact('contents', 'path');
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        $location = $this->applyPathPrefix($path);
        $destination = $this->applyPathPrefix($newpath);
        $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath));
        $this->ensureDirectory($parentDirectory);

        return rename($location, $destination);
    }

    /**
     * @inheritdoc
     */
    public function copy($path, $newpath)
    {
        $location = $this->applyPathPrefix($path);
        $destination = $this->applyPathPrefix($newpath);
        $this->ensureDirectory(dirname($destination));

        return copy($location, $destination);
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        $location = $this->applyPathPrefix($path);

        return unlink($location);
    }

    /**
     * @inheritdoc
     */
    public function listContents($directory = '', $recursive = false)
    {
        $result = [];
        $location = $this->applyPathPrefix($directory);

        if ( ! is_dir($location)) {
            return [];
        }

        $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location);

        foreach ($iterator as $file) {
            $path = $this->getFilePath($file);

            if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) {
                continue;
            }

            $result[] = $this->normalizeFileInfo($file);
        }

        return array_filter($result);
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        $location = $this->applyPathPrefix($path);
        $info = new SplFileInfo($location);

        return $this->normalizeFileInfo($info);
    }

    /**
     * @inheritdoc
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        $location = $this->applyPathPrefix($path);
        $finfo = new Finfo(FILEINFO_MIME_TYPE);
        $mimetype = $finfo->file($location);

        if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty'])) {
            $mimetype = Util\MimeType::detectByFilename($location);
        }

        return ['mimetype' => $mimetype];
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        $location = $this->applyPathPrefix($path);
        clearstatcache(false, $location);
        $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4));
        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;

        return compact('visibility');
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        $location = $this->applyPathPrefix($path);
        $type = is_dir($location) ? 'dir' : 'file';
        $success = chmod($location, $this->permissionMap[$type][$visibility]);

        if ($success === false) {
            return false;
        }

        return compact('visibility');
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, Config $config)
    {
        $location = $this->applyPathPrefix($dirname);
        $umask = umask(0);
        $visibility = $config->get('visibility', 'public');

        if ( ! is_dir($location) && ! mkdir($location, $this->permissionMap['dir'][$visibility], true)) {
            $return = false;
        } else {
            $return = ['path' => $dirname, 'type' => 'dir'];
        }

        umask($umask);

        return $return;
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        $location = $this->applyPathPrefix($dirname);

        if ( ! is_dir($location)) {
            return false;
        }

        $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST);

        /** @var SplFileInfo $file */
        foreach ($contents as $file) {
            $this->guardAgainstUnreadableFileInfo($file);
            $this->deleteFileInfoObject($file);
        }

        return rmdir($location);
    }

    /**
     * @param SplFileInfo $file
     */
    protected function deleteFileInfoObject(SplFileInfo $file)
    {
        switch ($file->getType()) {
            case 'dir':
                rmdir($file->getRealPath());
                break;
            case 'link':
                unlink($file->getPathname());
                break;
            default:
                unlink($file->getRealPath());
        }
    }

    /**
     * Normalize the file info.
     *
     * @param SplFileInfo $file
     *
     * @return array
     */
    protected function normalizeFileInfo(SplFileInfo $file)
    {
        if ( ! $file->isLink()) {
            return $this->mapFileInfo($file);
        }

        if ($this->linkHandling & self::DISALLOW_LINKS) {
            throw NotSupportedException::forLink($file);
        }
    }

    /**
     * Get the normalized path from a SplFileInfo object.
     *
     * @param SplFileInfo $file
     *
     * @return string
     */
    protected function getFilePath(SplFileInfo $file)
    {
        $location = $file->getPathname();
        $path = $this->removePathPrefix($location);

        return trim(str_replace('\\', '/', $path), '/');
    }

    /**
     * @param string $path
     * @param int    $mode
     *
     * @return RecursiveIteratorIterator
     */
    protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST)
    {
        return new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
            $mode
        );
    }

    /**
     * @param string $path
     *
     * @return DirectoryIterator
     */
    protected function getDirectoryIterator($path)
    {
        $iterator = new DirectoryIterator($path);

        return $iterator;
    }

    /**
     * @param SplFileInfo $file
     *
     * @return array
     */
    protected function mapFileInfo(SplFileInfo $file)
    {
        $normalized = [
            'type' => $file->getType(),
            'path' => $this->getFilePath($file),
        ];

        $normalized['timestamp'] = $file->getMTime();

        if ($normalized['type'] === 'file') {
            $normalized['size'] = $file->getSize();
        }

        return $normalized;
    }

    /**
     * @inheritdoc
     */
    public function applyPathPrefix($path)
    {
        $prefixedPath = parent::applyPathPrefix($path);

        return str_replace('/', DIRECTORY_SEPARATOR, $prefixedPath);
    }

    /**
     * @param SplFileInfo $file
     *
     * @throws UnreadableFileException
     */
    protected function guardAgainstUnreadableFileInfo(SplFileInfo $file)
    {
        if ( ! $file->isReadable()) {
            throw UnreadableFileException::forFileInfo($file);
        }
    }
}
<?php

namespace League\Flysystem\Adapter;

use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
use League\Flysystem\Adapter\Polyfill\StreamedTrait;
use League\Flysystem\Config;

class NullAdapter extends AbstractAdapter
{
    use StreamedTrait;
    use StreamedCopyTrait;

    /**
     * Check whether a file is present.
     *
     * @param string $path
     *
     * @return bool
     */
    public function has($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, Config $config)
    {
        $type = 'file';
        $result = compact('contents', 'type', 'path');

        if ($visibility = $config->get('visibility')) {
            $result['visibility'] = $visibility;
        }

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, Config $config)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function listContents($directory = '', $recursive = false)
    {
        return [];
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function getSize($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        return false;
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        return compact('visibility');
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, Config $config)
    {
        return ['path' => $dirname, 'type' => 'dir'];
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        return false;
    }
}
<?php

namespace League\Flysystem\Adapter\Polyfill;

use LogicException;

trait NotSupportingVisibilityTrait
{
    /**
     * Get the visibility of a file.
     *
     * @param string $path
     *
     * @throws LogicException
     */
    public function getVisibility($path)
    {
        throw new LogicException(get_class($this) . ' does not support visibility. Path: ' . $path);
    }

    /**
     * Set the visibility for a file.
     *
     * @param string $path
     * @param string $visibility
     *
     * @throws LogicException
     */
    public function setVisibility($path, $visibility)
    {
        throw new LogicException(get_class($this) . ' does not support visibility. Path: ' . $path . ', visibility: ' . $visibility);
    }
}
<?php

namespace League\Flysystem\Adapter\Polyfill;

use League\Flysystem\Config;

trait StreamedCopyTrait
{
    /**
     * Copy a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function copy($path, $newpath)
    {
        $response = $this->readStream($path);

        if ($response === false || ! is_resource($response['stream'])) {
            return false;
        }

        $result = $this->writeStream($newpath, $response['stream'], new Config());

        if ($result !== false && is_resource($response['stream'])) {
            fclose($response['stream']);
        }

        return $result !== false;
    }

    // Required abstract method

    /**
     * @param string $path
     */
    abstract public function readStream($path);

    /**
     * @param string $path
     */
    abstract public function writeStream($path, $resource, Config $config);
}
<?php

namespace League\Flysystem\Adapter\Polyfill;

/**
 * A helper for adapters that only handle strings to provide read streams.
 */
trait StreamedReadingTrait
{
    /**
     * Reads a file as a stream.
     *
     * @param string $path
     *
     * @return array|false
     *
     * @see League\Flysystem\ReadInterface::readStream()
     */
    public function readStream($path)
    {
        if ( ! $data = $this->read($path)) {
            return false;
        }

        $stream = fopen('php://temp', 'w+b');
        fwrite($stream, $data['contents']);
        rewind($stream);
        $data['stream'] = $stream;
        unset($data['contents']);

        return $data;
    }

    /**
     * Reads a file.
     *
     * @param string $path
     *
     * @return array|false
     *
     * @see League\Flysystem\ReadInterface::read()
     */
    abstract public function read($path);
}
<?php

namespace League\Flysystem\Adapter\Polyfill;

trait StreamedTrait
{
    use StreamedReadingTrait;
    use StreamedWritingTrait;
}
<?php

namespace League\Flysystem\Adapter\Polyfill;

use League\Flysystem\Config;
use League\Flysystem\Util;

trait StreamedWritingTrait
{
    /**
     * Stream fallback delegator.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config
     * @param string   $fallback
     *
     * @return mixed fallback result
     */
    protected function stream($path, $resource, Config $config, $fallback)
    {
        Util::rewindStream($resource);
        $contents = stream_get_contents($resource);
        $fallbackCall = [$this, $fallback];

        return call_user_func($fallbackCall, $path, $contents, $config);
    }

    /**
     * Write using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config
     *
     * @return mixed false or file metadata
     */
    public function writeStream($path, $resource, Config $config)
    {
        return $this->stream($path, $resource, $config, 'write');
    }

    /**
     * Update a file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config   Config object or visibility setting
     *
     * @return mixed false of file metadata
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->stream($path, $resource, $config, 'update');
    }

    // Required abstract methods
    abstract public function write($pash, $contents, Config $config);
    abstract public function update($pash, $contents, Config $config);
}
<?php

namespace League\Flysystem\Adapter;

class SynologyFtp extends Ftpd
{
    // This class merely exists because of BC.
}
<?php

namespace League\Flysystem;

interface AdapterInterface extends ReadInterface
{
    /**
     * @const  VISIBILITY_PUBLIC  public visibility
     */
    const VISIBILITY_PUBLIC = 'public';

    /**
     * @const  VISIBILITY_PRIVATE  private visibility
     */
    const VISIBILITY_PRIVATE = 'private';

    /**
     * Write a new file.
     *
     * @param string $path
     * @param string $contents
     * @param Config $config   Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function write($path, $contents, Config $config);

    /**
     * Write a new file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config   Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function writeStream($path, $resource, Config $config);

    /**
     * Update a file.
     *
     * @param string $path
     * @param string $contents
     * @param Config $config   Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function update($path, $contents, Config $config);

    /**
     * Update a file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config   Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function updateStream($path, $resource, Config $config);

    /**
     * Rename a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function rename($path, $newpath);

    /**
     * Copy a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function copy($path, $newpath);

    /**
     * Delete a file.
     *
     * @param string $path
     *
     * @return bool
     */
    public function delete($path);

    /**
     * Delete a directory.
     *
     * @param string $dirname
     *
     * @return bool
     */
    public function deleteDir($dirname);

    /**
     * Create a directory.
     *
     * @param string $dirname directory name
     * @param Config $config
     *
     * @return array|false
     */
    public function createDir($dirname, Config $config);

    /**
     * Set the visibility for a file.
     *
     * @param string $path
     * @param string $visibility
     *
     * @return array|false file meta data
     */
    public function setVisibility($path, $visibility);
}
<?php

namespace League\Flysystem;

class Config
{
    /**
     * @var array
     */
    protected $settings = [];

    /**
     * @var Config
     */
    protected $fallback;

    /**
     * Constructor.
     *
     * @param array $settings
     */
    public function __construct(array $settings = [])
    {
        $this->settings = $settings;
    }

    /**
     * Get a setting.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed config setting or default when not found
     */
    public function get($key, $default = null)
    {
        if ( ! array_key_exists($key, $this->settings)) {
            return $this->getDefault($key, $default);
        }

        return $this->settings[$key];
    }

    /**
     * Check if an item exists by key.
     *
     * @param string $key
     *
     * @return bool
     */
    public function has($key)
    {
        if (array_key_exists($key, $this->settings)) {
            return true;
        }

        return $this->fallback instanceof Config
            ? $this->fallback->has($key)
            : false;
    }

    /**
     * Try to retrieve a default setting from a config fallback.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed config setting or default when not found
     */
    protected function getDefault($key, $default)
    {
        if ( ! $this->fallback) {
            return $default;
        }

        return $this->fallback->get($key, $default);
    }

    /**
     * Set a setting.
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return $this
     */
    public function set($key, $value)
    {
        $this->settings[$key] = $value;

        return $this;
    }

    /**
     * Set the fallback.
     *
     * @param Config $fallback
     *
     * @return $this
     */
    public function setFallback(Config $fallback)
    {
        $this->fallback = $fallback;

        return $this;
    }
}
<?php

namespace League\Flysystem;

/**
 * @internal
 */
trait ConfigAwareTrait
{
    /**
     * @var Config
     */
    protected $config;

    /**
     * Set the config.
     *
     * @param Config|array|null $config
     */
    protected function setConfig($config)
    {
        $this->config = $config ? Util::ensureConfig($config) : new Config;
    }

    /**
     * Get the Config.
     *
     * @return Config config object
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Convert a config array to a Config object with the correct fallback.
     *
     * @param array $config
     *
     * @return Config
     */
    protected function prepareConfig(array $config)
    {
        $config = new Config($config);
        $config->setFallback($this->getConfig());

        return $config;
    }
}
<?php

namespace League\Flysystem;

class Directory extends Handler
{
    /**
     * Delete the directory.
     *
     * @return bool
     */
    public function delete()
    {
        return $this->filesystem->deleteDir($this->path);
    }

    /**
     * List the directory contents.
     *
     * @param bool $recursive
     *
     * @return array|bool directory contents or false
     */
    public function getContents($recursive = false)
    {
        return $this->filesystem->listContents($this->path, $recursive);
    }
}
<?php

namespace League\Flysystem;

class Exception extends \Exception
{
    //
}
<?php

namespace League\Flysystem;

class File extends Handler
{
    /**
     * Check whether the file exists.
     *
     * @return bool
     */
    public function exists()
    {
        return $this->filesystem->has($this->path);
    }

    /**
     * Read the file.
     *
     * @return string file contents
     */
    public function read()
    {
        return $this->filesystem->read($this->path);
    }

    /**
     * Read the file as a stream.
     *
     * @return resource file stream
     */
    public function readStream()
    {
        return $this->filesystem->readStream($this->path);
    }

    /**
     * Write the new file.
     *
     * @param string $content
     *
     * @return bool success boolean
     */
    public function write($content)
    {
        return $this->filesystem->write($this->path, $content);
    }

    /**
     * Write the new file using a stream.
     *
     * @param resource $resource
     *
     * @return bool success boolean
     */
    public function writeStream($resource)
    {
        return $this->filesystem->writeStream($this->path, $resource);
    }

    /**
     * Update the file contents.
     *
     * @param string $content
     *
     * @return bool success boolean
     */
    public function update($content)
    {
        return $this->filesystem->update($this->path, $content);
    }

    /**
     * Update the file contents with a stream.
     *
     * @param resource $resource
     *
     * @return bool success boolean
     */
    public function updateStream($resource)
    {
        return $this->filesystem->updateStream($this->path, $resource);
    }

    /**
     * Create the file or update if exists.
     *
     * @param string $content
     *
     * @return bool success boolean
     */
    public function put($content)
    {
        return $this->filesystem->put($this->path, $content);
    }

    /**
     * Create the file or update if exists using a stream.
     *
     * @param resource $resource
     *
     * @return bool success boolean
     */
    public function putStream($resource)
    {
        return $this->filesystem->putStream($this->path, $resource);
    }

    /**
     * Rename the file.
     *
     * @param string $newpath
     *
     * @return bool success boolean
     */
    public function rename($newpath)
    {
        if ($this->filesystem->rename($this->path, $newpath)) {
            $this->path = $newpath;

            return true;
        }

        return false;
    }

    /**
     * Copy the file.
     *
     * @param string $newpath
     *
     * @return File|false new file or false
     */
    public function copy($newpath)
    {
        if ($this->filesystem->copy($this->path, $newpath)) {
            return new File($this->filesystem, $newpath);
        }

        return false;
    }

    /**
     * Get the file's timestamp.
     *
     * @return int unix timestamp
     */
    public function getTimestamp()
    {
        return $this->filesystem->getTimestamp($this->path);
    }

    /**
     * Get the file's mimetype.
     *
     * @return string mimetime
     */
    public function getMimetype()
    {
        return $this->filesystem->getMimetype($this->path);
    }

    /**
     * Get the file's visibility.
     *
     * @return string visibility
     */
    public function getVisibility()
    {
        return $this->filesystem->getVisibility($this->path);
    }

    /**
     * Get the file's metadata.
     *
     * @return array
     */
    public function getMetadata()
    {
        return $this->filesystem->getMetadata($this->path);
    }

    /**
     * Get the file size.
     *
     * @return int file size
     */
    public function getSize()
    {
        return $this->filesystem->getSize($this->path);
    }

    /**
     * Delete the file.
     *
     * @return bool success boolean
     */
    public function delete()
    {
        return $this->filesystem->delete($this->path);
    }
}
<?php

namespace League\Flysystem;

use Exception as BaseException;

class FileExistsException extends Exception
{
    /**
     * @var string
     */
    protected $path;

    /**
     * Constructor.
     *
     * @param string        $path
     * @param int           $code
     * @param BaseException $previous
     */
    public function __construct($path, $code = 0, BaseException $previous = null)
    {
        $this->path = $path;

        parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous);
    }

    /**
     * Get the path which was found.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}
<?php

namespace League\Flysystem;

use Exception as BaseException;

class FileNotFoundException extends Exception
{
    /**
     * @var string
     */
    protected $path;

    /**
     * Constructor.
     *
     * @param string     $path
     * @param int        $code
     * @param \Exception $previous
     */
    public function __construct($path, $code = 0, BaseException $previous = null)
    {
        $this->path = $path;

        parent::__construct('File not found at path: ' . $this->getPath(), $code, $previous);
    }

    /**
     * Get the path which was not found.
     *
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }
}
<?php

namespace League\Flysystem;

use InvalidArgumentException;
use League\Flysystem\Plugin\PluggableTrait;
use League\Flysystem\Util\ContentListingFormatter;

/**
 * @method array getWithMetadata(string $path, array $metadata)
 * @method bool  forceCopy(string $path, string $newpath)
 * @method bool  forceRename(string $path, string $newpath)
 * @method array listFiles(string $path = '', boolean $recursive = false)
 * @method array listPaths(string $path = '', boolean $recursive = false)
 * @method array listWith(array $keys = [], $directory = '', $recursive = false)
 */
class Filesystem implements FilesystemInterface
{
    use PluggableTrait;
    use ConfigAwareTrait;

    /**
     * @var AdapterInterface
     */
    protected $adapter;

    /**
     * Constructor.
     *
     * @param AdapterInterface $adapter
     * @param Config|array     $config
     */
    public function __construct(AdapterInterface $adapter, $config = null)
    {
        $this->adapter = $adapter;
        $this->setConfig($config);
    }

    /**
     * Get the Adapter.
     *
     * @return AdapterInterface adapter
     */
    public function getAdapter()
    {
        return $this->adapter;
    }

    /**
     * @inheritdoc
     */
    public function has($path)
    {
        $path = Util::normalizePath($path);

        return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path);
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, array $config = [])
    {
        $path = Util::normalizePath($path);
        $this->assertAbsent($path);
        $config = $this->prepareConfig($config);

        return (bool) $this->getAdapter()->write($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function writeStream($path, $resource, array $config = [])
    {
        if ( ! is_resource($resource)) {
            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
        }

        $path = Util::normalizePath($path);
        $this->assertAbsent($path);
        $config = $this->prepareConfig($config);

        Util::rewindStream($resource);

        return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function put($path, $contents, array $config = [])
    {
        $path = Util::normalizePath($path);
        $config = $this->prepareConfig($config);

        if ($this->has($path)) {
            return (bool) $this->getAdapter()->update($path, $contents, $config);
        }

        return (bool) $this->getAdapter()->write($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function putStream($path, $resource, array $config = [])
    {
        if ( ! is_resource($resource)) {
            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
        }

        $path = Util::normalizePath($path);
        $config = $this->prepareConfig($config);
        Util::rewindStream($resource);

        if ($this->has($path)) {
            return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
        }

        return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function readAndDelete($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);
        $contents = $this->read($path);

        if ($contents === false) {
            return false;
        }

        $this->delete($path);

        return $contents;
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, array $config = [])
    {
        $path = Util::normalizePath($path);
        $config = $this->prepareConfig($config);

        $this->assertPresent($path);

        return (bool) $this->getAdapter()->update($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function updateStream($path, $resource, array $config = [])
    {
        if ( ! is_resource($resource)) {
            throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
        }

        $path = Util::normalizePath($path);
        $config = $this->prepareConfig($config);
        $this->assertPresent($path);
        Util::rewindStream($resource);

        return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if ( ! ($object = $this->getAdapter()->read($path))) {
            return false;
        }

        return $object['contents'];
    }

    /**
     * @inheritdoc
     */
    public function readStream($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if ( ! $object = $this->getAdapter()->readStream($path)) {
            return false;
        }

        return $object['stream'];
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        $path = Util::normalizePath($path);
        $newpath = Util::normalizePath($newpath);
        $this->assertPresent($path);
        $this->assertAbsent($newpath);

        return (bool) $this->getAdapter()->rename($path, $newpath);
    }

    /**
     * @inheritdoc
     */
    public function copy($path, $newpath)
    {
        $path = Util::normalizePath($path);
        $newpath = Util::normalizePath($newpath);
        $this->assertPresent($path);
        $this->assertAbsent($newpath);

        return $this->getAdapter()->copy($path, $newpath);
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        return $this->getAdapter()->delete($path);
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        $dirname = Util::normalizePath($dirname);

        if ($dirname === '') {
            throw new RootViolationException('Root directories can not be deleted.');
        }

        return (bool) $this->getAdapter()->deleteDir($dirname);
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, array $config = [])
    {
        $dirname = Util::normalizePath($dirname);
        $config = $this->prepareConfig($config);

        return (bool) $this->getAdapter()->createDir($dirname, $config);
    }

    /**
     * @inheritdoc
     */
    public function listContents($directory = '', $recursive = false)
    {
        $directory = Util::normalizePath($directory);
        $contents = $this->getAdapter()->listContents($directory, $recursive);

        return (new ContentListingFormatter($directory, $recursive))->formatListing($contents);
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if ( ! $object = $this->getAdapter()->getMimetype($path)) {
            return false;
        }

        return $object['mimetype'];
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if ( ! $object = $this->getAdapter()->getTimestamp($path)) {
            return false;
        }

        return $object['timestamp'];
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        if (($object = $this->getAdapter()->getVisibility($path)) === false) {
            return false;
        }

        return $object['visibility'];
    }

    /**
     * @inheritdoc
     */
    public function getSize($path)
    {
        $path = Util::normalizePath($path);

        if (($object = $this->getAdapter()->getSize($path)) === false || ! isset($object['size'])) {
            return false;
        }

        return (int) $object['size'];
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        $path = Util::normalizePath($path);

        return (bool) $this->getAdapter()->setVisibility($path, $visibility);
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        $path = Util::normalizePath($path);
        $this->assertPresent($path);

        return $this->getAdapter()->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function get($path, Handler $handler = null)
    {
        $path = Util::normalizePath($path);

        if ( ! $handler) {
            $metadata = $this->getMetadata($path);
            $handler = $metadata['type'] === 'file' ? new File($this, $path) : new Directory($this, $path);
        }

        $handler->setPath($path);
        $handler->setFilesystem($this);

        return $handler;
    }

    /**
     * Assert a file is present.
     *
     * @param string $path path to file
     *
     * @throws FileNotFoundException
     *
     * @return void
     */
    public function assertPresent($path)
    {
        if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) {
            throw new FileNotFoundException($path);
        }
    }

    /**
     * Assert a file is absent.
     *
     * @param string $path path to file
     *
     * @throws FileExistsException
     *
     * @return void
     */
    public function assertAbsent($path)
    {
        if ($this->config->get('disable_asserts', false) === false && $this->has($path)) {
            throw new FileExistsException($path);
        }
    }
}
<?php

namespace League\Flysystem;

interface FilesystemInterface
{
    /**
     * Check whether a file exists.
     *
     * @param string $path
     *
     * @return bool
     */
    public function has($path);

    /**
     * Read a file.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file contents or false on failure.
     */
    public function read($path);

    /**
     * Retrieves a read-stream for a path.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return resource|false The path resource or false on failure.
     */
    public function readStream($path);

    /**
     * List contents of a directory.
     *
     * @param string $directory The directory to list.
     * @param bool   $recursive Whether to list recursively.
     *
     * @return array A list of file metadata.
     */
    public function listContents($directory = '', $recursive = false);

    /**
     * Get a file's metadata.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return array|false The file metadata or false on failure.
     */
    public function getMetadata($path);

    /**
     * Get a file's size.
     *
     * @param string $path The path to the file.
     *
     * @return int|false The file size or false on failure.
     */
    public function getSize($path);

    /**
     * Get a file's mime-type.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file mime-type or false on failure.
     */
    public function getMimetype($path);

    /**
     * Get a file's timestamp.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The timestamp or false on failure.
     */
    public function getTimestamp($path);

    /**
     * Get a file's visibility.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The visibility (public|private) or false on failure.
     */
    public function getVisibility($path);

    /**
     * Write a new file.
     *
     * @param string $path     The path of the new file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @throws FileExistsException
     *
     * @return bool True on success, false on failure.
     */
    public function write($path, $contents, array $config = []);

    /**
     * Write a new file using a stream.
     *
     * @param string   $path     The path of the new file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException If $resource is not a file handle.
     * @throws FileExistsException
     *
     * @return bool True on success, false on failure.
     */
    public function writeStream($path, $resource, array $config = []);

    /**
     * Update an existing file.
     *
     * @param string $path     The path of the existing file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function update($path, $contents, array $config = []);

    /**
     * Update an existing file using a stream.
     *
     * @param string   $path     The path of the existing file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException If $resource is not a file handle.
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function updateStream($path, $resource, array $config = []);

    /**
     * Rename a file.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileExistsException   Thrown if $newpath exists.
     * @throws FileNotFoundException Thrown if $path does not exist.
     *
     * @return bool True on success, false on failure.
     */
    public function rename($path, $newpath);

    /**
     * Copy a file.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileExistsException   Thrown if $newpath exists.
     * @throws FileNotFoundException Thrown if $path does not exist.
     *
     * @return bool True on success, false on failure.
     */
    public function copy($path, $newpath);

    /**
     * Delete a file.
     *
     * @param string $path
     *
     * @throws FileNotFoundException
     *
     * @return bool True on success, false on failure.
     */
    public function delete($path);

    /**
     * Delete a directory.
     *
     * @param string $dirname
     *
     * @throws RootViolationException Thrown if $dirname is empty.
     *
     * @return bool True on success, false on failure.
     */
    public function deleteDir($dirname);

    /**
     * Create a directory.
     *
     * @param string $dirname The name of the new directory.
     * @param array  $config  An optional configuration array.
     *
     * @return bool True on success, false on failure.
     */
    public function createDir($dirname, array $config = []);

    /**
     * Set the visibility for a file.
     *
     * @param string $path       The path to the file.
     * @param string $visibility One of 'public' or 'private'.
     *
     * @return bool True on success, false on failure.
     */
    public function setVisibility($path, $visibility);

    /**
     * Create a file or update if exists.
     *
     * @param string $path     The path to the file.
     * @param string $contents The file contents.
     * @param array  $config   An optional configuration array.
     *
     * @return bool True on success, false on failure.
     */
    public function put($path, $contents, array $config = []);

    /**
     * Create a file or update if exists.
     *
     * @param string   $path     The path to the file.
     * @param resource $resource The file handle.
     * @param array    $config   An optional configuration array.
     *
     * @throws InvalidArgumentException Thrown if $resource is not a resource.
     *
     * @return bool True on success, false on failure.
     */
    public function putStream($path, $resource, array $config = []);

    /**
     * Read and delete a file.
     *
     * @param string $path The path to the file.
     *
     * @throws FileNotFoundException
     *
     * @return string|false The file contents, or false on failure.
     */
    public function readAndDelete($path);

    /**
     * Get a file/directory handler.
     *
     * @param string  $path    The path to the file.
     * @param Handler $handler An optional existing handler to populate.
     *
     * @return Handler Either a file or directory handler.
     */
    public function get($path, Handler $handler = null);

    /**
     * Register a plugin.
     *
     * @param PluginInterface $plugin The plugin to register.
     *
     * @return $this
     */
    public function addPlugin(PluginInterface $plugin);
}
<?php

namespace League\Flysystem;

use BadMethodCallException;

abstract class Handler
{
    /**
     * @var string
     */
    protected $path;

    /**
     * @var FilesystemInterface
     */
    protected $filesystem;

    /**
     * Constructor.
     *
     * @param FilesystemInterface $filesystem
     * @param string              $path
     */
    public function __construct(FilesystemInterface $filesystem = null, $path = null)
    {
        $this->path = $path;
        $this->filesystem = $filesystem;
    }

    /**
     * Check whether the entree is a directory.
     *
     * @return bool
     */
    public function isDir()
    {
        return $this->getType() === 'dir';
    }

    /**
     * Check whether the entree is a file.
     *
     * @return bool
     */
    public function isFile()
    {
        return $this->getType() === 'file';
    }

    /**
     * Retrieve the entree type (file|dir).
     *
     * @return string file or dir
     */
    public function getType()
    {
        $metadata = $this->filesystem->getMetadata($this->path);

        return $metadata['type'];
    }

    /**
     * Set the Filesystem object.
     *
     * @param FilesystemInterface $filesystem
     *
     * @return $this
     */
    public function setFilesystem(FilesystemInterface $filesystem)
    {
        $this->filesystem = $filesystem;

        return $this;
    }
    
    /**
     * Retrieve the Filesystem object.
     *
     * @return FilesystemInterface
     */
    public function getFilesystem()
    {
        return $this->filesystem;
    }

    /**
     * Set the entree path.
     *
     * @param string $path
     *
     * @return $this
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * Retrieve the entree path.
     *
     * @return string path
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * Plugins pass-through.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($method, array $arguments)
    {
        array_unshift($arguments, $this->path);
        $callback = [$this->filesystem, $method];

        try {
            return call_user_func_array($callback, $arguments);
        } catch (BadMethodCallException $e) {
            throw new BadMethodCallException(
                'Call to undefined method '
                . get_called_class()
                . '::' . $method
            );
        }
    }
}
<?php

namespace League\Flysystem;

use InvalidArgumentException;
use League\Flysystem\Plugin\PluggableTrait;
use League\Flysystem\Plugin\PluginNotFoundException;
use LogicException;

/**
 * Class MountManager.
 *
 * Proxies methods to Filesystem (@see __call):
 *
 * @method AdapterInterface getAdapter($prefix)
 * @method Config getConfig($prefix)
 * @method bool has($path)
 * @method bool write($path, $contents, array $config = [])
 * @method bool writeStream($path, $resource, array $config = [])
 * @method bool put($path, $contents, $config = [])
 * @method bool putStream($path, $contents, $config = [])
 * @method string readAndDelete($path)
 * @method bool update($path, $contents, $config = [])
 * @method bool updateStream($path, $resource, $config = [])
 * @method string|false read($path)
 * @method resource|false readStream($path)
 * @method bool rename($path, $newpath)
 * @method bool delete($path)
 * @method bool deleteDir($dirname)
 * @method bool createDir($dirname, $config = [])
 * @method array listFiles($directory = '', $recursive = false)
 * @method array listPaths($directory = '', $recursive = false)
 * @method array getWithMetadata($path, array $metadata)
 * @method string|false getMimetype($path)
 * @method string|false getTimestamp($path)
 * @method string|false getVisibility($path)
 * @method int|false getSize($path);
 * @method bool setVisibility($path, $visibility)
 * @method array|false getMetadata($path)
 * @method Handler get($path, Handler $handler = null)
 * @method Filesystem flushCache()
 * @method void assertPresent($path)
 * @method void assertAbsent($path)
 * @method Filesystem addPlugin(PluginInterface $plugin)
 */
class MountManager
{
    use PluggableTrait;

    /**
     * @var array
     */
    protected $filesystems = [];

    /**
     * Constructor.
     *
     * @param array $filesystems
     */
    public function __construct(array $filesystems = [])
    {
        $this->mountFilesystems($filesystems);
    }

    /**
     * Mount filesystems.
     *
     * @param array $filesystems [:prefix => Filesystem,]
     *
     * @return $this
     */
    public function mountFilesystems(array $filesystems)
    {
        foreach ($filesystems as $prefix => $filesystem) {
            $this->mountFilesystem($prefix, $filesystem);
        }

        return $this;
    }

    /**
     * Mount filesystems.
     *
     * @param string              $prefix
     * @param FilesystemInterface $filesystem
     *
     * @return $this
     */
    public function mountFilesystem($prefix, FilesystemInterface $filesystem)
    {
        if ( ! is_string($prefix)) {
            throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.');
        }

        $this->filesystems[$prefix] = $filesystem;

        return $this;
    }

    /**
     * Get the filesystem with the corresponding prefix.
     *
     * @param string $prefix
     *
     * @throws LogicException
     *
     * @return FilesystemInterface
     */
    public function getFilesystem($prefix)
    {
        if ( ! isset($this->filesystems[$prefix])) {
            throw new LogicException('No filesystem mounted with prefix ' . $prefix);
        }

        return $this->filesystems[$prefix];
    }

    /**
     * Retrieve the prefix from an arguments array.
     *
     * @param array $arguments
     *
     * @return array [:prefix, :arguments]
     */
    public function filterPrefix(array $arguments)
    {
        if (empty($arguments)) {
            throw new LogicException('At least one argument needed');
        }

        $path = array_shift($arguments);

        if ( ! is_string($path)) {
            throw new InvalidArgumentException('First argument should be a string');
        }

        if ( ! preg_match('#^.+\:\/\/.*#', $path)) {
            throw new InvalidArgumentException('No prefix detected in path: ' . $path);
        }

        list($prefix, $path) = explode('://', $path, 2);
        array_unshift($arguments, $path);

        return [$prefix, $arguments];
    }

    /**
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array
     */
    public function listContents($directory = '', $recursive = false)
    {
        list($prefix, $arguments) = $this->filterPrefix([$directory]);
        $filesystem = $this->getFilesystem($prefix);
        $directory = array_shift($arguments);
        $result = $filesystem->listContents($directory, $recursive);

        foreach ($result as &$file) {
            $file['filesystem'] = $prefix;
        }

        return $result;
    }

    /**
     * Call forwarder.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($method, $arguments)
    {
        list($prefix, $arguments) = $this->filterPrefix($arguments);

        return $this->invokePluginOnFilesystem($method, $arguments, $prefix);
    }

    /**
     * @param $from
     * @param $to
     * @param array $config
     *
     * @return bool
     */
    public function copy($from, $to, array $config = [])
    {
        list($prefixFrom, $arguments) = $this->filterPrefix([$from]);

        $fsFrom = $this->getFilesystem($prefixFrom);
        $buffer = call_user_func_array([$fsFrom, 'readStream'], $arguments);

        if ($buffer === false) {
            return false;
        }

        list($prefixTo, $arguments) = $this->filterPrefix([$to]);

        $fsTo = $this->getFilesystem($prefixTo);
        $result =  call_user_func_array([$fsTo, 'writeStream'], array_merge($arguments, [$buffer, $config]));

        if (is_resource($buffer)) {
            fclose($buffer);
        }

        return $result;
    }

    /**
     * List with plugin adapter.
     *
     * @param array  $keys
     * @param string $directory
     * @param bool   $recursive
     */
    public function listWith(array $keys = [], $directory = '', $recursive = false)
    {
        list($prefix, $arguments) = $this->filterPrefix([$directory]);
        $directory = $arguments[0];
        $arguments = [$keys, $directory, $recursive];

        return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix);
    }

    /**
     * Move a file.
     *
     * @param $from
     * @param $to
     * @param array $config
     *
     * @return bool
     */
    public function move($from, $to, array $config = [])
    {
        $copied = $this->copy($from, $to, $config);

        if ($copied) {
            return $this->delete($from);
        }

        return false;
    }

    /**
     * Invoke a plugin on a filesystem mounted on a given prefix.
     *
     * @param $method
     * @param $arguments
     * @param $prefix
     *
     * @return mixed
     */
    public function invokePluginOnFilesystem($method, $arguments, $prefix)
    {
        $filesystem = $this->getFilesystem($prefix);

        try {
            return $this->invokePlugin($method, $arguments, $filesystem);
        } catch (PluginNotFoundException $e) {
            // Let it pass, it's ok, don't panic.
        }

        $callback = [$filesystem, $method];

        return call_user_func_array($callback, $arguments);
    }
}
<?php

namespace League\Flysystem;

use RuntimeException;
use SplFileInfo;

class NotSupportedException extends RuntimeException
{
    /**
     * Create a new exception for a link.
     *
     * @param SplFileInfo $file
     *
     * @return static
     */
    public static function forLink(SplFileInfo $file)
    {
        $message = 'Links are not supported, encountered link at ';

        return new static($message . $file->getPathname());
    }

    /**
     * Create a new exception for a link.
     *
     * @param string $systemType
     *
     * @return static
     */
    public static function forFtpSystemType($systemType)
    {
        $message = "The FTP system type '$systemType' is currently not supported.";

        return new static($message);
    }
}
<?php

namespace League\Flysystem\Plugin;

use League\Flysystem\FilesystemInterface;
use League\Flysystem\PluginInterface;

abstract class AbstractPlugin implements PluginInterface
{
    /**
     * @var FilesystemInterface
     */
    protected $filesystem;

    /**
     * Set the Filesystem object.
     *
     * @param FilesystemInterface $filesystem
     */
    public function setFilesystem(FilesystemInterface $filesystem)
    {
        $this->filesystem = $filesystem;
    }
}
<?php

namespace League\Flysystem\Plugin;

class EmptyDir extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'emptyDir';
    }

    /**
     * Empty a directory's contents.
     *
     * @param $dirname
     */
    public function handle($dirname)
    {
        $listing = $this->filesystem->listContents($dirname, false);

        foreach ($listing as $item) {
            if ($item['type'] === 'dir') {
                $this->filesystem->deleteDir($item['path']);
            } else {
                $this->filesystem->delete($item['path']);
            }
        }
    }
}
<?php

namespace League\Flysystem\Plugin;

use League\Flysystem\FileNotFoundException;

class ForcedCopy extends AbstractPlugin
{
    /**
     * @inheritdoc
     */
    public function getMethod()
    {
        return 'forceCopy';
    }

    /**
     * Copies a file, overwriting any existing files.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileNotFoundException Thrown if $path does not exist.
     *
     * @return bool True on success, false on failure.
     */
    public function handle($path, $newpath)
    {
        try {
            $deleted = $this->filesystem->delete($newpath);
        } catch (FileNotFoundException $e) {
            // The destination path does not exist. That's ok.
            $deleted = true;
        }

        if ($deleted) {
            return $this->filesystem->copy($path, $newpath);
        }

        return false;
    }
}
<?php

namespace League\Flysystem\Plugin;

use League\Flysystem\FileNotFoundException;

class ForcedRename extends AbstractPlugin
{
    /**
     * @inheritdoc
     */
    public function getMethod()
    {
        return 'forceRename';
    }

    /**
     * Renames a file, overwriting the destination if it exists.
     *
     * @param string $path    Path to the existing file.
     * @param string $newpath The new path of the file.
     *
     * @throws FileNotFoundException Thrown if $path does not exist.
     *
     * @return bool True on success, false on failure.
     */
    public function handle($path, $newpath)
    {
        try {
            $deleted = $this->filesystem->delete($newpath);
        } catch (FileNotFoundException $e) {
            // The destination path does not exist. That's ok.
            $deleted = true;
        }

        if ($deleted) {
            return $this->filesystem->rename($path, $newpath);
        }

        return false;
    }
}
<?php

namespace League\Flysystem\Plugin;

use InvalidArgumentException;

class GetWithMetadata extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'getWithMetadata';
    }

    /**
     * Get metadata for an object with required metadata.
     *
     * @param string $path     path to file
     * @param array  $metadata metadata keys
     *
     * @throws InvalidArgumentException
     *
     * @return array metadata
     */
    public function handle($path, array $metadata)
    {
        $object = $this->filesystem->getMetadata($path);

        if ( ! $object) {
            return false;
        }

        $keys = array_diff($metadata, array_keys($object));

        foreach ($keys as $key) {
            if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) {
                throw new InvalidArgumentException('Could not fetch metadata: ' . $key);
            }

            $object[$key] = $this->filesystem->{$method}($path);
        }

        return $object;
    }
}
<?php

namespace League\Flysystem\Plugin;

class ListFiles extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'listFiles';
    }

    /**
     * List all files in the directory.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array
     */
    public function handle($directory = '', $recursive = false)
    {
        $contents = $this->filesystem->listContents($directory, $recursive);

        $filter = function ($object) {
            return $object['type'] === 'file';
        };

        return array_values(array_filter($contents, $filter));
    }
}
<?php

namespace League\Flysystem\Plugin;

class ListPaths extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'listPaths';
    }

    /**
     * List all paths.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array paths
     */
    public function handle($directory = '', $recursive = false)
    {
        $result = [];
        $contents = $this->filesystem->listContents($directory, $recursive);

        foreach ($contents as $object) {
            $result[] = $object['path'];
        }

        return $result;
    }
}
<?php

namespace League\Flysystem\Plugin;

class ListWith extends AbstractPlugin
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod()
    {
        return 'listWith';
    }

    /**
     * List contents with metadata.
     *
     * @param array  $keys
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array listing with metadata
     */
    public function handle(array $keys = [], $directory = '', $recursive = false)
    {
        $contents = $this->filesystem->listContents($directory, $recursive);

        foreach ($contents as $index => $object) {
            if ($object['type'] === 'file') {
                $missingKeys = array_diff($keys, array_keys($object));
                $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object);
            }
        }

        return $contents;
    }

    /**
     * Get a meta-data value by key name.
     *
     * @param array $object
     * @param       $key
     *
     * @return array
     */
    protected function getMetadataByName(array $object, $key)
    {
        $method = 'get' . ucfirst($key);

        if ( ! method_exists($this->filesystem, $method)) {
            throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key);
        }

        $object[$key] = $this->filesystem->{$method}($object['path']);

        return $object;
    }
}
<?php

namespace League\Flysystem\Plugin;

use BadMethodCallException;
use League\Flysystem\FilesystemInterface;
use League\Flysystem\PluginInterface;
use LogicException;

trait PluggableTrait
{
    /**
     * @var array
     */
    protected $plugins = [];

    /**
     * Register a plugin.
     *
     * @param PluginInterface $plugin
     *
     * @return $this
     */
    public function addPlugin(PluginInterface $plugin)
    {
        $this->plugins[$plugin->getMethod()] = $plugin;

        return $this;
    }

    /**
     * Find a specific plugin.
     *
     * @param string $method
     *
     * @throws LogicException
     *
     * @return PluginInterface $plugin
     */
    protected function findPlugin($method)
    {
        if ( ! isset($this->plugins[$method])) {
            throw new PluginNotFoundException('Plugin not found for method: ' . $method);
        }

        if ( ! method_exists($this->plugins[$method], 'handle')) {
            throw new LogicException(get_class($this->plugins[$method]) . ' does not have a handle method.');
        }

        return $this->plugins[$method];
    }

    /**
     * Invoke a plugin by method name.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @return mixed
     */
    protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem)
    {
        $plugin = $this->findPlugin($method);
        $plugin->setFilesystem($filesystem);
        $callback = [$plugin, 'handle'];

        return call_user_func_array($callback, $arguments);
    }

    /**
     * Plugins pass-through.
     *
     * @param string $method
     * @param array  $arguments
     *
     * @throws BadMethodCallException
     *
     * @return mixed
     */
    public function __call($method, array $arguments)
    {
        try {
            return $this->invokePlugin($method, $arguments, $this);
        } catch (PluginNotFoundException $e) {
            throw new BadMethodCallException(
                'Call to undefined method '
                . get_class($this)
                . '::' . $method
            );
        }
    }
}
<?php

namespace League\Flysystem\Plugin;

use LogicException;

class PluginNotFoundException extends LogicException
{
    // This exception doesn't require additional information.
}
<?php

namespace League\Flysystem;

interface PluginInterface
{
    /**
     * Get the method name.
     *
     * @return string
     */
    public function getMethod();

    /**
     * Set the Filesystem object.
     *
     * @param FilesystemInterface $filesystem
     */
    public function setFilesystem(FilesystemInterface $filesystem);
}
<?php

namespace League\Flysystem;

interface ReadInterface
{
    /**
     * Check whether a file exists.
     *
     * @param string $path
     *
     * @return array|bool|null
     */
    public function has($path);

    /**
     * Read a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function read($path);

    /**
     * Read a file as a stream.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function readStream($path);

    /**
     * List contents of a directory.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array
     */
    public function listContents($directory = '', $recursive = false);

    /**
     * Get all the meta data of a file or directory.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getMetadata($path);

    /**
     * Get all the meta data of a file or directory.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getSize($path);

    /**
     * Get the mimetype of a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getMimetype($path);

    /**
     * Get the timestamp of a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getTimestamp($path);

    /**
     * Get the visibility of a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getVisibility($path);
}
<?php

namespace League\Flysystem;

use LogicException;

class RootViolationException extends LogicException
{
    //
}
<?php

namespace League\Flysystem;

final class SafeStorage
{
    /**
     * @var string
     */
    private $hash;

    /**
     * @var array
     */
    protected static $safeStorage = [];

    public function __construct()
    {
        $this->hash = spl_object_hash($this);
        static::$safeStorage[$this->hash] = [];
    }

    public function storeSafely($key, $value)
    {
        static::$safeStorage[$this->hash][$key] = $value;
    }

    public function retrieveSafely($key)
    {
        if (array_key_exists($key, static::$safeStorage[$this->hash])) {
            return static::$safeStorage[$this->hash][$key];
        }
    }

    public function __destruct()
    {
        unset(static::$safeStorage[$this->hash]);
    }
}
<?php

namespace League\Flysystem;

use SplFileInfo;

class UnreadableFileException extends Exception
{
    public static function forFileInfo(SplFileInfo $fileInfo)
    {
        return new static(
            sprintf(
                'Unreadable file encountered: %s',
                $fileInfo->getRealPath()
            )
        );
    }
}
<?php

namespace League\Flysystem\Util;

use League\Flysystem\Util;

/**
 * @internal
 */
class ContentListingFormatter
{
    /**
     * @var string
     */
    private $directory;
    /**
     * @var bool
     */
    private $recursive;

    /**
     * @param string $directory
     * @param bool   $recursive
     */
    public function __construct($directory, $recursive)
    {
        $this->directory = $directory;
        $this->recursive = $recursive;
    }

    /**
     * Format contents listing.
     *
     * @param array $listing
     *
     * @return array
     */
    public function formatListing(array $listing)
    {
        $listing = array_values(
            array_map(
                [$this, 'addPathInfo'],
                array_filter($listing, [$this, 'isEntryOutOfScope'])
            )
        );

        return $this->sortListing($listing);
    }

    private function addPathInfo(array $entry)
    {
        return $entry + Util::pathinfo($entry['path']);
    }

    /**
     * Determine if the entry is out of scope.
     *
     * @param array $entry
     *
     * @return bool
     */
    private function isEntryOutOfScope(array $entry)
    {
        if (empty($entry['path']) && $entry['path'] !== '0') {
            return false;
        }

        if ($this->recursive) {
            return $this->residesInDirectory($entry);
        }

        return $this->isDirectChild($entry);
    }

    /**
     * Check if the entry resides within the parent directory.
     *
     * @param $entry
     *
     * @return bool
     */
    private function residesInDirectory(array $entry)
    {
        if ($this->directory === '') {
            return true;
        }

        return strpos($entry['path'], $this->directory . '/') === 0;
    }

    /**
     * Check if the entry is a direct child of the directory.
     *
     * @param $entry
     *
     * @return bool
     */
    private function isDirectChild(array $entry)
    {
        return Util::dirname($entry['path']) === $this->directory;
    }

    /**
     * @param array $listing
     *
     * @return array
     */
    private function sortListing(array $listing)
    {
        usort(
            $listing,
            function ($a, $b) {
                return strcasecmp($a['path'], $b['path']);
            }
        );

        return $listing;
    }
}
<?php

namespace League\Flysystem\Util;

use Finfo;

/**
 * @internal
 */
class MimeType
{
    /**
     * Detects MIME Type based on given content.
     *
     * @param mixed $content
     *
     * @return string|null MIME Type or NULL if no mime type detected
     */
    public static function detectByContent($content)
    {
        if ( ! class_exists('Finfo') || ! is_string($content)) {
            return;
        }

        $finfo = new Finfo(FILEINFO_MIME_TYPE);
        $mimeType = $finfo->buffer($content);

        return $mimeType ?: null;
    }

    /**
     * Detects MIME Type based on file extension.
     *
     * @param string $extension
     *
     * @return string|null MIME Type or NULL if no extension detected
     */
    public static function detectByFileExtension($extension)
    {
        static $extensionToMimeTypeMap;

        if (! $extensionToMimeTypeMap) {
            $extensionToMimeTypeMap = static::getExtensionToMimeTypeMap();
        }

        if (isset($extensionToMimeTypeMap[$extension])) {
            return $extensionToMimeTypeMap[$extension];
        }

        return 'text/plain';
    }

    /**
     * @param string $filename
     *
     * @return string
     */
    public static function detectByFilename($filename)
    {
        $extension = pathinfo($filename, PATHINFO_EXTENSION);

        return empty($extension) ? 'text/plain' : static::detectByFileExtension($extension);
    }

    /**
     * @return array Map of file extension to MIME Type
     */
    public static function getExtensionToMimeTypeMap()
    {
        return [
            'hqx'   => 'application/mac-binhex40',
            'cpt'   => 'application/mac-compactpro',
            'csv'   => 'text/x-comma-separated-values',
            'bin'   => 'application/octet-stream',
            'dms'   => 'application/octet-stream',
            'lha'   => 'application/octet-stream',
            'lzh'   => 'application/octet-stream',
            'exe'   => 'application/octet-stream',
            'class' => 'application/octet-stream',
            'psd'   => 'application/x-photoshop',
            'so'    => 'application/octet-stream',
            'sea'   => 'application/octet-stream',
            'dll'   => 'application/octet-stream',
            'oda'   => 'application/oda',
            'pdf'   => 'application/pdf',
            'ai'    => 'application/pdf',
            'eps'   => 'application/postscript',
            'ps'    => 'application/postscript',
            'smi'   => 'application/smil',
            'smil'  => 'application/smil',
            'mif'   => 'application/vnd.mif',
            'xls'   => 'application/vnd.ms-excel',
            'ppt'   => 'application/powerpoint',
            'pptx'  => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'wbxml' => 'application/wbxml',
            'wmlc'  => 'application/wmlc',
            'dcr'   => 'application/x-director',
            'dir'   => 'application/x-director',
            'dxr'   => 'application/x-director',
            'dvi'   => 'application/x-dvi',
            'gtar'  => 'application/x-gtar',
            'gz'    => 'application/x-gzip',
            'gzip'  => 'application/x-gzip',
            '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/javascript',
            'swf'   => 'application/x-shockwave-flash',
            'sit'   => 'application/x-stuffit',
            'tar'   => 'application/x-tar',
            'tgz'   => 'application/x-tar',
            'z'     => 'application/x-compress',
            'xhtml' => 'application/xhtml+xml',
            'xht'   => 'application/xhtml+xml',
            'zip'   => 'application/x-zip',
            'rar'   => 'application/x-rar',
            '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',
            'jpg'   => 'image/jpeg',
            'jpeg'  => 'image/jpeg',
            'jpe'   => 'image/jpeg',
            'png'   => 'image/png',
            'gif'   => 'image/gif',
            'bmp'   => 'image/bmp',
            'tiff'  => 'image/tiff',
            'tif'   => 'image/tiff',
            'svg'   => 'image/svg+xml',
            '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'   => 'application/xml',
            'xsl'   => 'application/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',
            'docx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'dot'   => 'application/msword',
            'dotx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'xlsx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'word'  => 'application/msword',
            'xl'    => 'application/excel',
            'eml'   => 'message/rfc822',
            'json'  => 'application/json',
            'pem'   => 'application/x-x509-user-cert',
            'p10'   => 'application/x-pkcs10',
            'p12'   => 'application/x-pkcs12',
            'p7a'   => 'application/x-pkcs7-signature',
            'p7c'   => 'application/pkcs7-mime',
            'p7m'   => 'application/pkcs7-mime',
            'p7r'   => 'application/x-pkcs7-certreqresp',
            'p7s'   => 'application/pkcs7-signature',
            'crt'   => 'application/x-x509-ca-cert',
            'crl'   => 'application/pkix-crl',
            'der'   => 'application/x-x509-ca-cert',
            'kdb'   => 'application/octet-stream',
            'pgp'   => 'application/pgp',
            'gpg'   => 'application/gpg-keys',
            'sst'   => 'application/octet-stream',
            'csr'   => 'application/octet-stream',
            'rsa'   => 'application/x-pkcs7',
            'cer'   => 'application/pkix-cert',
            '3g2'   => 'video/3gpp2',
            '3gp'   => 'video/3gp',
            'mp4'   => 'video/mp4',
            'm4a'   => 'audio/x-m4a',
            'f4v'   => 'video/mp4',
            'webm'  => 'video/webm',
            'aac'   => 'audio/x-acc',
            'm4u'   => 'application/vnd.mpegurl',
            'm3u'   => 'text/plain',
            'xspf'  => 'application/xspf+xml',
            'vlc'   => 'application/videolan',
            'wmv'   => 'video/x-ms-wmv',
            'au'    => 'audio/x-au',
            'ac3'   => 'audio/ac3',
            'flac'  => 'audio/x-flac',
            'ogg'   => 'audio/ogg',
            'kmz'   => 'application/vnd.google-earth.kmz',
            'kml'   => 'application/vnd.google-earth.kml+xml',
            'ics'   => 'text/calendar',
            'zsh'   => 'text/x-scriptzsh',
            '7zip'  => 'application/x-7z-compressed',
            'cdr'   => 'application/cdr',
            'wma'   => 'audio/x-ms-wma',
            'jar'   => 'application/java-archive',
        ];
    }
}
<?php

namespace League\Flysystem\Util;

class StreamHasher
{
    /**
     * @var string
     */
    private $algo;

    /**
     * StreamHasher constructor.
     *
     * @param string $algo
     */
    public function __construct($algo)
    {
        $this->algo = $algo;
    }

    /**
     * @param $resource
     *
     * @return string
     */
    public function hash($resource)
    {
        rewind($resource);
        $context = hash_init($this->algo);
        hash_update_stream($context, $resource);
        fclose($resource);

        return hash_final($context);
    }
}
<?php

namespace League\Flysystem;

use League\Flysystem\Util\MimeType;
use LogicException;

class Util
{
    /**
     * Get normalized pathinfo.
     *
     * @param string $path
     *
     * @return array pathinfo
     */
    public static function pathinfo($path)
    {
        $pathinfo = pathinfo($path) + compact('path');
        $pathinfo['dirname'] = array_key_exists('dirname', $pathinfo)
            ? static::normalizeDirname($pathinfo['dirname']) : '';

        return $pathinfo;
    }

    /**
     * Normalize a dirname return value.
     *
     * @param string $dirname
     *
     * @return string normalized dirname
     */
    public static function normalizeDirname($dirname)
    {
        return $dirname === '.' ? '' : $dirname;
    }

    /**
     * Get a normalized dirname from a path.
     *
     * @param string $path
     *
     * @return string dirname
     */
    public static function dirname($path)
    {
        return static::normalizeDirname(dirname($path));
    }

    /**
     * Map result arrays.
     *
     * @param array $object
     * @param array $map
     *
     * @return array mapped result
     */
    public static function map(array $object, array $map)
    {
        $result = [];

        foreach ($map as $from => $to) {
            if ( ! isset($object[$from])) {
                continue;
            }

            $result[$to] = $object[$from];
        }

        return $result;
    }

    /**
     * Normalize path.
     *
     * @param string $path
     *
     * @throws LogicException
     *
     * @return string
     */
    public static function normalizePath($path)
    {
        // Remove any kind of funky unicode whitespace
        $normalized = preg_replace('#\p{C}+|^\./#u', '', $path);
        $normalized = static::normalizeRelativePath($normalized);

        if (preg_match('#(^|/)\.{2}(/|$)#', $normalized)) {
            throw new LogicException(
                'Path is outside of the defined root, path: [' . $path . '], resolved: [' . $normalized . ']'
            );
        }

        $normalized = preg_replace('#\\\{2,}#', '\\', trim($normalized, '\\'));
        $normalized = preg_replace('#/{2,}#', '/', trim($normalized, '/'));

        return $normalized;
    }

    /**
     * Normalize relative directories in a path.
     *
     * @param string $path
     *
     * @return string
     */
    public static function normalizeRelativePath($path)
    {
        // Path remove self referring paths ("/./").
        $path = preg_replace('#/\.(?=/)|^\./|(/|^)\./?$#', '', $path);

        // Regex for resolving relative paths
        $regex = '#/*[^/\.]+/\.\.(?=/|$)#Uu';

        while (preg_match($regex, $path)) {
            $path = preg_replace($regex, '', $path);
        }

        return $path;
    }

    /**
     * Normalize prefix.
     *
     * @param string $prefix
     * @param string $separator
     *
     * @return string normalized path
     */
    public static function normalizePrefix($prefix, $separator)
    {
        return rtrim($prefix, $separator) . $separator;
    }

    /**
     * Get content size.
     *
     * @param string $contents
     *
     * @return int content size
     */
    public static function contentSize($contents)
    {
        return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents);
    }

    /**
     * Guess MIME Type based on the path of the file and it's content.
     *
     * @param string $path
     * @param string|resource $content
     *
     * @return string|null MIME Type or NULL if no extension detected
     */
    public static function guessMimeType($path, $content)
    {
        $mimeType = MimeType::detectByContent($content);

        if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) {
            return $mimeType;
        }

        return MimeType::detectByFilename($path);
    }

    /**
     * Emulate directories.
     *
     * @param array $listing
     *
     * @return array listing with emulated directories
     */
    public static function emulateDirectories(array $listing)
    {
        $directories = [];
        $listedDirectories = [];

        foreach ($listing as $object) {
            list($directories, $listedDirectories) = static::emulateObjectDirectories(
                $object,
                $directories,
                $listedDirectories
            );
        }

        $directories = array_diff(array_unique($directories), array_unique($listedDirectories));

        foreach ($directories as $directory) {
            $listing[] = static::pathinfo($directory) + ['type' => 'dir'];
        }

        return $listing;
    }

    /**
     * Ensure a Config instance.
     *
     * @param null|array|Config $config
     *
     * @return Config config instance
     *
     * @throw  LogicException
     */
    public static function ensureConfig($config)
    {
        if ($config === null) {
            return new Config();
        }

        if ($config instanceof Config) {
            return $config;
        }

        if (is_array($config)) {
            return new Config($config);
        }

        throw new LogicException('A config should either be an array or a Flysystem\Config object.');
    }

    /**
     * Rewind a stream.
     *
     * @param resource $resource
     */
    public static function rewindStream($resource)
    {
        if (ftell($resource) !== 0 && static::isSeekableStream($resource)) {
            rewind($resource);
        }
    }

    public static function isSeekableStream($resource)
    {
        $metadata = stream_get_meta_data($resource);

        return $metadata['seekable'];
    }

    /**
     * Get the size of a stream.
     *
     * @param resource $resource
     *
     * @return int stream size
     */
    public static function getStreamSize($resource)
    {
        $stat = fstat($resource);

        return $stat['size'];
    }

    /**
     * Emulate the directories of a single object.
     *
     * @param array $object
     * @param array $directories
     * @param array $listedDirectories
     *
     * @return array
     */
    protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories)
    {
        if ($object['type'] === 'dir') {
            $listedDirectories[] = $object['path'];
        }

        if (empty($object['dirname'])) {
            return [$directories, $listedDirectories];
        }

        $parent = $object['dirname'];

        while ( ! empty($parent) && ! in_array($parent, $directories)) {
            $directories[] = $parent;
            $parent = static::dirname($parent);
        }

        if (isset($object['type']) && $object['type'] === 'dir') {
            $listedDirectories[] = $object['path'];

            return [$directories, $listedDirectories];
        }

        return [$directories, $listedDirectories];
    }
}
# Changelog

## 1.0.13 - 2016-06-21

### Fixed

* Uploading a remote stream no longer results in an unexpected exception.

## 1.0.12 - 2016-06-06

### Improved

* Responses are now streamed instead of downloaded fully.

## 1.0.11 - 2016-05-03

### Fixed

* [::has] A regression introduced in 1.0.10 is addressed.

## 1.0.10 - 2016-04-19

### Fixed

* [::has] The `has` method now also respects implicit directories.

## 1.0.9 - 2015-11-19

### Fixed

* [#49] Large listings only returned the last page of the listing.

## 1.0.8 - 2015-11-06

### Improved

* Non-recursive listings now retrieve a shallow listing for better performance.

## 1.0.7 - 2015-11-06

### Fixed

* The `copy` operation now `urlencode`'s the `CopySource` to allow characters like `+`.

## 1.0.6 - 2015-09-25

### Fixed

* The `has` operation now respects path prefix, bug introduced in 1.0.5.

## 1.0.5 - 2015-09-22

### Fixed

* `has` calls now use `doesObjectExist` rather than retrieving metadata.

## 1.0.4 - 2015-07-06

### Fixed

* Fixed delete return value.

## 1.0.3 - 2015-06-16

### Fixed

* Use an iterator for contents listing to break through the 1000 objects limit.

## 1.0.2 - 2015-06-06

### Fixed

* Exception due to misconfiguration no longer causes a fatal error but are properly rethrown.

## 1.0.1 - 2015-05-31

### Fixed

* Stable release depending in the first v3 release of the AWS SDK.
{
    "name": "league/flysystem-aws-s3-v3",
    "description": "Flysystem adapter for the AWS S3 SDK v3.x",
    "license": "MIT",
    "authors": [
        {
            "name": "Frank de Jonge",
            "email": "info@frenky.net"
        }
    ],
    "require": {
        "php": ">=5.5.0",
        "league/flysystem": "~1.0",
        "aws/aws-sdk-php": "^3.0.0"
    },
    "require-dev": {
        "phpspec/phpspec": "^2.0.0",
        "henrikbjorn/phpspec-code-coverage" : "~1.0.1"
    },
    "autoload": {
        "psr-4": {
            "League\\Flysystem\\AwsS3v3\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "League\\Flysystem\\AwsS3v3\\Stub\\": "stub"
        }
    },
    "config": {
        "bin-dir": "bin"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}
Copyright (c) 2014 Frank de Jonge

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.
---
suites:
      event_suite:
        namespace: League\Flysystem\AwsS3v3
        psr4_prefix: League\Flysystem\AwsS3v3
formatter.name: pretty
<?php

namespace League\Flysystem\AwsS3v3;

use Aws\Result;
use Aws\S3\Exception\DeleteMultipleObjectsException;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
use League\Flysystem\Util;

class AwsS3Adapter extends AbstractAdapter
{
    const PUBLIC_GRANT_URI = 'http://acs.amazonaws.com/groups/global/AllUsers';

    /**
     * @var array
     */
    protected static $resultMap = [
        'Body' => 'contents',
        'ContentLength' => 'size',
        'ContentType' => 'mimetype',
        'Size' => 'size',
    ];

    /**
     * @var array
     */
    protected static $metaOptions = [
        'CacheControl',
        'Expires',
        'StorageClass',
        'ServerSideEncryption',
        'Metadata',
        'ACL',
        'ContentType',
        'ContentEncoding',
        'ContentDisposition',
        'ContentLength',
    ];

    /**
     * @var S3Client
     */
    protected $s3Client;

    /**
     * @var string
     */
    protected $bucket;

    /**
     * @var array
     */
    protected $options = [];

    /**
     * Constructor.
     *
     * @param S3Client $client
     * @param string   $bucket
     * @param string   $prefix
     * @param array    $options
     */
    public function __construct(S3Client $client, $bucket, $prefix = '', array $options = [])
    {
        $this->s3Client = $client;
        $this->bucket = $bucket;
        $this->setPathPrefix($prefix);
        $this->options = $options;
    }

    /**
     * Get the S3Client bucket.
     *
     * @return string
     */
    public function getBucket()
    {
        return $this->bucket;
    }

    /**
     * Get the S3Client instance.
     *
     * @return S3Client
     */
    public function getClient()
    {
        return $this->s3Client;
    }

    /**
     * Write a new file.
     *
     * @param string $path
     * @param string $contents
     * @param Config $config Config object
     *
     * @return false|array false on failure file meta data on success
     */
    public function write($path, $contents, Config $config)
    {
        return $this->upload($path, $contents, $config);
    }

    /**
     * Update a file.
     *
     * @param string $path
     * @param string $contents
     * @param Config $config Config object
     *
     * @return false|array false on failure file meta data on success
     */
    public function update($path, $contents, Config $config)
    {
        return $this->upload($path, $contents, $config);
    }

    /**
     * Rename a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function rename($path, $newpath)
    {
        if ( ! $this->copy($path, $newpath)) {
            return false;
        }

        return $this->delete($path);
    }

    /**
     * Delete a file.
     *
     * @param string $path
     *
     * @return bool
     */
    public function delete($path)
    {
        $location = $this->applyPathPrefix($path);

        $command = $this->s3Client->getCommand(
            'deleteObject',
            [
                'Bucket' => $this->bucket,
                'Key' => $location,
            ]
        );

        $this->s3Client->execute($command);

        return ! $this->has($path);
    }

    /**
     * Delete a directory.
     *
     * @param string $dirname
     *
     * @return bool
     */
    public function deleteDir($dirname)
    {
        try {
            $prefix = $this->applyPathPrefix($dirname) . '/';
            $this->s3Client->deleteMatchingObjects($this->bucket, $prefix);
        } catch (DeleteMultipleObjectsException $exception) {
            return false;
        }

        return true;
    }

    /**
     * Create a directory.
     *
     * @param string $dirname directory name
     * @param Config $config
     *
     * @return bool|array
     */
    public function createDir($dirname, Config $config)
    {
        return $this->upload($dirname . '/', '', $config);
    }

    /**
     * Check whether a file exists.
     *
     * @param string $path
     *
     * @return bool
     */
    public function has($path)
    {
        $location = $this->applyPathPrefix($path);

        if ($this->s3Client->doesObjectExist($this->bucket, $location)) {
            return true;
        }

        return $this->doesDirectoryExist($location);
    }

    /**
     * Read a file.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function read($path)
    {
        $response = $this->readObject($path);

        if ($response !== false) {
            $response['contents'] = $response['contents']->getContents();
        }

        return $response;
    }

    /**
     * List contents of a directory.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array
     */
    public function listContents($directory = '', $recursive = false)
    {
        $prefix = $this->applyPathPrefix(rtrim($directory, '/') . '/');
        $options = ['Bucket' => $this->bucket, 'Prefix' => ltrim($prefix, '/')];

        if ($recursive === false) {
            $options['Delimiter'] = '/';
        }

        $listing = $this->retrievePaginatedListing($options);
        $normalizer = [$this, 'normalizeResponse'];
        $normalized = array_map($normalizer, $listing);

        return Util::emulateDirectories($normalized);
    }

    /**
     * @param array $options
     *
     * @return array
     */
    protected function retrievePaginatedListing(array $options)
    {
        $resultPaginator = $this->s3Client->getPaginator('ListObjects', $options);
        $listing = [];

        foreach ($resultPaginator as $result) {
            $listing = array_merge($listing, $result->get('Contents') ?: [], $result->get('CommonPrefixes') ?: []);
        }

        return $listing;
    }

    /**
     * Get all the meta data of a file or directory.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function getMetadata($path)
    {
        $command = $this->s3Client->getCommand(
            'headObject',
            [
                'Bucket' => $this->bucket,
                'Key' => $this->applyPathPrefix($path),
            ]
        );

        /* @var Result $result */
        try {
            $result = $this->s3Client->execute($command);
        } catch (S3Exception $exception) {
            $response = $exception->getResponse();

            if ($response !== null && $response->getStatusCode() === 404) {
                return false;
            }

            throw $exception;
        }

        return $this->normalizeResponse($result->toArray(), $path);
    }

    /**
     * Get all the meta data of a file or directory.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Get the mimetype of a file.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function getMimetype($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Get the timestamp of a file.
     *
     * @param string $path
     *
     * @return false|array
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Write a new file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function writeStream($path, $resource, Config $config)
    {
        return $this->upload($path, $resource, $config);
    }

    /**
     * Update a file using a stream.
     *
     * @param string   $path
     * @param resource $resource
     * @param Config   $config Config object
     *
     * @return array|false false on failure file meta data on success
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->upload($path, $resource, $config);
    }

    /**
     * Copy a file.
     *
     * @param string $path
     * @param string $newpath
     *
     * @return bool
     */
    public function copy($path, $newpath)
    {
        $command = $this->s3Client->getCommand(
            'copyObject',
            [
                'Bucket' => $this->bucket,
                'Key' => $this->applyPathPrefix($newpath),
                'CopySource' => urlencode($this->bucket . '/' . $this->applyPathPrefix($path)),
                'ACL' => $this->getRawVisibility($path) === AdapterInterface::VISIBILITY_PUBLIC
                    ? 'public-read' : 'private',
            ] + $this->options
        );

        try {
            $this->s3Client->execute($command);
        } catch (S3Exception $e) {
            return false;
        }

        return true;
    }

    /**
     * Read a file as a stream.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function readStream($path)
    {
        $response = $this->readObject($path);

        if ($response !== false) {
            $response['stream'] = $response['contents']->detach();
            unset($response['contents']);
        }

        return $response;
    }

    /**
     * Read an object and normalize the response.
     *
     * @param $path
     *
     * @return array|bool
     */
    protected function readObject($path)
    {
        $command = $this->s3Client->getCommand(
            'getObject',
            [
                'Bucket' => $this->bucket,
                'Key' => $this->applyPathPrefix($path),
                '@http' => [
                    'stream' => true,
                ],
            ]
        );

        try {
            /** @var Result $response */
            $response = $this->s3Client->execute($command);
        } catch (S3Exception $e) {
            return false;
        }

        return $this->normalizeResponse($response->toArray(), $path);
    }

    /**
     * Set the visibility for a file.
     *
     * @param string $path
     * @param string $visibility
     *
     * @return array|false file meta data
     */
    public function setVisibility($path, $visibility)
    {
        $command = $this->s3Client->getCommand(
            'putObjectAcl',
            [
                'Bucket' => $this->bucket,
                'Key' => $this->applyPathPrefix($path),
                'ACL' => $visibility === AdapterInterface::VISIBILITY_PUBLIC ? 'public-read' : 'private',
            ]
        );

        try {
            $this->s3Client->execute($command);
        } catch (S3Exception $exception) {
            return false;
        }

        return compact('path', 'visibility');
    }

    /**
     * Get the visibility of a file.
     *
     * @param string $path
     *
     * @return array|false
     */
    public function getVisibility($path)
    {
        return ['visibility' => $this->getRawVisibility($path)];
    }

    /**
     * {@inheritdoc}
     */
    public function applyPathPrefix($prefix)
    {
        return ltrim(parent::applyPathPrefix($prefix), '/');
    }

    /**
     * {@inheritdoc}
     */
    public function setPathPrefix($prefix)
    {
        $prefix = ltrim($prefix, '/');

        return parent::setPathPrefix($prefix);
    }

    /**
     * Get the object acl presented as a visibility.
     *
     * @param string $path
     *
     * @return string
     */
    protected function getRawVisibility($path)
    {
        $command = $this->s3Client->getCommand(
            'getObjectAcl',
            [
                'Bucket' => $this->bucket,
                'Key' => $this->applyPathPrefix($path),
            ]
        );

        $result = $this->s3Client->execute($command);
        $visibility = AdapterInterface::VISIBILITY_PRIVATE;

        foreach ($result->get('Grants') as $grant) {
            if (
                isset($grant['Grantee']['URI'])
                && $grant['Grantee']['URI'] === self::PUBLIC_GRANT_URI
                && $grant['Permission'] === 'READ'
            ) {
                $visibility = AdapterInterface::VISIBILITY_PUBLIC;
                break;
            }
        }

        return $visibility;
    }

    /**
     * Upload an object.
     *
     * @param        $path
     * @param        $body
     * @param Config $config
     *
     * @return array
     */
    protected function upload($path, $body, Config $config)
    {
        $key = $this->applyPathPrefix($path);
        $options = $this->getOptionsFromConfig($config);
        $acl = isset($options['ACL']) ? $options['ACL'] : 'private';

        if ( ! isset($options['ContentType']) && is_string($body)) {
            $options['ContentType'] = Util::guessMimeType($path, $body);
        }

        if ( ! isset($options['ContentLength'])) {
            $options['ContentLength'] = is_string($body) ? Util::contentSize($body) : Util::getStreamSize($body);
        }

        if ($options['ContentLength'] === null) {
            unset($options['ContentLength']);
        }

        $this->s3Client->upload($this->bucket, $key, $body, $acl, ['params' => $options]);

        return $this->normalizeResponse($options, $key);
    }

    /**
     * Get options from the config.
     *
     * @param Config $config
     *
     * @return array
     */
    protected function getOptionsFromConfig(Config $config)
    {
        $options = $this->options;

        if ($visibility = $config->get('visibility')) {
            // For local reference
            $options['visibility'] = $visibility;
            // For external reference
            $options['ACL'] = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? 'public-read' : 'private';
        }

        if ($mimetype = $config->get('mimetype')) {
            // For local reference
            $options['mimetype'] = $mimetype;
            // For external reference
            $options['ContentType'] = $mimetype;
        }

        foreach (static::$metaOptions as $option) {
            if ( ! $config->has($option)) {
                continue;
            }
            $options[$option] = $config->get($option);
        }

        return $options;
    }

    /**
     * Normalize the object result array.
     *
     * @param array  $response
     * @param string $path
     *
     * @return array
     */
    protected function normalizeResponse(array $response, $path = null)
    {
        $result = [
            'path' => $path ?: $this->removePathPrefix(
                isset($response['Key']) ? $response['Key'] : $response['Prefix']
            ),
        ];
        $result = array_merge($result, Util::pathinfo($result['path']));

        if (isset($response['LastModified'])) {
            $result['timestamp'] = strtotime($response['LastModified']);
        }

        if (substr($result['path'], -1) === '/') {
            $result['type'] = 'dir';
            $result['path'] = rtrim($result['path'], '/');

            return $result;
        }

        return array_merge($result, Util::map($response, static::$resultMap), ['type' => 'file']);
    }

    /**
     * @param $location
     *
     * @return bool
     */
    protected function doesDirectoryExist($location)
    {
        // Maybe this isn't an actual key, but a prefix.
        // Do a prefix listing of objects to determine.
        $command = $this->s3Client->getCommand(
            'listObjects',
            [
                'Bucket' => $this->bucket,
                'Prefix' => rtrim($location, '/') . '/',
                'MaxKeys' => 1,
            ]
        );

        try {
            $result = $this->s3Client->execute($command);

            return $result['Contents'] || $result['CommonPrefixes'];
        } catch (S3Exception $e) {
            if ($e->getStatusCode() === 403) {
                return false;
            }

            throw $e;
        }
    }
}
{
    "name": "league/flysystem-azure",
    "description": "Flysystem adapter for Windows Azure",
    "license": "MIT",
    "authors": [
        {
            "name": "Frank de Jonge",
            "email": "info@frenky.net"
        }
    ],
    "require": {
        "php": ">=5.5.0",
        "league/flysystem": "~1.0",
        "microsoft/azure-storage": "~0.10.1"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0",
        "mockery/mockery": "~0.9"
    },
    "autoload": {
        "psr-4": {
            "League\\Flysystem\\Azure\\": "src/"
        }
    },
    "config": {
        "bin-dir": "bin"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}
Copyright (c) 2014 Frank de Jonge

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.
<?php

namespace League\Flysystem\Azure;

/**
 * @deprecated deprecated since version 1.0.1
 */
class Adapter extends AzureAdapter
{
    // This is here purely because of BC.
}
<?php

namespace League\Flysystem\Azure;

use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
use League\Flysystem\Config;
use League\Flysystem\Util;
use MicrosoftAzure\Storage\Blob\Internal\IBlob;
use MicrosoftAzure\Storage\Blob\Models\BlobPrefix;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;
use MicrosoftAzure\Storage\Blob\Models\CopyBlobResult;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsResult;
use MicrosoftAzure\Storage\Common\ServiceException;

class AzureAdapter extends AbstractAdapter
{
    use NotSupportingVisibilityTrait;

    /**
     * @var string
     */
    protected $container;

    /**
     * @var IBlob
     */
    protected $client;

    /**
     * @var string[]
     */
    protected static $metaOptions = [
        'CacheControl',
        'ContentType',
        'Metadata',
        'ContentLanguage',
        'ContentEncoding',
    ];

    /**
     * Constructor.
     *
     * @param IBlob  $azureClient
     * @param string $container
     */
    public function __construct(IBlob $azureClient, $container, $prefix = null)
    {
        $this->client = $azureClient;
        $this->container = $container;
        $this->setPathPrefix($prefix);
    }

    /**
     * {@inheritdoc}
     */
    public function write($path, $contents, Config $config)
    {
        return $this->upload($path, $contents, $config);
    }

    /**
     * {@inheritdoc}
     */
    public function writeStream($path, $resource, Config $config)
    {
        return $this->upload($path, $resource, $config);
    }

    /**
     * {@inheritdoc}
     */
    public function update($path, $contents, Config $config)
    {
        return $this->upload($path, $contents, $config);
    }

    /**
     * {@inheritdoc}
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->upload($path, $resource, $config);
    }

    /**
     * {@inheritdoc}
     */
    public function rename($path, $newpath)
    {
        $this->copy($path, $newpath);

        return $this->delete($path);
    }

    public function copy($path, $newpath)
    {
        $path = $this->applyPathPrefix($path);
        $newpath = $this->applyPathPrefix($newpath);

        $this->client->copyBlob($this->container, $newpath, $this->container, $path);

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function delete($path)
    {
        $path = $this->applyPathPrefix($path);

        $this->client->deleteBlob($this->container, $path);

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function deleteDir($dirname)
    {
        $dirname = $this->applyPathPrefix($dirname);

        $options = new ListBlobsOptions();
        $options->setPrefix($dirname . '/');

        /** @var ListBlobsResult $listResults */
        $listResults = $this->client->listBlobs($this->container, $options);

        foreach ($listResults->getBlobs() as $blob) {
            /** @var \MicrosoftAzure\Storage\Blob\Models\Blob $blob */
            $this->client->deleteBlob($this->container, $blob->getName());
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function createDir($dirname, Config $config)
    {
        $this->write(rtrim($dirname, '/').'/', ' ', $config);

        return ['path' => $dirname, 'type' => 'dir'];
    }

    /**
     * {@inheritdoc}
     */
    public function has($path)
    {
        $path = $this->applyPathPrefix($path);

        try {
            $this->client->getBlobMetadata($this->container, $path);
        } catch (ServiceException $e) {
            if ($e->getCode() !== 404) {
                throw $e;
            }

            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function read($path)
    {
        $path = $this->applyPathPrefix($path);

        /** @var \MicrosoftAzure\Storage\Blob\Models\GetBlobResult $blobResult */
        $blobResult = $this->client->getBlob($this->container, $path);
        $properties = $blobResult->getProperties();
        $content = $this->streamContentsToString($blobResult->getContentStream());

        return $this->normalizeBlobProperties($path, $properties) + ['contents' => $content];
    }

    /**
     * {@inheritdoc}
     */
    public function readStream($path)
    {
        $path = $this->applyPathPrefix($path);

        /** @var \MicrosoftAzure\Storage\Blob\Models\GetBlobResult $blobResult */
        $blobResult = $this->client->getBlob($this->container, $path);
        $properties = $blobResult->getProperties();

        return $this->normalizeBlobProperties($path, $properties) + ['stream' => $blobResult->getContentStream()];
    }

    /**
     * {@inheritdoc}
     */
    public function listContents($directory = '', $recursive = false)
    {
        $directory = $this->applyPathPrefix($directory);

        // Append trailing slash only for directory other than root (which after normalization is an empty string).
        // Listing for / doesn't work properly otherwise.
        if (strlen($directory)) {
            $directory = rtrim($directory, '/') . '/';
        }

        $options = new ListBlobsOptions();
        $options->setPrefix($directory);

        if (!$recursive) {
            $options->setDelimiter('/');
        }

        /** @var ListBlobsResult $listResults */
        $listResults = $this->client->listBlobs($this->container, $options);

        $contents = [];

        foreach ($listResults->getBlobs() as $blob) {
            $contents[] = $this->normalizeBlobProperties($blob->getName(), $blob->getProperties());
        }

        if (!$recursive) {
            $contents = array_merge($contents, array_map([$this, 'normalizeBlobPrefix'], $listResults->getBlobPrefixes()));
        }

        return Util::emulateDirectories($contents);
    }

    /**
     * {@inheritdoc}
     */
    public function getMetadata($path)
    {
        $path = $this->applyPathPrefix($path);

        /** @var \MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesResult $result */
        $result = $this->client->getBlobProperties($this->container, $path);

        return $this->normalizeBlobProperties($path, $result->getProperties());
    }

    /**
     * {@inheritdoc}
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getMimetype($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * Builds the normalized output array.
     *
     * @param string $path
     * @param int    $timestamp
     * @param mixed  $content
     *
     * @return array
     */
    protected function normalize($path, $timestamp, $content = null)
    {
        $data = [
            'path' => $path,
            'timestamp' => (int) $timestamp,
            'dirname' => Util::dirname($path),
            'type' => 'file',
        ];

        if (is_string($content)) {
            $data['contents'] = $content;
        }

        return $data;
    }

    /**
     * Builds the normalized output array from a Blob object.
     *
     * @param string         $path
     * @param BlobProperties $properties
     *
     * @return array
     */
    protected function normalizeBlobProperties($path, BlobProperties $properties)
    {
        if (substr($path, -1) === '/') {
            return ['type' => 'dir', 'path' => $this->removePathPrefix(rtrim($path, '/'))];
        }

        $path = $this->removePathPrefix($path);

        return [
            'path' => $path,
            'timestamp' => (int) $properties->getLastModified()->format('U'),
            'dirname' => Util::dirname($path),
            'mimetype' => $properties->getContentType(),
            'size' => $properties->getContentLength(),
            'type' => 'file',
        ];
    }

    /**
     * Builds the normalized output array from a BlobPrefix object.
     *
     * @param BlobPrefix $blobPrefix
     *
     * @return array
     */
    protected function normalizeBlobPrefix(BlobPrefix $blobPrefix)
    {
        return ['type' => 'dir', 'path' => $this->removePathPrefix(rtrim($blobPrefix->getName(), '/'))];
    }

    /**
     * Retrieves content streamed by Azure into a string.
     *
     * @param resource $resource
     *
     * @return string
     */
    protected function streamContentsToString($resource)
    {
        return stream_get_contents($resource);
    }

    /**
     * Upload a file.
     *
     * @param string           $path     Path
     * @param string|resource  $contents Either a string or a stream.
     * @param Config           $config   Config
     *
     * @return array
     */
    protected function upload($path, $contents, Config $config)
    {
        $path = $this->applyPathPrefix($path);

        /** @var CopyBlobResult $result */
        $result = $this->client->createBlockBlob($this->container, $path, $contents, $this->getOptionsFromConfig($config));

        return $this->normalize($path, $result->getLastModified()->format('U'), $contents);
    }

    /**
     * Retrieve options from a Config instance.
     *
     * @param Config $config
     *
     * @return CreateBlobOptions
     */
    protected function getOptionsFromConfig(Config $config)
    {
        $options = new CreateBlobOptions();

        foreach (static::$metaOptions as $option) {
            if (!$config->has($option)) {
                continue;
            }

            call_user_func([$options, "set$option"], $config->get($option));
        }

        if ($mimetype = $config->get('mimetype')) {
            $options->setContentType($mimetype);
        }

        return $options;
    }
}
vendor
bin
composer.lock
coverage
coverage.xmlfilter:
    paths: [src/*]
checks:
    php:
        code_rating: true
        remove_extra_empty_lines: true
        remove_php_closing_tag: true
        remove_trailing_whitespace: true
        fix_use_statements:
            remove_unused: true
            preserve_multiple: false
            preserve_blanklines: true
            order_alphabetically: true
        fix_php_opening_tag: true
        fix_linefeed: true
        fix_line_ending: true
        fix_identation_4spaces: true
        fix_doc_comments: true
tools:
    external_code_coverage:
        timeout: 600
        runs: 3
    php_code_coverage: false
    php_code_sniffer:
        config:
            standard: PSR2
        filter:
            paths: ['src']
    php_loc:
        enabled: true
        excluded_dirs: [vendor, spec, stubs]
    php_cpd:
        enabled: true
        excluded_dirs: [vendor, spec, stubs]language: php

php:
  - 5.4
  - 5.5
  - 5.6
  - hhvm

install:
  - travis_retry composer install --no-interaction --prefer-source

script:
  - bin/phpunit

after_script:
  - wget https://scrutinizer-ci.com/ocular.phar
  - bash -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.xml; fi;'
{
    "name": "league/flysystem-dropbox",
    "description": "Flysystem adapter for Dropbox",
    "license": "MIT",
    "authors": [
        {
            "name": "Frank de Jonge",
            "email": "info@frenky.net"
        }
    ],
    "require": {
        "php": ">=5.4.0",
        "league/flysystem": "~1.0",
        "dropbox/dropbox-sdk": "~1.1"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.8"
    },
    "autoload": {
        "psr-4": {
            "League\\Flysystem\\Dropbox\\": "src/"
        }
    },
    "config": {
        "bin-dir": "bin"
    },
    "min-stability": "dev",
    "prefer-stable": true,
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="./phpunit.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="true"
         verbose="true"
>
    <testsuites>
        <testsuite name="flysystem/tests">
            <directory suffix=".php">./tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">./src/</directory>
        </whitelist>
    </filter>
</phpunit>
<?php

include __DIR__.'/vendor/autoload.php';
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="./phpunit.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="true"
         verbose="true"
>
    <testsuites>
        <testsuite name="flysystem/tests">
            <directory suffix=".php">./tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">./src/</directory>
        </whitelist>
    </filter>
    <logging>
        <log type="coverage-text" target="php://stdout" showUncoveredFiles="true"/>
        <log type="coverage-html" target="coverage" showUncoveredFiles="true"/>
        <log type="coverage-clover" target="coverage.xml" showUncoveredFiles="true"/>
    </logging>
</phpunit>
# Flysystem Adapter for Dropbox

[![Author](http://img.shields.io/badge/author-@frankdejonge-blue.svg?style=flat-square)](https://twitter.com/frankdejonge)
[![Build Status](https://img.shields.io/travis/thephpleague/flysystem-dropbox/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/flysystem-dropbox)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/flysystem-dropbox.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-dropbox/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/flysystem-dropbox.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-dropbox)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Packagist Version](https://img.shields.io/packagist/v/league/flysystem-dropbox.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-dropbox)
[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem-dropbox.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-dropbox)


## Installation

```bash
composer require league/flysystem-dropbox
```

## Usage

Visit https://www.dropbox.com/developers/apps and get your "App secret".

You can also generate OAuth access token for testing using the Dropbox App Console without going through the authorization flow.

~~~ php
use League\Flysystem\Dropbox\DropboxAdapter;
use League\Flysystem\Filesystem;
use Dropbox\Client;

$client = new Client($accessToken, $appSecret);
$adapter = new DropboxAdapter($client, [$prefix]);

$filesystem = new Filesystem($adapter);
~~~
<?php

namespace League\Flysystem\Dropbox;

use Dropbox\Client;
use Dropbox\Exception;
use Dropbox\Exception_BadResponseCode;
use Dropbox\WriteMode;
use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
use League\Flysystem\Config;
use League\Flysystem\Util;

class DropboxAdapter extends AbstractAdapter
{
    use NotSupportingVisibilityTrait;

    /**
     * @var array
     */
    protected static $resultMap = [
        'bytes' => 'size',
        'mime_type' => 'mimetype',
    ];

    /**
     * @var Client
     */
    protected $client;

    /**
     * Constructor.
     *
     * @param Client $client
     * @param string $prefix
     */
    public function __construct(Client $client, $prefix = null)
    {
        $this->client = $client;
        $this->setPathPrefix($prefix);
    }

    /**
     * {@inheritdoc}
     */
    public function has($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function write($path, $contents, Config $config)
    {
        return $this->upload($path, $contents, WriteMode::add());
    }

    /**
     * {@inheritdoc}
     */
    public function writeStream($path, $resource, Config $config)
    {
        return $this->uploadStream($path, $resource, WriteMode::add());
    }

    /**
     * {@inheritdoc}
     */
    public function update($path, $contents, Config $config)
    {
        return $this->upload($path, $contents, WriteMode::force());
    }

    /**
     * {@inheritdoc}
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->uploadStream($path, $resource, WriteMode::force());
    }

    /**
     * {@inheritdoc}
     */
    public function read($path)
    {
        if ( ! $object = $this->readStream($path)) {
            return false;
        }

        $object['contents'] = stream_get_contents($object['stream']);
        fclose($object['stream']);
        unset($object['stream']);

        return $object;
    }

    /**
     * {@inheritdoc}
     */
    public function readStream($path)
    {
        $stream = fopen('php://temp', 'w+');
        $location = $this->applyPathPrefix($path);

        if ( ! $this->client->getFile($location, $stream)) {
            fclose($stream);

            return false;
        }

        rewind($stream);

        return compact('stream');
    }

    /**
     * {@inheritdoc}
     */
    public function rename($path, $newpath)
    {
        $path = $this->applyPathPrefix($path);
        $newpath = $this->applyPathPrefix($newpath);

        try {
            $this->client->move($path, $newpath);
        } catch (Exception $e) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function copy($path, $newpath)
    {
        $path = $this->applyPathPrefix($path);
        $newpath = $this->applyPathPrefix($newpath);

        try {
            $this->client->copy($path, $newpath);
        } catch (Exception $e) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function delete($path)
    {
        $location = $this->applyPathPrefix($path);
        $result = $this->client->delete($location);

        return isset($result['is_deleted']) ? $result['is_deleted'] : false;
    }

    /**
     * {@inheritdoc}
     */
    public function deleteDir($path)
    {
        return $this->delete($path);
    }

    /**
     * {@inheritdoc}
     */
    public function createDir($path, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $result = $this->client->createFolder($location);

        if ($result === null) {
            return false;
        }

        return $this->normalizeResponse($result, $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getMetadata($path)
    {
        $location = $this->applyPathPrefix($path);

        try {
            $object = $this->client->getMetadata($location);
        } catch(Exception_BadResponseCode $e) {
            if ($e->getStatusCode() === 301) {
                return false;
            }

            throw $e;
        }

        if ( ! $object) {
            return false;
        }

        return $this->normalizeResponse($object, $path);
    }

    /**
     * {@inheritdoc}
     */
    public function getMimetype($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * {@inheritdoc}
     */
    public function listContents($directory = '', $recursive = false)
    {
        $listing = [];
        $directory = trim($directory, '/.');
        $location = $this->applyPathPrefix($directory);

        if ( ! $result = $this->client->getMetadataWithChildren($location)) {
            return [];
        }

        foreach ($result['contents'] as $object) {
            $path = $this->removePathPrefix($object['path']);
            $listing[] = $this->normalizeResponse($object, $path);

            if ($recursive && $object['is_dir']) {
                $listing = array_merge($listing, $this->listContents($path, true));
            }
        }

        return $listing;
    }

    /**
     * Apply the path prefix.
     *
     * @param string $path
     *
     * @return string prefixed path
     */
    public function applyPathPrefix($path)
    {

        $path = parent::applyPathPrefix($path);

        return '/' . ltrim(rtrim($path, '/'), '/');
    }

    /**
     * Do the actual upload of a string file.
     *
     * @param string    $path
     * @param string    $contents
     * @param WriteMode $mode
     *
     * @return array|false file metadata
     */
    protected function upload($path, $contents, WriteMode $mode)
    {
        $location = $this->applyPathPrefix($path);

        if ( ! $result = $this->client->uploadFileFromString($location, $mode, $contents)) {
            return false;
        }

        return $this->normalizeResponse($result, $path);
    }

    /**
     * Do the actual upload of a file resource.
     *
     * @param string    $path
     * @param resource  $resource
     * @param WriteMode $mode
     *
     * @return array|false file metadata
     */
    protected function uploadStream($path, $resource, WriteMode $mode)
    {
        $location = $this->applyPathPrefix($path);

        // If size is zero, consider it unknown.
        $size = Util::getStreamSize($resource) ?: null;

        if ( ! $result = $this->client->uploadFile($location, $mode, $resource, $size)) {
            return false;
        }

        return $this->normalizeResponse($result, $path);
    }

    /**
     * Normalize a Dropbox response.
     *
     * @param array $response
     *
     * @return array
     */
    protected function normalizeResponse(array $response)
    {
        $result = ['path' => ltrim($this->removePathPrefix($response['path']), '/')];

        if (isset($response['modified'])) {
            $result['timestamp'] = strtotime($response['modified']);
        }

        $result = array_merge($result, Util::map($response, static::$resultMap));
        $result['type'] = $response['is_dir'] ? 'dir' : 'file';

        return $result;
    }
}
<?php

use Dropbox\Client;
use Dropbox\Exception_BadResponseCode;
use League\Flysystem\Config;
use League\Flysystem\Dropbox\DropboxAdapter as Dropbox;
use Prophecy\Argument;

class DropboxTests extends PHPUnit_Framework_TestCase
{
    public function dropboxProvider()
    {
        $mock = $this->prophesize('Dropbox\Client');

        return [
            [new Dropbox($mock->reveal(), 'prefix'), $mock],
        ];
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testWrite($adapter, $mock)
    {
        $mock->uploadFileFromString(Argument::any(), Argument::any(), Argument::any())->willReturn([
            'is_dir'   => false,
            'modified' => '10 September 2000',
            'path' => '/prefix/something',
        ], false);

        $result = $adapter->write('something', 'contents', new Config());
        $this->assertInternalType('array', $result);
        $this->assertArrayHasKey('type', $result);
        $this->assertEquals('file', $result['type']);
        $this->assertFalse($adapter->write('something', 'something', new Config()));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testUpdate(Dropbox $adapter, $mock)
    {
        $mock->uploadFileFromString(Argument::any(), Argument::any(), Argument::any())->willReturn([
            'is_dir'   => false,
            'modified' => '10 September 2000',
            'path' => '/prefix/something'
        ], false);

        $result = $adapter->update('something', 'contents', new Config());
        $this->assertInternalType('array', $result);
        $this->assertArrayHasKey('type', $result);
        $this->assertEquals('file', $result['type']);
        $this->assertFalse($adapter->update('something', 'something', new Config()));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testWriteStream(Dropbox $adapter, $mock)
    {
        $mock->uploadFile(Argument::any(), Argument::any(), Argument::any(), null)->willReturn([
            'is_dir'   => false,
            'modified' => '10 September 2000',
            'path' => '/prefix/something'
        ], false);

        $result = $adapter->writeStream('something', tmpfile(), new Config());
        $this->assertInternalType('array', $result);
        $this->assertArrayHasKey('type', $result);
        $this->assertEquals('file', $result['type']);
        $this->assertFalse($adapter->writeStream('something', tmpfile(), new Config()));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testUpdateStream(Dropbox $adapter, $mock)
    {
        $mock->uploadFile(Argument::any(), Argument::any(), Argument::any(), null)->willReturn([
            'is_dir'   => false,
            'modified' => '10 September 2000',
            'path' => '/prefix/something'
        ], false);

        $result = $adapter->updateStream('something', tmpfile(), new Config());
        $this->assertInternalType('array', $result);
        $this->assertArrayHasKey('type', $result);
        $this->assertEquals('file', $result['type']);
        $this->assertFalse($adapter->updateStream('something', tmpfile(), new Config()));
    }

    public function metadataProvider()
    {
        return [
            ['getMetadata'],
            ['getMimetype'],
            ['getTimestamp'],
            ['getSize'],
            ['has'],
        ];
    }

    /**
     * @dataProvider  metadataProvider
     */
    public function testMetadataCalls($method)
    {
        $mock = $this->prophesize('Dropbox\Client');
        $mock->getMetadata('/one')->willReturn([
            'is_dir'   => false,
            'modified' => '10 September 2000',
            'path' => '/one'
        ], false);

        $adapter = new Dropbox($mock->reveal());
        $this->assertInternalType('array', $adapter->{$method}('one', 'two'));
        $this->assertFalse($adapter->{$method}('one', 'two'));
    }

    public function testMetadataFileWasMovedFailure()
    {
        $mock = $this->prophesize('Dropbox\Client');
        $mock->getMetadata('/one')->willThrow(new Exception_BadResponseCode('ERROR', 301));

        $adapter = new Dropbox($mock->reveal());
        $this->assertFalse($adapter->has('one'));
    }

    public function testMetadataFileWasNotMovedFailure()
    {
        $this->setExpectedException('Dropbox\Exception_BadResponseCode');
        $mock = $this->prophesize('Dropbox\Client');
        $mock->getMetadata('/one')->willThrow(new Exception_BadResponseCode('ERROR', 500));

        (new Dropbox($mock->reveal()))->has('one');
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testRead($adapter, $mock)
    {
        $stream = tmpfile();
        fwrite($stream, 'something');
        $mock->getFile(Argument::any(), Argument::any())->willReturn($stream, false);
        $this->assertInternalType('array', $adapter->read('something'));
        $this->assertFalse($adapter->read('something'));
        fclose($stream);
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testReadStream(Dropbox $adapter, $mock)
    {
        $stream = tmpfile();
        fwrite($stream, 'something');
        $mock->getFile(Argument::any(), Argument::any())->willReturn($stream, false);
        $this->assertInternalType('array', $adapter->readStream('something'));
        $this->assertFalse($adapter->readStream('something'));
        fclose($stream);
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testDelete(Dropbox $adapter, $mock)
    {
        $mock->delete('/prefix/something')->willReturn(['is_deleted' => true]);
        $this->assertTrue($adapter->delete('something'));
        $this->assertTrue($adapter->deleteDir('something'));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testCreateDir(Dropbox $adapter, $mock)
    {
        $mock->createFolder('/prefix/fail/please')->willReturn(null);
        $mock->createFolder('/prefix/pass/please')->willReturn([
            'is_dir' => true,
            'path'   => '/prefix/pass/please',
        ]);
        $this->assertFalse($adapter->createDir('fail/please', new Config()));
        $expected = ['path' => 'pass/please', 'type' => 'dir'];
        $this->assertEquals($expected, $adapter->createDir('pass/please', new Config()));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testListContents(Dropbox $adapter, $mock)
    {
        $mock->getMetadataWithChildren(Argument::type('string'))->willReturn(
            ['contents' => [
                ['is_dir' => true, 'path' => 'dirname'],
            ]],
            ['contents' => [
                ['is_dir' => false, 'path' => 'dirname/file'],
            ]],
            false
        );

        $result = $adapter->listContents('', true);
        $this->assertCount(2, $result);
        $this->assertEquals([], $adapter->listContents('', false));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testRename($adapter, $mock)
    {
        $mock->move(Argument::type('string'), Argument::type('string'))->willReturn(['is_dir' => false, 'path' => 'something']);
        $this->assertTrue($adapter->rename('something', 'something'));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testRenameFail($adapter, $mock)
    {
        $mock->move('/prefix/something', '/prefix/something')->willThrow(new \Dropbox\Exception('Message'));

        $this->assertFalse($adapter->rename('something', 'something'));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testCopy($adapter, $mock)
    {
        $mock->copy(Argument::type('string'), Argument::type('string'))->willReturn(['is_dir' => false, 'path' => 'something']);
        $this->assertTrue($adapter->copy('something', 'something'));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testCopyFail($adapter, $mock)
    {
        $mock->copy(Argument::any(), Argument::any())->willThrow(new \Dropbox\Exception('Message'));

        $this->assertFalse($adapter->copy('something', 'something'));
    }

    /**
     * @dataProvider  dropboxProvider
     */
    public function testGetClient($adapter)
    {
        $this->assertInstanceOf('Dropbox\Client', $adapter->getClient());
    }
}
{
    "name": "league/flysystem-sftp",
    "description": "Flysystem adapter for SFTP",
    "license": "MIT",
    "authors": [
        {
            "name": "Frank de Jonge",
            "email": "info@frenky.net"
        }
    ],
    "require": {
        "php": ">=5.4.0",
        "league/flysystem": "~1.0",
        "phpseclib/phpseclib": "~2.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0",
        "mockery/mockery": "0.9.*"
    },
    "autoload": {
        "psr-4": {
            "League\\Flysystem\\Sftp\\": "src/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}
# Flysystem Adapter for SFTP

[![Author](http://img.shields.io/badge/author-@frankdejonge-blue.svg?style=flat-square)](https://twitter.com/frankdejonge)
[![Build Status](https://img.shields.io/travis/thephpleague/flysystem-sftp/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/flysystem-sftp)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/flysystem-sftp.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-sftp/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/flysystem-sftp.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-sftp)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Packagist Version](https://img.shields.io/packagist/v/league/flysystem-sftp.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-sftp)
[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem-sftp.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-sftp)

This adapter uses phpseclib to provide a SFTP adapter for Flysystem.

## Installation

```bash
composer require league/flysystem-sftp
```

## Usage

```php
use League\Flysystem\Sftp\SftpAdapter;
use League\Flysystem\Filesystem;

$adapter = new SftpAdapter([
    'host' => 'example.com',
    'port' => 22,
    'username' => 'username',
    'password' => 'password',
    'privateKey' => 'path/to/or/contents/of/privatekey',
    'root' => '/path/to/root',
    'timeout' => 10,
    'agent' => true,
    'directoryPerm' => 0755
]);

$filesystem = new Filesystem($adapter);
```
<?php

namespace League\Flysystem\Sftp;

use InvalidArgumentException;
use League\Flysystem\Adapter\AbstractFtpAdapter;
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
use League\Flysystem\Util;
use LogicException;
use phpseclib\Net\SFTP;
use phpseclib\Crypt\RSA;
use phpseclib\System\SSH\Agent;
use RuntimeException;

class SftpAdapter extends AbstractFtpAdapter
{
    use StreamedCopyTrait;

    /**
     * @var int
     */
    protected $port = 22;

    /**
     * @var string
     */
    protected $hostFingerprint;

    /**
     * @var string
     */
    protected $privatekey;

    /**
     * @var bool
     */
    protected $useAgent = false;

    /**
     * @var Agent
     */
    private $agent;

    /**
     * @var array
     */
    protected $configurable = ['host', 'hostFingerprint', 'port', 'username', 'password', 'useAgent', 'agent', 'timeout', 'root', 'privateKey', 'permPrivate', 'permPublic', 'directoryPerm', 'NetSftpConnection'];

    /**
     * @var array
     */
    protected $statMap = ['mtime' => 'timestamp', 'size' => 'size'];

    /**
     * @var int
     */
    protected $directoryPerm = 0744;

    /**
     * Prefix a path.
     *
     * @param string $path
     *
     * @return string
     */
    protected function prefix($path)
    {
        return $this->root.ltrim($path, $this->separator);
    }

    /**
     * Set the finger print of the public key of the host you are connecting to.
     *
     * If the key does not match the server identification, the connection will
     * be aborted.
     *
     * @param string $fingerprint Example: '88:76:75:96:c1:26:7c:dd:9f:87:50:db:ac:c4:a8:7c'.
     *
     * @return $this
     */
    public function setHostFingerprint($fingerprint)
    {
        $this->hostFingerprint = $fingerprint;

        return $this;
    }

    /**
     * Set the private key (string or path to local file).
     *
     * @param string $key
     *
     * @return $this
     */
    public function setPrivateKey($key)
    {
        $this->privatekey = $key;

        return $this;
    }

    /**
     * @param boolean $useAgent
     *
     * @return $this
     */
    public function setUseAgent($useAgent)
    {
        $this->useAgent = (bool) $useAgent;

        return $this;
    }

    /**
     * @param Agent $agent
     *
     * @return $this
     */
    public function setAgent(Agent $agent)
    {
        $this->agent = $agent;

        return $this;
    }

    /**
     * Set permissions for new directory
     *
     * @param int $directoryPerm
     *
     * @return $this
     */
    public function setDirectoryPerm($directoryPerm)
    {
        $this->directoryPerm = $directoryPerm;

        return $this;
    }

    /**
     * Get permissions for new directory
     *
     * @return int
     */
    public function getDirectoryPerm()
    {
        return $this->directoryPerm;
    }

    /**
     * Inject the SFTP instance.
     *
     * @param SFTP $connection
     *
     * @return $this
     */
    public function setNetSftpConnection(SFTP $connection)
    {
        $this->connection = $connection;

        return $this;
    }

    /**
     * Connect.
     */
    public function connect()
    {
        $this->connection = $this->connection ?: new SFTP($this->host, $this->port, $this->timeout);
        $this->login();
        $this->setConnectionRoot();
    }

    /**
     * Login.
     *
     * @throws LogicException
     */
    protected function login()
    {
        if ($this->hostFingerprint) {
            $actualFingerprint = $this->getHexFingerprintFromSshPublicKey($this->connection->getServerPublicHostKey());

            if (0 !== strcasecmp($this->hostFingerprint, $actualFingerprint)) {
                throw new LogicException('The authenticity of host '.$this->host.' can\'t be established.');
            }
        }

        $authentication = $this->getAuthentication();

        if (! $this->connection->login($this->getUsername(), $authentication)) {
            throw new LogicException('Could not login with username: '.$this->getUsername().', host: '.$this->host);
        }

        if ($authentication instanceof Agent) {
            $authentication->startSSHForwarding($this->connection);
        }
    }

    /**
     * Convert the SSH RSA public key into a hex formatted fingerprint.
     *
     * @param string $publickey
     * @return string Hex formatted fingerprint, e.g. '88:76:75:96:c1:26:7c:dd:9f:87:50:db:ac:c4:a8:7c'.
     */
    private function getHexFingerprintFromSshPublicKey ($publickey)
    {
        $content = explode(' ', $publickey, 3);
        return implode(':', str_split(md5(base64_decode($content[1])), 2));
    }

    /**
     * Set the connection root.
     */
    protected function setConnectionRoot()
    {
        $root = $this->getRoot();

        if (! $root) {
            return;
        }

        if (! $this->connection->chdir($root)) {
            throw new RuntimeException('Root is invalid or does not exist: '.$root);
        }
        $this->root = $this->connection->pwd() . $this->separator;
    }

    /**
     * Get the password, either the private key or a plain text password.
     *
     * @return Agent|RSA|string
     */
    public function getAuthentication()
    {
        if ($this->useAgent) {
            return $this->getAgent();
        }

        if ($this->privatekey) {
            return $this->getPrivateKey();
        }

        return $this->getPassword();
    }

    /**
     * Get the private get with the password or private key contents.
     *
     * @return RSA
     */
    public function getPrivateKey()
    {
        if (@is_file($this->privatekey)) {
            $this->privatekey = file_get_contents($this->privatekey);
        }

        $key = new RSA();

        if ($password = $this->getPassword()) {
            $key->setPassword($password);
        }

        $key->loadKey($this->privatekey);

        return $key;
    }

    /**
     * @return Agent|bool
     */
    public function getAgent()
    {
        if ( ! $this->agent instanceof Agent) {
            $this->agent = new Agent();
        }

        return $this->agent;
    }

    /**
     * List the contents of a directory.
     *
     * @param string $directory
     * @param bool   $recursive
     *
     * @return array
     */
    protected function listDirectoryContents($directory, $recursive = true)
    {
        $result = [];
        $connection = $this->getConnection();
        $location = $this->prefix($directory);
        $listing = $connection->rawlist($location);

        if ($listing === false) {
            return [];
        }

        foreach ($listing as $filename => $object) {
            if (in_array($filename, ['.', '..'])) {
                continue;
            }

            $path = empty($directory) ? $filename : ($directory.'/'.$filename);
            $result[] = $this->normalizeListingObject($path, $object);

            if ($recursive && isset($object['type']) && $object['type'] === NET_SFTP_TYPE_DIRECTORY) {
                $result = array_merge($result, $this->listDirectoryContents($path));
            }
        }

        return $result;
    }

    /**
     * Normalize a listing response.
     *
     * @param string $path
     * @param array  $object
     *
     * @return array
     */
    protected function normalizeListingObject($path, array $object)
    {
        $permissions = $this->normalizePermissions($object['permissions']);
        $type = isset($object['type']) && ($object['type'] === 1) ? 'file' : 'dir' ;

        $timestamp = $object['mtime'];

        if ($type === 'dir') {
            return compact('path', 'timestamp', 'type');
        }

        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
        $size = (int) $object['size'];

        return compact('path', 'timestamp', 'type', 'visibility', 'size');
    }

    /**
     * Disconnect.
     */
    public function disconnect()
    {
        $this->connection = null;
    }

    /**
     * @inheritdoc
     */
    public function write($path, $contents, Config $config)
    {
        if ($this->upload($path, $contents, $config) === false) {
            return false;
        }

        return compact('contents', 'visibility', 'path');
    }

    /**
     * @inheritdoc
     */
    public function writeStream($path, $resource, Config $config)
    {
        if ($this->upload($path, $resource, $config) === false) {
            return false;
        }

        return compact('visibility', 'path');
    }

    /**
     * Upload a file.
     *
     * @param string          $path
     * @param string|resource $contents
     * @param Config          $config
     * @return bool
     */
    public function upload($path, $contents, Config $config)
    {
        $connection = $this->getConnection();
        $this->ensureDirectory(Util::dirname($path));
        $config = Util::ensureConfig($config);

        if (! $connection->put($path, $contents, SFTP::SOURCE_STRING)) {
            return false;
        }

        if ($config && $visibility = $config->get('visibility')) {
            $this->setVisibility($path, $visibility);
        }

        return true;
    }

    /**
     * @inheritdoc
     */
    public function read($path)
    {
        $connection = $this->getConnection();

        if (($contents = $connection->get($path)) === false) {
            return false;
        }

        return compact('contents');
    }

    /**
     * @inheritdoc
     */
    public function readStream($path)
    {
        $stream = tmpfile();
        $connection = $this->getConnection();

        if ($connection->get($path, $stream) === false) {
            fclose($stream);
            return false;
        }

        rewind($stream);

        return compact('stream');
    }

    /**
     * @inheritdoc
     */
    public function update($path, $contents, Config $config)
    {
        return $this->write($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function updateStream($path, $contents, Config $config)
    {
        return $this->writeStream($path, $contents, $config);
    }

    /**
     * @inheritdoc
     */
    public function delete($path)
    {
        $connection = $this->getConnection();

        return $connection->delete($path);
    }

    /**
     * @inheritdoc
     */
    public function rename($path, $newpath)
    {
        $connection = $this->getConnection();

        return $connection->rename($path, $newpath);
    }

    /**
     * @inheritdoc
     */
    public function deleteDir($dirname)
    {
        $connection = $this->getConnection();

        return $connection->delete($dirname, true);
    }

    /**
     * @inheritdoc
     */
    public function has($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getMetadata($path)
    {
        $connection = $this->getConnection();
        $info = $connection->stat($path);

        if ($info === false) {
            return false;
        }

        $result = Util::map($info, $this->statMap);
        $result['type'] = $info['type'] === NET_SFTP_TYPE_DIRECTORY ? 'dir' : 'file';
        $result['visibility'] = $info['permissions'] & $this->permPublic ? 'public' : 'private';

        return $result;
    }

    /**
     * @inheritdoc
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function getMimetype($path)
    {
        if (! $data = $this->read($path)) {
            return false;
        }

        $data['mimetype'] = Util::guessMimeType($path, $data['contents']);

        return $data;
    }

    /**
     * @inheritdoc
     */
    public function createDir($dirname, Config $config)
    {
        $connection = $this->getConnection();

        if (! $connection->mkdir($dirname, $this->directoryPerm, true)) {
            return false;
        }

        return ['path' => $dirname];
    }

    /**
     * @inheritdoc
     */
    public function getVisibility($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @inheritdoc
     */
    public function setVisibility($path, $visibility)
    {
        $visibility = ucfirst($visibility);

        if (! isset($this->{'perm'.$visibility})) {
            throw new InvalidArgumentException('Unknown visibility: '.$visibility);
        }

        $connection = $this->getConnection();

        return $connection->chmod($this->{'perm'.$visibility}, $path);
    }

    /**
     * @inheritdoc
     */
    public function isConnected()
    {
        if ($this->connection instanceof SFTP && $this->connection->isConnected()) {
            return true;
        }

        return false;
    }
}
# Changelog

### 1.0.5 - 2016-12-14

### Fixed

* Copy now also works with native webdav capabilities.

## 1.0.4 - 2016-08-11

### Fixed

* The getMetadata function now also catches http exceptions.

## 1.0.3 - 2016-07-11

### Improved

* The `sabre/dav` version is upped to ~3.1.

## 1.0.2 - 2016-03-17

### Improved

* Listing contents now prevents 301 redirects by adding a trailing slash.

## 1.0.1 - 2015-05-18

### Fixed

* Corrected namespace for missing object exception catching.
* Corrected last-modified return type handling (int -> [int])


## 1.0.0

Initial release
{
    "name": "league/flysystem-webdav",
    "description": "Flysystem adapter for WebDAV",
    "license": "MIT",
    "authors": [
        {
            "name": "Frank de Jonge",
            "email": "info@frenky.net"
        }
    ],
    "require": {
        "php": ">=5.5.0",
        "league/flysystem": "~1.0",
        "sabre/dav": "~3.1"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0",
        "mockery/mockery": "~0.9"
    },
    "autoload": {
        "psr-4": {
            "League\\Flysystem\\WebDAV\\": "src"
        }
    },
    "config": {
        "bin-dir": "bin"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}
Copyright (c) 2014 Frank de Jonge

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.
<?php

namespace League\Flysystem\WebDAV;

use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
use League\Flysystem\Adapter\Polyfill\StreamedTrait;
use League\Flysystem\Config;
use League\Flysystem\Util;
use LogicException;
use Sabre\DAV\Client;
use Sabre\DAV\Exception;
use Sabre\DAV\Exception\NotFound;
use Sabre\HTTP\ClientHttpException;
use Sabre\HTTP\HttpException;

class WebDAVAdapter extends AbstractAdapter
{
    use StreamedTrait;
    use StreamedCopyTrait {
        StreamedCopyTrait::copy as streamedCopy;
    }
    use NotSupportingVisibilityTrait;

    /**
     * @var array
     */
    protected static $resultMap = [
        '{DAV:}getcontentlength' => 'size',
        '{DAV:}getcontenttype' => 'mimetype',
        'content-length' => 'size',
        'content-type' => 'mimetype',
    ];

    /**
     * @var Client
     */
    protected $client;

    /**
     * @var bool
     */
    protected $useStreamedCopy = true;

    /**
     * Constructor.
     *
     * @param Client $client
     * @param string $prefix
     * @param bool $useStreamedCopy
     */
    public function __construct(Client $client, $prefix = null, $useStreamedCopy = true)
    {
        $this->client = $client;
        $this->setPathPrefix($prefix);
        $this->setUseStreamedCopy($useStreamedCopy);
    }
    
    protected function stream($path, $resource, Config $config, $fallback)
    {
		Util::rewindStream($resource);
        $fallbackCall = [$this, $fallback];

        return call_user_func($fallbackCall, $path, $resource, $config);
	}

    /**
     * url encode a path
     *
     * @param string $path
     *
     * @return string
     */
    protected function encodePath($path)
	{
		$a = explode('/', $path);
		for ($i=0; $i<count($a); $i++) {
			$a[$i] = rawurlencode($a[$i]);
		}
		return implode('/', $a);
	}

    /**
     * {@inheritdoc}
     */
    public function getMetadata($path)
    {
        $location = $this->applyPathPrefix($this->encodePath($path));

        try {
            $result = $this->client->propFind($location, [
                '{DAV:}displayname',
                '{DAV:}getcontentlength',
                '{DAV:}getcontenttype',
                '{DAV:}getlastmodified',
            ]);

            return $this->normalizeObject($result, $path);
        } catch (Exception $e) {
            return false;
        } catch (HttpException $e) {
            return false;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function has($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function read($path)
    {
        $location = $this->applyPathPrefix($this->encodePath($path));

        try {
            $response = $this->client->request('GET', $location);

            if ($response['statusCode'] !== 200) {
                return false;
            }

            return array_merge([
                'contents' => $response['body'],
                'timestamp' => strtotime(is_array($response['headers']['last-modified'])
                    ? current($response['headers']['last-modified'])
                    : $response['headers']['last-modified']),
                'path' => $path,
            ], Util::map($response['headers'], static::$resultMap));
        } catch (Exception $e) {
            return false;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function write($path, $contents, Config $config)
    {
        if (!$this->createDir(Util::dirname($path), $config)) {
            return false;
        }

        $location = $this->applyPathPrefix($this->encodePath($path));
        $response = $this->client->request('PUT', $location, $contents);

        if ($response['statusCode'] >= 400) {
            return false;
        }

        $result = compact('path', 'contents');

        if ($config->get('visibility')) {
            throw new LogicException(__CLASS__.' does not support visibility settings.');
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function update($path, $contents, Config $config)
    {
        return $this->write($path, $contents, $config);
    }

    /**
     * {@inheritdoc}
     */
    public function rename($path, $newpath)
    {
        $location = $this->applyPathPrefix($this->encodePath($path));
        $newLocation = $this->applyPathPrefix($this->encodePath($newpath));

        try {
            $response = $this->client->request('MOVE', '/'.ltrim($location, '/'), null, [
                'Destination' => '/'.ltrim($newLocation, '/'),
            ]);

            if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
                return true;
            }
        } catch (NotFound $e) {
            // Would have returned false here, but would be redundant
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function copy($path, $newpath)
    {
        if ($this->useStreamedCopy === true) {
            return $this->streamedCopy($path, $newpath);
        } else {
            return $this->nativeCopy($path, $newpath);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function delete($path)
    {
        $location = $this->applyPathPrefix($this->encodePath($path));

        try {
            $this->client->request('DELETE', $location);

            return true;
        } catch (NotFound $e) {
            return false;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function createDir($path, Config $config)
    {
        $encodedPath = $this->encodePath($path);
        $path = trim($path, '/');

        $result = compact('path') + ['type' => 'dir'];

        if (Util::normalizeDirname($path) === '' || $this->has($path)) {
            return $result;
        }

        $directories = explode('/', $path);
        if (count($directories) > 1) {
            $parentDirectories = array_splice($directories, 0, count($directories) - 1);
            if (!$this->createDir(implode('/', $parentDirectories), $config)) {
                return false;
            }
        }

        $location = $this->applyPathPrefix($encodedPath);
        $response = $this->client->request('MKCOL', $location);

        if ($response['statusCode'] !== 201) {
            return false;
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function deleteDir($dirname)
    {
        return $this->delete($dirname);
    }

    /**
     * {@inheritdoc}
     */
    public function listContents($directory = '', $recursive = false)
    {
        $location = $this->applyPathPrefix($this->encodePath($directory));
        $response = $this->client->propFind($location . '/', [
            '{DAV:}displayname',
            '{DAV:}getcontentlength',
            '{DAV:}getcontenttype',
            '{DAV:}getlastmodified',
        ], 1);

        array_shift($response);
        $result = [];

        foreach ($response as $path => $object) {
            $path = urldecode($this->removePathPrefix($path));
            $object = $this->normalizeObject($object, $path);
            $result[] = $object;

            if ($recursive && $object['type'] === 'dir') {
                $result = array_merge($result, $this->listContents($object['path'], true));
            }
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getMimetype($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @return boolean
     */
    public function getUseStreamedCopy()
    {
        return $this->useStreamedCopy;
    }

    /**
     * @param boolean $useStreamedCopy
     */
    public function setUseStreamedCopy($useStreamedCopy)
    {
        $this->useStreamedCopy = (bool)$useStreamedCopy;
    }

    /**
     * Copy a file through WebDav COPY method.
     *
     * @param string $path
     * @param string $newPath
     *
     * @return bool
     */
    protected function nativeCopy($path, $newPath)
    {
        if (!$this->createDir(Util::dirname($newPath), new Config())) {
            return false;
        }

        $location = $this->applyPathPrefix($this->encodePath($path));
        $newLocation = $this->applyPathPrefix($this->encodePath($newPath));

        try {
            $destination = $this->client->getAbsoluteUrl($newLocation);
            $response = $this->client->request('COPY', '/'.ltrim($location, '/'), null, [
                'Destination' => $destination,
            ]);

            if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
                return true;
            }
        } catch (NotFound $e) {
            // Would have returned false here, but would be redundant
        }

        return false;
    }

    /**
     * Normalise a WebDAV repsonse object.
     *
     * @param array  $object
     * @param string $path
     *
     * @return array
     */
    protected function normalizeObject(array $object, $path)
    {
        if (! isset($object['{DAV:}getcontentlength']) or $object['{DAV:}getcontentlength'] == "") {
            return ['type' => 'dir', 'path' => trim($path, '/')];
        }

        $result = Util::map($object, static::$resultMap);

        if (isset($object['{DAV:}getlastmodified'])) {
            $result['timestamp'] = strtotime($object['{DAV:}getlastmodified']);
        }

        $result['type'] = 'file';
        $result['path'] = trim($path, '/');

        return $result;
    }
}
build
composer.lock
docs
vendor
filter:
    paths: [src/*]
checks:
    php:
        code_rating: true
        remove_extra_empty_lines: true
        remove_php_closing_tag: true
        remove_trailing_whitespace: true
        fix_use_statements:
            remove_unused: true
            preserve_multiple: false
            preserve_blanklines: true
            order_alphabetically: true
        fix_php_opening_tag: true
        fix_linefeed: true
        fix_line_ending: true
        fix_identation_4spaces: true
        fix_doc_comments: true
tools:
    php_code_coverage: true
    php_code_sniffer:
        config:
            standard: PSR2
        filter:
            paths: ['src']
    php_loc:
        enabled: true
    php_cpd:
        enabled: true
language: php

php:
  - 5.5
  - 5.6
  - hhvm

install:
  - travis_retry composer install --no-interaction --prefer-source

script:
  - vendor/bin/phpunit

after_script:
  - wget https://scrutinizer-ci.com/ocular.phar
  - bash -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.xml; fi;'
# Changelog

All Notable changes to `flysystem-backblaze` will be documented in this file.

Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.

## NEXT - YYYY-MM-DD

### Added
- Nothing

### Deprecated
- Nothing

### Fixed
- Nothing

### Removed
- Nothing

### Security
- Nothing
{
    "name": "mhetreramesh/flysystem-backblaze",
    "type": "library",
    "description": "Backblaze adapter for the flysystem filesystem abstraction library",
    "keywords": ["flysystem", "filesystem", "api", "backblaze", "client"],
    "homepage": "https://github.com/mhetreramesh/flysystem-backblaze",
    "license": "MIT",
    "authors": [
        {
            "name": "Ramesh Mhetre",
            "email": "mhetreramesh@gmail.com",
            "homepage": "https://about.me/rameshmhetre",
            "role": "Developer"
        }
    ],
    "require": {
        "php": ">=5.5.0",
        "league/flysystem": "~1.0",
        "cwhite92/b2-sdk-php" : "^1.2"
    },
    "require-dev": {
        "phpunit/phpunit" : "~4.0||~5.0",
        "scrutinizer/ocular": "~1.1",
        "squizlabs/php_codesniffer": "~2.3"
    },
    "autoload": {
        "psr-4": {
            "Mhetreramesh\\Flysystem\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Mhetreramesh\\Flysystem\\Tests\\": "tests"
        }
    },
    "scripts": {
        "test": "phpunit",
        "format": "phpcbf --standard=psr2 src/"
    },
    "config": {
        "sort-packages": true
    }
}
# Contributor Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at `contact@thephpleague.com`. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
# Contributing

Contributions are **welcome** and will be fully **credited**.

We accept contributions via Pull Requests on [Github](https://github.com/mhetreramesh/flysystem-backblaze).


## Pull Requests

- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).

- **Add tests!** - Your patch won't be accepted if it doesn't have tests.

- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.

- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.

- **Create feature branches** - Don't ask us to pull from your master branch.

- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.

- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.


## Running Tests

``` bash
$ composer test
```


**Happy coding**!
<!-- Provide a general summary of the issue in the Title above -->

## Detailed description

Provide a detailed description of the change or addition you are proposing.

Make it clear if the issue is a bug, an enhancement or just a question.

## Context

Why is this change important to you? How would you use it?

How can it benefit other users?

## Possible implementation

Not obligatory, but suggest an idea for implementing addition or change.

## Your environment

Include as many relevant details about the environment you experienced the bug in and how to reproduce it.

* Version used (e.g. PHP 5.6, HHVM 3):
* Operating system and version (e.g. Ubuntu 16.04, Windows 7):
* Link to your project:
* ...
* ...
MIT License

Copyright (c) 2016 Ramesh Mhetre

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.
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="./phpunit.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="true"
         verbose="true"
>
    <testsuites>
        <testsuite name="flysystem/tests">
            <directory suffix=".php">./tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">./src/</directory>
        </whitelist>
    </filter>
</phpunit><?php

require __DIR__.'/vendor/autoload.php';<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="./phpunit.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="true"
         verbose="true"
>
    <testsuites>
        <testsuite name="flysystem/tests">
            <directory suffix=".php">./tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">./src/</directory>
        </whitelist>
    </filter>
    <logging>
        <log type="coverage-text" target="php://stdout" showUncoveredFiles="true"/>
        <log type="coverage-html" target="coverage" showUncoveredFiles="true"/>
        <log type="coverage-clover" target="coverage.xml" showUncoveredFiles="true"/>
    </logging>
</phpunit><!--- Provide a general summary of your changes in the Title above -->

## Description

Describe your changes in detail.

## Motivation and context

Why is this change required? What problem does it solve?

If it fixes an open issue, please link to the issue here (if you write `fixes #num`
or `closes #num`, the issue will be automatically closed when the pull is accepted.)

## How has this been tested?

Please describe in detail how you tested your changes.

Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc.

## Screenshots (if appropriate)

## Types of changes

What types of changes does your code introduce? Put an `x` in all the boxes that apply:
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

Go over all the following points, and put an `x` in all the boxes that apply.

Please, please, please, don't send your pull request until all of the boxes are ticked.

- [ ] I have read the **[CONTRIBUTING](CONTRIBUTING.md)** document.
- [ ] My pull request addresses exactly one patch/feature.
- [ ] I have created a branch for this patch/feature.
- [ ] Each individual commit in the pull request is meaningful.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] If my change requires a change to the documentation, I have updated it accordingly.
- [ ] My code follows the code style of this project.

If you're unsure about any of these, don't hesitate to ask. We're here to help!
# flysystem-backblaze

[![Author](http://img.shields.io/badge/author-@mhetreramesh-blue.svg?style=flat-square)](https://twitter.com/mhetreramesh)
[![Latest Version on Packagist](https://img.shields.io/packagist/v/mhetreramesh/flysystem-backblaze.svg?style=flat-square)](https://packagist.org/packages/mhetreramesh/flysystem-backblaze)
[![Software License][ico-license]](LICENSE.md)
[![Build Status](https://img.shields.io/travis/mhetreramesh/flysystem-backblaze/master.svg?style=flat-square)](https://travis-ci.org/mhetreramesh/flysystem-backblaze)
[![Coverage Status][ico-scrutinizer]][link-scrutinizer]
[![Quality Score][ico-code-quality]][link-code-quality]
[![Total Downloads](https://img.shields.io/packagist/dt/mhetreramesh/flysystem-backblaze.svg?style=flat-square)](https://packagist.org/packages/mhetreramesh/flysystem-backblaze)

Visit (https://secure.backblaze.com/b2_buckets.htm) and get your account id, application key.

The Backblaze adapter gives the possibility to use the Flysystem filesystem abstraction library with backblaze. It uses the [Backblaze B2 SDK](https://github.com/cwhite92/b2-sdk-php) to communicate with the API.

## Install

Via Composer

``` bash
$ composer require mhetreramesh/flysystem-backblaze
```

## Usage

``` php
use Mhetreramesh\Flysystem\BackblazeAdapter;
use League\Flysystem\Filesystem;
use ChrisWhite\B2\Client;

$client = new Client($accountId, $applicationKey);
$adapter = new BackblazeAdapter($client);

$filesystem = new Filesystem($adapter);
```

## Change log

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.

## Testing

``` bash
$ composer test
```

## Contributing

Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details.

## Security

If you discover any security related issues, please email mhetreramesh@gmail.com instead of using the issue tracker.

## Credits

- [Ramesh Mhetre][link-author]
- [All Contributors][link-contributors]

## License

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

[ico-version]: https://img.shields.io/packagist/v/mhetreramesh/flysystem-backblaze.svg?style=flat-square
[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
[ico-travis]: https://img.shields.io/travis/mhetreramesh/flysystem-backblaze/master.svg?style=flat-square
[ico-scrutinizer]: https://img.shields.io/scrutinizer/coverage/g/mhetreramesh/flysystem-backblaze.svg?style=flat-square
[ico-code-quality]: https://img.shields.io/scrutinizer/g/mhetreramesh/flysystem-backblaze.svg?style=flat-square
[ico-downloads]: https://img.shields.io/packagist/dt/mhetreramesh/flysystem-backblaze.svg?style=flat-square

[link-packagist]: https://packagist.org/packages/mhetreramesh/flysystem-backblaze
[link-travis]: https://travis-ci.org/mhetreramesh/flysystem-backblaze
[link-scrutinizer]: https://scrutinizer-ci.com/g/mhetreramesh/flysystem-backblaze/code-structure
[link-code-quality]: https://scrutinizer-ci.com/g/mhetreramesh/flysystem-backblaze
[link-downloads]: https://packagist.org/packages/mhetreramesh/flysystem-backblaze
[link-author]: https://github.com/mhetreramesh
[link-contributors]: ../../contributors
<?php

namespace Mhetreramesh\Flysystem;

use ChrisWhite\B2\Client;
use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
use League\Flysystem\Config;

class BackblazeAdapter extends AbstractAdapter {

    use NotSupportingVisibilityTrait;

    protected $client;

    protected $bucketName;

    public function __construct(Client $client, $bucketName)
    {
        $this->client = $client;
        $this->bucketName = $bucketName;
    }

    /**
     * {@inheritdoc}
     */
    public function has($path)
    {
        return $this->getClient()->fileExists(['FileName' => $path, 'BucketName' => $this->bucketName]);
    }

    /**
     * {@inheritdoc}
     */
    public function write($path, $contents, Config $config)
    {
        $file = $this->getClient()->upload([
            'BucketName' => $this->bucketName,
            'FileName' => $path,
            'Body' => $contents
        ]);
        return $this->getFileInfo($file);
    }

    /**
     * {@inheritdoc}
     */
    public function writeStream($path, $resource, Config $config)
    {
        $file = $this->getClient()->upload([
            'BucketName' => $this->bucketName,
            'FileName' => $path,
            'Body' => $resource
        ]);
        return $this->getFileInfo($file);
    }

    /**
     * {@inheritdoc}
     */
    public function update($path, $contents, Config $config)
    {
        echo 'update'; die;
    }

    /**
     * {@inheritdoc}
     */
    public function updateStream($path, $resource, Config $config)
    {
        echo 'updateStream'; die;
    }

    /**
     * {@inheritdoc}
     */
    public function read($path)
    {
        echo 'read'; die;
    }

    /**
     * {@inheritdoc}
     */
    public function readStream($path)
    {
        echo ''; die;
    }

    /**
     * {@inheritdoc}
     */
    public function rename($path, $newpath)
    {
        echo 'rename'; die;
    }

    /**
     * {@inheritdoc}
     */
    public function copy($path, $newpath)
    {
        return $this->getClient()->upload([
            'BucketName' => $this->bucketName,
            'FileName' => $newpath,
            'Body' => fopen($path)
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function delete($path)
    {
        return $this->getClient()->deleteFile(['FileName' => $path, 'BucketName' => $this->bucketName]);
    }

    /**
     * {@inheritdoc}
     */
    public function deleteDir($path)
    {
        return $this->getClient()->deleteFile(['FileName' => $path, 'BucketName' => $this->bucketName]);
    }

    /**
     * {@inheritdoc}
     */
    public function createDir($path, Config $config)
    {
        return $this->getClient()->upload([
            'BucketName' => $this->bucketName,
            'FileName' => $path
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function getMetadata($path)
    {
        echo 'getMetadata'; die;
    }

    /**
     * {@inheritdoc}
     */
    public function getMimetype($path)
    {
        echo 'getMimetype'; die;
    }

    /**
     * {@inheritdoc}
     */
    public function getSize($path)
    {
        $file = $this->getClient()->getFile(['FileName' => $path, 'BucketName' => $this->bucketName]);

        return $this->getFileInfo($file);
    }

    /**
     * {@inheritdoc}
     */
    public function getTimestamp($path)
    {
        $file = $this->getClient()->getFile(['FileName' => $path, 'BucketName' => $this->bucketName]);

        return $this->getFileInfo($file);
    }

    /**
     * {@inheritdoc}
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * {@inheritdoc}
     */
    public function listContents($directory = '', $recursive = false)
    {
        $fileObjects = $this->getClient()->listFiles([
            'BucketName' => $this->bucketName,
        ]);
        $result = [];
        foreach ($fileObjects as $fileObject) {
            $result[] = $this->getFileInfo($fileObject);
        }
        return $result;
    }

    /**
     * Get file info
     *
     * @param $file
     *
     * @return array
     */

    protected function getFileInfo($file)
    {
        $normalized = [
            'type' => 'file',
            'path' => $file->getName(),
            'timestamp' => substr($file->getUploadTimestamp(), 0, -3),
            'size' => $file->getSize()
        ];

        return $normalized;
    }
}
<?php

use ChrisWhite\B2\Client;
use Mhetreramesh\Flysystem\BackblazeAdapter as Backblaze;
use \ChrisWhite\B2\File;
use \League\Flysystem\Config;

class BackblazeAdapterTests extends PHPUnit_Framework_TestCase
{
    public function backblazeProvider()
    {
        $mock = $this->prophesize('ChrisWhite\B2\Client');
        return [
            [new Backblaze($mock->reveal(), 'my_bucket'), $mock],
        ];
    }

    /**
     * @dataProvider  backblazeProvider
     */
    public function testWrite($adapter, $mock)
    {
        $mock->upload(["BucketName" => "my_bucket", "FileName" => "something", "Body" => "contents"])->willReturn(new File('something','','','',''), false);
        $result = $adapter->write('something', 'contents', new Config());
        $this->assertInternalType('array', $result);
        $this->assertArrayHasKey('type', $result);
        $this->assertEquals('file', $result['type']);
    }
}.buildpath
.settings/*
.project
.cache.properties

/vendor/*
/output/*



Tracking Breaking Changes in 0.10.0
ALL
* Remove all pear dependencies: HTTP_Request2, Mail_mime, and Mail_mimeDecode. Use Guzzle as underlying http client library.
* Change root namespace from "WindowsAzure" to "MicrosoftAzure/Storage".
* When set metadata operations contains invalid characters, it throws a ServiceException with 400 bad request error instead of Http_Request2_LogicException.

BLOB
* MicrosoftAzure\Storage\Blob\Models\Blocks.setBlockId now requires a base64 encoded string.<?xml version="1.0"?>
<ruleset name="Basic">
 <description>Azure Storage SDK for PHP rules</description>
 
 <rule ref="Ecg.PHP.Goto.Found"/>
 <rule ref="Ecg.Strings.StringPosition.ImproperValueTesting"/>
 <rule ref="Ecg.Security.Superglobal.SuperglobalUsage"/>

 <rule ref="Generic.CodeAnalysis.ForLoopShouldBeWhileLoop"/>
 <rule ref="Generic.CodeAnalysis.ForLoopWithTestFunctionCall"/>
 <rule ref="Generic.CodeAnalysis.JumbledIncrementer"/>
 <rule ref="Generic.CodeAnalysis.UnconditionalIfStatement"/>
 <rule ref="Generic.CodeAnalysis.UnnecessaryFinalModifier"/>

 <rule ref="Generic.Files.LineLength"/>
 
 <rule ref="Generic.Formatting.DisallowMultipleStatements"/>
 <rule ref="Generic.Formatting.MultipleStatementAlignment"/>

 <rule ref="Generic.Metrics.CyclomaticComplexity"/>
 <rule ref="Generic.Metrics.NestingLevel"/>
 
 <rule ref="Generic.NamingConventions.ConstructorName"/>
 <rule ref="Generic.NamingConventions.UpperCaseConstantName"/>
 
 <rule ref="Generic.PHP.CharacterBeforePHPOpeningTag.Found"/>
 <rule ref="Generic.PHP.DeprecatedFunctions.Deprecated"/>
 <rule ref="Generic.PHP.DisallowShortOpenTag"/>
 <rule ref="Generic.PHP.NoSilencedErrors"/>
 <rule ref="Generic.PHP.Syntax"/>
 
 <rule ref="Generic.WhiteSpace.DisallowTabIndent"/>
 
 <rule ref="Squiz.PHP.Eval.Discouraged"/>
 
 <rule ref="Zend.Files.ClosingTag.NotAllowed">

 <rule ref="PEAR"/>
 
 </ruleset>
<?xml version="1.0"?>

<ruleset name="Basic"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
  <description>Sebastian Bergmann's ruleset</description>

  <rule ref="rulesets/codesize.xml/CyclomaticComplexity" />
  <rule ref="rulesets/codesize.xml/NPathComplexity" />
  <rule ref="rulesets/codesize.xml/ExcessiveClassComplexity" />
  <rule ref="rulesets/codesize.xml/ExcessiveClassLength" />
  <rule ref="rulesets/codesize.xml/ExcessiveMethodLength" />
  <rule ref="rulesets/codesize.xml/ExcessiveParameterList" />

  <rule ref="rulesets/design.xml/EvalExpression" />
  <rule ref="rulesets/design.xml/ExitExpression" />
  <rule ref="rulesets/design.xml/GotoStatement" />

  <rule ref="rulesets/naming.xml/ConstructorWithNameAsEnclosingClass" />

  <rule ref="rulesets/unusedcode.xml/UnusedFormalParameter" />
  <rule ref="rulesets/unusedcode.xml/UnusedLocalVariable" />
  <rule ref="rulesets/unusedcode.xml/UnusedPrivateField" />
  <rule ref="rulesets/unusedcode.xml/UnusedPrivateMethod" />
</ruleset>
<?xml version="1.0" encoding="UTF-8"?>
<project name="azure-storage-php" default="full-build">
 <!-- By default, we assume all tools to be on the $PATH 
 <property name="pdepend" value="pdepend.bat"/>
 <property name="phpcpd"  value="phpcpd.bat"/>
 <property name="phpcs"   value="phpcs.bat"/>
 <property name="phpdox"  value="phpdox.bat"/>
 <property name="phploc"  value="phploc.bat"/>
 <property name="phpmd"   value="phpmd.bat"/>
 <property name="phpunit" value="phpunit.bat"/> -->

 <!-- Use this when the tools are located as PHARs in ${basedir}/build/tools
 <property name="pdepend" value="${basedir}/build/tools/pdepend.phar"/>
 <property name="phpcpd"  value="${basedir}/build/tools/phpcpd.phar"/>
 <property name="phpcs"   value="${basedir}/build/tools/phpcs.phar"/>
 <property name="phpdox"  value="${basedir}/build/tools/phpdox.phar"/>
 <property name="phploc"  value="${basedir}/build/tools/phploc.phar"/>
 <property name="phpmd"   value="${basedir}/build/tools/phpmd.phar"/>
 <property name="phpunit" value="${basedir}/build/tools/phpunit.phar"/>  -->

 <!-- Use this when the tools are managed by Composer in ${basedir}/vendor/bin -->
 <property name="pdepend" value="${basedir}/vendor/bin/pdepend.bat"/>
 <property name="phpcpd"  value="${basedir}/vendor/bin/phpcpd.bat"/>
 <property name="phpcs"   value="${basedir}/vendor/bin/phpcs.bat"/>
 <property name="phpdox"  value="${basedir}/vendor/bin/phpdox.bat"/>
 <property name="phploc"  value="${basedir}/vendor/bin/phploc.bat"/>
 <property name="phpmd"   value="${basedir}/vendor/bin/phpmd.bat"/>
 <property name="phpunit" value="${basedir}/vendor/bin/phpunit.bat"/> 

 <target name="full-build"
         depends="prepare,static-analysis,phpunit,phpdox,-check-failure"
         description="Performs static analysis, runs the tests, and generates project documentation"/>

 <target name="full-build-parallel"
         depends="prepare,static-analysis-parallel,phpunit,phpdox,-check-failure"
         description="Performs static analysis (executing the tools in parallel), runs the tests, and generates project documentation"/>

 <target name="quick-build"
         depends="prepare,lint,phpunit-no-coverage"
         description="Performs a lint check and runs the tests (without generating code coverage reports)"/>

 <target name="static-analysis"
         depends="lint,phploc-ci,pdepend,phpmd-ci,phpcs-ci,phpcpd-ci"
         description="Performs static analysis" />

 <!-- Adjust the threadCount attribute's value to the number of CPUs -->
 <target name="static-analysis-parallel"
         description="Performs static analysis (executing the tools in parallel)">
  <parallel threadCount="2">
   <sequential>
    <antcall target="pdepend"/>
    <antcall target="phpmd-ci"/>
   </sequential>
   <antcall target="lint"/>
   <antcall target="phpcpd-ci"/>
   <antcall target="phpcs-ci"/>
   <antcall target="phploc-ci"/>
  </parallel>
 </target>

 <target name="clean"
         unless="clean.done"
         description="Cleanup build artifacts">
  <delete dir="${basedir}/build/api"/>
  <delete dir="${basedir}/build/coverage"/>
  <delete dir="${basedir}/build/logs"/>
  <delete dir="${basedir}/build/pdepend"/>
  <delete dir="${basedir}/build/phpdox"/>
  <property name="clean.done" value="true"/>
 </target>

 <target name="prepare"
         unless="prepare.done"
         depends="clean"
         description="Prepare for build">
  <mkdir dir="${basedir}/build/api"/>
  <mkdir dir="${basedir}/build/coverage"/>
  <mkdir dir="${basedir}/build/logs"/>
  <mkdir dir="${basedir}/build/pdepend"/>
  <mkdir dir="${basedir}/build/phpdox"/>
  <property name="prepare.done" value="true"/>
 </target>

 <target name="lint"
         unless="lint.done"
         description="Perform syntax check of sourcecode files">
  <apply executable="php" taskname="lint">
   <arg value="-l" />

   <fileset dir="${basedir}/src">
    <include name="**/*.php" />
    <modified />
   </fileset>

   <fileset dir="${basedir}/tests">
    <include name="**/*.php" />
    <modified />
   </fileset>
  </apply>

  <property name="lint.done" value="true"/>
 </target>

 <target name="phploc"
         unless="phploc.done"
         description="Measure project size using PHPLOC and print human readable output. Intended for usage on the command line.">
  <exec executable="${phploc}" taskname="phploc">
   <arg value="--count-tests" />
   <arg path="${basedir}/src" />
   <arg path="${basedir}/tests" />
  </exec>

  <property name="phploc.done" value="true"/>
 </target>

 <target name="phploc-ci"
         unless="phploc.done"
         depends="prepare"
         description="Measure project size using PHPLOC and log result in CSV and XML format. Intended for usage within a continuous integration environment.">
  <exec executable="${phploc}" taskname="phploc">
   <arg value="--count-tests" />
   <arg value="--log-csv" />
   <arg path="${basedir}/build/logs/phploc.csv" />
   <arg value="--log-xml" />
   <arg path="${basedir}/build/logs/phploc.xml" />
   <arg path="${basedir}/src" />
   <arg path="${basedir}/tests" />
  </exec>

  <property name="phploc.done" value="true"/>
 </target>

 <target name="pdepend"
         unless="pdepend.done"
         depends="prepare"
         description="Calculate software metrics using PHP_Depend and log result in XML format. Intended for usage within a continuous integration environment.">
  <exec executable="${pdepend}" taskname="pdepend">
   <arg value="--jdepend-xml=${basedir}/build/logs/jdepend.xml" />
   <arg value="--jdepend-chart=${basedir}/build/pdepend/dependencies.svg" />
   <arg value="--overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg" />
   <arg path="${basedir}/src" />
  </exec>

  <property name="pdepend.done" value="true"/>
 </target>

 <target name="phpmd"
         unless="phpmd.done"
         description="Perform project mess detection using PHPMD and print human readable output. Intended for usage on the command line before committing.">
  <exec executable="${phpmd}" taskname="phpmd">
   <arg path="${basedir}/src" />
   <arg value="text" />
   <arg path="${basedir}/build/phpmd.xml" />
  </exec>

  <property name="phpmd.done" value="true"/>
 </target>

 <target name="phpmd-ci"
         unless="phpmd.done"
         depends="prepare"
         description="Perform project mess detection using PHPMD and log result in XML format. Intended for usage within a continuous integration environment.">
  <exec executable="${phpmd}" taskname="phpmd">
   <arg path="${basedir}/src" />
   <arg value="xml" />
   <arg path="${basedir}/build/phpmd.xml" />
   <arg value="--reportfile" />
   <arg path="${basedir}/build/logs/pmd.xml" />
  </exec>

  <property name="phpmd.done" value="true"/>
 </target>

 <target name="phpcs"
         unless="phpcs.done"
         description="Find coding standard violations using PHP_CodeSniffer and print human readable output. Intended for usage on the command line before committing.">
  <exec executable="${phpcs}" taskname="phpcs">
   <arg value="--standard=PSR2" />
   <arg value="--extensions=php" />
   <arg value="--ignore=autoload.php" />
   <arg path="${basedir}/src" />
   <arg path="${basedir}/tests" />
  </exec>

  <property name="phpcs.done" value="true"/>
 </target>

 <target name="phpcs-ci"
         unless="phpcs.done"
         depends="prepare"
         description="Find coding standard violations using PHP_CodeSniffer and log result in XML format. Intended for usage within a continuous integration environment.">
  <exec executable="${phpcs}" taskname="phpcs">
   <arg value="--report=checkstyle" />
   <arg value="--report-file=${basedir}/build/logs/checkstyle.xml" />
   <arg value="--standard=PSR2" />
   <arg value="--extensions=php" />
   <arg value="--ignore=autoload.php" />
   <arg path="${basedir}/src" />
   <arg path="${basedir}/tests" />
  </exec>

  <property name="phpcs.done" value="true"/>
 </target>

 <target name="phpcpd"
         unless="phpcpd.done"
         description="Find duplicate code using PHPCPD and print human readable output. Intended for usage on the command line before committing.">
  <exec executable="${phpcpd}" taskname="phpcpd">
   <arg path="${basedir}/src" />
  </exec>

  <property name="phpcpd.done" value="true"/>
 </target>

 <target name="phpcpd-ci"
         unless="phpcpd.done"
         depends="prepare"
         description="Find duplicate code using PHPCPD and log result in XML format. Intended for usage within a continuous integration environment.">
  <exec executable="${phpcpd}" taskname="phpcpd">
   <arg value="--log-pmd" />
   <arg path="${basedir}/build/logs/pmd-cpd.xml" />
   <arg path="${basedir}/src" />
  </exec>

  <property name="phpcpd.done" value="true"/>
 </target>

 <target name="phpunit"
         unless="phpunit.done"
         depends="prepare"
         description="Run unit tests with PHPUnit">
  <exec executable="${phpunit}" resultproperty="result.phpunit" taskname="phpunit">
   <arg value="--configuration"/>
   <arg path="${basedir}/phpunit.xml.dist"/>
  </exec>

  <property name="phpunit.done" value="true"/>
 </target>

 <target name="phpunit-no-coverage"
         unless="phpunit.done"
         depends="prepare"
         description="Run unit tests with PHPUnit (without generating code coverage reports)">
  <exec executable="${phpunit}" failonerror="true" taskname="phpunit">
   <arg value="--configuration"/>
   <arg path="${basedir}/phpunit.xml.dist"/>
   <arg value="--no-coverage"/>
  </exec>

  <property name="phpunit.done" value="true"/>
 </target>

  <target name="phpunit-ft"
         unless="phpunit.done"
         depends="prepare"
         description="Run functional tests with PHPUnit">
  <exec executable="${phpunit}" resultproperty="result-ft.phpunit" taskname="phpunit">
   <arg value="--configuration"/>
   <arg path="${basedir}/phpunit.functional.xml.dist"/>
  </exec>
  
  <property name="phpunit-ft.done" value="true"/>
 </target>
 
 <target name="phpdox"
         unless="phpdox.done"
         depends="phploc-ci,phpcs-ci,phpmd-ci"
         description="Generate project documentation using phpDox">
  <exec executable="${phpdox}" dir="${basedir}/build" taskname="phpdox"/>

  <property name="phpdox.done" value="true"/>
 </target>

 <target name="-check-failure">
  <fail message="PHPUnit did not finish successfully">
   <condition>
    <not>
     <equals arg1="${result.phpunit}" arg2="0"/>
    </not>
   </condition>
  </fail>
 </target>
</project>2016.08 - version 0.10.2

ALL
* Allow passing an array of options to a service. Currently only Guzzle options are supported via the `http` parameter.

2016.05 - version 0.10.1

Blob
* Fixed the issue that blobs upload with size multiple of 4194304 bytes and larger than 33554432 bytes.
* Fixed the issue that extra / is appended in blob URL.

2016.04 - version 0.10.0

ALL
* Separated Azure Storage APIs in Azure-SDK-for-PHP to establish an independent release cycle.
* Remove all pear dependencies: HTTP_Request2, Mail_mime, and Mail_mimeDecode. Use Guzzle as underlying http client library.
* Update storage REST API version to 2015-04-05.
* Change root namespace from "WindowsAzure" to "MicrosoftAzure/Storage".
* When set metadata operations contains invalid characters, it throws a ServiceException with 400 bad request error instead of Http_Request2_LogicException.

Blob
* Fixed the issue that upload large block blob fails. (https://github.com/Azure/azure-sdk-for-php/pull/757)
* MicrosoftAzure\Storage\Blob\Models\Blocks.setBlockId now requires a base64 encoded string.

Table
* MicrosoftAzure\Storage\Table\Models\Property.getEdmType now returns EdmType::STRING instead of null if the property data type is not set in server.
{
    "name": "microsoft/azure-storage",
    "description": "This project provides a set of PHP client libraries that make it easy to access Microsoft Azure storage APIs.",
    "keywords": [ "php", "azure", "storage", "sdk" ],
    "license": "MIT",
    "authors": [
        {
            "name": "Azure Storage PHP SDK",
            "email": "dmsh@microsoft.com"
        }
    ],
    "require": {
        "php": ">=5.5.0",
		"guzzlehttp/guzzle": "~6.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.0",
        "mikey179/vfsStream": "~1.6",
        "pdepend/pdepend" : "~2.2",
        "sebastian/phpcpd": "~2.0",
        "squizlabs/php_codesniffer": "2.*",
        "phploc/phploc": "~2.1",
        "phpmd/phpmd": "@stable",
        "theseer/phpdox": "~0.8" 
    },
    "autoload": {
        "psr-4": {
            "MicrosoftAzure\\Storage\\": "src/"
        }
    },
    "autoload-dev": {
    	"psr-4": {
    	    "MicrosoftAzure\\Storage\\Tests\\": "tests/"
    	}
    },
	"extra": {
	    "branch-alias": {
		    "dev-master": "0.10.x-dev"
		}
	}
}
If you intend to contribute to the project, please make sure you've followed the instructions provided in the [Azure Projects Contribution Guidelines](http://azure.github.io/guidelines/).
## Project Setup
The Azure Storage development team uses [Eclipse for PHP Developers](http://www.eclipse.org/downloads/packages/eclipse-php-developers/mars2) so instructions will be tailored to that preference. However, any preferred IDE or other toolset should be usable.

### Install
* PHP 5.5, 5.6 or 7.0
* [Eclipse for PHP Developers](http://www.eclipse.org/downloads/packages/eclipse-php-developers/mars2)
* [Composer](https://getcomposer.org/) for php packages and tools management.
* [Apache Ant](http://ant.apache.org/manual/install.html) to drive build scripts.

### Development Environment Setup
To get the source code of the SDK via **git** just type:

```bash
git clone https://github.com/Azure/azure-storage-php.git
cd ./azure-storage-php
```

Run Composer to install all php package dependencies and tools:

```bash
composer install
```

### Open project from Eclipse
* Select **File->New->PHP Project**
* Enter the project name (e.g. azure-storage-php)
* Select **Create project at existing location** and navigate to the root directory of your local git repository
* Click **Finish**

## Tests

### Configuration
Authenticated access to Azure Storage is required to run the tests. Set the environment variable AZURE_STORAGE_CONNECTION_STRING to a valid connection string. You may use the below as a template:

```bash
Set AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=<Account>;AccountKey=<Key>
```

Please make sure there's no data inside the storage account used for test. Otherwise, test may fail because of the existing data.

### Running
You can use the following commands to run tests:

* All unit tests: ``ant phpunit`` or ``phpunit -c phpunit.xml.dist``
* All functional tests: ``ant phpunit-ft`` or ``phpunit -c phpunit.functional.dist.xml``
* One particular test case: ``phpunit -c phpunit.dist.xml --filter <case name>`` or ``phpunit -c phpunit.functional.dist.xml --filter <case name>``

### Testing Features
As you develop a feature, you'll need to write tests to ensure quality. Your changes should be covered by both unit tests and functional tests. The unit tests and functional tests codes should be placed under tests/unit and tests/functional respectively. You should also run existing tests related to your change to address any unexpected breaks.

## Pull Requests

### Guidelines
The following are the minimum requirements for any pull request that must be met before contributions can be accepted.
* Make sure you've signed the CLA before you start working on any change.
* Discuss any proposed contribution with the team via a GitHub issue **before** starting development.
* Code must be professional quality
	* You should strive to mimic the style with which we have written the library
	* Clean, well-commented, well-designed code
	* Try to limit the number of commits for a feature to 1-2. If you end up having too many we may ask you to squash your changes into fewer commits.
* [ChangeLog.md](ChangeLog.md) needs to be updated describing the new change
* Thoroughly test your feature

### Branching Policy
Changes should be based on the `dev` branch. We're following [semver](http://semver.org/). We generally release any breaking changes in the next major version (e.g. 1.0, 2.0) and non-breaking changes in the next minor or major version (e.g. 2.1, 2.2).

### Review Process
We expect all guidelines to be met before accepting a pull request. As such, we will work with you to address issues we find by leaving comments in your code. Please understand that it may take a few iterations before the code is accepted as we maintain high standards on code quality. Once we feel comfortable with a contribution, we will validate the change and accept the pull request.


Thank you for any contributions! Please let the team know if you have any questions or concerns about our contribution policy.The MIT License (MIT)

Copyright (c) 2016 Microsoft Corporation

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.<?xml version="1.0" encoding="UTF-8" ?>
<phpdoc>
  <title>Windows Azure SDK for PHP</title>
  <parser>
    <target>build/api</target>
  </parser>
  <transformer>
    <target>build/api</target>
  </transformer>
  <files>
    <directory>WindowsAzure/</directory>
  </files>
</phpdoc><?xml version="1.0" encoding="utf-8" ?>
<phpdox xmlns="http://xml.phpdox.net/config">
  <project name="WindowsAzure" source="${basedir}/src" workdir="${basedir}/build/api/xml">
    <collector backend="parser" />
    <generator output="${basedir}/build/api">
      <build engine="html" output="html"/>
    </generator>
  </project>
</phpdox><?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="vendor/autoload.php"
         backupGlobals="false"
         backupStaticAttributes="false"
         beStrictAboutTestsThatDoNotTestAnything="true"
         beStrictAboutOutputDuringTests="true"
         verbose="true">
  <testsuites>
    <testsuite name="azure-storage-php">
      <directory suffix="Test.php">tests/functional/</directory>
    </testsuite>
  </testsuites>

  <logging>
    <log type="coverage-html" target="build/coverage" title="azure-storage-php"
         charset="UTF-8" yui="true" highlight="true"
         lowUpperBound="35" highLowerBound="70"/>
    <log type="coverage-clover" target="build/logs/clover.xml"/>
    <log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
  </logging>

  <filter>
    <whitelist addUncoveredFilesFromWhitelist="true">
      <directory suffix=".php">src</directory>
    </whitelist>
  </filter>
</phpunit>
<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="vendor/autoload.php"
         backupGlobals="false"
         backupStaticAttributes="false"
         beStrictAboutTestsThatDoNotTestAnything="true"
         beStrictAboutOutputDuringTests="true"
         verbose="true">
  <testsuites>
    <testsuite name="azure-storage-php">
      <directory suffix="Test.php">tests/unit</directory>
    </testsuite>
  </testsuites>

  <logging>
    <log type="coverage-html" target="build/coverage" title="azure-storage-php"
         charset="UTF-8" yui="true" highlight="true"
         lowUpperBound="35" highLowerBound="70"/>
    <log type="coverage-clover" target="build/logs/clover.xml"/>
    <log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
  </logging>

  <filter>
    <whitelist addUncoveredFilesFromWhitelist="true">
      <directory suffix=".php">src</directory>
    </whitelist>
  </filter>
</phpunit>
# Microsoft Azure Storage SDK for PHP

This project provides a set of PHP client libraries that make it easy to access Microsoft Azure Storage services (blobs, tables and queues). For documentation on how to host PHP applications on Microsoft Azure, please see the [Microsoft Azure PHP Developer Center](http://www.windowsazure.com/en-us/develop/php/).

> **Note**
> 
> If you are looking for the Service Bus, Service Runtime, Service Management or Media Services libraries, please visit https://github.com/Azure/azure-sdk-for-php.

# Features

* Blobs
  * create, list, and delete containers, work with container metadata and permissions, list blobs in container
  * create block and page blobs (from a stream or a string), work with blob blocks and pages, delete blobs
  * work with blob properties, metadata, leases, snapshot a blob
* Tables
  * create and delete tables
  * create, query, insert, update, merge, and delete entities
  * batch operations
* Queues
  * create, list, and delete queues, and work with queue metadata and properties
  * create, get, peek, update, delete messages
  
# Getting Started
## Minimum Requirements

* PHP 5.5 or above
* See [composer.json](composer.json) for dependencies


## Download Source Code

To get the source code from GitHub, type

    git clone https://github.com/Azure/azure-storage-php.git
    cd ./azure-storage-php


## Install via Composer

1. Create a file named **composer.json** in the root of your project and add the following code to it:
```json
    {
      "require": {
        "microsoft/azure-storage": "*"
      }
    }
```
2. Download **[composer.phar](http://getcomposer.org/composer.phar)** in your project root.

3. Open a command prompt and execute this in your project root

    php composer.phar install

## Usage

There are four basic steps that have to be performed before you can make a call to any Microsoft Azure Storage API when using the libraries. 

* First, include the autoloader script:
    
    require_once "vendor/autoload.php"; 
  
* Include the namespaces you are going to use.

  To create any Microsoft Azure service client you need to use the **ServicesBuilder** class:

    use MicrosoftAzure\Storage\Common\ServicesBuilder;

  To process exceptions you need:

    use MicrosoftAzure\Storage\Common\ServiceException;

  
* To instantiate the service client you will also need a valid [connection string](https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/). The format is: 

    DefaultEndpointsProtocol=[http|https];AccountName=[yourAccount];AccountKey=[yourKey]


* Instantiate a client object - a wrapper around the available calls for the given service.

```PHP
$tableClient = ServicesBuilder::getInstance()->createTableService($connectionString);
$blobClient = ServicesBuilder::getInstance()->createBlobService($connectionString);
$queueClient = ServicesBuilder::getInstance()->createQueueService($connectionString);
```

## Code samples

You can find samples in the [sample folder](samples)


# Migrate from [Azure SDK for PHP](https://github.com/Azure/azure-sdk-for-php/)

If you are using [Azure SDK for PHP](https://github.com/Azure/azure-sdk-for-php/) to access Azure Storage Service, we highly recommend you to migrate to this SDK for faster issue resolution and quicker feature implementation. We are working on supporting the latest service features (including SAS, CORS, append blob, file service, etc) as well as improvement on existing APIs.

For now, Microsoft Azure Storage SDK for PHP v0.10.2 shares almost the same interface as the storage blobs, tables and queues APIs in Azure SDK for PHP v0.4.3. However, there are some minor breaking changes need to be addressed during your migration. You can find the details in [BreakingChanges.md](BreakingChanges.md).

Please note that this library is still in preview and may contain more breaking changes in upcoming releases.
  
# Need Help?

Be sure to check out the Microsoft Azure [Developer Forums on Stack Overflow](http://go.microsoft.com/fwlink/?LinkId=234489) and [github issues](https://github.com/Azure/azure-storage-php/issues) if you have trouble with the provided code.

# Contribute Code or Provide Feedback

If you would like to become an active contributor to this project please follow the instructions provided in [Azure Projects Contribution Guidelines](http://azure.github.io/guidelines/).
You can find more details for contributing in the [CONTRIBUTING.md](CONTRIBUTING.md).
 
If you encounter any bugs with the library please file an issue in the [Issues](https://github.com/Azure/azure-storage-php/issues) section of the project.

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Samples
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Samples;

require_once "../vendor/autoload.php";

use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\PublicAccessType;
use MicrosoftAzure\Storage\Common\ServicesBuilder;
use MicrosoftAzure\Storage\Common\ServiceException;

$connectionString = 'DefaultEndpointsProtocol=https;AccountName=<yourAccount>;AccountKey=<yourKey>';
$blobClient = ServicesBuilder::getInstance()->createBlobService($connectionString);

// To create a container call createContainer.
createContainerSample($blobClient);

// To upload a file as a blob, use the BlobRestProxy->createBlockBlob method. This operation will
// create the blob if it doesnt exist, or overwrite it if it does. The code example below assumes 
// that the container has already been created and uses fopen to open the file as a stream.
uploadBlobSample($blobClient);

// To download blob into a file, use the BlobRestProxy->getBlob method. The example below assumes
// the blob to download has been already created.
downloadBlobSample($blobClient);

// To list the blobs in a container, use the BlobRestProxy->listBlobs method with a foreach loop to loop
// through the result. The following code outputs the name and URI of each blob in a container.
listBlobsSample($blobClient);

function createContainerSample($blobClient)
{
    // OPTIONAL: Set public access policy and metadata.
    // Create container options object.
    $createContainerOptions = new CreateContainerOptions();

    // Set public access policy. Possible values are
    // PublicAccessType::CONTAINER_AND_BLOBS and PublicAccessType::BLOBS_ONLY.
    // CONTAINER_AND_BLOBS: full public read access for container and blob data.
    // BLOBS_ONLY: public read access for blobs. Container data not available.
    // If this value is not specified, container data is private to the account owner.
    $createContainerOptions->setPublicAccess(PublicAccessType::CONTAINER_AND_BLOBS);

    // Set container metadata
    $createContainerOptions->addMetaData("key1", "value1");
    $createContainerOptions->addMetaData("key2", "value2");

    try {
        // Create container.
        $blobClient->createContainer("mycontainer", $createContainerOptions);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}

function uploadBlobSample($blobClient)
{
    $content = fopen("myfile.txt", "r");
    $blob_name = "myblob";
    
    try {
        //Upload blob
        $blobClient->createBlockBlob("mycontainer", $blob_name, $content);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}

function downloadBlobSample($blobClient)
{
    try {
        $getBlobResult = $blobClient->getBlob("mycontainer", "myblob");
    } catch (ServiceException $e) {
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
    
    file_put_contents("output.txt", $getBlobResult->getContentStream());
}

function listBlobsSample($blobClient)
{
    try {
        // List blobs.
        $blob_list = $blobClient->listBlobs("mycontainer");
        $blobs = $blob_list->getBlobs();
    
        foreach($blobs as $blob)
        {
            echo $blob->getName().": ".$blob->getUrl().PHP_EOL;
        }
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}<?php
/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Samples
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Samples;

require_once "../vendor/autoload.php";

use MicrosoftAzure\Storage\Common\ServicesBuilder;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;

$connectionString = 'DefaultEndpointsProtocol=https;AccountName=<yourAccount>;AccountKey=<yourKey>';
$queueClient = ServicesBuilder::getInstance()->createQueueService($connectionString);

// A QueueRestProxy object lets you create a queue with the createQueue method. When creating a queue,
// you can set options on the queue, but doing so is not required.
createQueueSample($queueClient);

// To add a message to a queue, use QueueRestProxy->createMessage. The method takes the queue name,
// the message text, and message options (which are optional). For compatibility with others you may
// need to base64 encode message.
addMessageToQueueSample($queueClient);

// You can peek at a message (or messages) at the front of a queue without removing it from the queue
// by calling QueueRestProxy->peekMessages.
peekNextMessageSample($queueClient);

// Your code removes a message from a queue in two steps. First, you call QueueRestProxy->listMessages,
// which makes the message invisible to any other code reading from the queue. By default, this message 
// will stay invisible for 30 seconds (if the message is not deleted in this time period, it will become
// visible on the queue again). To finish removing the message from the queue, you must call 
// QueueRestProxy->deleteMessage.
dequeueNextMessageSample($queueClient);

function createQueueSample($queueClient)
{
    $createQueueOptions = new CreateQueueOptions();
    $createQueueOptions->addMetaData("key1", "value1");
    $createQueueOptions->addMetaData("key2", "value2");
    
    try {
        // Create queue.
        $queueClient->createQueue("myqueue", $createQueueOptions);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}

function addMessageToQueueSample($queueClient)
{
    try {
        // Create message.
        $msg = "Hello World!";
        // optional: $msg = base64_encode($msg);
        $queueClient->createMessage("myqueue", $msg);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}

function peekNextMessageSample($queueClient)
{
    // OPTIONAL: Set peek message options.
    $message_options = new PeekMessagesOptions();
    $message_options->setNumberOfMessages(1); // Default value is 1.
    
    try {
        $peekMessagesResult = $queueClient->peekMessages("myqueue", $message_options);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
    
    $messages = $peekMessagesResult->getQueueMessages();
    
    // View messages.
    $messageCount = count($messages);
    if($messageCount <= 0){
        echo "There are no messages.".PHP_EOL;
    } else {
        foreach($messages as $message) {
            echo "Peeked message:".PHP_EOL;
            echo "Message Id: ".$message->getMessageId().PHP_EOL;
            echo "Date: ".date_format($message->getInsertionDate(), 'Y-m-d').PHP_EOL;
            $msg = $message->getMessageText();
            // optional: $msg = base64_decode($msg);
            echo "Message text: ".$msg.PHP_EOL.PHP_EOL;
        }
    }
}

function dequeueNextMessageSample($queueClient)
{
    // Get message.
    $listMessagesResult = $queueClient->listMessages("myqueue");
    $messages = $listMessagesResult->getQueueMessages();
    $message = $messages[0];
    
    // Process message
    
    // Get message Id and pop receipt.
    $messageId = $message->getMessageId();
    $popReceipt = $message->getPopReceipt();
    
    try {
        // Delete message.
        $queueClient->deleteMessage("myqueue", $messageId, $popReceipt);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}<?php
/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Samples
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Samples;

require_once "../vendor/autoload.php";

use MicrosoftAzure\Storage\Common\ServicesBuilder;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Table\Models\BatchOperations;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\EdmType;

$connectionString = 'DefaultEndpointsProtocol=https;AccountName=<yourAccount>;AccountKey=<yourKey>';
$tableClient = ServicesBuilder::getInstance()->createTableService($connectionString);

// To create a table call createTable.
createTableSample($tableClient);

// To add an entity to a table, create a new Entity object and pass it to TableRestProxy->insertEntity.
// Note that when you create an entity you must specify a PartitionKey and RowKey. These are the unique
// identifiers for an entity and are values that can be queried much faster than other entity properties.
// The system uses PartitionKey to automatically distribute the tables entities over many storage nodes.
insertEntitySample($tableClient);

// To add mutiple entities with one call, create a BatchOperations and pass it to TableRestProxy->batch.
// Note that all these entities must have the same PartitionKey value. BatchOperations supports to update,
// merge, delete entities as well. You can find more details in:
//   https://msdn.microsoft.com/library/azure/dd894038.aspx
batchInsertEntitiesSample($tableClient);

// To query for entities you can call queryEntities. The subset of entities you retrieve will be determined 
// by the filter you use (for more information, see Querying Tables and Entities):
//   https://msdn.microsoft.com/library/azure/dd894031.aspx
// You can also provide no filter at all.
queryEntitiesSample($tableClient);

function createTableSample($tableClient)
{
    try {
        // Create table.
        $tableClient->createTable("mytable");
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}

function insertEntitySample($tableClient)
{
    $entity = new Entity();
    $entity->setPartitionKey("pk");
    $entity->setRowKey("1");
    $entity->addProperty("PropertyName", EdmType::STRING, "Sample1");
    
    try{
        $tableClient->insertEntity("mytable", $entity);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}

function batchInsertEntitiesSample($tableClient)
{
    $batchOp = new BatchOperations();
    for ($i = 2; $i < 10; ++$i)
    {
        $entity = new Entity();
        $entity->setPartitionKey("pk");
        $entity->setRowKey(''.$i);
        $entity->addProperty("PropertyName", EdmType::STRING, "Sample".$i);
        
        $batchOp->addInsertEntity("mytable", $entity);
    }
    
    try {
        $tableClient->batch($batchOp);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
}

function queryEntitiesSample($tableClient)
{
    $filter = "RowKey ne '3'";
    
    try {
        $result = $tableClient->queryEntities("mytable", $filter);
    } catch(ServiceException $e){
        $code = $e->getCode();
        $error_message = $e->getMessage();
        echo $code.": ".$error_message.PHP_EOL;
    }
    
    $entities = $result->getEntities();
    
    foreach($entities as $entity){
        echo $entity->getPartitionKey().":".$entity->getRowKey().PHP_EOL;
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Blob;
use MicrosoftAzure\Storage\Common\Internal\HttpFormatter;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy;
use MicrosoftAzure\Storage\Blob\Internal\IBlob;
use MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions;
use MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult;
use MicrosoftAzure\Storage\Blob\Models\ListContainersOptions;
use MicrosoftAzure\Storage\Blob\Models\ListContainersResult;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult;
use MicrosoftAzure\Storage\Blob\Models\GetContainerACLResult;
use MicrosoftAzure\Storage\Blob\Models\SetContainerMetadataOptions;
use MicrosoftAzure\Storage\Blob\Models\DeleteContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsResult;
use MicrosoftAzure\Storage\Blob\Models\BlobType;
use MicrosoftAzure\Storage\Blob\Models\Block;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;
use MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesResult;
use MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult;
use MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult;
use MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataOptions;
use MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataResult;
use MicrosoftAzure\Storage\Blob\Models\GetBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobResult;
use MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\LeaseMode;
use MicrosoftAzure\Storage\Blob\Models\AcquireLeaseOptions;
use MicrosoftAzure\Storage\Blob\Models\AcquireLeaseResult;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult;
use MicrosoftAzure\Storage\Blob\Models\PageWriteOption;
use MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions;
use MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobBlockOptions;
use MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions;
use MicrosoftAzure\Storage\Blob\Models\BlockList;
use MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult;
use MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult;
use MicrosoftAzure\Storage\Blob\Models\PageRange;
use MicrosoftAzure\Storage\Blob\Models\CopyBlobResult;
use MicrosoftAzure\Storage\Blob\Models\BreakLeaseResult;

/**
 * This class constructs HTTP requests and receive HTTP responses for blob
 * service layer.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobRestProxy extends ServiceRestProxy implements IBlob
{
    /**
     * @var int Defaults to 32MB
     */
    private $_SingleBlobUploadThresholdInBytes = 33554432 ;

    /**
     * Get the value for SingleBlobUploadThresholdInBytes
     *
     * @return int
     */
    public function getSingleBlobUploadThresholdInBytes()
    {
        return $this->_SingleBlobUploadThresholdInBytes;
    }

    /**
     * Set the value for SingleBlobUploadThresholdInBytes, Max 64MB
     *
     * @param int $val The max size to send as a single blob block
     *
     * @return none
     */
    public function setSingleBlobUploadThresholdInBytes($val)
    {
        if ($val > 67108864) {
            // What should the proper action here be?
            $val = 67108864;
        } elseif ($val < 1) {
            // another spot that could use looking at
            $val = 33554432;
        }
        $this->_SingleBlobUploadThresholdInBytes = $val;
    }

    /**
     * Gets the copy blob source name with specified parameters. 
     * 
     * @param string                 $containerName The name of the container. 
     * @param string                 $blobName      The name of the blob.
     * @param Models\CopyBlobOptions $options       The optional parameters.
     *
     * @return string 
     */
    private function _getCopyBlobSourceName($containerName, $blobName, $options)
    {
        $sourceName = $this->_getBlobUrl($containerName, $blobName);

        if (!is_null($options->getSourceSnapshot())) {
            $sourceName .= '?snapshot=' . $options->getSourceSnapshot();
        }

        return $sourceName;
    }
    
    /**
     * Creates URI path for blob.
     * 
     * @param string $container The container name.
     * @param string $blob      The blob name.
     * 
     * @return string
     */
    private function _createPath($container, $blob)
    {
        $encodedBlob = urlencode($blob);
        // Unencode the forward slashes to match what the server expects.
        $encodedBlob = str_replace('%2F', '/', $encodedBlob);
        // Unencode the backward slashes to match what the server expects.
        $encodedBlob = str_replace('%5C', '/', $encodedBlob);
        // Re-encode the spaces (encoded as space) to the % encoding.
        $encodedBlob = str_replace('+', '%20', $encodedBlob);
        
        // Empty container means accessing default container
        if (empty($container)) {
            return $encodedBlob;
        } else {
            return $container . '/' . $encodedBlob;
        }
    }
    
    /**
     * Creates full URI to the given blob.
     * 
     * @param string $container The container name.
     * @param string $blob      The blob name.
     * 
     * @return string
     */
    private function _getBlobUrl($container, $blob)
    {
        $encodedBlob = urlencode($blob);
        // Unencode the forward slashes to match what the server expects.
        $encodedBlob = str_replace('%2F', '/', $encodedBlob);
        // Unencode the backward slashes to match what the server expects.
        $encodedBlob = str_replace('%5C', '/', $encodedBlob);
        // Re-encode the spaces (encoded as space) to the % encoding.
        $encodedBlob = str_replace('+', '%20', $encodedBlob);
        
        // Empty container means accessing default container
        if (empty($container)) {
            $encodedBlob = $encodedBlob;
        } else {
            $encodedBlob = $container . '/' . $encodedBlob;
        }
        
        if (substr($encodedBlob, 0, 1) != '/' && substr($this->getUri(), -1, 1) != '/')
        {
            $encodedBlob =  '/' .  $encodedBlob;
        }
        return $this->getUri() . $encodedBlob;
    }
    
    /**
     * Creates GetBlobPropertiesResult from headers array.
     * 
     * @param array $headers The HTTP response headers array.
     * 
     * @return GetBlobPropertiesResult
     */
    private function _getBlobPropertiesResultFromResponse($headers)
    {
        $result     = new GetBlobPropertiesResult();
        $properties = new BlobProperties();
        $d          = $headers[Resources::LAST_MODIFIED];
        $bType      = $headers[Resources::X_MS_BLOB_TYPE];
        $cLength    = intval($headers[Resources::CONTENT_LENGTH]);
        $lStatus    = Utilities::tryGetValue($headers, Resources::X_MS_LEASE_STATUS);
        $cType      = Utilities::tryGetValue($headers, Resources::CONTENT_TYPE);
        $cMD5       = Utilities::tryGetValue($headers, Resources::CONTENT_MD5);
        $cEncoding  = Utilities::tryGetValue($headers, Resources::CONTENT_ENCODING);
        $cLanguage  = Utilities::tryGetValue($headers, Resources::CONTENT_LANGUAGE);
        $cControl   = Utilities::tryGetValue($headers, Resources::CACHE_CONTROL);
        $etag       = $headers[Resources::ETAG];
        $metadata   = $this->getMetadataArray($headers);
        
        if (array_key_exists(Resources::X_MS_BLOB_SEQUENCE_NUMBER, $headers)) {
            $sNumber = intval($headers[Resources::X_MS_BLOB_SEQUENCE_NUMBER]);
            $properties->setSequenceNumber($sNumber);
        }
        
        $properties->setBlobType($bType);
        $properties->setCacheControl($cControl);
        $properties->setContentEncoding($cEncoding);
        $properties->setContentLanguage($cLanguage);
        $properties->setContentLength($cLength);
        $properties->setContentMD5($cMD5);
        $properties->setContentType($cType);
        $properties->setETag($etag);
        $properties->setLastModified(Utilities::rfc1123ToDateTime($d));
        $properties->setLeaseStatus($lStatus);
        
        $result->setProperties($properties);
        $result->setMetadata($metadata);
        
        return $result;
    }
    
    /**
     * Helper method for getContainerProperties and getContainerMetadata.
     * 
     * @param string                    $container The container name.
     * @param Models\BlobServiceOptions $options   The optional parameters.
     * @param string                    $operation The operation string. Should be
     * 'metadata' to get metadata.
     * 
     * @return Models\GetContainerPropertiesResult
     */
    private function _getContainerPropertiesImpl($container, $options = null,
        $operation = null
    ) {
        Validate::isString($container, 'container');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $container;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new BlobServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'container'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            $operation
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        
        $responseHeaders = HttpFormatter::formatHeaders($response->getHeaders());
        
        $result   = new GetContainerPropertiesResult();
        $metadata = $this->getMetadataArray($responseHeaders);
        $date     = Utilities::tryGetValue($responseHeaders, Resources::LAST_MODIFIED);
        $date     = Utilities::rfc1123ToDateTime($date);
        $result->setETag(Utilities::tryGetValue($responseHeaders, Resources::ETAG));
        $result->setMetadata($metadata);
        $result->setLastModified($date);
        
        return $result;
    }
    
    /**
     * Adds optional create blob headers.
     * 
     * @param CreateBlobOptions $options The optional parameters.
     * @param array             $headers The HTTP request headers.
     * 
     * @return array
     */
    private function _addCreateBlobOptionalHeaders($options, $headers)
    {
        $contentType         = $options->getContentType();
        $metadata            = $options->getMetadata();
        $blobContentType     = $options->getBlobContentType();
        $blobContentEncoding = $options->getBlobContentEncoding();
        $blobContentLanguage = $options->getBlobContentLanguage();
        $blobContentMD5      = $options->getBlobContentMD5();
        $blobCacheControl    = $options->getBlobCacheControl();
        $leaseId             = $options->getLeaseId();
        
        if (!is_null($contentType)) {
            $this->addOptionalHeader(
                $headers,
                Resources::CONTENT_TYPE,
                $options->getContentType()
            );
        } else {
            $this->addOptionalHeader(
                $headers,
                Resources::CONTENT_TYPE,
                Resources::BINARY_FILE_TYPE
            );
        }
        $headers = $this->addMetadataHeaders($headers, $metadata);
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_ENCODING,
            $options->getContentEncoding()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_LANGUAGE,
            $options->getContentLanguage()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_MD5,
            $options->getContentMD5()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CACHE_CONTROL,
            $options->getCacheControl()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $leaseId
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_TYPE,
            $blobContentType
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_ENCODING,
            $blobContentEncoding
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_LANGUAGE,
            $blobContentLanguage
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_MD5,
            $blobContentMD5
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CACHE_CONTROL,
            $blobCacheControl
        );
        
        return $headers;
    }
    
    /**
     * Adds Range header to the headers array.
     * 
     * @param array   $headers The HTTP request headers.
     * @param integer $start   The start byte.
     * @param integer $end     The end byte.
     * 
     * @return array
     */
    private function _addOptionalRangeHeader($headers, $start, $end)
    {
        if (!is_null($start) || !is_null($end)) {
            $range      = $start . '-' . $end;
            $rangeValue = 'bytes=' . $range;
            $this->addOptionalHeader($headers, Resources::RANGE, $rangeValue);
        }
        
        return $headers;
    }

    /**
     * Does the actual work for leasing a blob.
     * 
     * @param string             $leaseAction     The lease action string.
     * @param string             $container       The container name.
     * @param string             $blob            The blob to lease name.
     * @param string             $leaseId         The existing lease id.
     * @param BlobServiceOptions $options         The optional parameters.
     * @param AccessCondition    $accessCondition The access conditions.
     * 
     * @return array
     */
    private function _putLeaseImpl($leaseAction, $container, $blob, $leaseId, 
        $options, $accessCondition = null
    ) {
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        Validate::isString($container, 'container');
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::EMPTY_STRING;
        
        switch ($leaseAction) {
        case LeaseMode::ACQUIRE_ACTION:
            $this->addOptionalHeader($headers, Resources::X_MS_LEASE_DURATION, -1);
            $statusCode = Resources::STATUS_CREATED;
            break;
        case LeaseMode::RENEW_ACTION:
            $statusCode = Resources::STATUS_OK;
            break;
        case LeaseMode::RELEASE_ACTION:
            $statusCode = Resources::STATUS_OK;
            break;
        case LeaseMode::BREAK_ACTION:
            $statusCode = Resources::STATUS_ACCEPTED;
            break;
        default:
            throw new \Exception(Resources::NOT_IMPLEMENTED_MSG);
        }
        
        if (!is_null($options)) {
            $options = new BlobServiceOptions();
        }
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $accessCondition
        );

        $this->addOptionalHeader($headers, Resources::X_MS_LEASE_ID, $leaseId);
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ACTION,
            $leaseAction
        );
        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'lease');
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode
        );
        
        return HttpFormatter::formatHeaders($response->getHeaders());
    }
    
    /**
     * Does actual work for create and clear blob pages.
     * 
     * @param string                 $action    Either clear or create.
     * @param string                 $container The container name.
     * @param string                 $blob      The blob name.
     * @param PageRange              $range     The page ranges.
     * @param string|resource        $content   The content stream.
     * @param CreateBlobPagesOptions $options   The optional parameters.
     * 
     * @return CreateBlobPagesResult
     */
    private function _updatePageBlobPagesImpl($action, $container, $blob, $range,
        $content, $options = null
    ) {
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        Validate::isString($container, 'container');
        Validate::isTrue(
            $range instanceof PageRange,
            sprintf(
                Resources::INVALID_PARAM_MSG,
                'range',
                get_class(new PageRange())
            )
        );
        Validate::isTrue(
            is_string($content) || is_resource($content),
            sprintf(Resources::INVALID_PARAM_MSG, 'content', 'string|resource')
        );
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_CREATED;
        // If read file failed for any reason it will throw an exception.
        $body = is_resource($content) ? stream_get_contents($content) : $content;
        
        if (is_null($options)) {
            $options = new CreateBlobPagesOptions();
        }
        
        $headers = $this->_addOptionalRangeHeader(
            $headers, $range->getStart(), $range->getEnd()
        );
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_MD5,
            $options->getContentMD5()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_PAGE_WRITE,
            $action
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::URL_ENCODED_CONTENT_TYPE
        );
        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'page');
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode, 
            $body
        );
        
        return CreateBlobPagesResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }
    
    /**
     * Gets the properties of the Blob service.
     * 
     * @param Models\BlobServiceOptions $options The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452239.aspx
     */
    public function getServiceProperties($options = null)
    {
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new BlobServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'service'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'properties'
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode
        );
        $parsed   = $this->dataSerializer->unserialize($response->getBody());
        
        return GetServicePropertiesResult::create($parsed);
    }

    /**
     * Sets the properties of the Blob service.
     * 
     * It's recommended to use getServiceProperties, alter the returned object and
     * then use setServiceProperties with this altered object.
     * 
     * @param ServiceProperties         $serviceProperties The service properties.
     * @param Models\BlobServiceOptions $options           The optional parameters.
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452235.aspx
     */
    public function setServiceProperties($serviceProperties, $options = null)
    {
        Validate::isTrue(
            $serviceProperties instanceof ServiceProperties,
            Resources::INVALID_SVC_PROP_MSG
        );
                
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $statusCode  = Resources::STATUS_ACCEPTED;
        $path        = Resources::EMPTY_STRING;
        $body        = $serviceProperties->toXml($this->dataSerializer);
        
        if (is_null($options)) {
            $options = new BlobServiceOptions();
        }
    
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'service'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'properties'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::URL_ENCODED_CONTENT_TYPE
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode, 
            $body
        );
    }
    
    /**
     * Lists all of the containers in the given storage account.
     * 
     * @param Models\ListContainersOptions $options The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Blob\Models\ListContainersResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179352.aspx
     */
    public function listContainers($options = null)
    {
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new ListContainersOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'list'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_PREFIX,
            $options->getPrefix()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_MARKER,
            $options->getMarker()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_MAX_RESULTS,
            $options->getMaxResults()
        );
        $isInclude = $options->getIncludeMetadata();
        $isInclude = $isInclude ? 'metadata' : null;
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_INCLUDE,
            $isInclude
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode
        );

        $parsed = $this->dataSerializer->unserialize($response->getBody());
        
        return ListContainersResult::create($parsed);
    }
    
    /**
     * Creates a new container in the given storage account.
     * 
     * @param string                        $container The container name.
     * @param Models\CreateContainerOptions $options   The optional parameters.
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179468.aspx
     */
    public function createContainer($container, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::notNullOrEmpty($container, 'container');
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array(Resources::QP_REST_TYPE => 'container');
        $path        = $container;
        $statusCode  = Resources::STATUS_CREATED;
        
        if (is_null($options)) {
            $options = new CreateContainerOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );

        $metadata = $options->getMetadata();
        $headers  = $this->generateMetadataHeaders($metadata);
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_PUBLIC_ACCESS,
            $options->getPublicAccess()
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
    }
    
    /**
     * Creates a new container in the given storage account.
     * 
     * @param string                        $container The container name.
     * @param Models\DeleteContainerOptions $options   The optional parameters.
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179408.aspx
     */
    public function deleteContainer($container, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::notNullOrEmpty($container, 'container');
        
        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $container;
        $statusCode  = Resources::STATUS_ACCEPTED;
        
        if (is_null($options)) {
            $options = new DeleteContainerOptions();
        }
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'container'
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode
        );
    }
    
    /**
     * Returns all properties and metadata on the container.
     * 
     * @param string                    $container name
     * @param Models\BlobServiceOptions $options   optional parameters
     * 
     * @return Models\GetContainerPropertiesResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179370.aspx
     */
    public function getContainerProperties($container, $options = null)
    {
        return $this->_getContainerPropertiesImpl($container, $options);
    }
    
    /**
     * Returns only user-defined metadata for the specified container.
     * 
     * @param string                    $container name
     * @param Models\BlobServiceOptions $options   optional parameters
     * 
     * @return Models\GetContainerPropertiesResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691976.aspx 
     */
    public function getContainerMetadata($container, $options = null)
    {
        return $this->_getContainerPropertiesImpl($container, $options, 'metadata');
    }
    
    /**
     * Gets the access control list (ACL) and any container-level access policies 
     * for the container.
     * 
     * @param string                    $container The container name.
     * @param Models\BlobServiceOptions $options   The optional parameters.
     * 
     * @return Models\GetContainerAclResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179469.aspx
     */
    public function getContainerAcl($container, $options = null)
    {
        Validate::isString($container, 'container');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $container;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new BlobServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'container'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'acl'
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode
        );
        
        $responseHeaders = HttpFormatter::formatHeaders($response->getHeaders());
        
        $access       = Utilities::tryGetValue($responseHeaders, Resources::X_MS_BLOB_PUBLIC_ACCESS);
        $etag         = Utilities::tryGetValue($responseHeaders, Resources::ETAG);
        $modified     = Utilities::tryGetValue($responseHeaders, Resources::LAST_MODIFIED);
        $modifiedDate = Utilities::convertToDateTime($modified);
        $parsed       = $this->dataSerializer->unserialize($response->getBody());
                
        return GetContainerAclResult::create($access, $etag, $modifiedDate, $parsed);
    }
    
    /**
     * Sets the ACL and any container-level access policies for the container.
     * 
     * @param string                    $container name
     * @param Models\ContainerAcl       $acl       access control list for container
     * @param Models\BlobServiceOptions $options   optional parameters
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179391.aspx
     */
    public function setContainerAcl($container, $acl, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::notNullOrEmpty($acl, 'acl');
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $container;
        $statusCode  = Resources::STATUS_OK;
        $body        = $acl->toXml($this->dataSerializer);
        
        if (is_null($options)) {
            $options = new BlobServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'container'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'acl'
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_PUBLIC_ACCESS,
            $acl->getPublicAccess()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::URL_ENCODED_CONTENT_TYPE
        );

        $this->send(
            $method,    
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode, 
            $body
        );
    }
    
    /**
     * Sets metadata headers on the container.
     * 
     * @param string                             $container name
     * @param array                              $metadata  metadata key/value pair.
     * @param Models\SetContainerMetadataOptions $options   optional parameters
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179362.aspx
     */
    public function setContainerMetadata($container, $metadata, $options = null)
    {
        Validate::isString($container, 'container');
        $this->validateMetadata($metadata);
        
        $method      = Resources::HTTP_PUT;
        $headers     = $this->generateMetadataHeaders($metadata);
        $postParams  = array();
        $queryParams = array();
        $path        = $container;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new SetContainerMetadataOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'container'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'metadata'
        );
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers,
            $options->getAccessCondition()
        );

        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
    }
    
    /**
     * Lists all of the blobs in the given container.
     * 
     * @param string                  $container The container name.
     * @param Models\ListBlobsOptions $options   The optional parameters.
     * 
     * @return Models\ListBlobsResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135734.aspx
     */
    public function listBlobs($container, $options = null)
    {
        Validate::isString($container, 'container');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $container;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new ListBlobsOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'container'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'list'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_PREFIX,
            str_replace('\\', '/', $options->getPrefix())
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_MARKER,
            $options->getMarker()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_DELIMITER,
            $options->getDelimiter()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_MAX_RESULTS,
            $options->getMaxResults()
        );
        
        $includeMetadata         = $options->getIncludeMetadata();
        $includeSnapshots        = $options->getIncludeSnapshots();
        $includeUncommittedBlobs = $options->getIncludeUncommittedBlobs();
        
        $includeValue = $this->groupQueryValues(
            array(
                $includeMetadata ? 'metadata' : null, 
                $includeSnapshots ? 'snapshots' : null, 
                $includeUncommittedBlobs ? 'uncommittedblobs' : null
            )
        );
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_INCLUDE,
            $includeValue
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams,
            $postParams,
            $path, 
            $statusCode
        );

        $parsed = $this->dataSerializer->unserialize($response->getBody());
        
        return ListBlobsResult::create($parsed);
    }
    
    /**
     * Creates a new page blob. Note that calling createPageBlob to create a page
     * blob only initializes the blob.
     * To add content to a page blob, call createBlobPages method.
     * 
     * @param string                   $container The container name.
     * @param string                   $blob      The blob name.
     * @param integer                  $length    Specifies the maximum size for the
     * page blob, up to 1 TB. The page blob size must be aligned to a 512-byte 
     * boundary.
     * @param Models\CreateBlobOptions $options   The optional parameters.
     * 
     * @return CopyBlobResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179451.aspx
     */
    public function createPageBlob($container, $blob, $length, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        Validate::isInteger($length, 'length');
        Validate::notNull($length, 'length');
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_CREATED;
        
        if (is_null($options)) {
            $options = new CreateBlobOptions();
        }
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_TYPE,
            BlobType::PAGE_BLOB
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_LENGTH,
            $length
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_SEQUENCE_NUMBER,
            $options->getSequenceNumber()
        );
        $headers = $this->_addCreateBlobOptionalHeaders($options, $headers);
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode
        );
        
        return CopyBlobResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }
    
    /**
     * Creates a new block blob or updates the content of an existing block blob.
     * 
     * Updating an existing block blob overwrites any existing metadata on the blob.
     * Partial updates are not supported with createBlockBlob the content of the
     * existing blob is overwritten with the content of the new blob. To perform a
     * partial update of the content o  f a block blob, use the createBlockList
     * method.
     * Note that the default content type is application/octet-stream.
     * 
     * @param string                   $container The name of the container.
     * @param string                   $blob      The name of the blob.
     * @param string|resource          $content   The content of the blob.
     * @param Models\CreateBlobOptions $options   The optional parameters.
     * 
     * @return CopyBlobResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179451.aspx
     */
    public function createBlockBlob($container, $blob, $content, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        Validate::isTrue(
            is_string($content) || is_resource($content),
            sprintf(Resources::INVALID_PARAM_MSG, 'content', 'string|resource')
        );
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $bodySize    = false;
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_CREATED;
        
        if (is_null($options)) {
            $options = new CreateBlobOptions();
        }
        
        if (is_resource($content)) {
            $cStat = fstat($content);
            // if the resource is a remote file, $cStat will be false
            if ($cStat) {
                $bodySize = $cStat['size'];
            }
        } else {
            $bodySize = strlen($content);
        }

        // if we have a size we can try to one shot this, else failsafe on block upload
        if (is_int($bodySize) && $bodySize <= $this->_SingleBlobUploadThresholdInBytes) {
            $headers = $this->_addCreateBlobOptionalHeaders($options, $headers);
            
            $this->addOptionalHeader(
                $headers,
                Resources::X_MS_BLOB_TYPE,
                BlobType::BLOCK_BLOB
            );
            $this->addOptionalQueryParam(
                $queryParams,
                Resources::QP_TIMEOUT,
                $options->getTimeout()
            );

            // If read file failed for any reason it will throw an exception.
            $body = is_resource($content) ? stream_get_contents($content) : $content;

            $response = $this->send(
                $method, 
                $headers, 
                $queryParams, 
                $postParams,
                $path, 
                $statusCode,
                $body
            );
        } else {
            // This is for large or failsafe upload
            $end       = 0;
            $counter   = 0;
            $body      = '';
            $blockIds  = array();
            // if threshold is lower than 4mb, honor threshold, else use 4mb
            $blockSize = ($this->_SingleBlobUploadThresholdInBytes < 4194304) ? $this->_SingleBlobUploadThresholdInBytes : 4194304;
            while(!$end) {
                if (is_resource($content)) {
                    $body = fread($content, $blockSize);
                    if (feof($content)) {
                        $end = 1;
                    }
                } else {
                    if (strlen($content) <= $blockSize) {
                        $body = $content;
                        $end = 1;
                    } else {
                        $body = substr($content, 0, $blockSize);
                        $content = substr_replace($content, '', 0, $blockSize);
                    }
                }
                if (!empty($body)) {
                    $block = new Block();
                    $block->setBlockId(base64_encode(str_pad($counter++, 6, '0', STR_PAD_LEFT)));
                    $block->setType('Uncommitted');
                    array_push($blockIds, $block);
                    $this->createBlobBlock($container, $blob, $block->getBlockId(), $body);
                }
            }
            $response = $this->commitBlobBlocks($container, $blob, $blockIds, $options);
        }
        return CopyBlobResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }
    
    /**
     * Clears a range of pages from the blob.
     * 
     * @param string                        $container name of the container
     * @param string                        $blob      name of the blob
     * @param Models\PageRange              $range     Can be up to the value of the
     * blob's full size. Note that ranges must be aligned to 512 (0-511, 512-1023)
     * @param Models\CreateBlobPagesOptions $options   optional parameters
     * 
     * @return Models\CreateBlobPagesResult.
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx
     */
    public function clearBlobPages($container, $blob, $range, $options = null)
    {
        return $this->_updatePageBlobPagesImpl(
            PageWriteOption::CLEAR_OPTION,
            $container,
            $blob,
            $range,
            Resources::EMPTY_STRING,
            $options
        );
    }
    
    /**
     * Creates a range of pages to a page blob.
     * 
     * @param string                        $container name of the container
     * @param string                        $blob      name of the blob
     * @param Models\PageRange              $range     Can be up to 4 MB in size
     * Note that ranges must be aligned to 512 (0-511, 512-1023)
     * @param string                        $content   the blob contents.
     * @param Models\CreateBlobPagesOptions $options   optional parameters
     * 
     * @return Models\CreateBlobPagesResult.
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx
     */
    public function createBlobPages($container, $blob, $range, $content,
        $options = null
    ) {
        return $this->_updatePageBlobPagesImpl(
            PageWriteOption::UPDATE_OPTION,
            $container,
            $blob,
            $range,
            $content,
            $options
        );
    }
    
    /**
     * Creates a new block to be committed as part of a block blob.
     * 
     * @param string                        $container name of the container
     * @param string                        $blob      name of the blob
     * @param string                        $blockId   must be less than or equal to 
     * 64 bytes in size. For a given blob, the length of the value specified for the
     * blockid parameter must be the same size for each block.
     * @param string                        $content   the blob block contents
     * @param Models\CreateBlobBlockOptions $options   optional parameters
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135726.aspx
     */
    public function createBlobBlock($container, $blob, $blockId, $content,
        $options = null
    ) {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        Validate::isString($blockId, 'blockId');
        Validate::notNullOrEmpty($blockId, 'blockId');
        Validate::isTrue(
            is_string($content) || is_resource($content),
            sprintf(Resources::INVALID_PARAM_MSG, 'content', 'string|resource')
        );
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_CREATED;
        $body        = $content;
        
        if (is_null($options)) {
            $options = new CreateBlobBlockOptions();
        }
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_MD5,
            $options->getContentMD5()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::URL_ENCODED_CONTENT_TYPE
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'block'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_BLOCKID,
            $blockId
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode,
            $body
        );
        
        return CopyBlobResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }
    
    /**
     * This method writes a blob by specifying the list of block IDs that make up the
     * blob. In order to be written as part of a blob, a block must have been 
     * successfully written to the server in a prior createBlobBlock method.
     * 
     * You can call Put Block List to update a blob by uploading only those blocks 
     * that have changed, then committing the new and existing blocks together. 
     * You can do this by specifying whether to commit a block from the committed 
     * block list or from the uncommitted block list, or to commit the most recently
     * uploaded version of the block, whichever list it may belong to.
     * 
     * @param string                         $container The container name.
     * @param string                         $blob      The blob name.
     * @param Models\BlockList|array         $blockList The block entries.
     * @param Models\CommitBlobBlocksOptions $options   The optional parameters.
     * 
     * @return CopyBlobResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179467.aspx 
     */
    public function commitBlobBlocks($container, $blob, $blockList, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        Validate::isTrue(
            $blockList instanceof BlockList || is_array($blockList),
            sprintf(
                Resources::INVALID_PARAM_MSG,
                'blockList',
                get_class(new BlockList())
            )
        );
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_CREATED;
        $isArray     = is_array($blockList);
        $blockList   = $isArray ? BlockList::create($blockList) : $blockList;
        $body        = $blockList->toXml($this->dataSerializer);
        
        if (is_null($options)) {
            $options = new CommitBlobBlocksOptions();
        }
        
        $blobContentType     = $options->getBlobContentType();
        $blobContentEncoding = $options->getBlobContentEncoding();
        $blobContentLanguage = $options->getBlobContentLanguage();
        $blobContentMD5      = $options->getBlobContentMD5();
        $blobCacheControl    = $options->getBlobCacheControl();
        $leaseId             = $options->getLeaseId();
        $contentType         = Resources::URL_ENCODED_CONTENT_TYPE;
        
        $metadata = $options->getMetadata();
        $headers  = $this->generateMetadataHeaders($metadata);
        $headers  = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $leaseId
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CACHE_CONTROL,
            $blobCacheControl
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_TYPE,
            $blobContentType
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_ENCODING,
            $blobContentEncoding
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_LANGUAGE,
            $blobContentLanguage
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_MD5,
            $blobContentMD5
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            $contentType
        );
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'blocklist'
        );
        
        return $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode, 
            $body
        );
    }
    
    /**
     * Retrieves the list of blocks that have been uploaded as part of a block blob.
     * 
     * There are two block lists maintained for a blob:
     * 1) Committed Block List: The list of blocks that have been successfully 
     *    committed to a given blob with commitBlobBlocks.
     * 2) Uncommitted Block List: The list of blocks that have been uploaded for a 
     *    blob using Put Block (REST API), but that have not yet been committed. 
     *    These blocks are stored in Windows Azure in association with a blob, but do
     *    not yet form part of the blob.
     * 
     * @param string                       $container name of the container
     * @param string                       $blob      name of the blob
     * @param Models\ListBlobBlocksOptions $options   optional parameters
     * 
     * @return Models\ListBlobBlocksResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179400.aspx
     */
    public function listBlobBlocks($container, $blob, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new ListBlobBlocksOptions();
        }
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_BLOCK_LIST_TYPE,
            $options->getBlockListType()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_SNAPSHOT,
            $options->getSnapshot()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'blocklist'
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams,
            $path, 
            $statusCode
        );

        $parsed = $this->dataSerializer->unserialize($response->getBody());
        
        return ListBlobBlocksResult::create(HttpFormatter::formatHeaders($response->getHeaders()), $parsed);
    }
    
    /**
     * Returns all properties and metadata on the blob.
     * 
     * @param string                          $container name of the container
     * @param string                          $blob      name of the blob
     * @param Models\GetBlobPropertiesOptions $options   optional parameters
     * 
     * @return Models\GetBlobPropertiesResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179394.aspx
     */
    public function getBlobProperties($container, $blob, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        
        $method      = Resources::HTTP_HEAD;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new GetBlobPropertiesOptions();
        }
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_SNAPSHOT,
            $options->getSnapshot()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );        

        $headers = $response->getHeaders();
        $formattedHeaders = HttpFormatter::formatHeaders($headers);
        
        return $this->_getBlobPropertiesResultFromResponse($formattedHeaders);
    }
    
    /**
     * Returns all properties and metadata on the blob.
     * 
     * @param string                        $container name of the container
     * @param string                        $blob      name of the blob
     * @param Models\GetBlobMetadataOptions $options   optional parameters
     * 
     * @return Models\GetBlobMetadataResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179350.aspx
     */
    public function getBlobMetadata($container, $blob, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        
        $method      = Resources::HTTP_HEAD;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new GetBlobMetadataOptions();
        }
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_SNAPSHOT,
            $options->getSnapshot()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'metadata'
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        $responseHeaders = HttpFormatter::formatHeaders($response->getHeaders());
        $metadata = $this->getMetadataArray($responseHeaders);
        
        return GetBlobMetadataResult::create($responseHeaders, $metadata);
    }
    
    /**
     * Returns a list of active page ranges for a page blob. Active page ranges are 
     * those that have been populated with data.
     * 
     * @param string                           $container name of the container
     * @param string                           $blob      name of the blob
     * @param Models\ListPageBlobRangesOptions $options   optional parameters
     * 
     * @return Models\ListPageBlobRangesResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691973.aspx
     */
    public function listPageBlobRanges($container, $blob, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new ListPageBlobRangesOptions();
        }
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $headers = $this->_addOptionalRangeHeader(
            $headers, $options->getRangeStart(), $options->getRangeEnd()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_SNAPSHOT,
            $options->getSnapshot()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'pagelist'
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        $parsed   = $this->dataSerializer->unserialize($response->getBody());
        
        return ListPageBlobRangesResult::create(HttpFormatter::formatHeaders($response->getHeaders()), $parsed);
    }
    
    /**
     * Sets system properties defined for a blob.
     * 
     * @param string                          $container name of the container
     * @param string                          $blob      name of the blob
     * @param Models\SetBlobPropertiesOptions $options   optional parameters
     * 
     * @return Models\SetBlobPropertiesResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691966.aspx
     */
    public function setBlobProperties($container, $blob, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new SetBlobPropertiesOptions();
        }
        
        $blobContentType     = $options->getBlobContentType();
        $blobContentEncoding = $options->getBlobContentEncoding();
        $blobContentLanguage = $options->getBlobContentLanguage();
        $blobContentLength   = $options->getBlobContentLength();
        $blobContentMD5      = $options->getBlobContentMD5();
        $blobCacheControl    = $options->getBlobCacheControl();
        $leaseId             = $options->getLeaseId();
        $sNumberAction       = $options->getSequenceNumberAction();
        $sNumber             = $options->getSequenceNumber();
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $leaseId
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CACHE_CONTROL,
            $blobCacheControl
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_TYPE,
            $blobContentType
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_ENCODING,
            $blobContentEncoding
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_LANGUAGE,
            $blobContentLanguage
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_LENGTH,
            $blobContentLength
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_CONTENT_MD5,
            $blobContentMD5
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_SEQUENCE_NUMBER_ACTION,
            $sNumberAction
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_BLOB_SEQUENCE_NUMBER,
            $sNumber
        );

        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'properties');
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        
        return SetBlobPropertiesResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }
    
    /**
     * Sets metadata headers on the blob.
     * 
     * @param string                        $container name of the container
     * @param string                        $blob      name of the blob
     * @param array                         $metadata  key/value pair representation
     * @param Models\SetBlobMetadataOptions $options   optional parameters
     * 
     * @return Models\SetBlobMetadataResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179414.aspx
     */
    public function setBlobMetadata($container, $blob, $metadata, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        $this->validateMetadata($metadata);
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new SetBlobMetadataOptions();
        }
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        $headers = $this->addMetadataHeaders($headers, $metadata);
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'metadata'
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        
        return SetBlobMetadataResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }
    
    /**
     * Reads or downloads a blob from the system, including its metadata and 
     * properties.
     * 
     * @param string                $container name of the container
     * @param string                $blob      name of the blob
     * @param Models\GetBlobOptions $options   optional parameters
     * 
     * @return Models\GetBlobResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179440.aspx
     */
    public function getBlob($container, $blob, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = array(
            Resources::STATUS_OK,
            Resources::STATUS_PARTIAL_CONTENT
        );
        
        if (is_null($options)) {
            $options = new GetBlobOptions();
        }
        
        $getMD5  = $options->getComputeRangeMD5();
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        $headers = $this->_addOptionalRangeHeader(
            $headers, $options->getRangeStart(), $options->getRangeEnd()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_RANGE_GET_CONTENT_MD5,
            $getMD5 ? 'true' : null
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_SNAPSHOT,
            $options->getSnapshot()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        $metadata = $this->getMetadataArray(HttpFormatter::formatHeaders($response->getHeaders()));
        
        return GetBlobResult::create(
            HttpFormatter::formatHeaders($response->getHeaders()),
            $response->getBody(),
            $metadata
        );
    }
    
    /**
     * Deletes a blob or blob snapshot.
     * 
     * Note that if the snapshot entry is specified in the $options then only this
     * blob snapshot is deleted. To delete all blob snapshots, do not set Snapshot 
     * and just set getDeleteSnaphotsOnly to true.
     * 
     * @param string                   $container name of the container
     * @param string                   $blob      name of the blob
     * @param Models\DeleteBlobOptions $options   optional parameters
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179413.aspx
     */
    public function deleteBlob($container, $blob, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        
        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $this->_createPath($container, $blob);
        $statusCode  = Resources::STATUS_ACCEPTED;
        
        if (is_null($options)) {
            $options = new DeleteBlobOptions();
        }
        
        if (is_null($options->getSnapshot())) {
            $delSnapshots = $options->getDeleteSnaphotsOnly() ? 'only' : 'include';
            $this->addOptionalHeader(
                $headers,
                Resources::X_MS_DELETE_SNAPSHOTS,
                $delSnapshots
            );
        } else {
            $this->addOptionalQueryParam(
                $queryParams,
                Resources::QP_SNAPSHOT,
                $options->getSnapshot()
            );
        }
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, $options->getAccessCondition()
        );
        
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
    }
    
    /**
     * Creates a snapshot of a blob.
     * 
     * @param string                           $container The name of the container.
     * @param string                           $blob      The name of the blob.
     * @param Models\CreateBlobSnapshotOptions $options   The optional parameters.
     * 
     * @return Models\CreateBlobSnapshotResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691971.aspx
     */
    public function createBlobSnapshot($container, $blob, $options = null)
    {
        Validate::isString($container, 'container');
        Validate::isString($blob, 'blob');
        Validate::notNullOrEmpty($blob, 'blob');
        
        $method             = Resources::HTTP_PUT;
        $headers            = array();
        $postParams         = array();
        $queryParams        = array();
        $path               = $this->_createPath($container, $blob);
        $expectedStatusCode = Resources::STATUS_CREATED;
        
        if (is_null($options)) {
            $options = new CreateBlobSnapshotOptions();
        }  
        
        $queryParams[Resources::QP_COMP] = 'snapshot';
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );

        $headers = $this->addOptionalAccessConditionHeader(
            $headers,
            $options->getAccessCondition()
        );
        $headers = $this->addMetadataHeaders($headers, $options->getMetadata());
        $this->addOptionalHeader(
            $headers,
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $expectedStatusCode
        );
        
        return CreateBlobSnapshotResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }
    
    /**
     * Copies a source blob to a destination blob within the same storage account.
     * 
     * @param string                 $destinationContainer name of the destination 
     * container
     * @param string                 $destinationBlob      name of the destination 
     * blob
     * @param string                 $sourceContainer      name of the source 
     * container
     * @param string                 $sourceBlob           name of the source
     * blob
     * @param Models\CopyBlobOptions $options              optional parameters
     * 
     * @return CopyBlobResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd894037.aspx
     */
    public function copyBlob(
        $destinationContainer, 
        $destinationBlob,
        $sourceContainer, 
        $sourceBlob, 
        $options = null
    ) {

        $method              = Resources::HTTP_PUT;
        $headers             = array();
        $postParams          = array();
        $queryParams         = array();
        $destinationBlobPath = $this->_createPath(
            $destinationContainer,
            $destinationBlob
        );
        $statusCode          = Resources::STATUS_ACCEPTED;
        
        if (is_null($options)) {
            $options = new CopyBlobOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $sourceBlobPath = $this->_getCopyBlobSourceName(
            $sourceContainer, 
            $sourceBlob,
            $options
        );
        
        $headers = $this->addOptionalAccessConditionHeader(
            $headers, 
            $options->getAccessCondition()
        );
        
        $headers = $this->addOptionalSourceAccessConditionHeader(
            $headers,
            $options->getSourceAccessCondition()
        );
        
        $this->addOptionalHeader(
            $headers, 
            Resources::X_MS_COPY_SOURCE, 
            $sourceBlobPath
        );
        
        $headers = $this->addMetadataHeaders($headers, $options->getMetadata());
        
        $this->addOptionalHeader(
            $headers, 
            Resources::X_MS_LEASE_ID,
            $options->getLeaseId()
        );
        
        $this->addOptionalHeader(
            $headers, 
            Resources::X_MS_SOURCE_LEASE_ID,
            $options->getSourceLeaseId()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $destinationBlobPath, 
            $statusCode
        );
        
        return CopyBlobResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }
        
    /**
     * Establishes an exclusive one-minute write lock on a blob. To write to a locked
     * blob, a client must provide a lease ID.
     * 
     * @param string                     $container name of the container
     * @param string                     $blob      name of the blob
     * @param Models\AcquireLeaseOptions $options   optional parameters
     * 
     * @return Models\AcquireLeaseResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx
     */
    public function acquireLease($container, $blob, $options = null)
    {
        $headers = $this->_putLeaseImpl(
            LeaseMode::ACQUIRE_ACTION,
            $container,
            $blob,
            null /* leaseId */,
            is_null($options) ? new AcquireLeaseOptions() : $options,
            is_null($options) ? null : $options->getAccessCondition()
        );
        
        return AcquireLeaseResult::create($headers);
    }
    
    /**
     * Renews an existing lease
     * 
     * @param string                    $container name of the container
     * @param string                    $blob      name of the blob
     * @param string                    $leaseId   lease id when acquiring
     * @param Models\BlobServiceOptions $options   optional parameters
     * 
     * @return Models\AcquireLeaseResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx
     */
    public function renewLease($container, $blob, $leaseId, $options = null)
    {
        $headers = $this->_putLeaseImpl(
            LeaseMode::RENEW_ACTION,
            $container,
            $blob,
            $leaseId,
            is_null($options) ? new BlobServiceOptions() : $options
        );
        
        return AcquireLeaseResult::create($headers);
    }
    
    /**
     * Frees the lease if it is no longer needed so that another client may 
     * immediately acquire a lease against the blob.
     * 
     * @param string                    $container name of the container
     * @param string                    $blob      name of the blob
     * @param string                    $leaseId   lease id when acquiring
     * @param Models\BlobServiceOptions $options   optional parameters
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx
     */
    public function releaseLease($container, $blob, $leaseId, $options = null)
    {
        $this->_putLeaseImpl(
            LeaseMode::RELEASE_ACTION,
            $container,
            $blob,
            $leaseId,
            is_null($options) ? new BlobServiceOptions() : $options
        );
    }
    
    /**
     * Ends the lease but ensure that another client cannot acquire a new lease until
     * the current lease period has expired.
     * 
     * @param string                    $container name of the container
     * @param string                    $blob      name of the blob
     * @param Models\BlobServiceOptions $options   optional parameters
     * 
     * @return BreakLeaseResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx
     */
    public function breakLease($container, $blob, $options = null)
    {
        $headers = $this->_putLeaseImpl(
            LeaseMode::BREAK_ACTION,
            $container,
            $blob,
            null,
            is_null($options) ? new BlobServiceOptions() : $options
        );
        
        return BreakLeaseResult::create($headers);
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Internal;
use MicrosoftAzure\Storage\Common\Internal\FilterableService;

/**
 * This interface has all REST APIs provided by Windows Azure for Blob service.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 * @see       http://msdn.microsoft.com/en-us/library/windowsazure/dd135733.aspx
 */
interface IBlob extends FilterableService
{
    /**
    * Gets the properties of the Blob service.
    * 
    * @param Models\BlobServiceOptions $options optional blob service options.
    * 
    * @return MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452239.aspx
    */
    public function getServiceProperties($options = null);

    /**
    * Sets the properties of the Blob service.
    * 
    * @param ServiceProperties         $serviceProperties new service properties
    * @param Models\BlobServiceOptions $options           optional parameters
    * 
    * @return none.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452235.aspx
    */
    public function setServiceProperties($serviceProperties, $options = null);

    /**
    * Lists all of the containers in the given storage account.
    * 
    * @param Models\ListContainersOptions $options optional parameters
    * 
    * @return MicrosoftAzure\Storage\Blob\Models\ListContainersResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179352.aspx
    */
    public function listContainers($options = null);

    /**
    * Creates a new container in the given storage account.
    * 
    * @param string                        $container name
    * @param Models\CreateContainerOptions $options   optional parameters
    * 
    * @return none.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179468.aspx
    */
    public function createContainer($container, $options = null);

    /**
    * Creates a new container in the given storage account.
    * 
    * @param string                        $container name
    * @param Models\DeleteContainerOptions $options   optional parameters
    * 
    * @return none.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179408.aspx
    */
    public function deleteContainer($container, $options = null);

    /**
    * Returns all properties and metadata on the container.
    * 
    * @param string                    $container name
    * @param Models\BlobServiceOptions $options   optional parameters
    * 
    * @return Models\GetContainerPropertiesResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179370.aspx
    */
    public function getContainerProperties($container, $options = null);

    /**
    * Returns only user-defined metadata for the specified container.
    * 
    * @param string                    $container name
    * @param Models\BlobServiceOptions $options   optional parameters
    * 
    * @return Models\GetContainerPropertiesResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691976.aspx 
    */
    public function getContainerMetadata($container, $options = null);

    /**
    * Gets the access control list (ACL) and any container-level access policies 
    * for the container.
    * 
    * @param string                    $container name
    * @param Models\BlobServiceOptions $options   optional parameters
    * 
    * @return Models\GetContainerAclResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179469.aspx
    */
    public function getContainerAcl($container, $options = null);

    /**
    * Sets the ACL and any container-level access policies for the container.
    * 
    * @param string                    $container name
    * @param Models\ContainerAcl       $acl       access control list for container
    * @param Models\BlobServiceOptions $options   optional parameters
    * 
    * @return none.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179391.aspx
    */
    public function setContainerAcl($container, $acl, $options = null);

    /**
    * Sets metadata headers on the container.
    * 
    * @param string                             $container name
    * @param array                              $metadata  metadata key/value pair.
    * @param Models\SetContainerMetadataOptions $options   optional parameters
    * 
    * @return none.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179362.aspx
    */
    public function setContainerMetadata($container, $metadata, $options = null);

    /**
    * Lists all of the blobs in the given container.
    * 
    * @param string                  $container name
    * @param Models\ListBlobsOptions $options   optional parameters
    * 
    * @return Models\ListBlobsResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135734.aspx
    */
    public function listBlobs($container, $options = null);

    /**
    * Creates a new page blob. Note that calling createPageBlob to create a page
    * blob only initializes the blob.
    * To add content to a page blob, call createBlobPages method.
    * 
    * @param string                   $container name of the container
    * @param string                   $blob      name of the blob
    * @param int                      $length    specifies the maximum size for the
    * page blob, up to 1 TB. The page blob size must be aligned to a 512-byte 
    * boundary.
    * @param Models\CreateBlobOptions $options   optional parameters
    * 
    * @return CopyBlobResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179451.aspx
    */
    public function createPageBlob($container, $blob, $length, $options = null);

    /**
    * Creates a new block blob or updates the content of an existing block blob.
    * Updating an existing block blob overwrites any existing metadata on the blob.
    * Partial updates are not supported with createBlockBlob; the content of the
    * existing blob is overwritten with the content of the new blob. To perform a
    * partial update of the content of a block blob, use the createBlockList method.
    * 
    * @param string                   $container name of the container
    * @param string                   $blob      name of the blob
    * @param string                   $content   content of the blob
    * @param Models\CreateBlobOptions $options   optional parameters
    * 
    * @return CopyBlobResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179451.aspx
    */
    public function createBlockBlob($container, $blob, $content, $options = null);

    /**
    * Clears a range of pages from the blob.
    * 
    * @param string                        $container name of the container
    * @param string                        $blob      name of the blob
    * @param Models\PageRange              $range     Can be up to the value of the
    * blob's full size.
    * @param Models\CreateBlobPagesOptions $options   optional parameters
    * 
    * @return Models\CreateBlobPagesResult.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx
    */
    public function clearBlobPages($container, $blob, $range, $options = null);

    /**
    * Creates a range of pages to a page blob.
    * 
    * @param string                        $container name of the container
    * @param string                        $blob      name of the blob
    * @param Models\PageRange              $range     Can be up to 4 MB in size
    * @param string                        $content   the blob contents
    * @param Models\CreateBlobPagesOptions $options   optional parameters
    * 
    * @return Models\CreateBlobPagesResult.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx
    */
    public function createBlobPages($container, $blob, $range, $content,
        $options = null
    );

    /**
    * Creates a new block to be committed as part of a block blob.
    * 
    * @param string                        $container name of the container
    * @param string                        $blob      name of the blob
    * @param string                        $blockId   must be less than or equal to 
    * 64 bytes in size. For a given blob, the length of the value specified for the
    * blockid parameter must be the same size for each block.
    * @param string                        $content   the blob block contents
    * @param Models\CreateBlobBlockOptions $options   optional parameters
    * 
    * @return none.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135726.aspx
    */
    public function createBlobBlock($container, $blob, $blockId, $content,
        $options = null
    );

    /**
    * This method writes a blob by specifying the list of block IDs that make up the
    * blob. In order to be written as part of a blob, a block must have been 
    * successfully written to the server in a prior createBlobBlock method.
    * 
    * You can call Put Block List to update a blob by uploading only those blocks 
    * that have changed, then committing the new and existing blocks together. 
    * You can do this by specifying whether to commit a block from the committed 
    * block list or from the uncommitted block list, or to commit the most recently
    * uploaded version of the block, whichever list it may belong to.
    * 
    * @param string                         $container name of the container
    * @param string                         $blob      name of the blob
    * @param Models\BlockList               $blockList the block list entries
    * @param Models\CommitBlobBlocksOptions $options   optional parameters
    * 
    * @return none.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179467.aspx 
    */
    public function commitBlobBlocks($container, $blob, $blockList, $options = null);

    /**
    * Retrieves the list of blocks that have been uploaded as part of a block blob.
    * 
    * There are two block lists maintained for a blob:
    * 1) Committed Block List: The list of blocks that have been successfully 
    *    committed to a given blob with commitBlobBlocks.
    * 2) Uncommitted Block List: The list of blocks that have been uploaded for a 
    *    blob using Put Block (REST API), but that have not yet been committed. 
    *    These blocks are stored in Windows Azure in association with a blob, but do
    *    not yet form part of the blob.
    * 
    * @param string                       $container name of the container
    * @param string                       $blob      name of the blob
    * @param Models\ListBlobBlocksOptions $options   optional parameters
    * 
    * @return Models\ListBlobBlocksResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179400.aspx
    */
    public function listBlobBlocks($container, $blob, $options = null);

    /**
    * Returns all properties and metadata on the blob.
    * 
    * @param string                          $container name of the container
    * @param string                          $blob      name of the blob
    * @param Models\GetBlobPropertiesOptions $options   optional parameters
    * 
    * @return Models\GetBlobPropertiesResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179394.aspx
    */
    public function getBlobProperties($container, $blob, $options = null);

    /**
    * Returns all properties and metadata on the blob.
    * 
    * @param string                        $container name of the container
    * @param string                        $blob      name of the blob
    * @param Models\GetBlobMetadataOptions $options   optional parameters
    * 
    * @return Models\GetBlobMetadataResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179350.aspx
    */
    public function getBlobMetadata($container, $blob, $options = null);

    /**
    * Returns a list of active page ranges for a page blob. Active page ranges are 
    * those that have been populated with data.
    * 
    * @param string                           $container name of the container
    * @param string                           $blob      name of the blob
    * @param Models\ListPageBlobRangesOptions $options   optional parameters
    * 
    * @return Models\ListPageBlobRangesResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691973.aspx
    */
    public function listPageBlobRanges($container, $blob, $options = null);

    /**
    * Sets system properties defined for a blob.
    * 
    * @param string                          $container name of the container
    * @param string                          $blob      name of the blob
    * @param Models\SetBlobPropertiesOptions $options   optional parameters
    * 
    * @return Models\SetBlobPropertiesResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691966.aspx
    */
    public function setBlobProperties($container, $blob, $options = null);

    /**
    * Sets metadata headers on the blob.
    * 
    * @param string                        $container name of the container
    * @param string                        $blob      name of the blob
    * @param array                         $metadata  key/value pair representation
    * @param Models\SetBlobMetadataOptions $options   optional parameters
    * 
    * @return Models\SetBlobMetadataResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179414.aspx
    */
    public function setBlobMetadata($container, $blob, $metadata, $options = null);

    /**
    * Reads or downloads a blob from the system, including its metadata and 
    * properties.
    * 
    * @param string                $container name of the container
    * @param string                $blob      name of the blob
    * @param Models\GetBlobOptions $options   optional parameters
    * 
    * @return Models\GetBlobResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179440.aspx
    */
    public function getBlob($container, $blob, $options = null);

    /**
     * Deletes a blob or blob snapshot.
     * 
     * Note that if the snapshot entry is specified in the $options then only this
     * blob snapshot is deleted. To delete all blob snapshots, do not set Snapshot 
     * and just set getDeleteSnaphotsOnly to true.
     * 
     * @param string                   $container name of the container
     * @param string                   $blob      name of the blob
     * @param Models\DeleteBlobOptions $options   optional parameters
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179413.aspx
     */
    public function deleteBlob($container, $blob, $options = null);

    /**
    * Creates a snapshot of a blob.
    * 
    * @param string                           $container name of the container
    * @param string                           $blob      name of the blob
    * @param Models\CreateBlobSnapshotOptions $options   optional parameters
    * 
    * @return Models\CreateBlobSnapshotResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691971.aspx
    */
    public function createBlobSnapshot($container, $blob, $options = null);

    /**
    * Copies a source blob to a destination blob within the same storage account.
    * 
    * @param string                 $destinationContainer name of container
    * @param string                 $destinationBlob      name of blob
    * @param string                 $sourceContainer      name of container
    * @param string                 $sourceBlob           name of blob
    * @param Models\CopyBlobOptions $options              optional parameters
    * 
    * @return CopyBlobResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd894037.aspx
    */
    public function copyBlob($destinationContainer, $destinationBlob,
        $sourceContainer, $sourceBlob, $options = null
    );

    /**
    * Establishes an exclusive one-minute write lock on a blob. To write to a locked
    * blob, a client must provide a lease ID.
    * 
    * @param string                     $container name of the container
    * @param string                     $blob      name of the blob
    * @param Models\AcquireLeaseOptions $options   optional parameters
    * 
    * @return Models\AcquireLeaseResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx
    */
    public function acquireLease($container, $blob, $options = null);

    /**
    * Renews an existing lease
    * 
    * @param string                    $container name of the container
    * @param string                    $blob      name of the blob
    * @param string                    $leaseId   lease id when acquiring
    * @param Models\BlobServiceOptions $options   optional parameters
    * 
    * @return Models\AcquireLeaseResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx
    */
    public function renewLease($container, $blob, $leaseId, $options = null);

    /**
    * Frees the lease if it is no longer needed so that another client may 
    * immediately acquire a lease against the blob.
    * 
    * @param string                    $container name of the container
    * @param string                    $blob      name of the blob
    * @param string                    $leaseId   lease id when acquiring
    * @param Models\BlobServiceOptions $options   optional parameters
    * 
    * @return none
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx
    */
    public function releaseLease($container, $blob, $leaseId, $options = null);

    /**
    * Ends the lease but ensure that another client cannot acquire a new lease until
    * the current lease period has expired.
    * 
    * @param string                    $container name of the container
    * @param string                    $blob      name of the blob
    * @param Models\BlobServiceOptions $options   optional parameters
    * 
    * @return none
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx
    */
    public function breakLease($container, $blob, $options = null);
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\WindowsAzureUtilities;

/**
 * Represents a set of access conditions to be used for operations against the 
 * storage services.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AccessCondition
{
    /**
     * Represents the header type.
     * 
     * @var string
     */
    private $_header = Resources::EMPTY_STRING;
    
    /**
     * Represents the header value.
     * 
     * @var string
     */
    private $_value;

    /**
     * Constructor
     * 
     * @param string $headerType header name
     * @param string $value      header value
     */
    protected function __construct($headerType, $value)
    {
        $this->setHeader($headerType);
        $this->setValue($value);
    }
    
    /**
     * Specifies that no access condition is set.
     * 
     * @return \MicrosoftAzure\Storage\Blob\Models\AccessCondition 
     */
    public static function none()
    {
        return new AccessCondition(Resources::EMPTY_STRING, null);
    }
    
    /**
     * Returns an access condition such that an operation will be performed only if 
     * the resource's ETag value matches the specified ETag value.
     * <p>
     * Setting this access condition modifies the request to include the HTTP 
     * <i>If-Match</i> conditional header. If this access condition is set, the 
     * operation is performed only if the ETag of the resource matches the specified
     * ETag.
     * <p>
     * For more information, see 
     * <a href= 'http://go.microsoft.com/fwlink/?LinkID=224642&clcid=0x409'>
     * Specifying Conditional Headers for Blob Service Operations</a>.
     *
     * @param string $etag a string that represents the ETag value to check.
     *
     * @return \MicrosoftAzure\Storage\Blob\Models\AccessCondition
     */
    public static function ifMatch($etag)
    {
        return new AccessCondition(Resources::IF_MATCH, $etag);
    }
    
    /**
     * Returns an access condition such that an operation will be performed only if 
     * the resource has been modified since the specified time.
     * <p>
     * Setting this access condition modifies the request to include the HTTP 
     * <i>If-Modified-Since</i> conditional header. If this access condition is set,
     * the operation is performed only if the resource has been modified since the 
     * specified time.
     * <p>
     * For more information, see 
     * <a href= 'http://go.microsoft.com/fwlink/?LinkID=224642&clcid=0x409'>
     * Specifying Conditional Headers for Blob Service Operations</a>.
     *
     * @param \DateTime $lastModified date that represents the last-modified
     * time to check for the resource.
     *
     * @return \MicrosoftAzure\Storage\Blob\Models\AccessCondition
     */
    public static function ifModifiedSince($lastModified)
    {
        Validate::isDate($lastModified);
        return new AccessCondition(
            Resources::IF_MODIFIED_SINCE,
            $lastModified
        );
    }
    
    /**
     * Returns an access condition such that an operation will be performed only if 
     * the resource's ETag value does not match the specified ETag value.
     * <p>
     * Setting this access condition modifies the request to include the HTTP 
     * <i>If-None-Match</i> conditional header. If this access condition is set, the
     * operation is performed only if the ETag of the resource does not match the
     * specified ETag.
     * <p>
     * For more information,
     * see <a href= 'http://go.microsoft.com/fwlink/?LinkID=224642&clcid=0x409'>
     * Specifying Conditional Headers for Blob Service Operations</a>.
     *
     * @param string $etag string that represents the ETag value to check.
     *
     * @return \MicrosoftAzure\Storage\Blob\Models\AccessCondition
     */
    public static function ifNoneMatch($etag)
    {
        return new AccessCondition(Resources::IF_NONE_MATCH, $etag);
    }
    
    /**
     * Returns an access condition such that an operation will be performed only if
     * the resource has not been modified since the specified time.
     * <p>
     * Setting this access condition modifies the request to include the HTTP 
     * <i>If-Unmodified-Since</i> conditional header. If this access condition is
     * set, the operation is performed only if the resource has not been modified 
     * since the specified time.
     * <p>
     * For more information, see
     * <a href= 'http://go.microsoft.com/fwlink/?LinkID=224642&clcid=0x409'>
     * Specifying Conditional Headers for Blob Service Operations</a>.
     *
     * @param \DateTime $lastModified date that represents the last-modified
     * time to check for the resource.
     *
     * @return \MicrosoftAzure\Storage\Blob\Models\AccessCondition
     */
    public static function ifNotModifiedSince($lastModified)
    {
        Validate::isDate($lastModified);
        return new AccessCondition(
            Resources::IF_UNMODIFIED_SINCE,
            $lastModified
        );
    }
    
    /**
     * Sets header type
     * 
     * @param string $headerType can be one of Resources
     * 
     * @return none.
     */
    public function setHeader($headerType)
    {
        $valid = AccessCondition::isValid($headerType);
        Validate::isTrue($valid, Resources::INVALID_HT_MSG);
        
        $this->_header = $headerType;
    }
    
    /**
     * Gets header type
     * 
     * @return string.
     */
    public function getHeader()
    {
        return $this->_header;
    }
    
    /**
     * Sets the header value
     * 
     * @param string $value the value to use
     * 
     * @return none
     */
    public function setValue($value)
    {
        $this->_value = $value;
    }
    
    /**
     * Gets the header value
     * 
     * @return string
     */
    public function getValue()
    {
        return $this->_value;
    }
    
    /**
     * Check if the $headerType belongs to valid header types
     * 
     * @param string $headerType candidate header type
     * 
     * @return boolean 
     */
    public static function isValid($headerType)
    {
        if (   $headerType == Resources::EMPTY_STRING
            || $headerType == Resources::IF_UNMODIFIED_SINCE
            || $headerType == Resources::IF_MATCH
            || $headerType == Resources::IF_MODIFIED_SINCE
            || $headerType == Resources::IF_NONE_MATCH
        ) {
            return true;
        } else {
            return false;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Holds container access policy elements
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AccessPolicy
{
    /**
     * @var string
     */
    private $_start;
    
    /**
     * @var \DateTime
     */
    private $_expiry;
    
    /**
     * @var \DateTime
     */
    private $_permission;
    
    /**
     * Gets start.
     *
     * @return \DateTime.
     */
    public function getStart()
    {
        return $this->_start;
    }

    /**
     * Sets start.
     *
     * @param \DateTime $start value.
     * 
     * @return none.
     */
    public function setStart($start)
    {
        Validate::isDate($start);
        $this->_start = $start;
    }
    
    /**
     * Gets expiry.
     *
     * @return \DateTime.
     */
    public function getExpiry()
    {
        return $this->_expiry;
    }

    /**
     * Sets expiry.
     *
     * @param \DateTime $expiry value.
     * 
     * @return none.
     */
    public function setExpiry($expiry)
    {
        Validate::isDate($expiry);
        $this->_expiry = $expiry;
    }
    
    /**
     * Gets permission.
     *
     * @return string.
     */
    public function getPermission()
    {
        return $this->_permission;
    }

    /**
     * Sets permission.
     *
     * @param string $permission value.
     * 
     * @return none.
     */
    public function setPermission($permission)
    {
        $this->_permission = $permission;
    }
    
    /**
     * Converts this current object to XML representation.
     * 
     * @return array.
     */
    public function toArray()
    {
        $array = array();
        
        $array['Start']      = Utilities::convertToEdmDateTime($this->_start);
        $array['Expiry']     = Utilities::convertToEdmDateTime($this->_expiry);
        $array['Permission'] = $this->_permission;
        
        return $array;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Optional parameters for acquireLease wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AcquireLeaseOptions extends BlobServiceOptions
{
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * The result of calling acquireLease API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AcquireLeaseResult
{
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * Creates AcquireLeaseResult from response headers
     * 
     * @param array $headers response headers
     * 
     * @return AcquireLeaseResult
     */
    public static function create($headers)
    {
        $result = new AcquireLeaseResult();
        
        $result->setLeaseId(
            Utilities::tryGetValue($headers, Resources::X_MS_LEASE_ID)
        );
        
        return $result;
    }
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Represents windows azure blob object
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Blob
{
    /**
     * @var string
     */
    private $_name;
    
    /**
     * @var string
     */
    private $_url;
    
    /**
     * @var string
     */
    private $_snapshot;

    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * @var BlobProperties
     */
    private $_properties;

    /**
     * Gets blob name.
     *
     * @return string.
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Sets blob name.
     *
     * @param string $name value.
     * 
     * @return none.
     */
    public function setName($name)
    {
        $this->_name = $name;
    }
    
    /**
     * Gets blob snapshot.
     *
     * @return string.
     */
    public function getSnapshot()
    {
        return $this->_snapshot;
    }

    /**
     * Sets blob snapshot.
     *
     * @param string $snapshot value.
     * 
     * @return none.
     */
    public function setSnapshot($snapshot)
    {
        $this->_snapshot = $snapshot;
    }

    /**
     * Gets blob url.
     *
     * @return string.
     */
    public function getUrl()
    {
        return $this->_url;
    }

    /**
     * Sets blob url.
     *
     * @param string $url value.
     * 
     * @return none.
     */
    public function setUrl($url)
    {
        $this->_url = $url;
    }

    /**
     * Gets blob metadata.
     *
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets blob metadata.
     *
     * @param string $metadata value.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Gets blob properties.
     *
     * @return BlobProperties.
     */
    public function getProperties()
    {
        return $this->_properties;
    }

    /**
     * Sets blob properties.
     *
     * @param BlobProperties $properties value.
     * 
     * @return none.
     */
    public function setProperties($properties)
    {
        $this->_properties = $properties;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Holds available blob block types
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobBlockType
{
    const COMMITTED_TYPE   = 'Committed';
    const UNCOMMITTED_TYPE = 'Uncommitted';
    const LATEST_TYPE      = 'Latest';
    
    /**
     * Validates the provided type.
     * 
     * @param string $type The entry type.
     * 
     * @return boolean
     */
    public static function isValid($type)
    {
        switch ($type) {
        case self::COMMITTED_TYPE:
        case self::LATEST_TYPE:
        case self::UNCOMMITTED_TYPE:
        return true;
        
        default:
        return false;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Represents BlobPrefix object
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobPrefix
{
    /**
     * @var string
     */
    private $_name;
    
    /**
     * Gets blob name.
     *
     * @return string.
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Sets blob name.
     *
     * @param string $name value.
     * 
     * @return none.
     */
    public function setName($name)
    {
        $this->_name = $name;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Represents blob properties
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobProperties
{
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var string
     */
    private $_contentType;
    
    /**
     * @var integer
     */
    private $_contentLength;
    
    /**
     * @var string
     */
    private $_contentEncoding;
    
    /**
     * @var string
     */
    private $_contentLanguage;
    
    /**
     * @var string
     */
    private $_contentMD5;
    
    /**
     * @var string
     */
    private $_contentRange;
    
    /**
     * @var string
     */
    private $_cacheControl;
    
    /**
     * @var string
     */
    private $_blobType;
    
    /**
     * @var string
     */
    private $_leaseStatus;
    
    /**
     * @var integer
     */
    private $_sequenceNumber;
    
    /**
     * Creates BlobProperties object from $parsed response in array representation
     * 
     * @param array $parsed parsed response in array format.
     * 
     * @return BlobProperties
     */
    public static function create($parsed)
    {
        $result = new BlobProperties();
        $clean  = array_change_key_case($parsed);
        
        $date = Utilities::tryGetValue($clean, Resources::LAST_MODIFIED);
        $result->setBlobType(Utilities::tryGetValue($clean, 'blobtype'));
        $result->setContentLength(intval($clean[Resources::CONTENT_LENGTH]));
        $result->setETag(Utilities::tryGetValue($clean, Resources::ETAG));
        
        if (!is_null($date)) {
            $date = Utilities::rfc1123ToDateTime($date);
            $result->setLastModified($date);
        }
        
        $result->setLeaseStatus(Utilities::tryGetValue($clean, 'leasestatus'));
        $result->setLeaseStatus(
            Utilities::tryGetValue(
                $clean, Resources::X_MS_LEASE_STATUS, $result->getLeaseStatus()
            )
        );
        $result->setSequenceNumber(
            intval(
                Utilities::tryGetValue($clean, Resources::X_MS_BLOB_SEQUENCE_NUMBER)
            )
        );
        $result->setContentRange(
            Utilities::tryGetValue($clean, Resources::CONTENT_RANGE)
        );
        $result->setCacheControl(
            Utilities::tryGetValue($clean, Resources::CACHE_CONTROL)
        );
        $result->setBlobType(
            Utilities::tryGetValue(
                $clean, Resources::X_MS_BLOB_TYPE, $result->getBlobType()
            )
        );
        $result->setContentEncoding(
            Utilities::tryGetValue($clean, Resources::CONTENT_ENCODING)
        );
        $result->setContentLanguage(
            Utilities::tryGetValue($clean, Resources::CONTENT_LANGUAGE)
        );
        $result->setContentMD5(
            Utilities::tryGetValue($clean, Resources::CONTENT_MD5)
        );
        $result->setContentType(
            Utilities::tryGetValue($clean, Resources::CONTENT_TYPE)
        );
        
        return $result;
    }

    /**
     * Gets blob lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        Validate::isDate($lastModified);
        $this->_lastModified = $lastModified;
    }

    /**
     * Gets blob etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets blob etag.
     *
     * @param string $etag value.
     *
     * @return none.
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
    
    /**
     * Gets blob contentType.
     *
     * @return string.
     */
    public function getContentType()
    {
        return $this->_contentType;
    }

    /**
     * Sets blob contentType.
     *
     * @param string $contentType value.
     *
     * @return none.
     */
    public function setContentType($contentType)
    {
        $this->_contentType = $contentType;
    }
    
    /**
     * Gets blob contentRange.
     *
     * @return string.
     */
    public function getContentRange()
    {
        return $this->_contentRange;
    }

    /**
     * Sets blob contentRange.
     *
     * @param string $contentRange value.
     *
     * @return none.
     */
    public function setContentRange($contentRange)
    {
        $this->_contentRange = $contentRange;
    }
    
    /**
     * Gets blob contentLength.
     *
     * @return integer.
     */
    public function getContentLength()
    {
        return $this->_contentLength;
    }

    /**
     * Sets blob contentLength.
     *
     * @param integer $contentLength value.
     *
     * @return none.
     */
    public function setContentLength($contentLength)
    {
        Validate::isInteger($contentLength, 'contentLength');
        $this->_contentLength = $contentLength;
    }
    
    /**
     * Gets blob contentEncoding.
     *
     * @return string.
     */
    public function getContentEncoding()
    {
        return $this->_contentEncoding;
    }

    /**
     * Sets blob contentEncoding.
     *
     * @param string $contentEncoding value.
     *
     * @return none.
     */
    public function setContentEncoding($contentEncoding)
    {
        $this->_contentEncoding = $contentEncoding;
    }
    
    /**
     * Gets blob contentLanguage.
     *
     * @return string.
     */
    public function getContentLanguage()
    {
        return $this->_contentLanguage;
    }

    /**
     * Sets blob contentLanguage.
     *
     * @param string $contentLanguage value.
     *
     * @return none.
     */
    public function setContentLanguage($contentLanguage)
    {
        $this->_contentLanguage = $contentLanguage;
    }
    
    /**
     * Gets blob contentMD5.
     *
     * @return string.
     */
    public function getContentMD5()
    {
        return $this->_contentMD5;
    }

    /**
     * Sets blob contentMD5.
     *
     * @param string $contentMD5 value.
     *
     * @return none.
     */
    public function setContentMD5($contentMD5)
    {
        $this->_contentMD5 = $contentMD5;
    }
    
    /**
     * Gets blob cacheControl.
     *
     * @return string.
     */
    public function getCacheControl()
    {
        return $this->_cacheControl;
    }

    /**
     * Sets blob cacheControl.
     *
     * @param string $cacheControl value.
     *
     * @return none.
     */
    public function setCacheControl($cacheControl)
    {
        $this->_cacheControl = $cacheControl;
    }
    
    /**
     * Gets blob blobType.
     *
     * @return string.
     */
    public function getBlobType()
    {
        return $this->_blobType;
    }

    /**
     * Sets blob blobType.
     *
     * @param string $blobType value.
     *
     * @return none.
     */
    public function setBlobType($blobType)
    {
        $this->_blobType = $blobType;
    }
    
    /**
     * Gets blob leaseStatus.
     *
     * @return string.
     */
    public function getLeaseStatus()
    {
        return $this->_leaseStatus;
    }

    /**
     * Sets blob leaseStatus.
     *
     * @param string $leaseStatus value.
     *
     * @return none.
     */
    public function setLeaseStatus($leaseStatus)
    {
        $this->_leaseStatus = $leaseStatus;
    }
    
    /**
     * Gets blob sequenceNumber.
     *
     * @return int.
     */
    public function getSequenceNumber()
    {
        return $this->_sequenceNumber;
    }

    /**
     * Sets blob sequenceNumber.
     *
     * @param int $sequenceNumber value.
     *
     * @return none.
     */
    public function setSequenceNumber($sequenceNumber)
    {
        Validate::isInteger($sequenceNumber, 'sequenceNumber');
        $this->_sequenceNumber = $sequenceNumber;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Blob service options.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobServiceOptions
{
    private $_timeout;

    /**
     * Gets timeout.
     *
     * @return string.
     */
    public function getTimeout()
    {
        return $this->_timeout;
    }

    /**
     * Sets timeout.
     *
     * @param string $timeout value.
     * 
     * @return none.
     */
    public function setTimeout($timeout)
    {
        $this->_timeout = $timeout;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Encapsulates blob types
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobType
{
    const BLOCK_BLOB = 'BlockBlob';
    const PAGE_BLOB  = 'PageBlob';
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Holds information about blob block.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Block
{
    /**
     * @var string
     */
    private $_blockId;
    
    /**
     * @var string
     */
    private $_type;
    
    /**
     * Sets the blockId.
     * 
     * @param string $blockId The id of the block.
     * 
     * @return none
     */
    public function setBlockId($blockId)
    {
        $this->_blockId = $blockId;
    }
    
    /**
     * Gets the blockId.
     * 
     * @return string
     */
    public function getBlockId()
    {
        return $this->_blockId;
    }
    
    /**
     * Sets the type.
     * 
     * @param string $type The type of the block.
     * 
     * @return none
     */
    public function setType($type)
    {
        $this->_type = $type;
    }
    
    /**
     * Gets the type.
     * 
     * @return string
     */
    public function getType()
    {
        return $this->_type;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;
use MicrosoftAzure\Storage\Blob\Models\Block;

/**
 * Holds block list used for commitBlobBlocks
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlockList
{
    /**
     * @var array
     */
    private $_entries;
    public static $xmlRootName = 'BlockList';
    
    /**
     * Creates block list from array of blocks.
     * 
     * @param array $array The blocks array.
     * 
     * @return BlockList
     */
    public static function create($array)
    {
        $blockList = new BlockList();
        
        foreach ($array as $value) {
            $blockList->addEntry($value->getBlockId(), $value->getType());
        }
        
        return $blockList;
    }
    
    /**
     * Adds new entry to the block list entries.
     * 
     * @param string $blockId The block id.
     * @param string $type    The entry type, you can use BlobBlockType.
     * 
     * @return none
     */
    public function addEntry($blockId, $type)
    {
        Validate::isString($blockId, 'blockId');
        Validate::isTrue(
            BlobBlockType::isValid($type),
            sprintf(Resources::INVALID_BTE_MSG, get_class(new BlobBlockType()))
        );
        $block = new Block();
        $block->setBlockId($blockId);
        $block->setType($type);
        
        $this->_entries[] = $block;
    }
    
    /**
     * Addds committed block entry.
     * 
     * @param string $blockId The block id.
     * 
     * @return none
     */
    public function addCommittedEntry($blockId)
    {
        $this->addEntry($blockId, BlobBlockType::COMMITTED_TYPE);
    }
    
    /**
     * Addds uncommitted block entry.
     * 
     * @param string $blockId The block id.
     * 
     * @return none
     */
    public function addUncommittedEntry($blockId)
    {
        $this->addEntry($blockId, BlobBlockType::UNCOMMITTED_TYPE);
    }
    
    /**
     * Addds latest block entry.
     * 
     * @param string $blockId The block id.
     * 
     * @return none
     */
    public function addLatestEntry($blockId)
    {
        $this->addEntry($blockId, BlobBlockType::LATEST_TYPE);
    }
    
    /**
     * Gets blob block entry.
     * 
     * @param string $blockId The id of the block.
     * 
     * @return Block
     */
    public function getEntry($blockId)
    {
        foreach ($this->_entries as $value) {
            if ($blockId == $value->getBlockId()) {
                return $value;
            }
        }
        
        return null;
    }
    
    /**
     * Gets all blob block entries.
     * 
     * @return string
     */
    public function getEntries()
    {
        return $this->_entries;
    }
    
    /**
     * Converts the  BlockList object to XML representation
     * 
     * @param XmlSerializer $xmlSerializer The XML serializer.
     * 
     * @return string
     */
    public function toXml($xmlSerializer)
    {
        $properties = array(XmlSerializer::ROOT_NAME => self::$xmlRootName);
        $array      = array();
        
        foreach ($this->_entries as $value) {
            $array[] = array(
                $value->getType() => $value->getBlockId()
            );
        }
        
        return $xmlSerializer->serialize($array, $properties);
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * The result of calling breakLease API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BreakLeaseResult
{
    /**
     * @var string
     */
    private $_leaseTime;
    
    /**
     * Creates BreakLeaseResult from response headers
     * 
     * @param array $headers response headers
     * 
     * @return BreakLeaseResult
     */
    public static function create($headers)
    {
        $result = new BreakLeaseResult();
        
        $result->setLeaseTime(
            Utilities::tryGetValue($headers, Resources::X_MS_LEASE_TIME)
        );
        
        return $result;
    }
    
    /**
     * Gets lease time.
     * 
     * @return string
     */
    public function getLeaseTime()
    {
        return $this->_leaseTime;
    }
    
    /**
     * Sets lease time.
     * 
     * @param string $leaseTime the blob lease time.
     * 
     * @return none
     */
    public function setLeaseTime($leaseTime)
    {
        $this->_leaseTime = $leaseTime;
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Optional parameters for commitBlobBlocks
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CommitBlobBlocksOptions extends BlobServiceOptions
{
     /**
     * @var string
     */
    private $_blobContentType;
    
    /**
     * @var string
     */
    private $_blobContentEncoding;
    
    /**
     * @var string
     */
    private $_blobContentLanguage;
    
    /**
     * @var string
     */
    private $_blobContentMD5;
    
    /**
     * @var string
     */
    private $_blobCacheControl;
    
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets blob ContentType.
     *
     * @return string.
     */
    public function getBlobContentType()
    {
        return $this->_blobContentType;
    }

    /**
     * Sets blob ContentType.
     *
     * @param string $blobContentType value.
     *
     * @return none.
     */
    public function setBlobContentType($blobContentType)
    {
        $this->_blobContentType = $blobContentType;
    }
    
    /**
     * Gets blob ContentEncoding.
     *
     * @return string.
     */
    public function getBlobContentEncoding()
    {
        return $this->_blobContentEncoding;
    }

    /**
     * Sets blob ContentEncoding.
     *
     * @param string $blobContentEncoding value.
     *
     * @return none.
     */
    public function setBlobContentEncoding($blobContentEncoding)
    {
        $this->_blobContentEncoding = $blobContentEncoding;
    }
    
    /**
     * Gets blob ContentLanguage.
     *
     * @return string.
     */
    public function getBlobContentLanguage()
    {
        return $this->_blobContentLanguage;
    }

    /**
     * Sets blob ContentLanguage.
     *
     * @param string $blobContentLanguage value.
     *
     * @return none.
     */
    public function setBlobContentLanguage($blobContentLanguage)
    {
        $this->_blobContentLanguage = $blobContentLanguage;
    }
    
    /**
     * Gets blob ContentMD5.
     *
     * @return string.
     */
    public function getBlobContentMD5()
    {
        return $this->_blobContentMD5;
    }

    /**
     * Sets blob ContentMD5.
     *
     * @param string $blobContentMD5 value.
     *
     * @return none.
     */
    public function setBlobContentMD5($blobContentMD5)
    {
        $this->_blobContentMD5 = $blobContentMD5;
    }
    
    /**
     * Gets blob cache control.
     *
     * @return string.
     */
    public function getBlobCacheControl()
    {
        return $this->_blobCacheControl;
    }
    
    /**
     * Sets blob cacheControl.
     *
     * @param string $blobCacheControl value to use.
     * 
     * @return none.
     */
    public function setBlobCacheControl($blobCacheControl)
    {
        $this->_blobCacheControl = $blobCacheControl;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets blob metadata.
     *
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets blob metadata.
     *
     * @param string $metadata value.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * WindowsAzure container object.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Container
{
    /**
     * @var string
     */
    private $_name;
    
    /**
     * @var string
     */
    private $_url;
    
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * @var ContainerProperties
     */
    private $_properties;

    /**
     * Gets container name.
     *
     * @return string.
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Sets container name.
     *
     * @param string $name value.
     * 
     * @return none.
     */
    public function setName($name)
    {
        $this->_name = $name;
    }

    /**
     * Gets container url.
     *
     * @return string.
     */
    public function getUrl()
    {
        return $this->_url;
    }

    /**
     * Sets container url.
     *
     * @param string $url value.
     * 
     * @return none.
     */
    public function setUrl($url)
    {
        $this->_url = $url;
    }

    /**
     * Gets container metadata.
     *
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets container metadata.
     *
     * @param array $metadata value.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Gets container properties
     * 
     * @return ContainerProperties
     */
    public function getProperties()
    {
        return $this->_properties;
    }
    
    /**
     * Sets container properties
     * 
     * @param ContainerProperties $properties container properties
     * 
     * @return none.
     */
    public function setProperties($properties)
    {
        $this->_properties = $properties;
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Blob\Models\AccessPolicy;
use MicrosoftAzure\Storage\Blob\Models\SignedIdentifier;
use MicrosoftAzure\Storage\Blob\Models\PublicAccessType;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * Holds conatiner ACL members.
 * 
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ContainerAcl
{
    /**
     * All available types can be found in PublicAccessType
     *
     * @var string
     */
    private $_publicAccess;

    /**
     * @var array
     */
    private $_signedIdentifiers = array();
    
    /*
     * The root name of XML elemenet representation.
     * 
     * @var string
     */
    public static $xmlRootName = 'SignedIdentifiers';


    /**
     * Parses the given array into signed identifiers.
     * 
     * @param string $publicAccess The container public access.
     * @param array  $parsed       The parsed response into array representation.
     * 
     * @return none
     */
    public static function create($publicAccess, $parsed)
    {
        $result                     = new ContainerAcl();
        $result->_publicAccess      = $publicAccess;
        $result->_signedIdentifiers = array();
        
        if (!empty($parsed) && is_array($parsed['SignedIdentifier'])) {
            $entries = $parsed['SignedIdentifier'];
            $temp    = Utilities::getArray($entries);

            foreach ($temp as $value) {
                $startString  = urldecode($value['AccessPolicy']['Start']);
                $expiryString = urldecode($value['AccessPolicy']['Expiry']);
                $start        = Utilities::convertToDateTime($startString);
                $expiry       = Utilities::convertToDateTime($expiryString);
                $permission   = $value['AccessPolicy']['Permission'];
                $id           = $value['Id'];
                $result->addSignedIdentifier($id, $start, $expiry, $permission);
            }
        }
        
        return $result;
    }

    /**
     * Gets container signed modifiers.
     *
     * @return array.
     */
    public function getSignedIdentifiers()
    {
        return $this->_signedIdentifiers;
    }

    /**
     * Sets container signed modifiers.
     *
     * @param array $signedIdentifiers value.
     *
     * @return none.
     */
    public function setSignedIdentifiers($signedIdentifiers)
    {
        $this->_signedIdentifiers = $signedIdentifiers;
    }

    /**
     * Gets container publicAccess.
     *
     * @return string.
     */
    public function getPublicAccess()
    {
        return $this->_publicAccess;
    }

    /**
     * Sets container publicAccess.
     *
     * @param string $publicAccess value.
     *
     * @return none.
     */
    public function setPublicAccess($publicAccess)
    {
        Validate::isTrue(
            PublicAccessType::isValid($publicAccess),
            Resources::INVALID_BLOB_PAT_MSG
        );
        $this->_publicAccess = $publicAccess;
    }

    /**
     * Adds new signed modifier
     * 
     * @param string    $id         a unique id for this modifier
     * @param \DateTime $start      The time at which the Shared Access Signature
     * becomes valid. If omitted, start time for this call is assumed to be
     * the time when the Blob service receives the request.
     * @param \DateTime $expiry     The time at which the Shared Access Signature
     * becomes invalid. This field may be omitted if it has been specified as
     * part of a container-level access policy.
     * @param string    $permission The permissions associated with the Shared
     * Access Signature. The user is restricted to operations allowed by the
     * permissions. Valid permissions values are read (r), write (w), delete (d) and
     * list (l).
     * 
     * @return none.
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh508996.aspx
     */
    public function addSignedIdentifier($id, $start, $expiry, $permission)
    {
        Validate::isString($id, 'id');
        Validate::isDate($start);
        Validate::isDate($expiry);
        Validate::isString($permission, 'permission');
        
        $accessPolicy = new AccessPolicy();
        $accessPolicy->setStart($start);
        $accessPolicy->setExpiry($expiry);
        $accessPolicy->setPermission($permission);
        
        $signedIdentifier = new SignedIdentifier();
        $signedIdentifier->setId($id);
        $signedIdentifier->setAccessPolicy($accessPolicy);
        
        $this->_signedIdentifiers[] = $signedIdentifier;
    }
    
    /**
     * Converts this object to array representation for XML serialization 
     * 
     * @return array.
     */
    public function toArray()
    {
        $array = array();
        
        foreach ($this->_signedIdentifiers as $value) {
            $array[] = $value->toArray();
        }
        
        return $array;
    }
    
    /**
     * Converts this current object to XML representation.
     * 
     * @param XmlSerializer $xmlSerializer The XML serializer.
     * 
     * @return string.
     */
    public function toXml($xmlSerializer)
    {
        $properties = array(
            XmlSerializer::DEFAULT_TAG => 'SignedIdentifier',
            XmlSerializer::ROOT_NAME   => self::$xmlRootName
        );
        
        return $xmlSerializer->serialize($this->toArray(), $properties);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Holds container properties fields
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ContainerProperties
{
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * Gets container lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets container lastModified.
     *
     * @param \DateTime $lastModified value.
     * 
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        $this->_lastModified = $lastModified;
    }
    
    /**
     * Gets container etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets container etag.
     *
     * @param string $etag value.
     * 
     * @return none.
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * optional parameters for CopyBlobOptions wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CopyBlobOptions extends BlobServiceOptions
{

    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * @var AccessCondition
     */
    private $_sourceAccessCondition;
    
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * @var string 
     */
    private $_sourceSnapshot;
    
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var sourceLeaseId
     */
    private $_sourceLeaseId;
  
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets source access condition
     * 
     * @return SourceAccessCondition
     */
    public function getSourceAccessCondition()
    {
        return $this->_sourceAccessCondition;
    }
    
    /**
     * Sets source access condition
     * 
     * @param SourceAccessCondition $sourceAccessCondition value to use.
     * 
     * @return none.
     */
    public function setSourceAccessCondition($sourceAccessCondition)
    {
        $this->_sourceAccessCondition = $sourceAccessCondition;
    }
    
    /**
     * Gets metadata.
     *
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets metadata.
     *
     * @param array $metadata value.
     *
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Gets source snapshot. 
     * 
     * @return string
     */
    public function getSourceSnapshot()
    {
        return $this->_sourceSnapshot;
    }
       
    /**
     * Sets source snapshot. 
     * 
     * @param string $sourceSnapshot value.
     * 
     * @return none
     */
    public function setSourceSnapshot($sourceSnapshot)
    {
        $this->_sourceSnapshot = $sourceSnapshot;
    }
   
    /**
     * Gets lease ID.
     *
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }

    /**
     * Sets lease ID.
     *
     * @param string $leaseId value.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets source lease ID.
     *
     * @return string
     */
    public function getSourceLeaseId()
    {
        return $this->_sourceLeaseId;
    }

    /**
     * Sets source lease ID.
     *
     * @param string $sourceLeaseId value.
     * 
     * @return none
     */
    public function setSourceLeaseId($sourceLeaseId)
    {
        $this->_sourceLeaseId = $sourceLeaseId;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * The result of calling copyBlob API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CopyBlobResult
{
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * Creates CopyBlobResult object from the response of the copy blob request.
     * 
     * @param array $headers The HTTP response headers in array representation.
     * 
     * @return CopyBlobResult
     */
    public static function create($headers)
    {
        $result = new CopyBlobResult();
        $result->setETag(Utilities::tryGetValueInsensitive(
                Resources::ETAG,
                $headers));
        if (Utilities::arrayKeyExistsInsensitive(Resources::LAST_MODIFIED, $headers)) {
            $lastModified = Utilities::tryGetValueInsensitive(
                Resources::LAST_MODIFIED,
                $headers);
            $result->setLastModified(Utilities::rfc1123ToDateTime($lastModified));
        }
        
        return $result;
    }
    
    /**
     * Gets ETag.
     * 
     * @return string
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets ETag.
     *
     * @param string $etag value.
     *
     * @return none
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
    
    /**
     * Gets blob lastModified.
     *
     * @return \DateTime
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none
     */
    public function setLastModified($lastModified)
    {
        $this->_lastModified = $lastModified;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Optional parameters for createBlobBlock wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobBlockOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_contentMD5;
    
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets blob contentMD5.
     *
     * @return string.
     */
    public function getContentMD5()
    {
        return $this->_contentMD5;
    }

    /**
     * Sets blob contentMD5.
     *
     * @param string $contentMD5 value.
     *
     * @return none.
     */
    public function setContentMD5($contentMD5)
    {
        $this->_contentMD5 = $contentMD5;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * optional parameters for createXXXBlob wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_contentType;
    
    /**
     * @var string
     */
    private $_contentEncoding;
    
    /**
     * @var string
     */
    private $_contentLanguage;
    
    /**
     * @var string
     */
    private $_contentMD5;
    
    /**
     * @var string
     */
    private $_cacheControl;
    
    /**
     * @var string
     */
    private $_blobContentType;
    
    /**
     * @var string
     */
    private $_blobContentEncoding;
    
    /**
     * @var string
     */
    private $_blobContentLanguage;
    
    /**
     * @var integer
     */
    private $_blobContentLength;

    /**
     * @var string
     */
    private $_blobContentMD5;
    
    /**
     * @var string
     */
    private $_blobCacheControl;
    
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var integer
     */
    private $_sequenceNumber;
    
    /**
     * @var string
     */
    private $_sequenceNumberAction;

    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets blob ContentType.
     *
     * @return string.
     */
    public function getBlobContentType()
    {
        return $this->_blobContentType;
    }

    /**
     * Sets blob ContentType.
     *
     * @param string $blobContentType value.
     *
     * @return none.
     */
    public function setBlobContentType($blobContentType)
    {
        $this->_blobContentType = $blobContentType;
    }
    
    /**
     * Gets blob ContentEncoding.
     *
     * @return string.
     */
    public function getBlobContentEncoding()
    {
        return $this->_blobContentEncoding;
    }

    /**
     * Sets blob ContentEncoding.
     *
     * @param string $blobContentEncoding value.
     *
     * @return none.
     */
    public function setBlobContentEncoding($blobContentEncoding)
    {
        $this->_blobContentEncoding = $blobContentEncoding;
    }
    
    /**
     * Gets blob ContentLanguage.
     *
     * @return string.
     */
    public function getBlobContentLanguage()
    {
        return $this->_blobContentLanguage;
    }

    /**
     * Sets blob ContentLanguage.
     *
     * @param string $blobContentLanguage value.
     *
     * @return none.
     */
    public function setBlobContentLanguage($blobContentLanguage)
    {
        $this->_blobContentLanguage = $blobContentLanguage;
    }
    
    /**
     * Gets blob ContentLength.
     *
     * @return integer.
     */
    public function getBlobContentLength()
    {
        return $this->_blobContentLength;
    }

    /**
     * Sets blob ContentLength.
     *
     * @param integer $blobContentLength value.
     *
     * @return none.
     */
    public function setBlobContentLength($blobContentLength)
    {
        Validate::isInteger($blobContentLength, 'blobContentLength');
        $this->_blobContentLength = $blobContentLength;
    }

    /**
     * Gets blob ContentMD5.
     *
     * @return string.
     */
    public function getBlobContentMD5()
    {
        return $this->_blobContentMD5;
    }

    /**
     * Sets blob ContentMD5.
     *
     * @param string $blobContentMD5 value.
     *
     * @return none.
     */
    public function setBlobContentMD5($blobContentMD5)
    {
        $this->_blobContentMD5 = $blobContentMD5;
    }
    
    /**
     * Gets blob cache control.
     *
     * @return string.
     */
    public function getBlobCacheControl()
    {
        return $this->_blobCacheControl;
    }
    
    /**
     * Sets blob cacheControl.
     *
     * @param string $blobCacheControl value to use.
     * 
     * @return none.
     */
    public function setBlobCacheControl($blobCacheControl)
    {
        $this->_blobCacheControl = $blobCacheControl;
    }
    
    /**
     * Gets blob contentType.
     *
     * @return string.
     */
    public function getContentType()
    {
        return $this->_contentType;
    }

    /**
     * Sets blob contentType.
     *
     * @param string $contentType value.
     *
     * @return none.
     */
    public function setContentType($contentType)
    {
        $this->_contentType = $contentType;
    }
    
    /**
     * Gets contentEncoding.
     *
     * @return string.
     */
    public function getContentEncoding()
    {
        return $this->_contentEncoding;
    }

    /**
     * Sets contentEncoding.
     *
     * @param string $contentEncoding value.
     *
     * @return none.
     */
    public function setContentEncoding($contentEncoding)
    {
        $this->_contentEncoding = $contentEncoding;
    }
    
    /**
     * Gets contentLanguage.
     *
     * @return string.
     */
    public function getContentLanguage()
    {
        return $this->_contentLanguage;
    }

    /**
     * Sets contentLanguage.
     *
     * @param string $contentLanguage value.
     *
     * @return none.
     */
    public function setContentLanguage($contentLanguage)
    {
        $this->_contentLanguage = $contentLanguage;
    }
    
    /**
     * Gets contentMD5.
     *
     * @return string.
     */
    public function getContentMD5()
    {
        return $this->_contentMD5;
    }

    /**
     * Sets contentMD5.
     *
     * @param string $contentMD5 value.
     *
     * @return none.
     */
    public function setContentMD5($contentMD5)
    {
        $this->_contentMD5 = $contentMD5;
    }
    
    /**
     * Gets cacheControl.
     *
     * @return string.
     */
    public function getCacheControl()
    {
        return $this->_cacheControl;
    }
    
    /**
     * Sets cacheControl.
     *
     * @param string $cacheControl value to use.
     * 
     * @return none.
     */
    public function setCacheControl($cacheControl)
    {
        $this->_cacheControl = $cacheControl;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets blob metadata.
     *
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets blob metadata.
     *
     * @param string $metadata value.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Gets blob sequenceNumber.
     *
     * @return int.
     */
    public function getSequenceNumber()
    {
        return $this->_sequenceNumber;
    }

    /**
     * Sets blob sequenceNumber.
     *
     * @param int $sequenceNumber value.
     *
     * @return none.
     */
    public function setSequenceNumber($sequenceNumber)
    {
        Validate::isInteger($sequenceNumber, 'sequenceNumber');
        $this->_sequenceNumber = $sequenceNumber;
    }
    
    /**
     * Gets blob sequenceNumberAction.
     *
     * @return string.
     */
    public function getSequenceNumberAction()
    {
        return $this->_sequenceNumberAction;
    }

    /**
     * Sets blob sequenceNumberAction.
     *
     * @param string $sequenceNumberAction value.
     *
     * @return none.
     */
    public function setSequenceNumberAction($sequenceNumberAction)
    {
        $this->_sequenceNumberAction = $sequenceNumberAction;
    }

    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Optional parameters for create and clear blob pages
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobPagesOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_contentMD5;
    
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets blob contentMD5.
     *
     * @return string.
     */
    public function getContentMD5()
    {
        return $this->_contentMD5;
    }

    /**
     * Sets blob contentMD5.
     *
     * @param string $contentMD5 value.
     *
     * @return none.
     */
    public function setContentMD5($contentMD5)
    {
        $this->_contentMD5 = $contentMD5;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds result of calling create or clear blob pages
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobPagesResult
{
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var integer
     */
    private $_sequenceNumber;
    
    /**
     * @var string
     */
    private $_contentMD5;
    
    /**
     * Creates CreateBlobPagesResult object from $parsed response in array 
     * representation
     * 
     * @param array $headers HTTP response headers
     * 
     * @return CreateBlobPagesResult
     */
    public static function create($headers)
    {
        $result = new CreateBlobPagesResult();
        $clean  = array_change_key_case($headers);
        
        $date = $clean[Resources::LAST_MODIFIED];
        $date = Utilities::rfc1123ToDateTime($date);
        $result->setETag($clean[Resources::ETAG]);
        $result->setLastModified($date);
        $result->setContentMD5(
            Utilities::tryGetValue($clean, Resources::CONTENT_MD5)
        );
        $result->setSequenceNumber(
            intval(
                Utilities::tryGetValue($clean, Resources::X_MS_BLOB_SEQUENCE_NUMBER)
            )
        );
        
        return $result;
    }
    
    /**
     * Gets blob lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        Validate::isDate($lastModified);
        $this->_lastModified = $lastModified;
    }

    /**
     * Gets blob etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets blob etag.
     *
     * @param string $etag value.
     *
     * @return none.
     */
    public function setETag($etag)
    {
        Validate::isString($etag, 'etag');
        $this->_etag = $etag;
    }
    
    /**
     * Gets blob contentMD5.
     *
     * @return string.
     */
    public function getContentMD5()
    {
        return $this->_contentMD5;
    }

    /**
     * Sets blob contentMD5.
     *
     * @param string $contentMD5 value.
     *
     * @return none.
     */
    public function setContentMD5($contentMD5)
    {
        $this->_contentMD5 = $contentMD5;
    }
    
    /**
     * Gets blob sequenceNumber.
     *
     * @return int.
     */
    public function getSequenceNumber()
    {
        return $this->_sequenceNumber;
    }

    /**
     * Sets blob sequenceNumber.
     *
     * @param int $sequenceNumber value.
     *
     * @return none.
     */
    public function setSequenceNumber($sequenceNumber)
    {
        Validate::isInteger($sequenceNumber, 'sequenceNumber');
        $this->_sequenceNumber = $sequenceNumber;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * The optional parameters for createBlobSnapshot wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobSnapshotOptions extends BlobServiceOptions
{
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * Gets metadata.
     *
     * @return array
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets metadata.
     *
     * @param array $metadata The metadata array.
     *
     * @return none
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Gets access condition.
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition.
     * 
     * @param AccessCondition $accessCondition The access condition object.
     * 
     * @return none
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets lease Id.
     *
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }

    /**
     * Sets lease Id.
     *
     * @param string $leaseId The lease Id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }

}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * The result of creating Blob snapshot. 
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobSnapshotResult
{
    /**
     * A DateTime value which uniquely identifies the snapshot. 
     * @var string
     */
    private $_snapshot;
            
    /**
     * The ETag for the destination blob. 
     * @var string
     */
    private $_etag;
    
    /**
     * The date/time that the copy operation to the destination blob completed. 
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * Creates CreateBlobSnapshotResult object from the response of the 
     * create Blob snapshot request.
     * 
     * @param array $headers The HTTP response headers in array representation.
     * 
     * @return CreateBlobSnapshotResult
     */
    public static function create($headers)
    {
        $result                 = new CreateBlobSnapshotResult();
        $headerWithLowerCaseKey = array_change_key_case($headers);
        
        $result->setETag($headerWithLowerCaseKey[Resources::ETAG]);
        
        $result->setLastModified(
            Utilities::rfc1123ToDateTime(
                $headerWithLowerCaseKey[Resources::LAST_MODIFIED]
            )
        );
        
        $result->setSnapshot($headerWithLowerCaseKey[Resources::X_MS_SNAPSHOT]);
        
        return $result;
    }
    
    /**
     * Gets snapshot. 
     *
     * @return string
     */
    public function getSnapshot()
    {
        return $this->_snapshot;
    }
    
    /**
     * Sets snapshot.
     * 
     * @param string $snapshot value.
     *
     * @return none
     */
    public function setSnapshot($snapshot)
    {
        $this->_snapshot = $snapshot;
    }
    
    /**
     * Gets ETag.
     * 
     * @return string
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets ETag.
     *
     * @param string $etag value.
     *
     * @return none
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
    
    /**
     * Gets blob lastModified.
     *
     * @return \DateTime
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none
     */
    public function setLastModified($lastModified)
    {
        $this->_lastModified = $lastModified;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Optional parameters for createContainer API
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateContainerOptions extends BlobServiceOptions
{
    /**
     * @var string 
     */
    private $_publicAccess;
    
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * Gets container public access.
     * 
     * @return string.
     */
    public function getPublicAccess()
    {
        return $this->_publicAccess;
    }
    
    /**
     * Specifies whether data in the container may be accessed publicly and the level
     * of access. Possible values include: 
     * 1) container: Specifies full public read access for container and blob data.
     *    Clients can enumerate blobs within the container via anonymous request, but
     *    cannot enumerate containers within the storage account.
     * 2) blob: Specifies public read access for blobs. Blob data within this 
     *    container can be read via anonymous request, but container data is not 
     *    available. Clients cannot enumerate blobs within the container via 
     *    anonymous request.
     * If this value is not specified in the request, container data is private to 
     * the account owner.
     * 
     * @param string $publicAccess access modifier for the container
     * 
     * @return none.
     */
    public function setPublicAccess($publicAccess)
    {
        Validate::isString($publicAccess, 'publicAccess');
        $this->_publicAccess = $publicAccess;
    }
    
    /**
     * Gets user defined metadata.
     * 
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }
    
    /**
     * Sets user defined metadata. This metadata should be added without the header
     * prefix (x-ms-meta-*).
     * 
     * @param array $metadata user defined metadata object in array form.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Adds new metadata element. This element should be added without the header
     * prefix (x-ms-meta-*).
     * 
     * @param string $key   metadata key element.
     * @param string $value metadata value element.
     * 
     * @return none.
     */
    public function addMetadata($key, $value)
    {
        $this->_metadata[$key] = $value;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Optional parameters for deleteBlob wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DeleteBlobOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var string
     */
    private $_snapshot;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * @var boolean
     */
    private $_deleteSnaphotsOnly;
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets blob snapshot.
     *
     * @return string.
     */
    public function getSnapshot()
    {
        return $this->_snapshot;
    }

    /**
     * Sets blob snapshot.
     *
     * @param string $snapshot value.
     * 
     * @return none.
     */
    public function setSnapshot($snapshot)
    {
        $this->_snapshot = $snapshot;
    }
    
    /**
     * Gets blob deleteSnaphotsOnly.
     *
     * @return boolean.
     */
    public function getDeleteSnaphotsOnly()
    {
        return $this->_deleteSnaphotsOnly;
    }

    /**
     * Sets blob deleteSnaphotsOnly.
     *
     * @param string $deleteSnaphotsOnly value.
     * 
     * @return boolean.
     */
    public function setDeleteSnaphotsOnly($deleteSnaphotsOnly)
    {
        Validate::isBoolean($deleteSnaphotsOnly);
        $this->_deleteSnaphotsOnly = $deleteSnaphotsOnly;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * The optional for deleteContainer API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DeleteContainerOptions extends BlobServiceOptions
{
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Optional parameters for getBlobMetadata wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobMetadataOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var string
     */
    private $_snapshot;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets blob snapshot.
     *
     * @return string.
     */
    public function getSnapshot()
    {
        return $this->_snapshot;
    }

    /**
     * Sets blob snapshot.
     *
     * @param string $snapshot value.
     * 
     * @return none.
     */
    public function setSnapshot($snapshot)
    {
        $this->_snapshot = $snapshot;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds results of calling getBlobMetadata wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobMetadataResult
{
    
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * Creates GetBlobMetadataResult from response headers.
     * 
     * @param array $headers  The HTTP response headers.
     * @param array $metadata The blob metadata array.
     * 
     * @return GetBlobMetadataResult
     */
    public static function create($headers, $metadata)
    {
        $result = new GetBlobMetadataResult();
        $date   = $headers[Resources::LAST_MODIFIED];
        $result->setLastModified(Utilities::rfc1123ToDateTime($date));
        $result->setETag($headers[Resources::ETAG]);
        $result->setMetadata(is_null($metadata) ? array() : $metadata);
        
        return $result;
    }
    
    /**
     * Gets blob lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        Validate::isDate($lastModified);
        $this->_lastModified = $lastModified;
    }

    /**
     * Gets blob etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets blob etag.
     *
     * @param string $etag value.
     *
     * @return none.
     */
    public function setETag($etag)
    {
        Validate::isString($etag, 'etag');
        $this->_etag = $etag;
    }
    
    /**
     * Gets blob metadata.
     *
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets blob metadata.
     *
     * @param string $metadata value.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Optional parameters for getBlob wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var string
     */
    private $_snapshot;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * @var boolean
     */
    private $_computeRangeMD5;
    
    /**
     * @var integer
     */
    private $_rangeStart;
    
    /**
     * @var integer
     */
    private $_rangeEnd;
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets blob snapshot.
     *
     * @return string.
     */
    public function getSnapshot()
    {
        return $this->_snapshot;
    }

    /**
     * Sets blob snapshot.
     *
     * @param string $snapshot value.
     * 
     * @return none.
     */
    public function setSnapshot($snapshot)
    {
        $this->_snapshot = $snapshot;
    }
    
    /**
     * Gets rangeStart
     * 
     * @return integer
     */
    public function getRangeStart()
    {
        return $this->_rangeStart;
    }
    
    /**
     * Sets rangeStart
     * 
     * @param integer $rangeStart the blob lease id.
     * 
     * @return none
     */
    public function setRangeStart($rangeStart)
    {
        Validate::isInteger($rangeStart, 'rangeStart');
        $this->_rangeStart = $rangeStart;
    }
    
    /**
     * Gets rangeEnd
     * 
     * @return integer
     */
    public function getRangeEnd()
    {
        return $this->_rangeEnd;
    }
    
    /**
     * Sets rangeEnd
     * 
     * @param integer $rangeEnd range end value in bytes
     * 
     * @return none
     */
    public function setRangeEnd($rangeEnd)
    {
        Validate::isInteger($rangeEnd, 'rangeEnd');
        $this->_rangeEnd = $rangeEnd;
    }
    
    /**
     * Gets computeRangeMD5
     * 
     * @return boolean
     */
    public function getComputeRangeMD5()
    {
        return $this->_computeRangeMD5;
    }
    
    /**
     * Sets computeRangeMD5
     * 
     * @param boolean $computeRangeMD5 value
     * 
     * @return none
     */
    public function setComputeRangeMD5($computeRangeMD5)
    {
        Validate::isBoolean($computeRangeMD5);
        $this->_computeRangeMD5 = $computeRangeMD5;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Optional parameters for getBlobProperties wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobPropertiesOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var string
     */
    private $_snapshot;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets blob snapshot.
     *
     * @return string.
     */
    public function getSnapshot()
    {
        return $this->_snapshot;
    }

    /**
     * Sets blob snapshot.
     *
     * @param string $snapshot value.
     * 
     * @return none.
     */
    public function setSnapshot($snapshot)
    {
        $this->_snapshot = $snapshot;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Holds result of calling getBlobProperties
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobPropertiesResult
{
    /**
     * @var BlobProperties
     */
    private $_properties;
    
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * Gets blob metadata.
     *
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets blob metadata.
     *
     * @param string $metadata value.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Gets blob properties.
     *
     * @return BlobProperties.
     */
    public function getProperties()
    {
        return $this->_properties;
    }

    /**
     * Sets blob properties.
     *
     * @param BlobProperties $properties value.
     * 
     * @return none.
     */
    public function setProperties($properties)
    {
        $this->_properties = $properties;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds result of GetBlob API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobResult
{
    /**
     * @var BlobProperties
     */
    private $_properties;
    
    /**
     * @var array
     */
    private $_metadata;
    
    /**
     * @var resource
     */
    private $_contentStream;
    
    /**
     * Creates GetBlobResult from getBlob call.
     * 
     * @param array  $headers  The HTTP response headers.
     * @param string $body     The response body.
     * @param array  $metadata The blob metadata.
     * 
     * @return GetBlobResult
     */
    public static function create($headers, $body, $metadata)
    {
        $result = new GetBlobResult();
        $result->setContentStream(Utilities::stringToStream($body));
        $result->setProperties(BlobProperties::create($headers));
        $result->setMetadata(is_null($metadata) ? array() : $metadata);
        
        return $result;
    }
    
    /**
     * Gets blob metadata.
     *
     * @return array
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets blob metadata.
     *
     * @param string $metadata value.
     * 
     * @return none
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Gets blob properties.
     *
     * @return BlobProperties
     */
    public function getProperties()
    {
        return $this->_properties;
    }

    /**
     * Sets blob properties.
     *
     * @param BlobProperties $properties value.
     * 
     * @return none
     */
    public function setProperties($properties)
    {
        $this->_properties = $properties;
    }
    
    /**
     * Gets blob contentStream.
     *
     * @return resource
     */
    public function getContentStream()
    {
        return $this->_contentStream;
    }

    /**
     * Sets blob contentStream.
     *
     * @param resource $contentStream The stream handle.
     * 
     * @return none
     */
    public function setContentStream($contentStream)
    {
        $this->_contentStream = $contentStream;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ContainerAcl;

/**
 * Holds container ACL
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetContainerAclResult
{
    /**
     * @var ContainerAcl
     */
    private $_containerACL;
    
    /**
     * @var \DateTime
     */
    private $_lastModified;

    /**
     * @var string
     */
    private $_etag;
    
    /**
     * Parses the given array into signed identifiers
     * 
     * @param string    $publicAccess container public access
     * @param string    $etag         container etag
     * @param \DateTime $lastModified last modification date
     * @param array     $parsed       parsed response into array
     * representation
     * 
     * @return none.
     */
    public static function create($publicAccess, $etag, $lastModified, $parsed)
    {
        $result = new GetContainerAclResult();
        $result->setETag($etag);
        $result->setLastModified($lastModified);
        $acl = ContainerAcl::create($publicAccess, $parsed);
        $result->setContainerAcl($acl);
        
        return $result;
    }
    
    /**
     * Gets container ACL
     * 
     * @return ContainerAcl
     */
    public function getContainerAcl()
    {
        return $this->_containerACL;
    }
    
    /**
     * Sets container ACL
     * 
     * @param ContainerAcl $containerACL value.
     * 
     * @return none.
     */
    public function setContainerAcl($containerACL)
    {
        $this->_containerACL = $containerACL;
    }
    
    /**
     * Gets container lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets container lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        $this->_lastModified = $lastModified;
    }

    /**
     * Gets container etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets container etag.
     *
     * @param string $etag value.
     *
     * @return none.
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Holds result of getContainerProperties and getContainerMetadata
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetContainerPropertiesResult
{
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var array
     */
    private $_metadata; 
    
    /**
     * Any operation that modifies the container or its properties or metadata 
     * updates the last modified time. Operations on blobs do not affect the last 
     * modified time of the container.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets container lastModified.
     *
     * @param \DateTime $lastModified value.
     * 
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        $this->_lastModified = $lastModified;
    }
    
    /**
     * The entity tag for the container. If the request version is 2011-08-18 or 
     * newer, the ETag value will be in quotes.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets container etag.
     *
     * @param string $etag value.
     * 
     * @return none.
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
    
    /**
     * Gets user defined metadata.
     * 
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }
    
    /**
     * Sets user defined metadata. This metadata should be added without the header
     * prefix (x-ms-meta-*).
     * 
     * @param array $metadata user defined metadata object in array form.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Modes for leasing a blob
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class LeaseMode
{
    const ACQUIRE_ACTION = 'acquire';
    const RENEW_ACTION   = 'renew';
    const RELEASE_ACTION = 'release';
    const BREAK_ACTION   = 'break';
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Optional parameters for listBlobBlock wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListBlobBlocksOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var string
     */
    private $_snapshot;
    
    /**
     * @var boolean
     */
    private $_includeUncommittedBlobs;
    
    /**
     * @var boolean
     */
    private $_includeCommittedBlobs;
    
    /**
     * Holds result of list type. You can access it by this order:
     * $_listType[$this->_includeUncommittedBlobs][$this->_includeCommittedBlobs]
     * 
     * @var array
     */
    private static $_listType;
    
    /**
     * Constructs the static variable $listType.
     */
    public function __construct()
    {
        self::$_listType[true][true]   = 'all';
        self::$_listType[true][false]  = 'uncommitted';
        self::$_listType[false][true]  = 'committed';
        self::$_listType[false][false] = 'all';
        
        $this->_includeUncommittedBlobs = false;
        $this->_includeCommittedBlobs   = false;    
    }
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets blob snapshot.
     *
     * @return string.
     */
    public function getSnapshot()
    {
        return $this->_snapshot;
    }

    /**
     * Sets blob snapshot.
     *
     * @param string $snapshot value.
     * 
     * @return none.
     */
    public function setSnapshot($snapshot)
    {
        $this->_snapshot = $snapshot;
    }
    
    /**
     * Sets the include uncommittedBlobs flag.
     *
     * @param bool $includeUncommittedBlobs value.
     * 
     * @return none.
     */
    public function setIncludeUncommittedBlobs($includeUncommittedBlobs)
    {
        Validate::isBoolean($includeUncommittedBlobs);
        $this->_includeUncommittedBlobs = $includeUncommittedBlobs;
    }
    
    /**
     * Indicates if uncommittedBlobs is included or not.
     * 
     * @return boolean.
     */
    public function getIncludeUncommittedBlobs()
    {
        return $this->_includeUncommittedBlobs;
    }
    
    /**
     * Sets the include committedBlobs flag.
     *
     * @param bool $includeCommittedBlobs value.
     * 
     * @return none.
     */
    public function setIncludeCommittedBlobs($includeCommittedBlobs)
    {
        Validate::isBoolean($includeCommittedBlobs);
        $this->_includeCommittedBlobs = $includeCommittedBlobs;
    }
    
    /**
     * Indicates if committedBlobs is included or not.
     * 
     * @return boolean.
     */
    public function getIncludeCommittedBlobs()
    {
        return $this->_includeCommittedBlobs;
    }
    
    /**
     * Gets block list type.
     * 
     * @return string
     */
    public function getBlockListType()
    {
        $includeUncommitted = $this->_includeUncommittedBlobs;
        $includeCommitted   = $this->_includeCommittedBlobs;
        
        return self::$_listType[$includeUncommitted][$includeCommitted];
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds result of listBlobBlocks
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListBlobBlocksResult
{
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var string
     */
    private $_contentType;
    
    /**
     * @var integer
     */
    private $_contentLength;
    
    /**
     * @var array
     */
    private $_committedBlocks;
    
    /**
     * @var array
     */
    private $_uncommittedBlocks;
    
    /**
     * Gets block entries from parsed response
     * 
     * @param array  $parsed HTTP response
     * @param string $type   Block type
     * 
     * @return array
     */
    private static function _getEntries($parsed, $type)
    {
        $entries = array();
        
        if (is_array($parsed)) {
            $rawEntries = array();
         
            if (   array_key_exists($type, $parsed)
                &&     is_array($parsed[$type])
                &&     !empty($parsed[$type])
            ) {
                $rawEntries = Utilities::getArray($parsed[$type]['Block']);
            }
            
            foreach ($rawEntries as $value) {
                $entries[$value['Name']] = $value['Size'];
            }
        }
        
        return $entries;
    }
    
    /**
     * Creates ListBlobBlocksResult from given response headers and parsed body
     * 
     * @param array $headers HTTP response headers
     * @param array $parsed  HTTP response body in array representation
     * 
     * @return ListBlobBlocksResult
     */
    public static function create($headers, $parsed)
    {
        $result = new ListBlobBlocksResult();
        $clean  = array_change_key_case($headers);
        
        $result->setETag(Utilities::tryGetValue($clean, Resources::ETAG));
        $date = Utilities::tryGetValue($clean, Resources::LAST_MODIFIED);
        if (!is_null($date)) {
            $date = Utilities::rfc1123ToDateTime($date);
            $result->setLastModified($date);
        }
        $result->setContentLength(
            intval(
                Utilities::tryGetValue($clean, Resources::X_MS_BLOB_CONTENT_LENGTH)
            )
        );
        $result->setContentType(
            Utilities::tryGetValue($clean, Resources::CONTENT_TYPE)
        );
        
        $result->_uncommittedBlocks = self::_getEntries(
            $parsed, 'UncommittedBlocks'
        );
        $result->_committedBlocks   = self::_getEntries($parsed, 'CommittedBlocks');
        
        return $result;
    }
    
    /**
     * Gets blob lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        Validate::isDate($lastModified);
        $this->_lastModified = $lastModified;
    }

    /**
     * Gets blob etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets blob etag.
     *
     * @param string $etag value.
     *
     * @return none.
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
    
    /**
     * Gets blob contentType.
     *
     * @return string.
     */
    public function getContentType()
    {
        return $this->_contentType;
    }

    /**
     * Sets blob contentType.
     *
     * @param string $contentType value.
     *
     * @return none.
     */
    public function setContentType($contentType)
    {
        $this->_contentType = $contentType;
    }
    
    /**
     * Gets blob contentLength.
     *
     * @return integer.
     */
    public function getContentLength()
    {
        return $this->_contentLength;
    }

    /**
     * Sets blob contentLength.
     *
     * @param integer $contentLength value.
     *
     * @return none.
     */
    public function setContentLength($contentLength)
    {
        Validate::isInteger($contentLength, 'contentLength');
        $this->_contentLength = $contentLength;
    }
    
    /**
     * Gets uncommitted blocks
     * 
     * @return array
     */
    public function getUncommittedBlocks()
    {
        return $this->_uncommittedBlocks;
    }
    
    /**
     * Sets uncommitted blocks
     * 
     * @param array $uncommittedBlocks The uncommitted blocks entries
     * 
     * @return none.
     */
    public function setUncommittedBlocks($uncommittedBlocks)
    {
        $this->_uncommittedBlocks = $uncommittedBlocks;
    }
    
    /**
     * Gets committed blocks
     * 
     * @return array
     */
    public function getCommittedBlocks()
    {
        return $this->_committedBlocks;
    }
    
    /**
     * Sets committed blocks
     * 
     * @param array $committedBlocks The committed blocks entries
     * 
     * @return none.
     */
    public function setCommittedBlocks($committedBlocks)
    {
        $this->_committedBlocks = $committedBlocks;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Optional parameters for listBlobs API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListBlobsOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_prefix;
    
    /**
     * @var string
     */
    private $_marker;
    
    /**
     * @var string
     */
    private $_delimiter;
    
    /**
     * @var integer
     */
    private $_maxResults;
    
    /**
     * @var boolean
     */
    private $_includeMetadata;
    
    /**
     * @var boolean
     */
    private $_includeSnapshots;
    
    /**
     * @var boolean
     */
    private $_includeUncommittedBlobs;

    /**
     * Gets prefix.
     *
     * @return string.
     */
    public function getPrefix()
    {
        return $this->_prefix;
    }

    /**
     * Sets prefix.
     *
     * @param string $prefix value.
     * 
     * @return none.
     */
    public function setPrefix($prefix)
    {
        Validate::isString($prefix, 'prefix');
        $this->_prefix = $prefix;
    }
    
    /**
     * Gets delimiter.
     *
     * @return string.
     */
    public function getDelimiter()
    {
        return $this->_delimiter;
    }

    /**
     * Sets prefix.
     *
     * @param string $delimiter value.
     * 
     * @return none.
     */
    public function setDelimiter($delimiter)
    {
        Validate::isString($delimiter, 'delimiter');
        $this->_delimiter = $delimiter;
    }

    /**
     * Gets marker.
     * 
     * @return string.
     */
    public function getMarker()
    {
        return $this->_marker;
    }

    /**
     * Sets marker.
     *
     * @param string $marker value.
     * 
     * @return none.
     */
    public function setMarker($marker)
    {
        Validate::isString($marker, 'marker');
        $this->_marker = $marker;
    }

    /**
     * Gets max results.
     * 
     * @return integer.
     */
    public function getMaxResults()
    {
        return $this->_maxResults;
    }

    /**
     * Sets max results.
     *
     * @param integer $maxResults value.
     * 
     * @return none.
     */
    public function setMaxResults($maxResults)
    {
        Validate::isInteger($maxResults, 'maxResults');
        $this->_maxResults = $maxResults;
    }

    /**
     * Indicates if metadata is included or not.
     * 
     * @return boolean.
     */
    public function getIncludeMetadata()
    {
        return $this->_includeMetadata;
    }

    /**
     * Sets the include metadata flag.
     *
     * @param bool $includeMetadata value.
     * 
     * @return none.
     */
    public function setIncludeMetadata($includeMetadata)
    {
        Validate::isBoolean($includeMetadata);
        $this->_includeMetadata = $includeMetadata;
    }
    
    /**
     * Indicates if snapshots is included or not.
     * 
     * @return boolean.
     */
    public function getIncludeSnapshots()
    {
        return $this->_includeSnapshots;
    }

    /**
     * Sets the include snapshots flag.
     *
     * @param bool $includeSnapshots value.
     * 
     * @return none.
     */
    public function setIncludeSnapshots($includeSnapshots)
    {
        Validate::isBoolean($includeSnapshots);
        $this->_includeSnapshots = $includeSnapshots;
    }
    
    /**
     * Indicates if uncommittedBlobs is included or not.
     * 
     * @return boolean.
     */
    public function getIncludeUncommittedBlobs()
    {
        return $this->_includeUncommittedBlobs;
    }

    /**
     * Sets the include uncommittedBlobs flag.
     *
     * @param bool $includeUncommittedBlobs value.
     * 
     * @return none.
     */
    public function setIncludeUncommittedBlobs($includeUncommittedBlobs)
    {
        Validate::isBoolean($includeUncommittedBlobs);
        $this->_includeUncommittedBlobs = $includeUncommittedBlobs;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Blob\Models\Blob;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;

/**
 * Hold result of calliing listBlobs wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListBlobsResult
{
    /**
     * @var array
     */
    private $_blobPrefixes;
            
    /**
     * @var array
     */
    private $_blobs;
    
    /**
     * @var string
     */
    private $_delimiter;
    
    /**
     * @var string
     */
    private $_prefix;
    
    /**
     * @var string
     */
    private $_marker;
    
    /**
     * @var string
     */
    private $_nextMarker;
    
    /**
     * @var integer
     */
    private $_maxResults;
    
    /**
     * @var string 
     */
    private $_containerName;

    /**
     * Creates ListBlobsResult object from parsed XML response.
     *
     * @param array $parsed XML response parsed into array.
     * 
     * @return ListBlobsResult
     */
    public static function create($parsed)
    {
        $result                 = new ListBlobsResult();
        $serviceEndpoint        = Utilities::tryGetKeysChainValue(
            $parsed,
            Resources::XTAG_ATTRIBUTES,
            Resources::XTAG_SERVICE_ENDPOINT
        );
        $containerName          = Utilities::tryGetKeysChainValue(
            $parsed,
            Resources::XTAG_ATTRIBUTES,
            Resources::XTAG_CONTAINER_NAME
        );
        $result->_containerName = $containerName;
        $result->_prefix        = Utilities::tryGetValue(
            $parsed, Resources::QP_PREFIX
        );
        $result->_marker        = Utilities::tryGetValue(
            $parsed, Resources::QP_MARKER
        );
        $result->_nextMarker    = Utilities::tryGetValue(
            $parsed, Resources::QP_NEXT_MARKER
        );
        $result->_maxResults    = intval(
            Utilities::tryGetValue($parsed, Resources::QP_MAX_RESULTS, 0)
        );
        $result->_delimiter     = Utilities::tryGetValue(
            $parsed, Resources::QP_DELIMITER
        );
        $result->_blobs         = array();
        $result->_blobPrefixes  = array();
        $rawBlobs               = array();
        $rawBlobPrefixes        = array();
        
        if (   is_array($parsed['Blobs'])
            && array_key_exists('Blob', $parsed['Blobs'])
        ) {
            $rawBlobs = Utilities::getArray($parsed['Blobs']['Blob']);
        }
        
        foreach ($rawBlobs as $value) {
            $blob = new Blob();
            $blob->setName($value['Name']);
            $blob->setUrl($serviceEndpoint . $containerName . '/' . $value['Name']);
            $blob->setSnapshot(Utilities::tryGetValue($value, 'Snapshot'));
            $blob->setProperties(
                BlobProperties::create(
                    Utilities::tryGetValue($value, 'Properties')
                )
            );
            $blob->setMetadata(
                Utilities::tryGetValue($value, Resources::QP_METADATA, array())
            );
            
            $result->_blobs[] = $blob;
        }
        
        if (   is_array($parsed['Blobs'])
            && array_key_exists('BlobPrefix', $parsed['Blobs'])
        ) {
            $rawBlobPrefixes = Utilities::getArray($parsed['Blobs']['BlobPrefix']);
        }
        
        foreach ($rawBlobPrefixes as $value) {
            $blobPrefix = new BlobPrefix();
            $blobPrefix->setName($value['Name']);
            
            $result->_blobPrefixes[] = $blobPrefix;
        }
        
        return $result;
    }
    
    /**
     * Gets blobs.
     *
     * @return array
     */
    public function getBlobs()
    {
        return $this->_blobs;
    }
    
    /**
     * Sets blobs.
     *
     * @param array $blobs list of blobs
     * 
     * @return none
     */
    public function setBlobs($blobs)
    {
        $this->_blobs = array();
        foreach ($blobs as $blob) {
            $this->_blobs[] = clone $blob;
        }
    }
    
    /**
     * Gets blobPrefixes.
     *
     * @return array
     */
    public function getBlobPrefixes()
    {
        return $this->_blobPrefixes;
    }
    
    /**
     * Sets blobPrefixes.
     *
     * @param array $blobPrefixes list of blobPrefixes
     * 
     * @return none
     */
    public function setBlobPrefixes($blobPrefixes)
    {
        $this->_blobPrefixes = array();
        foreach ($blobPrefixes as $blob) {
            $this->_blobPrefixes[] = clone $blob;
        }
    }

    /**
     * Gets prefix.
     *
     * @return string
     */
    public function getPrefix()
    {
        return $this->_prefix;
    }

    /**
     * Sets prefix.
     *
     * @param string $prefix value.
     * 
     * @return none
     */
    public function setPrefix($prefix)
    {
        $this->_prefix = $prefix;
    }
    
    /**
     * Gets prefix.
     *
     * @return string
     */
    public function getDelimiter()
    {
        return $this->_delimiter;
    }

    /**
     * Sets prefix.
     *
     * @param string $delimiter value.
     * 
     * @return none
     */
    public function setDelimiter($delimiter)
    {
        $this->_delimiter = $delimiter;
    }

    /**
     * Gets marker.
     * 
     * @return string
     */
    public function getMarker()
    {
        return $this->_marker;
    }

    /**
     * Sets marker.
     *
     * @param string $marker value.
     * 
     * @return none
     */
    public function setMarker($marker)
    {
        $this->_marker = $marker;
    }

    /**
     * Gets max results.
     * 
     * @return integer
     */
    public function getMaxResults()
    {
        return $this->_maxResults;
    }

    /**
     * Sets max results.
     *
     * @param integer $maxResults value.
     * 
     * @return none
     */
    public function setMaxResults($maxResults)
    {
        $this->_maxResults = $maxResults;
    }

    /**
     * Gets next marker.
     * 
     * @return string
     */
    public function getNextMarker()
    {
        return $this->_nextMarker;
    }

    /**
     * Sets next marker.
     *
     * @param string $nextMarker value.
     * 
     * @return none
     */
    public function setNextMarker($nextMarker)
    {
        $this->_nextMarker = $nextMarker;
    }
    
    /**
     * Gets container name.
     * 
     * @return string
     */
    public function getContainerName()
    {
        return $this->_containerName;
    }

    /**
     * Sets container name.
     *
     * @param string $containerName value.
     * 
     * @return none
     */
    public function setContainerName($containerName)
    {
        $this->_containerName = $containerName;
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions;
use \MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Options for listBlobs API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListContainersOptions extends BlobServiceOptions
{
    /**
     * Filters the results to return only containers whose name begins with the 
     * specified prefix.
     * 
     * @var string
     */
    private $_prefix;
    
    /**
     * Identifies the portion of the list to be returned with the next list operation
     * The operation returns a marker value within the 
     * response body if the list returned was not complete. The marker value may 
     * then be used in a subsequent call to request the next set of list items.
     * The marker value is opaque to the client.
     * 
     * @var string
     */
    private $_marker;
    
    /**
     * Specifies the maximum number of containers to return. If the request does not
     * specify maxresults, or specifies a value greater than 5,000, the server will
     * return up to 5,000 items. If the parameter is set to a value less than or
     * equal to zero, the server will return status code 400 (Bad Request).
     * 
     * @var string
     */
    private $_maxResults;
    
    /**
     * Include this parameter to specify that the container's metadata be returned
     * as part of the response body.
     * 
     * @var bool
     */
    private $_includeMetadata;

    /**
     * Gets prefix.
     *
     * @return string.
     */
    public function getPrefix()
    {
        return $this->_prefix;
    }

    /**
     * Sets prefix.
     *
     * @param string $prefix value.
     * 
     * @return none.
     */
    public function setPrefix($prefix)
    {
        Validate::isString($prefix, 'prefix');
        $this->_prefix = $prefix;
    }

    /**
     * Gets marker.
     * 
     * @return string.
     */
    public function getMarker()
    {
        return $this->_marker;
    }

    /**
     * Sets marker.
     *
     * @param string $marker value.
     * 
     * @return none.
     */
    public function setMarker($marker)
    {
        Validate::isString($marker, 'marker');
        $this->_marker = $marker;
    }

    /**
     * Gets max results.
     * 
     * @return string.
     */
    public function getMaxResults()
    {
        return $this->_maxResults;
    }

    /**
     * Sets max results.
     *
     * @param string $maxResults value.
     * 
     * @return none.
     */
    public function setMaxResults($maxResults)
    {
        Validate::isString($maxResults, 'maxResults');
        $this->_maxResults = $maxResults;
    }

    /**
     * Indicates if metadata is included or not.
     * 
     * @return string.
     */
    public function getIncludeMetadata()
    {
        return $this->_includeMetadata;
    }

    /**
     * Sets the include metadata flag.
     *
     * @param bool $includeMetadata value.
     * 
     * @return none.
     */
    public function setIncludeMetadata($includeMetadata)
    {
        Validate::isBoolean($includeMetadata);
        $this->_includeMetadata = $includeMetadata;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\Container;
use MicrosoftAzure\Storage\Tests\Unit\Common\Internal\UtilitiesTest;

/**
 * Container to hold list container response object.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListContainersResult
{
    /**
     * @var array
     */
    private $_containers;
    
    /**
     * @var string
     */
    private $_prefix;
    
    /**
     * @var string
     */
    private $_marker;
    
    /**
     * @var string
     */
    private $_nextMarker;
    
    /**
     * @var integer
     */
    private $_maxResults;
    
    /**
     * @var string
     */
    private $_accountName;

    /**
     * Creates ListBlobResult object from parsed XML response.
     *
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return ListBlobResult
     */
    public static function create($parsedResponse)
    {
        $result               = new ListContainersResult();
        $serviceEndpoint      = Utilities::tryGetKeysChainValue(
            $parsedResponse,
            Resources::XTAG_ATTRIBUTES,
            Resources::XTAG_SERVICE_ENDPOINT
        );
        $result->_accountName = Utilities::tryParseAccountNameFromUrl(
            $serviceEndpoint
        );
        $result->_prefix      = Utilities::tryGetValue(
            $parsedResponse, Resources::QP_PREFIX
        );
        $result->_marker      = Utilities::tryGetValue(
            $parsedResponse, Resources::QP_MARKER
        );
        $result->_nextMarker  = Utilities::tryGetValue(
            $parsedResponse, Resources::QP_NEXT_MARKER
        );
        $result->_maxResults  = Utilities::tryGetValue(
            $parsedResponse, Resources::QP_MAX_RESULTS
        );
        $result->_containers  = array();
        $rawContainer         = array();
        
        if ( !empty($parsedResponse['Containers']) ) {
            $containersArray = $parsedResponse['Containers']['Container'];
            $rawContainer    = Utilities::getArray($containersArray);
        }
        
        foreach ($rawContainer as $value) {
            $container = new Container();
            $container->setName($value['Name']);
            $container->setUrl($serviceEndpoint . $value['Name']);
            $container->setMetadata(
                Utilities::tryGetValue($value, Resources::QP_METADATA, array())
            );
            $properties = new ContainerProperties();
            $date       = $value['Properties']['Last-Modified'];
            $date       = Utilities::rfc1123ToDateTime($date);
            $properties->setLastModified($date);
            $properties->setETag($value['Properties']['Etag']);
            $container->setProperties($properties);
            $result->_containers[] = $container;
        }
        
        return $result;
    }

    /**
     * Sets containers.
     *
     * @param array $containers list of containers.
     * 
     * @return none
     */
    public function setContainers($containers)
    {
        $this->_containers = array();
        foreach ($containers as $container) {
            $this->_containers[] = clone $container;
        }
    }
    
    /**
     * Gets containers.
     *
     * @return array
     */
    public function getContainers()
    {
        return $this->_containers;
    }

    /**
     * Gets prefix.
     *
     * @return string
     */
    public function getPrefix()
    {
        return $this->_prefix;
    }

    /**
     * Sets prefix.
     *
     * @param string $prefix value.
     * 
     * @return none
     */
    public function setPrefix($prefix)
    {
        $this->_prefix = $prefix;
    }

    /**
     * Gets marker.
     * 
     * @return string
     */
    public function getMarker()
    {
        return $this->_marker;
    }

    /**
     * Sets marker.
     *
     * @param string $marker value.
     * 
     * @return none
     */
    public function setMarker($marker)
    {
        $this->_marker = $marker;
    }

    /**
     * Gets max results.
     * 
     * @return string
     */
    public function getMaxResults()
    {
        return $this->_maxResults;
    }

    /**
     * Sets max results.
     *
     * @param string $maxResults value.
     * 
     * @return none
     */
    public function setMaxResults($maxResults)
    {
        $this->_maxResults = $maxResults;
    }

    /**
     * Gets next marker.
     * 
     * @return string
     */
    public function getNextMarker()
    {
        return $this->_nextMarker;
    }

    /**
     * Sets next marker.
     *
     * @param string $nextMarker value.
     * 
     * @return none
     */
    public function setNextMarker($nextMarker)
    {
        $this->_nextMarker = $nextMarker;
    }
    
    /**
     * Gets account name.
     * 
     * @return string
     */
    public function getAccountName()
    {
        return $this->_accountName;
    }

    /**
     * Sets account name.
     *
     * @param string $accountName value.
     * 
     * @return none
     */
    public function setAccountName($accountName)
    {
        $this->_accountName = $accountName;
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Optional parameters for listPageBlobRanges wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListPageBlobRangesOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var string
     */
    private $_snapshot;
    
    /**
     * @var integer
     */
    private $_rangeStart;
    
    /**
     * @var integer
     */
    private $_rangeEnd;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets blob snapshot.
     *
     * @return string.
     */
    public function getSnapshot()
    {
        return $this->_snapshot;
    }

    /**
     * Sets blob snapshot.
     *
     * @param string $snapshot value.
     * 
     * @return none.
     */
    public function setSnapshot($snapshot)
    {
        $this->_snapshot = $snapshot;
    }
    
    /**
     * Gets rangeStart
     * 
     * @return integer
     */
    public function getRangeStart()
    {
        return $this->_rangeStart;
    }
    
    /**
     * Sets rangeStart
     * 
     * @param integer $rangeStart the blob lease id.
     * 
     * @return none
     */
    public function setRangeStart($rangeStart)
    {
        Validate::isInteger($rangeStart, 'rangeStart');
        $this->_rangeStart = $rangeStart;
    }
    
    /**
     * Gets rangeEnd
     * 
     * @return integer
     */
    public function getRangeEnd()
    {
        return $this->_rangeEnd;
    }
    
    /**
     * Sets rangeEnd
     * 
     * @param integer $rangeEnd range end value in bytes
     * 
     * @return none
     */
    public function setRangeEnd($rangeEnd)
    {
        Validate::isInteger($rangeEnd, 'rangeEnd');
        $this->_rangeEnd = $rangeEnd;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Blob\Models\PageRange;

/**
 * Holds result of calling listPageBlobRanges wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListPageBlobRangesResult
{
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var integer
     */
    private $_contentLength;
    
    /**
     * @var array
     */
    private $_pageRanges;
    
    /**
     * Creates BlobProperties object from $parsed response in array representation
     * 
     * @param array $headers HTTP response headers
     * @param array $parsed  parsed response in array format.
     * 
     * @return ListPageBlobRangesResult
     */
    public static function create($headers, $parsed)
    {
        $result  = new ListPageBlobRangesResult();
        $headers = array_change_key_case($headers);
        
        $date          = $headers[Resources::LAST_MODIFIED];
        $date          = Utilities::rfc1123ToDateTime($date);
        $blobLength    = intval($headers[Resources::X_MS_BLOB_CONTENT_LENGTH]);
        $rawPageRanges = array();
        
        if (!empty($parsed['PageRange'])) {
            $parsed        = array_change_key_case($parsed);
            $rawPageRanges = Utilities::getArray($parsed['pagerange']);
        }
        
        $result->_pageRanges = array();
        foreach ($rawPageRanges as $value) {
            $result->_pageRanges[] = new PageRange(
                intval($value['Start']), intval($value['End'])
            );
        }
        
        $result->setContentLength($blobLength);
        $result->setETag($headers[Resources::ETAG]);
        $result->setLastModified($date);
        
        return $result;
    }
    
    /**
     * Gets blob lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        Validate::isDate($lastModified);
        $this->_lastModified = $lastModified;
    }

    /**
     * Gets blob etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets blob etag.
     *
     * @param string $etag value.
     *
     * @return none.
     */
    public function setETag($etag)
    {
        Validate::isString($etag, 'etag');
        $this->_etag = $etag;
    }
    
    /**
     * Gets blob contentLength.
     *
     * @return integer.
     */
    public function getContentLength()
    {
        return $this->_contentLength;
    }

    /**
     * Sets blob contentLength.
     *
     * @param integer $contentLength value.
     *
     * @return none.
     */
    public function setContentLength($contentLength)
    {
        Validate::isInteger($contentLength, 'contentLength');
        $this->_contentLength = $contentLength;
    }
    
    /**
     * Gets page ranges
     * 
     * @return array
     */
    public function getPageRanges()
    {
        return $this->_pageRanges;
    }
    
    /**
     * Sets page ranges
     * 
     * @param array $pageRanges page ranges to set
     * 
     * @return none
     */
    public function setPageRanges($pageRanges)
    {
        $this->_pageRanges = array();
        foreach ($pageRanges as $pageRange) {
            $this->_pageRanges[] = clone $pageRange;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Holds info about page range used in HTTP requests
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PageRange
{
    /**
     * @var integer
     */
    private $_start;
    
    /**
     * @var integer
     */
    private $_end;

    /**
     * Constructor
     * 
     * @param integer $start the page start value
     * @param integer $end   the page end value
     * 
     * @return PageRange
     */
    public function __construct($start = null, $end = null)
    {
        $this->_start = $start;
        $this->_end   = $end;
    }
    
    /**
     * Sets page start range
     * 
     * @param integer $start the page range start
     * 
     * @return none.
     */
    public function setStart($start)
    {
        $this->_start = $start;
    }
    
    /**
     * Gets page start range
     * 
     * @return integer
     */
    public function getStart()
    {
        return $this->_start;
    }
    
    /**
     * Sets page end range
     * 
     * @param integer $end the page range end
     * 
     * @return none.
     */
    public function setEnd($end)
    {
        $this->_end = $end;
    }
    
    /**
     * Gets page end range
     * 
     * @return integer
     */
    public function getEnd()
    {
        return $this->_end;
    }
    
    /**
     * Gets page range length
     * 
     * @return integer
     */
    public function getLength()
    {
        return $this->_end - $this->_start + 1;
    }
    
    /**
     * Sets page range length
     * 
     * @param integer $value new page range
     * 
     * @return none
     */
    public function setLength($value)
    {
        $this->_end = $this->_start + $value - 1;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * Holds available blob page write options
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PageWriteOption
{
    const CLEAR_OPTION  = 'clear';
    const UPDATE_OPTION = 'update';
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Holds public acces types for a container.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PublicAccessType
{
    const NONE                = Resources::EMPTY_STRING;
    const BLOBS_ONLY          = 'blob';
    const CONTAINER_AND_BLOBS = 'container';
    
    /**
     * Validates the public access.
     * 
     * @param string $type The public access type.
     * 
     * @return boolean
     */
    public static function isValid($type)
    {
        switch ($type) {
        case self::NONE:
        case self::BLOBS_ONLY:
        case self::CONTAINER_AND_BLOBS:
        return true;

        default:
        return false;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;

/**
 * The optional parameters for setBlobMetadata API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetBlobMetadataOptions extends BlobServiceOptions
{
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds results of calling getBlobMetadata wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetBlobMetadataResult
{
    
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * Creates SetBlobMetadataResult from response headers.
     * 
     * @param array $headers response headers
     * 
     * @return SetBlobMetadataResult
     */
    public static function create($headers)
    {
        $result = new SetBlobMetadataResult();
        $date   = $headers[Resources::LAST_MODIFIED];
        $result->setLastModified(Utilities::rfc1123ToDateTime($date));
        $result->setETag($headers[Resources::ETAG]);
        
        return $result;
    }
    
    /**
     * Gets blob lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        Validate::isDate($lastModified);
        $this->_lastModified = $lastModified;
    }

    /**
     * Gets blob etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets blob etag.
     *
     * @param string $etag value.
     *
     * @return none.
     */
    public function setETag($etag)
    {
        Validate::isString($etag, 'etag');
        $this->_etag = $etag;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Optional parameters for setBlobProperties wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetBlobPropertiesOptions extends BlobServiceOptions
{
    /**
     * @var BlobProperties
     */
    private $_blobProperties;
    
    /**
     * @var string
     */
    private $_leaseId;
    
    /**
     * @var string
     */
    private $_sequenceNumberAction;
    
    /**
     * @var AccessCondition
     */
    private $_accessCondition;
    
    /**
     * Creates a new SetBlobPropertiesOptions with a specified BlobProperties 
     * instance.
     * 
     * @param BlobProperties $blobProperties The blob properties instance.
     */
    public function __construct($blobProperties = null)
    {
        $this->_blobProperties = is_null($blobProperties) 
                                 ? new BlobProperties() : clone $blobProperties;
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
    
    /**
     * Gets blob sequenceNumber.
     *
     * @return integer.
     */
    public function getSequenceNumber()
    {
        return $this->_blobProperties->getSequenceNumber();
    }

    /**
     * Sets blob sequenceNumber.
     *
     * @param integer $sequenceNumber value.
     *
     * @return none.
     */
    public function setSequenceNumber($sequenceNumber)
    {
        $this->_blobProperties->setSequenceNumber($sequenceNumber);
    }
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getSequenceNumberAction()
    {
        return $this->_sequenceNumberAction;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $sequenceNumberAction action.
     * 
     * @return none
     */
    public function setSequenceNumberAction($sequenceNumberAction)
    {
        $this->_sequenceNumberAction = $sequenceNumberAction;
    }
    
    /**
     * Gets lease Id for the blob
     * 
     * @return string
     */
    public function getLeaseId()
    {
        return $this->_leaseId;
    }
    
    /**
     * Sets lease Id for the blob
     * 
     * @param string $leaseId the blob lease id.
     * 
     * @return none
     */
    public function setLeaseId($leaseId)
    {
        $this->_leaseId = $leaseId;
    }
    
    /**
     * Gets blob blobContentLength.
     *
     * @return integer.
     */
    public function getBlobContentLength()
    {
        return $this->_blobProperties->getContentLength();
    }

    /**
     * Sets blob blobContentLength.
     *
     * @param integer $blobContentLength value.
     *
     * @return none.
     */
    public function setBlobContentLength($blobContentLength)
    {
        $this->_blobProperties->setContentLength($blobContentLength);
    }
    
    /**
     * Gets blob ContentType.
     *
     * @return string.
     */
    public function getBlobContentType()
    {
        return $this->_blobProperties->getContentType();
    }

    /**
     * Sets blob ContentType.
     *
     * @param string $blobContentType value.
     *
     * @return none.
     */
    public function setBlobContentType($blobContentType)
    {
        $this->_blobProperties->setContentType($blobContentType);
    }
    
    /**
     * Gets blob ContentEncoding.
     *
     * @return string.
     */
    public function getBlobContentEncoding()
    {
        return $this->_blobProperties->getContentEncoding();
    }

    /**
     * Sets blob ContentEncoding.
     *
     * @param string $blobContentEncoding value.
     *
     * @return none.
     */
    public function setBlobContentEncoding($blobContentEncoding)
    {
        $this->_blobProperties->setContentEncoding($blobContentEncoding);
    }
    
    /**
     * Gets blob ContentLanguage.
     *
     * @return string.
     */
    public function getBlobContentLanguage()
    {
        return $this->_blobProperties->getContentLanguage();
    }

    /**
     * Sets blob ContentLanguage.
     *
     * @param string $blobContentLanguage value.
     *
     * @return none.
     */
    public function setBlobContentLanguage($blobContentLanguage)
    {
        $this->_blobProperties->setContentLanguage($blobContentLanguage);
    }
    
    /**
     * Gets blob ContentMD5.
     *
     * @return string.
     */
    public function getBlobContentMD5()
    {
        return $this->_blobProperties->getContentMD5();
    }

    /**
     * Sets blob ContentMD5.
     *
     * @param string $blobContentMD5 value.
     *
     * @return none.
     */
    public function setBlobContentMD5($blobContentMD5)
    {
        $this->_blobProperties->setContentMD5($blobContentMD5);
    }
    
    /**
     * Gets blob cache control.
     *
     * @return string.
     */
    public function getBlobCacheControl()
    {
        return $this->_blobProperties->getCacheControl();
    }
    
    /**
     * Sets blob cacheControl.
     *
     * @param string $blobCacheControl value to use.
     * 
     * @return none.
     */
    public function setBlobCacheControl($blobCacheControl)
    {
        $this->_blobProperties->setCacheControl($blobCacheControl);
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds result of calling setBlobProperties wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetBlobPropertiesResult
{
    /**
     * @var \DateTime
     */
    private $_lastModified;
    
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var integer
     */
    private $_sequenceNumber;
    
    /**
     * Creates SetBlobPropertiesResult from response headers.
     * 
     * @param array $headers response headers
     * 
     * @return SetBlobPropertiesResult
     */
    public static function create($headers)
    {
        $result = new SetBlobPropertiesResult();
        $date   = $headers[Resources::LAST_MODIFIED];
        $result->setLastModified(Utilities::rfc1123ToDateTime($date));
        $result->setETag($headers[Resources::ETAG]);
        if (array_key_exists(Resources::X_MS_BLOB_SEQUENCE_NUMBER, $headers)) {
            $sNumber = $headers[Resources::X_MS_BLOB_SEQUENCE_NUMBER];
            $result->setSequenceNumber(intval($sNumber));
        }
        
        return $result;
    }
    
    /**
     * Gets blob lastModified.
     *
     * @return \DateTime.
     */
    public function getLastModified()
    {
        return $this->_lastModified;
    }

    /**
     * Sets blob lastModified.
     *
     * @param \DateTime $lastModified value.
     *
     * @return none.
     */
    public function setLastModified($lastModified)
    {
        Validate::isDate($lastModified);
        $this->_lastModified = $lastModified;
    }

    /**
     * Gets blob etag.
     *
     * @return string.
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets blob etag.
     *
     * @param string $etag value.
     *
     * @return none.
     */
    public function setETag($etag)
    {
        Validate::isString($etag, 'etag');
        $this->_etag = $etag;
    }
    
    /**
     * Gets blob sequenceNumber.
     *
     * @return int.
     */
    public function getSequenceNumber()
    {
        return $this->_sequenceNumber;
    }

    /**
     * Sets blob sequenceNumber.
     *
     * @param int $sequenceNumber value.
     *
     * @return none.
     */
    public function setSequenceNumber($sequenceNumber)
    {
        Validate::isInteger($sequenceNumber, 'sequenceNumber');
        $this->_sequenceNumber = $sequenceNumber;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions;

/**
 * Optional parameters for setContainerMetadata wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetContainerMetadataOptions extends BlobServiceOptions
{
    /**
     * @var AccessCondition 
     */
    private $_accessCondition;
    
    /**
     * Constructs the access condition object with none option. 
     */
    public function __construct()
    {
        $this->_accessCondition = AccessCondition::none();
    }
    
    /**
     * Gets access condition
     * 
     * @return AccessCondition
     */
    public function getAccessCondition()
    {
        return $this->_accessCondition;
    }
    
    /**
     * Sets access condition
     * 
     * @param AccessCondition $accessCondition value to use.
     * 
     * @return none.
     */
    public function setAccessCondition($accessCondition)
    {
        $this->_accessCondition = $accessCondition;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\AccessPolicy;

/**
 * Holds container signed identifiers.
 * 
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SignedIdentifier
{
    private $_id;
    private $_accessPolicy;
    
    /**
     * Gets id.
     *
     * @return string.
     */
    public function getId()
    {
        return $this->_id;
    }

    /**
     * Sets id.
     *
     * @param string $id value.
     * 
     * @return none.
     */
    public function setId($id)
    {
        $this->_id = $id;
    }
    
    /**
     * Gets accessPolicy.
     *
     * @return string.
     */
    public function getAccessPolicy()
    {
        return $this->_accessPolicy;
    }

    /**
     * Sets accessPolicy.
     *
     * @param string $accessPolicy value.
     * 
     * @return none.
     */
    public function setAccessPolicy($accessPolicy)
    {
        $this->_accessPolicy = $accessPolicy;
    }
    
    /**
     * Converts this current object to XML representation.
     * 
     * @return array.
     */
    public function toArray()
    {
        $array = array();
        
        $array['SignedIdentifier']['Id']           = $this->_id;
        $array['SignedIdentifier']['AccessPolicy'] = $this->_accessPolicy->toArray();
        
        return $array;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\ConnectionStringSource;

/**
 * Configuration manager for accessing Windows Azure settings.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CloudConfigurationManager
{
    /**
     * @var boolean
     */
    private static $_isInitialized = false;
    
    /**
     * The list of connection string sources.
     * 
     * @var array
     */
    private static $_sources;
    
    /**
     * Restrict users from creating instances from this class
     */
    private function __construct()
    {
        
    }
    
    /**
     * Initializes the connection string source providers.
     * 
     * @return none 
     */
    private static function _init()
    {
        if (!self::$_isInitialized) {
            self::$_sources = array();
            
            // Get list of default connection string sources.
            $default = ConnectionStringSource::getDefaultSources();
            foreach ($default as $name => $provider) {
                self::$_sources[$name] = $provider;
            }
            
            self::$_isInitialized = true;
        }
    }
    
    /**
     * Gets a connection string from all available sources.
     * 
     * @param string $key The connection string key name.
     * 
     * @return string If the key does not exist return null.
     */
    public static function getConnectionString($key)
    {
        Validate::isString($key, 'key');
        
        self::_init();
        $value = null;
        
        foreach (self::$_sources as $source) {
            $value = call_user_func_array($source, array($key));
            
            if (!empty($value)) {
                break;
            }
        }
        
        return $value;
    }
    
    /**
     * Registers a new connection string source provider. If the source to get 
     * registered is a default source, only the name of the source is required.
     * 
     * @param string   $name     The source name.
     * @param callable $provider The source callback.
     * @param boolean  $prepend  When true, the $provider is processed first when 
     * calling getConnectionString. When false (the default) the $provider is 
     * processed after the existing callbacks.
     * 
     * @return none
     */
    public static function registerSource($name, $provider = null, $prepend = false)
    {
        Validate::isString($name, 'name');
        Validate::notNullOrEmpty($name, 'name');
        
        self::_init();
        $default = ConnectionStringSource::getDefaultSources();
        
        // Try to get callback if the user is trying to register a default source.
        $provider = Utilities::tryGetValue($default, $name, $provider);
        
        Validate::notNullOrEmpty($provider, 'callback');
        
        if ($prepend) {
            self::$_sources = array_merge(
                array($name => $provider),
                self::$_sources
            );
            
        } else {
            self::$_sources[$name] = $provider;
        }
    }
    
    /**
     * Unregisters a connection string source.
     * 
     * @param string $name The source name.
     * 
     * @return callable
     */
    public static function unregisterSource($name)
    {
        Validate::isString($name, 'name');
        Validate::notNullOrEmpty($name, 'name');
        
        self::_init();
        
        $sourceCallback = Utilities::tryGetValue(self::$_sources, $name);
        
        if (!is_null($sourceCallback)) {
            unset(self::$_sources[$name]);
        }
        
        return $sourceCallback;
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal\Authentication;

/**
 * Interface for azure authentication schemes.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
interface IAuthScheme
{
    /**
     * Returns authorization header to be included in the request.
     *
     * @param array  $headers     request headers.
     * @param string $url         reuqest url.
     * @param array  $queryParams query variables.
     * @param string $httpMethod  request http method.
     *
     * @see Specifying the Authorization Header section at
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     *
     * @abstract
     *
     * @return string
     */
    public function getAuthorizationHeader($headers, $url, $queryParams,
        $httpMethod
    );
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Provides shared key authentication scheme for blob and queue. For more info
 * check: http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SharedKeyAuthScheme extends StorageAuthScheme
{
    protected $includedHeaders;

    /**
     * Constructor.
     *
     * @param string $accountName storage account name.
     * @param string $accountKey  storage account primary or secondary key.
     * 
     * @return 
     * MicrosoftAzure\Storage\Common\Internal\Authentication\SharedKeyAuthScheme
     */
    public function __construct($accountName, $accountKey)
    {
        parent::__construct($accountName, $accountKey);

        $this->includedHeaders   = array();
        $this->includedHeaders[] = Resources::CONTENT_ENCODING;
        $this->includedHeaders[] = Resources::CONTENT_LANGUAGE;
        $this->includedHeaders[] = Resources::CONTENT_LENGTH;
        $this->includedHeaders[] = Resources::CONTENT_MD5;
        $this->includedHeaders[] = Resources::CONTENT_TYPE;
        $this->includedHeaders[] = Resources::DATE;
        $this->includedHeaders[] = Resources::IF_MODIFIED_SINCE;
        $this->includedHeaders[] = Resources::IF_MATCH;
        $this->includedHeaders[] = Resources::IF_NONE_MATCH;
        $this->includedHeaders[] = Resources::IF_UNMODIFIED_SINCE;
        $this->includedHeaders[] = Resources::RANGE;
    }

    /**
     * Computes the authorization signature for blob and queue shared key.
     *
     * @param array  $headers     request headers.
     * @param string $url         reuqest url.
     * @param array  $queryParams query variables.
     * @param string $httpMethod  request http method.
     * 
     * @see Blob and Queue Services (Shared Key Authentication) at
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     * 
     * @return string
     */
    protected function computeSignature($headers, $url, $queryParams, $httpMethod)
    {
        $canonicalizedHeaders = parent::computeCanonicalizedHeaders($headers);
        
        $canonicalizedResource = parent::computeCanonicalizedResource(
            $url, $queryParams
        );

        $stringToSign   = array();
        $stringToSign[] = strtoupper($httpMethod);
        
        foreach ($this->includedHeaders as $header) {
            $stringToSign[] = Utilities::tryGetValue($headers, $header);
        }
        
        if (count($canonicalizedHeaders) > 0) {
            $stringToSign[] = implode("\n", $canonicalizedHeaders);
        }

        $stringToSign[] = $canonicalizedResource;
        $stringToSign   = implode("\n", $stringToSign);
        
        return $stringToSign;
    }
    
    /**
     * Returns authorization header to be included in the request.
     *
     * @param array  $headers     request headers.
     * @param string $url         reuqest url.
     * @param array  $queryParams query variables.
     * @param string $httpMethod  request http method.
     * 
     * @see Specifying the Authorization Header section at 
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     * 
     * @return string
     */
    public function getAuthorizationHeader($headers, $url, $queryParams, $httpMethod)
    {
        $signature = $this->computeSignature(
            $headers, $url, $queryParams, $httpMethod
        );
        
        return 'SharedKey ' . $this->accountName . ':' . base64_encode(
            hash_hmac('sha256', $signature, base64_decode($this->accountKey), true)
        );
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Authentication\IAuthScheme;


/**
 * Base class for azure authentication schemes.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
abstract class StorageAuthScheme implements IAuthScheme
{
    protected $accountName;
    protected $accountKey;

    /**
     * Constructor.
     *
     * @param string $accountName storage account name.
     * @param string $accountKey  storage account primary or secondary key.
     *
     * @return
     * MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme
     */
    public function __construct($accountName, $accountKey)
    {
        $this->accountKey  = $accountKey;
        $this->accountName = $accountName;
    }

    /**
     * Computes canonicalized headers for headers array.
     *
     * @param array $headers request headers.
     *
     * @see Constructing the Canonicalized Headers String section at
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     *
     * @return array
     */
    protected function computeCanonicalizedHeaders($headers)
    {
        $canonicalizedHeaders = array();
        $normalizedHeaders    = array();
        $validPrefix          =  Resources::X_MS_HEADER_PREFIX;

        if (is_null($normalizedHeaders)) {
            return $canonicalizedHeaders;
        }

        foreach ($headers as $header => $value) {
            // Convert header to lower case.
            $header = strtolower($header);

            // Retrieve all headers for the resource that begin with x-ms-,
            // including the x-ms-date header.
            if (Utilities::startsWith($header, $validPrefix)) {
                // Unfold the string by replacing any breaking white space
                // (meaning what splits the headers, which is \r\n) with a single
                // space.
                $value = str_replace("\r\n", ' ', $value);

                // Trim any white space around the colon in the header.
                $value  = ltrim($value);
                $header = rtrim($header);

                $normalizedHeaders[$header] = $value;
            }
        }

        // Sort the headers lexicographically by header name, in ascending order.
        // Note that each header may appear only once in the string.
        ksort($normalizedHeaders);

        foreach ($normalizedHeaders as $key => $value) {
            $canonicalizedHeaders[] = $key . ':' . $value;
        }

        return $canonicalizedHeaders;
    }

    /**
     * Computes canonicalized resources from URL using Table formar
     *
     * @param string $url         request url.
     * @param array  $queryParams request query variables.
     *
     * @see Constructing the Canonicalized Resource String section at
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     *
     * @return string
     */
    protected function computeCanonicalizedResourceForTable($url, $queryParams)
    {
        $queryParams = array_change_key_case($queryParams);

        // 1. Beginning with an empty string (""), append a forward slash (/),
        //    followed by the name of the account that owns the accessed resource.
        $canonicalizedResource = '/' . $this->accountName;

        // 2. Append the resource's encoded URI path, without any query parameters.
        $canonicalizedResource .= parse_url($url, PHP_URL_PATH);

        // 3. The query string should include the question mark and the comp
        //    parameter (for example, ?comp=metadata). No other parameters should
        //    be included on the query string.
        if (array_key_exists(Resources::QP_COMP, $queryParams)) {
            $canonicalizedResource .= '?' . Resources::QP_COMP . '=';
            $canonicalizedResource .= $queryParams[Resources::QP_COMP];
        }

        return $canonicalizedResource;
    }

    /**
     * Computes canonicalized resources from URL.
     *
     * @param string $url         request url.
     * @param array  $queryParams request query variables.
     *
     * @see Constructing the Canonicalized Resource String section at
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     *
     * @return string
     */
    protected function computeCanonicalizedResource($url, $queryParams)
    {
        $queryParams = array_change_key_case($queryParams);

        // 1. Beginning with an empty string (""), append a forward slash (/),
        //    followed by the name of the account that owns the accessed resource.
        $canonicalizedResource = '/' . $this->accountName;

        // 2. Append the resource's encoded URI path, without any query parameters.
        $canonicalizedResource .= parse_url($url, PHP_URL_PATH);

        // 3. Retrieve all query parameters on the resource URI, including the comp
        //    parameter if it exists.
        // 4. Sort the query parameters lexicographically by parameter name, in
        //    ascending order.
        if (count($queryParams) > 0) {
            ksort($queryParams);
        }

        // 5. Convert all parameter names to lowercase.
        // 6. URL-decode each query parameter name and value.
        // 7. Append each query parameter name and value to the string in the
        //    following format:
        //      parameter-name:parameter-value
        // 9. Group query parameters
        // 10. Append a new line character (\n) after each name-value pair.
        foreach ($queryParams as $key => $value) {
            // Grouping query parameters
            $values = explode(Resources::SEPARATOR, $value);
            sort($values);
            $separated = implode(Resources::SEPARATOR, $values);

            $canonicalizedResource .= "\n" . $key . ':' . $separated;
        }

        return $canonicalizedResource;
    }

    /**
     * Computes the authorization signature.
     *
     * @param array  $headers     request headers.
     * @param string $url         reuqest url.
     * @param array  $queryParams query variables.
     * @param string $httpMethod  request http method.
     *
     * @see check all authentication schemes at
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     *
     * @abstract
     *
     * @return string
     */
    abstract protected function computeSignature($headers, $url, $queryParams,
        $httpMethod
    );
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      http://github.com/windowsazure/azure-sdk-for-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Provides shared key authentication scheme for blob and queue. For more info
 * check: http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      http://github.com/windowsazure/azure-sdk-for-php
 */
class TableSharedKeyLiteAuthScheme extends StorageAuthScheme
{
    protected $includedHeaders;

    /**
     * Constructor.
     *
     * @param string $accountName storage account name.
     * @param string $accountKey  storage account primary or secondary key.
     * 
     * @return TableSharedKeyLiteAuthScheme
     */
    public function __construct($accountName, $accountKey)
    {
        parent::__construct($accountName, $accountKey);

        $this->includedHeaders   = array();
        $this->includedHeaders[] = Resources::DATE;
    }

    /**
     * Computes the authorization signature for blob and queue shared key.
     *
     * @param array  $headers     request headers.
     * @param string $url         reuqest url.
     * @param array  $queryParams query variables.
     * @param string $httpMethod  request http method.
     * 
     * @see Blob and Queue Services (Shared Key Authentication) at
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     * 
     * @return string
     */
    protected function computeSignature($headers, $url, $queryParams, $httpMethod)
    {
        $canonicalizedResource = parent::computeCanonicalizedResourceForTable(
            $url, $queryParams
        );
        
        $stringToSign = array();

        foreach ($this->includedHeaders as $header) {
            $stringToSign[] = Utilities::tryGetValue($headers, $header);
        }

        $stringToSign[] = $canonicalizedResource;
        $stringToSign   = implode("\n", $stringToSign);    
        
        return $stringToSign;
    }
    
    /**
     * Returns authorization header to be included in the request.
     *
     * @param array  $headers     request headers.
     * @param string $url         reuqest url.
     * @param array  $queryParams query variables.
     * @param string $httpMethod  request http method.
     * 
     * @see Specifying the Authorization Header section at 
     *      http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx
     * 
     * @return string
     */
    public function getAuthorizationHeader($headers, $url, $queryParams, $httpMethod)
    {
        $signature = $this->computeSignature(
            $headers, $url, $queryParams, $httpMethod
        );

        return 'SharedKeyLite ' . $this->accountName . ':' . base64_encode(
            hash_hmac('sha256', $signature, base64_decode($this->accountKey), true)
        );
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;

/**
 * Helper methods for parsing connection strings. The rules for formatting connection
 * strings are defined here:
 * www.connectionstrings.com/articles/show/important-rules-for-connection-strings
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ConnectionStringParser
{
    /**
     * @var string
     */
    private $_argumentName;
    
    /**
     * @var string
     */
    private $_value;
    
    /**
     * @var integer
     */
    private $_pos;
    
    /**
     * @var string
     */
    private $_state;
    
    /**
     * Parses the connection string into a collection of key/value pairs.
     * 
     * @param string $argumentName     Name of the argument to be used in error 
     * messages.
     * @param string $connectionString Connection string.
     * 
     * @return array
     * 
     * @static
     */
    public static function parseConnectionString($argumentName, $connectionString)
    {
        Validate::isString($argumentName, 'argumentName');
        Validate::notNullOrEmpty($argumentName, 'argumentName');
        Validate::isString($connectionString, 'connectionString');
        Validate::notNullOrEmpty($connectionString, 'connectionString');
        
        $parser = new ConnectionStringParser($argumentName, $connectionString);
        return $parser->_parse();
    }
    
    /**
     * Initializes the object.
     * 
     * @param string $argumentName Name of the argument to be used in error 
     * messages.
     * @param string $value        Connection string.
     */
    private function __construct($argumentName, $value)
    {
        $this->_argumentName = $argumentName;
        $this->_value        = $value;
        $this->_pos          = 0;
        $this->_state        = ParserState::EXPECT_KEY;
    }
    
    /**
     * Parses the connection string.
     * 
     * @return array
     * 
     * @throws \RuntimeException
     */
    private function _parse()
    {
        $key                    = null;
        $value                  = null;
        $connectionStringValues = array();
        
        while (true) {
            $this->_skipWhiteSpaces();
            
            if (   $this->_pos == strlen($this->_value)
                && $this->_state != ParserState::EXPECT_VALUE
            ) {
                // Not stopping after the end has been reached and a value is 
                // expected results in creating an empty value, which we expect.
                break;
            }
            
            switch ($this->_state) {
            case ParserState::EXPECT_KEY:
                $key          = $this->_extractKey();
                $this->_state = ParserState::EXPECT_ASSIGNMENT;
                break;
            
            case ParserState::EXPECT_ASSIGNMENT:
                $this->_skipOperator('=');
                $this->_state = ParserState::EXPECT_VALUE;
                break;
            
            case ParserState::EXPECT_VALUE:
                $value                        = $this->_extractValue();
                $this->_state                 = ParserState::EXPECT_SEPARATOR;
                $connectionStringValues[$key] = $value;
                $key                          = null;
                $value                        = null;
                break;
            
            default:
                $this->_skipOperator(';');
                $this->_state = ParserState::EXPECT_KEY;
                break;
            }
        }
        
        // Must end parsing in the valid state (expected key or separator)
        if ($this->_state == ParserState::EXPECT_ASSIGNMENT) {
            throw $this->_createException(
                $this->_pos,
                Resources::MISSING_CONNECTION_STRING_CHAR,
                '='
            );
        }
        
        return $connectionStringValues;
    }
    
    /**
     *Generates an invalid connection string exception with the detailed error 
     * message.
     * 
     * @param integer $position    The position of the error.
     * @param string  $errorString The short error formatting string.
     * 
     * @return \RuntimeException
     */
    private function _createException($position, $errorString)
    {
        $arguments = func_get_args();
        
        // Remove first argument (position)
        unset($arguments[0]);
        
        // Create a short error message.
        $errorString = sprintf($errorString, $arguments);
        
        // Add position.
        $errorString = sprintf(
            Resources::ERROR_PARSING_STRING,
            $errorString,
            $position
        );
        
        // Create final error message.
        $errorString = sprintf(
            Resources::INVALID_CONNECTION_STRING,
            $this->_argumentName,
            $errorString
        );
        
        return new \RuntimeException($errorString);
    }
    
    /**
     * Skips whitespaces at the current position.
     * 
     * @return none
     */
    private function _skipWhiteSpaces()
    {
        while (   $this->_pos < strlen($this->_value)
              &&  ctype_space($this->_value[$this->_pos])
        ) {
            $this->_pos++;
        }
    }
    
    /**
     * Extracts the key's value.
     * 
     * @return string 
     */
    private function _extractValue()
    {
        $value = Resources::EMPTY_STRING;
        
        if ($this->_pos < strlen($this->_value)) {
            $ch = $this->_value[$this->_pos];
            
            if ($ch == '"' || $ch == '\'') {
                // Value is contained between double quotes or skipped single quotes.
                $this->_pos++;
                $value = $this->_extractString($ch);
            } else {
                $firstPos = $this->_pos;
                $isFound  = false;
                
                while ($this->_pos < strlen($this->_value) && !$isFound) {
                    $ch = $this->_value[$this->_pos];
                    
                    if ($ch == ';') {
                        $isFound = true;
                    } else {
                        $this->_pos++;
                    }
                }
                
                $value = rtrim(
                    substr($this->_value, $firstPos, $this->_pos - $firstPos)
                );
            }
        }
        
        return $value;
    }
    
    /**
     * Extracts key at the current position.
     * 
     * @return string 
     */
    private function _extractKey()
    {
        $key      = null;
        $firstPos = $this->_pos;
        $ch       = $this->_value[$this->_pos];
        
        if ($ch == '"' || $ch == '\'') {
            $this->_pos++;
            $key = $this->_extractString($ch);
        } else if ($ch == ';' || $ch == '=') {
            // Key name was expected.
            throw $this->_createException(
                $firstPos,
                Resources::ERROR_CONNECTION_STRING_MISSING_KEY
            );
        } else {
            while ($this->_pos < strlen($this->_value)) {
                $ch = $this->_value[$this->_pos];
                
                // At this point we've read the key, break.
                if ($ch == '=') {
                    break;
                }
                
                $this->_pos++;
            }
            $key = rtrim(substr($this->_value, $firstPos, $this->_pos - $firstPos));
        }
        
        if (strlen($key) == 0) {
            // Empty key name.
            throw $this->_createException(
                $firstPos,
                Resources::ERROR_CONNECTION_STRING_EMPTY_KEY
            );
        }
        
        return $key;
    }
    
    /**
     * Extracts the string until the given quotation mark.
     * 
     * @param string $quote The quotation mark terminating the string.
     * 
     * @return string
     */
    private function _extractString($quote)
    {
        $firstPos = $this->_pos;
        
        while (   $this->_pos < strlen($this->_value)
              &&  $this->_value[$this->_pos] != $quote
        ) {
            $this->_pos++;
        }
        
        if ($this->_pos == strlen($this->_value)) {
            // Runaway string.
            throw $this->_createException(
                $this->_pos,
                Resources::ERROR_CONNECTION_STRING_MISSING_CHARACTER,
                $quote
            );
        }
        
        return substr($this->_value, $firstPos, $this->_pos++ - $firstPos);
    }
    
    /**
     * Skips specified operator.
     * 
     * @param string $operatorChar The operator character.
     * 
     * @return none
     * 
     * @throws \RuntimeException
     */
    private function _skipOperator($operatorChar)
    {
        if ($this->_value[$this->_pos] != $operatorChar) {
            // Character was expected.
            throw $this->_createException(
                $this->_pos,
                Resources::MISSING_CONNECTION_STRING_CHAR,
                $operatorChar
            );
        }
        
        $this->_pos++;
    }
}

/**
 * State of the connection string parser.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ParserState
{
    const EXPECT_KEY        = 'ExpectKey';
    const EXPECT_ASSIGNMENT = 'ExpectAssignment';
    const EXPECT_VALUE      = 'ExpectValue';
    const EXPECT_SEPARATOR  = 'ExpectSeparator';
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;

/**
 * Holder for default connection string sources used in CloudConfigurationManager.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ConnectionStringSource
{
    /**
     * The list of all sources which comes as default.
     * 
     * @var type 
     */
    private static $_defaultSources;
    
    /**
     * @var boolean
     */
    private static $_isInitialized;
    
    /**
     * Environment variable source name.
     */
    const ENVIRONMENT_SOURCE = 'environment_source';
    
    /**
     * Initializes the default sources.
     * 
     * @return none
     */
    private static function _init()
    {
        if (!self::$_isInitialized) {
            self::$_defaultSources = array(
                self::ENVIRONMENT_SOURCE => array(__CLASS__, 'environmentSource')
            );
            self::$_isInitialized  = true;
        }        
    }
    
    /**
     * Gets a connection string value from the system environment.
     * 
     * @param string $key The connection string name.
     * 
     * @return string
     */
    public static function environmentSource($key)
    {
        Validate::isString($key, 'key');
        
        return getenv($key);
    }
    
    /**
     * Gets list of default sources.
     * 
     * @return array
     */
    public static function getDefaultSources()
    {
        self::_init();
        return self::$_defaultSources;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;

/**
 * Interface for service with filers.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
interface FilterableService
{
    /**
    * Adds new filter to proxy object and returns new BlobRestProxy with
    * that filter.
    *
    * @param MicrosoftAzure\Storage\Common\Internal\IServiceFilter $filter Filter to add for 
    * the pipeline.
    * 
    * @return mix.
    */
    public function withFilter($filter);
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Filters;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\IServiceFilter;
use MicrosoftAzure\Storage\Common\Internal\HttpFormatter;
use GuzzleHttp\Psr7;

/**
 * Adds authentication header to the http request object.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AuthenticationFilter implements IServiceFilter
{
    /**
     * @var MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme
     */
    private $_authenticationScheme;

    /**
     * Creates AuthenticationFilter with the passed scheme.
     * 
     * @param StorageAuthScheme $authenticationScheme The authentication scheme.
     */
    public function __construct($authenticationScheme)
    {
        $this->_authenticationScheme = $authenticationScheme;
    }
    
    /**
     * Adds authentication header to the request headers.
     *
     * @param \GuzzleHttp\Psr7\Request $request HTTP request object.
     * 
     * @return \GuzzleHttp\Psr7\Request
     */
    public function handleRequest($request)
    {    
        $requestHeaders = HttpFormatter::formatHeaders($request->getHeaders());
        
        $signedKey = $this->_authenticationScheme->getAuthorizationHeader(
            $requestHeaders, $request->getUri(),
            \GuzzleHttp\Psr7\parse_query($request->getUri()->getQuery()), $request->getMethod()
        );
        return $request->withHeader(Resources::AUTHENTICATION, $signedKey);
    }
    
    /**
     * Does nothing with the response.
     *
     * @param \GuzzleHttp\Psr7\Request  $request  HTTP request object.
     * @param \GuzzleHttp\Psr7\Response $response HTTP response object.
     *
     * @return \GuzzleHttp\Psr7\Response
     */
    public function handleResponse($request, $response)
    {
        // Do nothing with the response.
        return $response;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Filters;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\IServiceFilter;

/**
 * Adds date header to the http request.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DateFilter implements IServiceFilter
{   
    /**
     * Adds date (in GMT format) header to the request headers.
     *
     * @param \GuzzleHttp\Psr7\Request $request HTTP request object.
     * 
     * @return \GuzzleHttp\Psr7\Request
     */
    public function handleRequest($request)
    {
        $date = gmdate(Resources::AZURE_DATE_FORMAT, time());
        return $request->withHeader(Resources::DATE, $date);
    }
    
    /**
     * Does nothing with the response.
     *
     * @param \GuzzleHttp\Psr7\Request  $request  HTTP request object.
     * @param \GuzzleHttp\Psr7\Response $response HTTP response object.
     *
     * @return \GuzzleHttp\Psr7\Request\Response
     */
    public function handleResponse($request, $response)
    {
        // Do nothing with the response.
        return $response;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Filters;

/**
 * The exponential retry policy.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ExponentialRetryPolicy extends RetryPolicy
{
    /**
     * @var integer
     */
    private $_deltaBackoffIntervalInMs;
    
    /**
     * @var integer
     */
    private $_maximumAttempts;
    
    /**
     * @var integer
     */
    private $_resolvedMaxBackoff;
    
    /**
     * @var integer
     */
    private $_resolvedMinBackoff;
    
    /**
     * @var array
     */
    private $_retryableStatusCodes;
    
    /**
     * Initializes new object from ExponentialRetryPolicy.
     * 
     * @param array   $retryableStatusCodes The retryable status codes.
     * @param integer $deltaBackoff         The backoff time delta.
     * @param integer $maximumAttempts      The number of max attempts.
     */
    public function __construct($retryableStatusCodes, 
        $deltaBackoff = parent::DEFAULT_CLIENT_BACKOFF, 
        $maximumAttempts = parent::DEFAULT_CLIENT_RETRY_COUNT
    ) {
        $this->_deltaBackoffIntervalInMs = $deltaBackoff;
        $this->_maximumAttempts          = $maximumAttempts;
        $this->_resolvedMaxBackoff       = parent::DEFAULT_MAX_BACKOFF;
        $this->_resolvedMinBackoff       = parent::DEFAULT_MIN_BACKOFF;
        $this->_retryableStatusCodes     = $retryableStatusCodes;
        sort($retryableStatusCodes);
    }
    
    /**
     * Indicates if there should be a retry or not.
     * 
     * @param integer                   $retryCount The retry count.
     * @param \GuzzleHttp\Psr7\Response $response   The HTTP response object.
     * 
     * @return boolean
     */
    public function shouldRetry($retryCount, $response)
    {
        if (  $retryCount >= $this->_maximumAttempts
            || array_search($response->getStatusCode(), $this->_retryableStatusCodes)
            || is_null($response)     
        ) {
            return false;
        } else {
            return true;
        }
    }
    
    /**
     * Calculates the backoff for the retry policy.
     * 
     * @param integer                   $retryCount The retry count.
     * @param \GuzzleHttp\Psr7\Response $response   The HTTP response object.
     * 
     * @return integer
     */
    public function calculateBackoff($retryCount, $response)
    {
        // Calculate backoff Interval between 80% and 120% of the desired
        // backoff, multiply by 2^n -1 for
        // exponential
        $incrementDelta   = (int) (pow(2, $retryCount) - 1);
        $boundedRandDelta = (int) ($this->_deltaBackoffIntervalInMs * 0.8)
            + mt_rand(
                0,
                (int) ($this->_deltaBackoffIntervalInMs * 1.2)
                - (int) ($this->_deltaBackoffIntervalInMs * 0.8)
            );
        $incrementDelta  *= $boundedRandDelta;

        // Enforce max / min backoffs
        return min(
            $this->_resolvedMinBackoff + $incrementDelta,
            $this->_resolvedMaxBackoff
        );
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Filters;
use MicrosoftAzure\Storage\Common\Internal\IServiceFilter;

/**
 * Adds all passed headers to the HTTP request headers.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class HeadersFilter implements IServiceFilter
{
    /**
     * @var array
     */
    private $_headers;
    
    /**
     * Constructor
     * 
     * @param array $headers static headers to be added.
     * 
     * @return HeadersFilter
     */
    public function __construct($headers)
    {
        $this->_headers = $headers;
    }
    
    /**
     * Adds static header(s) to the HTTP request headers
     *
     * @param \GuzzleHttp\Psr7\Request $request HTTP request object.
     * 
     * @return \GuzzleHttp\Psr7\Request
     */
    public function handleRequest($request)
    {
        $result = $request;
        
        foreach ($this->_headers as $key => $value) {
            $headers = $request->getHeaders();
            if (!array_key_exists($key, $headers)) {
                $result = $result->withHeader($key, $value);
            }
        }
        
        return $result;
    }
    
    /**
     * Does nothing with the response.
     *
     * @param \GuzzleHttp\Psr7\Request  $request  HTTP request object.
     * @param \GuzzleHttp\Psr7\Response $response HTTP response object.
     *
     * @return \GuzzleHttp\Psr7\Response
     */
    public function handleResponse($request, $response)
    {
        // Do nothing with the response.
        return $response;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Filters;

/**
 * The retry policy abstract class.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
abstract class RetryPolicy
{
    const DEFAULT_CLIENT_BACKOFF     = 30000;
    const DEFAULT_CLIENT_RETRY_COUNT = 3;
    const DEFAULT_MAX_BACKOFF        = 90000;
    const DEFAULT_MIN_BACKOFF        = 300;
    
    /**
     * Indicates if there should be a retry or not.
     * 
     * @param integer                   $retryCount The retry count.
     * @param \GuzzleHttp\Psr7\Response $response   The HTTP response object.
     * 
     * @return boolean
     */
    public abstract function shouldRetry($retryCount, $response);
    
    /**
     * Calculates the backoff for the retry policy.
     * 
     * @param integer                   $retryCount The retry count.
     * @param \GuzzleHttp\Psr7\Response $response   The HTTP response object.
     * 
     * @return integer
     */
    public abstract function calculateBackoff($retryCount, $response);
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Filters;
use MicrosoftAzure\Storage\Common\Internal\IServiceFilter;
use GuzzleHttp\Client;

/**
 * Short description
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class RetryPolicyFilter implements IServiceFilter
{
    /**
     * @var RetryPolicy
     */
    private $_retryPolicy;
    
    /**
     * @var \GuzzleHttp\Client
     */
    private $_client;
    
    /**
     * Initializes new object from RetryPolicyFilter.
     * 
     * @param \GuzzleHttp\Client $client      The http client to send request.
     * @param RetryPolicy        $retryPolicy The retry policy object.
     */
    public function __construct($client, $retryPolicy)
    {
        $this->_client = $client;
        $this->_retryPolicy = $retryPolicy;
    }

    /**
     * Handles the request before sending.
     * 
     * @param \GuzzleHttp\Psr7\Request $request The HTTP request.
     * 
     * @return \GuzzleHttp\Psr7\Request
     */
    public function handleRequest($request)
    {
        return $request;
    }

    /**
     * Handles the response after sending.
     * 
     * @param \GuzzleHttp\Psr7\Request  $request  The HTTP request.
     * @param \GuzzleHttp\Psr7\Response $response The HTTP response.
     * 
     * @return \GuzzleHttp\Psr7\Response
     */
    public function handleResponse($request, $response)
    {
        for ($retryCount = 0;; $retryCount++) {
            $shouldRetry = $this->_retryPolicy->shouldRetry(
                $retryCount,
                $response
            );
            
            if (!$shouldRetry) {
                return $response;
            }
            
            // Backoff for some time according to retry policy
            $backoffTime = $this->_retryPolicy->calculateBackoff(
                $retryCount,
                $response
            );
            sleep($backoffTime * 0.001);
            $response = $this->_client->send($request);
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Http
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Http;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Holds basic elements for making HTTP call.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Http
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class HttpCallContext
{
    /**
     * The HTTP method used to make this call.
     * 
     * @var string
     */
    private $_method;    
    
    /**
     * HTTP request headers.
     * 
     * @var array
     */
    private $_headers;
    
    /**
     * The URI query parameters.
     * 
     * @var array
     */
    private $_queryParams;

    /** 
     * The HTTP POST parameters.
     * 
     * @var array.
     */
    private $_postParameters;
    
    /**
     * @var string
     */
    private $_uri;
    
    /**
     * The URI path.
     * 
     * @var string
     */
    private $_path;
    
    /**
     * The expected status codes.
     * 
     * @var array
     */
    private $_statusCodes;
    
    /**
     * The HTTP request body.
     * 
     * @var string
     */
    private $_body;
    
    /**
     * Default constructor.
     */
    public function __construct()
    {
        $this->_method         = null;
        $this->_body           = null;
        $this->_path           = null;
        $this->_uri            = null;
        $this->_queryParams    = array();
        $this->_postParameters = array();
        $this->_statusCodes    = array();
        $this->_headers        = array();
    }
    
    /**
     * Gets method.
     * 
     * @return string
     */
    public function getMethod()
    {
        return $this->_method;
    }
    
    /**
     * Sets method.
     * 
     * @param string $method The method value.
     * 
     * @return none
     */
    public function setMethod($method)
    {
        Validate::isString($method, 'method');
        
        $this->_method = $method;
    }
    
    /**
     * Gets headers.
     * 
     * @return array
     */
    public function getHeaders()
    {
        return $this->_headers;
    }
    
    /**
     * Sets headers.
     * 
     * Ignores the header if its value is empty.
     * 
     * @param array $headers The headers value.
     * 
     * @return none
     */
    public function setHeaders($headers)
    {
        $this->_headers = array();
        foreach ($headers as $key => $value) {
            $this->addHeader($key, $value);
        }
    }
    
    /**
     * Gets queryParams.
     * 
     * @return array
     */
    public function getQueryParameters()
    {
        return $this->_queryParams;
    }
    
    /**
     * Sets queryParams.
     * 
     * Ignores the query variable if its value is empty.
     * 
     * @param array $queryParams The queryParams value.
     * 
     * @return none
     */
    public function setQueryParameters($queryParams)
    {
        $this->_queryParams = array();
        foreach ($queryParams as $key => $value) {
            $this->addQueryParameter($key, $value);
        }
    }
    
    /**
     * Gets uri.
     * 
     * @return string
     */
    public function getUri()
    {
        return $this->_uri;
    }
    
    /**
     * Sets uri.
     * 
     * @param string $uri The uri value.
     * 
     * @return none
     */
    public function setUri($uri)
    {
        Validate::isString($uri, 'uri');
        
        $this->_uri = $uri;
    }
    
    /**
     * Gets path.
     * 
     * @return string
     */
    public function getPath()
    {
        return $this->_path;
    }
    
    /**
     * Sets path.
     * 
     * @param string $path The path value.
     * 
     * @return none
     */
    public function setPath($path)
    {
        Validate::isString($path, 'path');
        
        $this->_path = $path;
    }
    
    /**
     * Gets statusCodes.
     * 
     * @return array
     */
    public function getStatusCodes()
    {
        return $this->_statusCodes;
    }
    
    /**
     * Sets statusCodes.
     * 
     * @param array $statusCodes The statusCodes value.
     * 
     * @return none
     */
    public function setStatusCodes($statusCodes)
    {
        $this->_statusCodes = array();
        foreach ($statusCodes as $value) {
            $this->addStatusCode($value);
        }
    }
    
    /**
     * Gets body.
     * 
     * @return string
     */
    public function getBody()
    {
        return $this->_body;
    }
    
    /**
     * Sets body.
     * 
     * @param string $body The body value.
     * 
     * @return none
     */
    public function setBody($body)
    {
        Validate::isString($body, 'body');
        
        $this->_body = $body;
    }
    
    /**
     * Adds or sets header pair.
     * 
     * @param string $name  The HTTP header name.
     * @param string $value The HTTP header value.
     * 
     * @return none
     */
    public function addHeader($name, $value)
    {
        Validate::isString($name, 'name');
        Validate::isString($value, 'value');
        
        $this->_headers[$name] = $value;
    }
    
    /**
     * Adds or sets header pair.
     * 
     * Ignores header if it's value satisfies empty().
     * 
     * @param string $name  The HTTP header name.
     * @param string $value The HTTP header value.
     * 
     * @return none
     */
    public function addOptionalHeader($name, $value)
    {
        Validate::isString($name, 'name');
        Validate::isString($value, 'value');
        
        if (!empty($value)) {
            $this->_headers[$name] = $value;
        }
    }
    
    /**
     * Removes header from the HTTP request headers.
     * 
     * @param string $name The HTTP header name.
     * 
     * @return none
     */
    public function removeHeader($name)
    {
        Validate::isString($name, 'name');
        Validate::notNullOrEmpty($name, 'name');
        
        unset($this->_headers[$name]);
    }
    
    /**
     * Adds or sets query parameter pair.
     * 
     * @param string $name  The URI query parameter name.
     * @param string $value The URI query parameter value.
     * 
     * @return none
     */
    public function addQueryParameter($name, $value)
    {
        Validate::isString($name, 'name');
        Validate::isString($value, 'value');
        
        $this->_queryParams[$name] = $value;
    }
    
    /** 
     * Gets HTTP POST parameters. 
     *
     * @return array
     */
    public function getPostParameters()
    {   
        return $this->_postParameters;
    }

    /** 
     * Sets HTTP POST parameters.
     * 
     * @param array $postParameters The HTTP POST parameters.
     * 
     * @return none
     */
    public function setPostParameters($postParameters)
    {
        Validate::isArray($postParameters, 'postParameters');
        $this->_postParameters = $postParameters;
    }
    
    /**
     * Adds or sets query parameter pair.
     * 
     * Ignores query parameter if it's value satisfies empty().
     * 
     * @param string $name  The URI query parameter name.
     * @param string $value The URI query parameter value.
     * 
     * @return none
     */
    public function addOptionalQueryParameter($name, $value)
    {
        Validate::isString($name, 'name');
        Validate::isString($value, 'value');
        
        if (!empty($value)) {
            $this->_queryParams[$name] = $value;
        }
    }
    
    /**
     * Adds status code to the expected status codes.
     * 
     * @param integer $statusCode The expected status code.
     * 
     * @return none
     */
    public function addStatusCode($statusCode)
    {
        Validate::isInteger($statusCode, 'statusCode');
        
        $this->_statusCodes[] = $statusCode;
    }
    
    /**
     * Gets header value.
     * 
     * @param string $name The header name.
     * 
     * @return mix
     */
    public function getHeader($name)
    {
        return Utilities::tryGetValue($this->_headers, $name);
    }
    
    /**
     * Converts the context object to string.
     * 
     * @return string 
     */
    public function __toString()
    {
        $headers = Resources::EMPTY_STRING;
        $uri     = $this->_uri;
        
        if ($uri[strlen($uri)-1] != '/')
        {
            $uri = $uri.'/';
        }
        
        foreach ($this->_headers as $key => $value) {
            $headers .= "$key: $value\n";
        }
        
        $str  = "$this->_method $uri$this->_path HTTP/1.1\n$headers\n";
        $str .= "$this->_body";
        
        return $str;
    }
}


<?php
/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal;

class HttpFormatter
{
    /**
     * Convert a http headers array into an uniformed format for further process
     * 
     * @param array $headers headers for format
     * 
     * @return array
     */
    public static function formatHeaders($headers)
    {
        $result = array();
        foreach ($headers as $key => $value)
        {
            if (is_array($value) && count($value) == 1)
            {
                $result[strtolower($key)] = $value[0];
            }
            else 
            {
                $result[strtolower($key)] = $value;
            }
        }
        
        return $result;
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Exception thrown if an argument type does not match with the expected type. 
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class InvalidArgumentTypeException extends \InvalidArgumentException
{
    /**
     * Constructor.
     *
     * @param string $validType The valid type that should be provided by the user.
     * @param string $name      The parameter name.
     * 
     * @return MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException
     */
    public function __construct($validType, $name = null)
    {
        parent::__construct(
            sprintf(Resources::INVALID_PARAM_MSG, $name, $validType)
        );
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;

/**
 * ServceFilter is called when the sending the request and after receiving the 
 * response.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
interface IServiceFilter
{
    /**
     * Processes HTTP request before send.
     *
     * @param mix $request HTTP request object.
     * 
     * @return mix processed HTTP request object.
     */
    public function handleRequest($request);

    /**
     * Processes HTTP response after send.
     *
     * @param mix $request  HTTP request object.
     * @param mix $response HTTP response object.
     * 
     * @return mix processed HTTP response object.
     */
    public function handleResponse($request, $response);
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;

/**
 * Logger class for debugging purpose.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Logger
{
    /**
     * @var string
     */
    private static $_filePath;

    /**
     * Logs $var to file.
     *
     * @param mix    $var The data to log.
     * @param string $tip The help message.
     * 
     * @static
     * 
     * @return none
     */
    public static function log($var, $tip = Resources::EMPTY_STRING)
    {
        if (!empty($tip)) {
            error_log($tip . "\n", 3, self::$_filePath);
        }
        
        if (is_array($var) || is_object($var)) {
            error_log(print_r($var, true), 3, self::$_filePath);
        } else {
            error_log($var . "\n", 3, self::$_filePath);
        }
    }
    
    /**
     * Sets file path to use.
     *
     * @param string $filePath The log file path.
     * 
     * @static
     * 
     * @return none
     */
    public static function setLogFile($filePath)
    {
        self::$_filePath = $filePath;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal;

/**
 * Project resources.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Resources
{
    // @codingStandardsIgnoreStart

    // Connection strings
    const USE_DEVELOPMENT_STORAGE_NAME = 'UseDevelopmentStorage';
    const DEVELOPMENT_STORAGE_PROXY_URI_NAME = 'DevelopmentStorageProxyUri';
    const DEFAULT_ENDPOINTS_PROTOCOL_NAME = 'DefaultEndpointsProtocol';
    const ACCOUNT_NAME_NAME = 'AccountName';
    const ACCOUNT_KEY_NAME = 'AccountKey';
    const BLOB_ENDPOINT_NAME = 'BlobEndpoint';
    const QUEUE_ENDPOINT_NAME = 'QueueEndpoint';
    const TABLE_ENDPOINT_NAME = 'TableEndpoint';
    const SHARED_ACCESS_SIGNATURE_NAME = 'SharedAccessSignature';
    const DEV_STORE_NAME = 'devstoreaccount1';
    const DEV_STORE_KEY = 'Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==';
    const BLOB_BASE_DNS_NAME = 'blob.core.windows.net';
    const QUEUE_BASE_DNS_NAME = 'queue.core.windows.net';
    const TABLE_BASE_DNS_NAME = 'table.core.windows.net';
    const DEV_STORE_CONNECTION_STRING = 'BlobEndpoint=127.0.0.1:10000;QueueEndpoint=127.0.0.1:10001;TableEndpoint=127.0.0.1:10002;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==';
    const SUBSCRIPTION_ID_NAME = 'SubscriptionID';
    const CERTIFICATE_PATH_NAME = 'CertificatePath';

    // Messages
    const INVALID_TYPE_MSG = 'The provided variable should be of type: ';
    const INVALID_META_MSG = 'Metadata cannot contain newline characters.';
    const AZURE_ERROR_MSG = "Fail:\nCode: %s\nValue: %s\ndetails (if any): %s.";
    const NOT_IMPLEMENTED_MSG = 'This method is not implemented.';
    const NULL_OR_EMPTY_MSG = "'%s' can't be NULL or empty.";
    const NULL_MSG = "'%s' can't be NULL.";
    const INVALID_URL_MSG = 'Provided URL is invalid.';
    const INVALID_HT_MSG = 'The header type provided is invalid.';
    const INVALID_EDM_MSG = 'The provided EDM type is invalid.';
    const INVALID_PROP_MSG = 'One of the provided properties is not an instance of class Property';
    const INVALID_ENTITY_MSG = 'The provided entity object is invalid.';
    const INVALID_VERSION_MSG = 'Server does not support any known protocol versions.';
    const INVALID_BO_TYPE_MSG = 'Batch operation name is not supported or invalid.';
    const INVALID_BO_PN_MSG = 'Batch operation parameter is not supported.';
    const INVALID_OC_COUNT_MSG = 'Operations and contexts must be of same size.';
    const INVALID_EXC_OBJ_MSG = 'Exception object type should be ServiceException.';
    const NULL_TABLE_KEY_MSG = 'Partition and row keys can\'t be NULL.';
    const BATCH_ENTITY_DEL_MSG = 'The entity was deleted successfully.';
    const INVALID_PROP_VAL_MSG = "'%s' property value must satisfy %s.";
    const INVALID_PARAM_MSG = "The provided variable '%s' should be of type '%s'";
    const INVALID_STRING_LENGTH = "The provided variable '%s' should be of %s characters long";
    const INVALID_BTE_MSG = "The blob block type must exist in %s";
    const INVALID_BLOB_PAT_MSG = 'The provided access type is invalid.';
    const INVALID_SVC_PROP_MSG = 'The provided service properties is invalid.';
    const UNKNOWN_SRILZER_MSG = 'The provided serializer type is unknown';
    const INVALID_CREATE_SERVICE_OPTIONS_MSG = 'Must provide valid location or affinity group.';
    const INVALID_UPDATE_SERVICE_OPTIONS_MSG = 'Must provide either description or label.';
    const INVALID_CONFIG_MSG = 'Config object must be of type Configuration';
    const INVALID_ACH_MSG = 'The provided access condition header is invalid';
    const INVALID_RECEIVE_MODE_MSG = 'The receive message option is in neither RECEIVE_AND_DELETE nor PEEK_LOCK mode.';
    const INVALID_CONFIG_URI = "The provided URI '%s' is invalid. It has to pass the check 'filter_var(<user_uri>, FILTER_VALIDATE_URL)'.";
    const INVALID_CONFIG_VALUE = "The provided config value '%s' does not belong to the valid values subset:\n%s";
    const INVALID_ACCOUNT_KEY_FORMAT = "The provided account key '%s' is not a valid base64 string. It has to pass the check 'base64_decode(<user_account_key>, true)'.";
    const MISSING_CONNECTION_STRING_SETTINGS = "The provided connection string '%s' does not have complete configuration settings.";
    const INVALID_CONNECTION_STRING_SETTING_KEY = "The setting key '%s' is not found in the expected configuration setting keys:\n%s";
    const INVALID_CERTIFICATE_PATH = "The provided certificate path '%s' is invalid.";
    const INSTANCE_TYPE_VALIDATION_MSG = 'The type of %s is %s but is expected to be %s.';
    const MISSING_CONNECTION_STRING_CHAR = "Missing %s character";
    const ERROR_PARSING_STRING = "'%s' at position %d.";
    const INVALID_CONNECTION_STRING = "Argument '%s' is not a valid connection string: '%s'";
    const ERROR_CONNECTION_STRING_MISSING_KEY = 'Missing key name';
    const ERROR_CONNECTION_STRING_EMPTY_KEY = 'Empty key name';
    const ERROR_CONNECTION_STRING_MISSING_CHARACTER = "Missing %s character";
    const ERROR_EMPTY_SETTINGS = 'No keys were found in the connection string';
    const MISSING_LOCK_LOCATION_MSG = 'The lock location of the brokered message is missing.';
    const INVALID_SLOT = "The provided deployment slot '%s' is not valid. Only 'staging' and 'production' are accepted.";
    const INVALID_DEPLOYMENT_LOCATOR_MSG = 'A slot or deployment name must be provided.';
    const INVALID_CHANGE_MODE_MSG = "The change mode must be 'Auto' or 'Manual'. Use Mode class constants for that purpose.";
    const INVALID_DEPLOYMENT_STATUS_MSG = "The change mode must be 'Running' or 'Suspended'. Use DeploymentStatus class constants for that purpose.";
    const ERROR_OAUTH_GET_ACCESS_TOKEN = 'Unable to get oauth access token for endpoint \'%s\', account name \'%s\'';
    const ERROR_OAUTH_SERVICE_MISSING = 'OAuth service missing for account name \'%s\'';
    const ERROR_METHOD_NOT_FOUND = 'Method \'%s\' not found in object class \'%s\'';
    const ERROR_INVALID_DATE_STRING = 'Parameter \'%s\' is not a date formatted string \'%s\'';

    // HTTP Headers
    const X_MS_HEADER_PREFIX                 = 'x-ms-';
    const X_MS_META_HEADER_PREFIX            = 'x-ms-meta-';
    const X_MS_APPROXIMATE_MESSAGES_COUNT    = 'x-ms-approximate-messages-count';
    const X_MS_POPRECEIPT                    = 'x-ms-popreceipt';
    const X_MS_TIME_NEXT_VISIBLE             = 'x-ms-time-next-visible';
    const X_MS_BLOB_PUBLIC_ACCESS            = 'x-ms-blob-public-access';
    const X_MS_VERSION                       = 'x-ms-version';
    const X_MS_DATE                          = 'x-ms-date';
    const X_MS_BLOB_SEQUENCE_NUMBER          = 'x-ms-blob-sequence-number';
    const X_MS_BLOB_SEQUENCE_NUMBER_ACTION   = 'x-ms-sequence-number-action';
    const X_MS_BLOB_TYPE                     = 'x-ms-blob-type';
    const X_MS_BLOB_CONTENT_TYPE             = 'x-ms-blob-content-type';
    const X_MS_BLOB_CONTENT_ENCODING         = 'x-ms-blob-content-encoding';
    const X_MS_BLOB_CONTENT_LANGUAGE         = 'x-ms-blob-content-language';
    const X_MS_BLOB_CONTENT_MD5              = 'x-ms-blob-content-md5';
    const X_MS_BLOB_CACHE_CONTROL            = 'x-ms-blob-cache-control';
    const X_MS_BLOB_CONTENT_LENGTH           = 'x-ms-blob-content-length';
    const X_MS_COPY_SOURCE                   = 'x-ms-copy-source';
    const X_MS_RANGE                         = 'x-ms-range';
    const X_MS_RANGE_GET_CONTENT_MD5         = 'x-ms-range-get-content-md5';
    const X_MS_LEASE_DURATION                = 'x-ms-lease-duration';
    const X_MS_LEASE_ID                      = 'x-ms-lease-id';
    const X_MS_LEASE_TIME                    = 'x-ms-lease-time';
    const X_MS_LEASE_STATUS                  = 'x-ms-lease-status';
    const X_MS_LEASE_ACTION                  = 'x-ms-lease-action';
    const X_MS_DELETE_SNAPSHOTS              = 'x-ms-delete-snapshots';
    const X_MS_PAGE_WRITE                    = 'x-ms-page-write';
    const X_MS_SNAPSHOT                      = 'x-ms-snapshot';
    const X_MS_SOURCE_IF_MODIFIED_SINCE      = 'x-ms-source-if-modified-since';
    const X_MS_SOURCE_IF_UNMODIFIED_SINCE    = 'x-ms-source-if-unmodified-since';
    const X_MS_SOURCE_IF_MATCH               = 'x-ms-source-if-match';
    const X_MS_SOURCE_IF_NONE_MATCH          = 'x-ms-source-if-none-match';
    const X_MS_SOURCE_LEASE_ID               = 'x-ms-source-lease-id';
    const X_MS_CONTINUATION_NEXTTABLENAME    = 'x-ms-continuation-nexttablename';
    const X_MS_CONTINUATION_NEXTPARTITIONKEY = 'x-ms-continuation-nextpartitionkey';
    const X_MS_CONTINUATION_NEXTROWKEY       = 'x-ms-continuation-nextrowkey';
    const X_MS_REQUEST_ID                    = 'x-ms-request-id';
    const ETAG                               = 'etag';
    const LAST_MODIFIED                      = 'last-modified';
    const DATE                               = 'date';
    const AUTHENTICATION                     = 'authorization';
    const WRAP_AUTHORIZATION                 = 'WRAP access_token="%s"';
    const CONTENT_ENCODING                   = 'content-encoding';
    const CONTENT_LANGUAGE                   = 'content-language';
    const CONTENT_LENGTH                     = 'content-length';
    const CONTENT_LENGTH_NO_SPACE            = 'contentlength';
    const CONTENT_MD5                        = 'content-md5';
    const CONTENT_TYPE                       = 'content-type';
    const CONTENT_ID                         = 'content-id';
    const CONTENT_RANGE                      = 'content-range';
    const CACHE_CONTROL                      = 'cache-control';
    const IF_MODIFIED_SINCE                  = 'if-modified-since';
    const IF_MATCH                           = 'if-match';
    const IF_NONE_MATCH                      = 'if-none-match';
    const IF_UNMODIFIED_SINCE                = 'if-unmodified-since';
    const RANGE                              = 'range';
    const DATA_SERVICE_VERSION               = 'dataserviceversion';
    const MAX_DATA_SERVICE_VERSION           = 'maxdataserviceversion';
    const ACCEPT_HEADER                      = 'accept';
    const ACCEPT_CHARSET                     = 'accept-charset';
    const USER_AGENT                         = 'User-Agent';

    // Type
    const QUEUE_TYPE_NAME              = 'IQueue';
    const BLOB_TYPE_NAME               = 'IBlob';
    const TABLE_TYPE_NAME              = 'ITable';

    // WRAP
    const WRAP_ACCESS_TOKEN            = 'wrap_access_token';
    const WRAP_ACCESS_TOKEN_EXPIRES_IN = 'wrap_access_token_expires_in';
    const WRAP_NAME                    = 'wrap_name';
    const WRAP_PASSWORD                = 'wrap_password';
    const WRAP_SCOPE                   = 'wrap_scope';

    // HTTP Methods
    const HTTP_GET    = 'GET';
    const HTTP_PUT    = 'PUT';
    const HTTP_POST   = 'POST';
    const HTTP_HEAD   = 'HEAD';
    const HTTP_DELETE = 'DELETE';
    const HTTP_MERGE  = 'MERGE';

    // Misc
    const EMPTY_STRING           = '';
    const SEPARATOR              = ',';
    const AZURE_DATE_FORMAT      = 'D, d M Y H:i:s T';
    const TIMESTAMP_FORMAT       = 'Y-m-d H:i:s';
    const EMULATED               = 'EMULATED';
    const EMULATOR_BLOB_URI      = '127.0.0.1:10000';
    const EMULATOR_QUEUE_URI     = '127.0.0.1:10001';
    const EMULATOR_TABLE_URI     = '127.0.0.1:10002';
    const ASTERISK               = '*';
    const SERVICE_MANAGEMENT_URL = 'https://management.core.windows.net';
    const HTTP_SCHEME            = 'http';
    const HTTPS_SCHEME           = 'https';
    const SETTING_NAME = 'SettingName';
    const SETTING_CONSTRAINT = 'SettingConstraint';
    const DEV_STORE_URI = 'http://127.0.0.1';
    const SERVICE_URI_FORMAT = "%s://%s.%s";
    const WRAP_ENDPOINT_URI_FORMAT = "https://%s-sb.accesscontrol.windows.net/WRAPv0.9";

    // Xml Namespaces
    const WA_XML_NAMESPACE   = 'http://schemas.microsoft.com/windowsazure';
    const ATOM_XML_NAMESPACE = 'http://www.w3.org/2005/Atom';
    const DS_XML_NAMESPACE   = 'http://schemas.microsoft.com/ado/2007/08/dataservices';
    const DSM_XML_NAMESPACE  = 'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata';
    const XSI_XML_NAMESPACE  = 'http://www.w3.org/2001/XMLSchema-instance';


    // Header values
    const SDK_VERSION                                   = '0.10.0';
    const STORAGE_API_LATEST_VERSION                    = '2015-04-05';
    const DATA_SERVICE_VERSION_VALUE                    = '1.0;NetFx';
    const MAX_DATA_SERVICE_VERSION_VALUE                = '2.0;NetFx';
    const ACCEPT_HEADER_VALUE                           = 'application/atom+xml,application/xml';
    const ATOM_ENTRY_CONTENT_TYPE                       = 'application/atom+xml;type=entry;charset=utf-8';
    const ATOM_FEED_CONTENT_TYPE                        = 'application/atom+xml;type=feed;charset=utf-8';
    const ACCEPT_CHARSET_VALUE                          = 'utf-8';
    const INT32_MAX                                     = 2147483647;

    // Query parameter names
    const QP_PREFIX             = 'Prefix';
    const QP_MAX_RESULTS        = 'MaxResults';
    const QP_METADATA           = 'Metadata';
    const QP_MARKER             = 'Marker';
    const QP_NEXT_MARKER        = 'NextMarker';
    const QP_COMP               = 'comp';
    const QP_VISIBILITY_TIMEOUT = 'visibilitytimeout';
    const QP_POPRECEIPT         = 'popreceipt';
    const QP_NUM_OF_MESSAGES    = 'numofmessages';
    const QP_PEEK_ONLY          = 'peekonly';
    const QP_MESSAGE_TTL        = 'messagettl';
    const QP_INCLUDE            = 'include';
    const QP_TIMEOUT            = 'timeout';
    const QP_DELIMITER          = 'Delimiter';
    const QP_REST_TYPE          = 'restype';
    const QP_SNAPSHOT           = 'snapshot';
    const QP_BLOCKID            = 'blockid';
    const QP_BLOCK_LIST_TYPE    = 'blocklisttype';
    const QP_SELECT             = '$select';
    const QP_TOP                = '$top';
    const QP_SKIP               = '$skip';
    const QP_FILTER             = '$filter';
    const QP_NEXT_TABLE_NAME    = 'NextTableName';
    const QP_NEXT_PK            = 'NextPartitionKey';
    const QP_NEXT_RK            = 'NextRowKey';
    const QP_ACTION             = 'action';
    const QP_EMBED_DETAIL       = 'embed-detail';

    // Query parameter values
    const QPV_REGENERATE = 'regenerate';
    const QPV_CONFIG     = 'config';
    const QPV_STATUS     = 'status';
    const QPV_UPGRADE    = 'upgrade';
    const QPV_WALK_UPGRADE_DOMAIN = 'walkupgradedomain';
    const QPV_REBOOT = 'reboot';
    const QPV_REIMAGE = 'reimage';
    const QPV_ROLLBACK = 'rollback';

    // Request body content types
    const URL_ENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded';
    const XML_CONTENT_TYPE         = 'application/xml';
    const JSON_CONTENT_TYPE        = 'application/json';
    const BINARY_FILE_TYPE         = 'application/octet-stream';
    const XML_ATOM_CONTENT_TYPE    = 'application/atom+xml';
    const HTTP_TYPE                = 'application/http';
    const MULTIPART_MIXED_TYPE     = 'multipart/mixed';

    // Common used XML tags
    const XTAG_ATTRIBUTES                 = '@attributes';
    const XTAG_NAMESPACE                  = '@namespace';
    const XTAG_LABEL                      = 'Label';
    const XTAG_NAME                       = 'Name';
    const XTAG_DESCRIPTION                = 'Description';
    const XTAG_LOCATION                   = 'Location';
    const XTAG_AFFINITY_GROUP             = 'AffinityGroup';
    const XTAG_HOSTED_SERVICES            = 'HostedServices';
    const XTAG_STORAGE_SERVICES           = 'StorageServices';
    const XTAG_STORAGE_SERVICE            = 'StorageService';
    const XTAG_DISPLAY_NAME               = 'DisplayName';
    const XTAG_SERVICE_NAME               = 'ServiceName';
    const XTAG_URL                        = 'Url';
    const XTAG_ID                         = 'ID';
    const XTAG_STATUS                     = 'Status';
    const XTAG_HTTP_STATUS_CODE           = 'HttpStatusCode';
    const XTAG_CODE                       = 'Code';
    const XTAG_MESSAGE                    = 'Message';
    const XTAG_STORAGE_SERVICE_PROPERTIES = 'StorageServiceProperties';
    const XTAG_SERVICE_ENDPOINT           = 'ServiceEndpoint';
    const XTAG_ENDPOINT                   = 'Endpoint';
    const XTAG_ENDPOINTS                  = 'Endpoints';
    const XTAG_PRIMARY                    = 'Primary';
    const XTAG_SECONDARY                  = 'Secondary';
    const XTAG_KEY_TYPE                   = 'KeyType';
    const XTAG_STORAGE_SERVICE_KEYS       = 'StorageServiceKeys';
    const XTAG_ERROR                      = 'Error';
    const XTAG_HOSTED_SERVICE             = 'HostedService';
    const XTAG_HOSTED_SERVICE_PROPERTIES  = 'HostedServiceProperties';
    const XTAG_CREATE_HOSTED_SERVICE      = 'CreateHostedService';
    const XTAG_CREATE_STORAGE_SERVICE_INPUT = 'CreateStorageServiceInput';
    const XTAG_UPDATE_STORAGE_SERVICE_INPUT = 'UpdateStorageServiceInput';
    const XTAG_CREATE_AFFINITY_GROUP = 'CreateAffinityGroup';
    const XTAG_UPDATE_AFFINITY_GROUP = 'UpdateAffinityGroup';
    const XTAG_UPDATE_HOSTED_SERVICE = 'UpdateHostedService';
    const XTAG_PACKAGE_URL = 'PackageUrl';
    const XTAG_CONFIGURATION = 'Configuration';
    const XTAG_START_DEPLOYMENT = 'StartDeployment';
    const XTAG_TREAT_WARNINGS_AS_ERROR = 'TreatWarningsAsError';
    const XTAG_CREATE_DEPLOYMENT = 'CreateDeployment';
    const XTAG_DEPLOYMENT_SLOT = 'DeploymentSlot';
    const XTAG_PRIVATE_ID = 'PrivateID';
    const XTAG_ROLE_INSTANCE_LIST = 'RoleInstanceList';
    const XTAG_UPGRADE_DOMAIN_COUNT = 'UpgradeDomainCount';
    const XTAG_ROLE_LIST = 'RoleList';
    const XTAG_SDK_VERSION = 'SdkVersion';
    const XTAG_INPUT_ENDPOINT_LIST = 'InputEndpointList';
    const XTAG_LOCKED = 'Locked';
    const XTAG_ROLLBACK_ALLOWED = 'RollbackAllowed';
    const XTAG_UPGRADE_STATUS = 'UpgradeStatus';
    const XTAG_UPGRADE_TYPE = 'UpgradeType';
    const XTAG_CURRENT_UPGRADE_DOMAIN_STATE = 'CurrentUpgradeDomainState';
    const XTAG_CURRENT_UPGRADE_DOMAIN = 'CurrentUpgradeDomain';
    const XTAG_ROLE_NAME = 'RoleName';
    const XTAG_INSTANCE_NAME = 'InstanceName';
    const XTAG_INSTANCE_STATUS = 'InstanceStatus';
    const XTAG_INSTANCE_UPGRADE_DOMAIN = 'InstanceUpgradeDomain';
    const XTAG_INSTANCE_FAULT_DOMAIN = 'InstanceFaultDomain';
    const XTAG_INSTANCE_SIZE = 'InstanceSize';
    const XTAG_INSTANCE_STATE_DETAILS = 'InstanceStateDetails';
    const XTAG_INSTANCE_ERROR_CODE = 'InstanceErrorCode';
    const XTAG_OS_VERSION = 'OsVersion';
    const XTAG_ROLE_INSTANCE = 'RoleInstance';
    const XTAG_ROLE = 'Role';
    const XTAG_INPUT_ENDPOINT = 'InputEndpoint';
    const XTAG_VIP = 'Vip';
    const XTAG_PORT = 'Port';
    const XTAG_DEPLOYMENT = 'Deployment';
    const XTAG_DEPLOYMENTS = 'Deployments';
    const XTAG_REGENERATE_KEYS = 'RegenerateKeys';
    const XTAG_SWAP = 'Swap';
    const XTAG_PRODUCTION = 'Production';
    const XTAG_SOURCE_DEPLOYMENT = 'SourceDeployment';
    const XTAG_CHANGE_CONFIGURATION = 'ChangeConfiguration';
    const XTAG_MODE = 'Mode';
    const XTAG_UPDATE_DEPLOYMENT_STATUS = 'UpdateDeploymentStatus';
    const XTAG_ROLE_TO_UPGRADE = 'RoleToUpgrade';
    const XTAG_FORCE = 'Force';
    const XTAG_UPGRADE_DEPLOYMENT = 'UpgradeDeployment';
    const XTAG_UPGRADE_DOMAIN = 'UpgradeDomain';
    const XTAG_WALK_UPGRADE_DOMAIN = 'WalkUpgradeDomain';
    const XTAG_ROLLBACK_UPDATE_OR_UPGRADE = 'RollbackUpdateOrUpgrade';
    const XTAG_CONTAINER_NAME = 'ContainerName';
    const XTAG_ACCOUNT_NAME = 'AccountName';

    // PHP URL Keys
    const PHP_URL_SCHEME   = 'scheme';
    const PHP_URL_HOST     = 'host';
    const PHP_URL_PORT     = 'port';
    const PHP_URL_USER     = 'user';
    const PHP_URL_PASS     = 'pass';
    const PHP_URL_PATH     = 'path';
    const PHP_URL_QUERY    = 'query';
    const PHP_URL_FRAGMENT = 'fragment';

    // Status Codes
    const STATUS_OK                = 200;
    const STATUS_CREATED           = 201;
    const STATUS_ACCEPTED          = 202;
    const STATUS_NO_CONTENT        = 204;
    const STATUS_PARTIAL_CONTENT   = 206;
    const STATUS_MOVED_PERMANENTLY = 301;

    // @codingStandardsIgnoreEnd
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Base class for all REST proxies.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class RestProxy
{   
    /**
     * @var array
     */
    private $_filters;
    
    /**
     * @var MicrosoftAzure\Storage\Common\Internal\Serialization\ISerializer
     */
    protected $dataSerializer;
    
    /**
     * @var string
     */
    private $_uri;
    
    /**
     * Initializes new RestProxy object.
     *
     * @param ISerializer $dataSerializer The data serializer.
     * @param string      $uri            The uri of the service.
     */
    public function __construct($dataSerializer, $uri)
    {
        $this->_filters       = array();
        $this->dataSerializer = $dataSerializer;
        $this->_uri           = $uri;
    }
    
    /**
     * Gets HTTP filters that will process each request.
     * 
     * @return array
     */
    public function getFilters()
    {
        return $this->_filters;
    }

    /**
     * Gets the Uri of the service.
     * 
     * @return string
     */
    public function getUri()
    {
        return $this->_uri;
    }

    /** 
     * Sets the Uri of the service. 
     *
     * @param string $uri The URI of the request.
     * 
     * @return none
     */
    public function setUri($uri)
    {
        $this->_uri = $uri;
    }

    /**
     * Adds new filter to new service rest proxy object and returns that object back.
     *
     * @param MicrosoftAzure\Storage\Common\Internal\IServiceFilter $filter Filter to add for 
     * the pipeline.
     * 
     * @return RestProxy.
     */
    public function withFilter($filter)
    {
        $serviceProxyWithFilter             = clone $this;
        $serviceProxyWithFilter->_filters[] = $filter;

        return $serviceProxyWithFilter;
    }
    
    /**
     * Adds optional query parameter.
     * 
     * Doesn't add the value if it satisfies empty().
     * 
     * @param array  &$queryParameters The query parameters.
     * @param string $key              The query variable name.
     * @param string $value            The query variable value.
     * 
     * @return none
     */
    protected function addOptionalQueryParam(&$queryParameters, $key, $value)
    {
        Validate::isArray($queryParameters, 'queryParameters');
        Validate::isString($key, 'key');
        Validate::isString($value, 'value');
                
        if (!is_null($value) && Resources::EMPTY_STRING !== $value) {
            $queryParameters[$key] = $value;
        }
    }
    
    /**
     * Adds optional header.
     * 
     * Doesn't add the value if it satisfies empty().
     * 
     * @param array  &$headers The HTTP header parameters.
     * @param string $key      The HTTP header name.
     * @param string $value    The HTTP header value.
     * 
     * @return none
     */
    protected function addOptionalHeader(&$headers, $key, $value)
    {
        Validate::isArray($headers, 'headers');
        Validate::isString($key, 'key');
        Validate::isString($value, 'value');
                
        if (!is_null($value) && Resources::EMPTY_STRING !== $value) {
            $headers[$key] = $value;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Serialization;

/**
 * The serialization interface.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
interface ISerializer
{

    /** 
     * Serialize an object into a XML.
     * 
     * @param Object $targetObject The target object to be serialized. 
     * @param string $rootName     The name of the root.
     *
     * @return string
     */
    public static function objectSerialize($targetObject, $rootName);

    /**
     * Serializes given array. The array indices must be string to use them as
     * as element name.
     * 
     * @param array $array      The object to serialize represented in array.
     * @param array $properties The used properties in the serialization process.
     * 
     * @return string
     */
    public function serialize($array, $properties = null);

    
    /**
     * Unserializes given serialized string.
     * 
     * @param string $serialized The serialized object in string representation.
     * 
     * @return array
     */
    public function unserialize($serialized);
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal\Serialization;
use MicrosoftAzure\Storage\Common\Internal\Validate;
/**
 * Perform JSON serialization / deserialization
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class JsonSerializer implements ISerializer
{
    /**
     * Serialize an object with specified root element name.
     *
     * @param object $targetObject The target object.
     * @param string $rootName     The name of the root element.
     *
     * @return string
     */
    public static function objectSerialize($targetObject, $rootName)
    {
        Validate::notNull($targetObject, 'targetObject');
        Validate::isString($rootName, 'rootName');

        $contianer = new \stdClass();

        $contianer->$rootName = $targetObject;

        return json_encode($contianer);
    }

    /**
     * Serializes given array. The array indices must be string to use them as
     * as element name.
     *
     * @param array $array      The object to serialize represented in array.
     * @param array $properties The used properties in the serialization process.
     *
     * @return string
     */
    public function serialize($array, $properties = null)
    {
        Validate::isArray($array, 'array');

        return json_encode($array);
    }

    /**
     * Unserializes given serialized string to array.
     *
     * @param string $serialized The serialized object in string representation.
     *
     * @return array
     */
    public function unserialize($serialized)
    {
        Validate::isString($serialized, 'serialized');

        $json = json_decode($serialized);
        if ($json && !is_array($json)) {
            return get_object_vars($json);
        } else {
            return $json;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal\Serialization;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Short description
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class XmlSerializer implements ISerializer
{
    const STANDALONE  = 'standalone';
    const ROOT_NAME   = 'rootName';
    const DEFAULT_TAG = 'defaultTag';
    
    /**
     * Converts a SimpleXML object to an Array recursively
     * ensuring all sub-elements are arrays as well.
     *
     * @param string $sxml The SimpleXML object.
     * @param array  $arr  The array into which to store results.
     * 
     * @return array
     */
    private function _sxml2arr($sxml, $arr = null)
    {
        foreach ((array) $sxml as $key => $value) {
            if (is_object($value) || (is_array($value))) {
                $arr[$key] = $this->_sxml2arr($value);
            } else {
                $arr[$key] = $value;
            }
        }

        return $arr; 
    }
    
    /**
     * Takes an array and produces XML based on it.
     *
     * @param XMLWriter $xmlw       XMLWriter object that was previously instanted
     * and is used for creating the XML.
     * @param array     $data       Array to be converted to XML.
     * @param string    $defaultTag Default XML tag to be used if none specified.
     * 
     * @return void
     */
    private function _arr2xml(\XMLWriter $xmlw, $data, $defaultTag = null)
    {
        foreach ($data as $key => $value) {
            if ($key === Resources::XTAG_ATTRIBUTES) {
                foreach ($value as $attributeName => $attributeValue) {
                    $xmlw->writeAttribute($attributeName, $attributeValue);
                }
            } else if (is_array($value)) {
                if (!is_int($key)) {
                    if ($key != Resources::EMPTY_STRING) {
                        $xmlw->startElement($key);
                    } else {
                        $xmlw->startElement($defaultTag);
                    }
                }
                
                $this->_arr2xml($xmlw, $value);
                
                if (!is_int($key)) {
                    $xmlw->endElement();
                }
            } else {
                $xmlw->writeElement($key, $value);
            }
        }
    }

    /**
     * Gets the attributes of a specified object if get attributes 
     * method is exposed. 
     *
     * @param object $targetObject The target object. 
     * @param array  $methodArray  The array of method of the target object.
     * 
     * @return mixed
     */
    private static function _getInstanceAttributes($targetObject, $methodArray)
    {
        foreach ($methodArray as $method) {
            if ($method->name == 'getAttributes') {
                $classProperty = $method->invoke($targetObject);
                return $classProperty;
            }
        }
        return null;
    }

    /** 
     * Serialize an object with specified root element name. 
     * 
     * @param object $targetObject The target object. 
     * @param string $rootName     The name of the root element. 
     * 
     * @return string
     */
    public static function objectSerialize($targetObject, $rootName)
    {
        Validate::notNull($targetObject, 'targetObject');
        Validate::isString($rootName, 'rootName');
        $xmlWriter = new \XmlWriter();
        $xmlWriter->openMemory(); 
        $xmlWriter->setIndent(true);
        $reflectionClass = new \ReflectionClass($targetObject);
        $methodArray     = $reflectionClass->getMethods();
        $attributes      = self::_getInstanceAttributes(
            $targetObject, 
            $methodArray
        );
         
        $xmlWriter->startElement($rootName);
        if (!is_null($attributes)) {
            foreach (array_keys($attributes) as $attributeKey) {
                $xmlWriter->writeAttribute(
                    $attributeKey, 
                    $attributes[$attributeKey]
                );
            }
        }

        foreach ($methodArray as $method) {
            if ((strpos($method->name, 'get') === 0) 
                && $method->isPublic() 
                && ($method->name != 'getAttributes')
            ) {
                $variableName  = substr($method->name, 3);
                $variableValue = $method->invoke($targetObject);
                if (!empty($variableValue)) {
                    if (gettype($variableValue) === 'object') {
                        $xmlWriter->writeRaw(
                            XmlSerializer::objectSerialize(
                                $variableValue, $variableName
                            )
                        );
                    } else {
                        $xmlWriter->writeElement($variableName, $variableValue);
                    } 
                }
            }
        } 
        $xmlWriter->endElement();
        return $xmlWriter->outputMemory(true);
    }

    /**
     * Serializes given array. The array indices must be string to use them as
     * as element name.
     * 
     * @param array $array      The object to serialize represented in array.
     * @param array $properties The used properties in the serialization process.
     * 
     * @return string
     */
    public function serialize($array, $properties = null)
    {
        $xmlVersion   = '1.0';
        $xmlEncoding  = 'UTF-8';
        $standalone   = Utilities::tryGetValue($properties, self::STANDALONE);
        $defaultTag   = Utilities::tryGetValue($properties, self::DEFAULT_TAG);
        $rootName     = Utilities::tryGetValue($properties, self::ROOT_NAME);
        $docNamespace = Utilities::tryGetValue(
            $array,
            Resources::XTAG_NAMESPACE,
            null
        );

        if (!is_array($array)) {
            return false;
        }

        $xmlw = new \XmlWriter();
        $xmlw->openMemory();
        $xmlw->setIndent(true);
        $xmlw->startDocument($xmlVersion, $xmlEncoding, $standalone);
        
        if (is_null($docNamespace)) {
            $xmlw->startElement($rootName);
        } else {
            foreach ($docNamespace as $uri => $prefix) {
                $xmlw->startElementNS($prefix, $rootName, $uri);
                break;
            }
        }
        
        unset($array[Resources::XTAG_NAMESPACE]);
        self::_arr2xml($xmlw, $array, $defaultTag);

        $xmlw->endElement();

        return $xmlw->outputMemory(true);
    }
    
    /**
     * Unserializes given serialized string.
     * 
     * @param string $serialized The serialized object in string representation.
     * 
     * @return array
     */
    public function unserialize($serialized)
    {
        $sxml = new \SimpleXMLElement($serialized);

        return $this->_sxml2arr($sxml);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Uri;

/**
 * Base class for all services rest proxies.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServiceRestProxy extends RestProxy
{
    /**
     * @var string
     */
    private $_accountName;

    /**
     *
     * @var \Uri
     */
       private $_psrUri;

    /**
     * @var array
     */
    private $_options;

    /**
     * Initializes new ServiceRestProxy object.
     *
     * @param string      $uri            The storage account uri.
     * @param string      $accountName    The name of the account.
     * @param ISerializer $dataSerializer The data serializer.
     * @param array       $options        Array of options for the service
     */
    public function __construct($uri, $accountName, $dataSerializer, $options = [])
    {
        if ($uri[strlen($uri)-1] != '/')
        {
            $uri = $uri . '/';
        }

        parent::__construct($dataSerializer, $uri);

        $this->_accountName = $accountName;
        $this->_psrUri = new \GuzzleHttp\Psr7\Uri($uri);
        $this->_options = array_merge(array('http' => array()), $options);
    }

    /**
     * Gets the account name.
     *
     * @return string
     */
    public function getAccountName()
    {
        return $this->_accountName;
    }

    /**
     * Sends HTTP request with the specified parameters.
     *
     * @param string $method         HTTP method used in the request
     * @param array  $headers        HTTP headers.
     * @param array  $queryParams    URL query parameters.
     * @param array  $postParameters The HTTP POST parameters.
     * @param string $path           URL path
     * @param int    $statusCode     Expected status code received in the response
     * @param string $body           Request body
     * @param array  $clientOptions  Guzzle Client options
     *
     * @return GuzzleHttp\Psr7\Response
     */
    protected function send(
        $method,
        $headers,
        $queryParams,
        $postParameters,
        $path,
        $statusCode,
        $body = Resources::EMPTY_STRING
    ) {
        // add query parameters into headers
        $uri = $this->_psrUri;
        if ($path != NULL)
        {
            $uri = $uri->withPath($path);
        }

        if ($queryParams != NULL)
        {
            $queryString = Psr7\build_query($queryParams);
            $uri = $uri->withQuery($queryString);
        }

        // add post parameters into bodys
        $actualBody = NULL;
        if (empty($body))
        {
            if (empty($headers['content-type']))
            {
                $headers['content-type'] = 'application/x-www-form-urlencoded';
                $actualBody = Psr7\build_query($postParameters);
            }
        }
        else
        {
            $actualBody = $body;
        }

        $request = new Request(
                $method,
                $uri,
                $headers,
                $actualBody);

        $client = new \GuzzleHttp\Client(
            array_merge(
                $this->_options['http'],
                array(
                    "defaults" => array(
                        "allow_redirects" => true, "exceptions" => true,
                        "decode_content" => true,
                    ),
                    'cookies' => true,
                    'verify' => false,
                    // For testing with Fiddler
                    // 'proxy' => "localhost:8888",
                )
            )
        );

        $bodySize = $request->getBody()->getSize();
        if ($bodySize > 0)
        {
            $request = $request->withHeader('content-length', $bodySize);
        }

        // Apply filters to the requests
        foreach ($this->getFilters() as $filter) {
            $request = $filter->handleRequest($request);
        }

        try {
            $response = $client->send($request);
            self::throwIfError(
                    $response->getStatusCode(),
                    $response->getReasonPhrase(),
                    $response->getBody(),
                    $statusCode);
            return $response;
        }
        catch(\GuzzleHttp\Exception\RequestException $e)
        {
            if ($e->hasResponse())
            {
                $response = $e->getResponse();
                self::throwIfError(
                        $response->getStatusCode(),
                        $response->getReasonPhrase(),
                        $response->getBody(),
                        $statusCode);
                return $response;
            }
            else
            {
                throw $e;
            }
        }
    }

    protected function sendContext($context)
    {
        return $this->send(
                $context->getMethod(),
                $context->getHeaders(),
                $context->getQueryParameters(),
                $context->getPostParameters(),
                $context->getPath(),
                $context->getStatusCodes(),
                $context->getBody());
    }

    /**
     * Throws ServiceException if the recieved status code is not expected.
     *
     * @param string $actual   The received status code.
     * @param string $reason   The reason phrase.
     * @param string $message  The detailed message (if any).
     * @param string $expected The expected status codes.
     *
     * @return none
     *
     * @static
     *
     * @throws ServiceException
     */
    public static function throwIfError($actual, $reason, $message, $expected)
    {
        $expectedStatusCodes = is_array($expected) ? $expected : array($expected);

        if (!in_array($actual, $expectedStatusCodes)) {
            throw new ServiceException($actual, $reason, $message);
        }
    }

    /**
     * Adds optional header to headers if set
     *
     * @param array           $headers         The array of request headers.
     * @param AccessCondition $accessCondition The access condition object.
     *
     * @return array
     */
    public function addOptionalAccessConditionHeader($headers, $accessCondition)
    {
        if (!is_null($accessCondition)) {
            $header = $accessCondition->getHeader();

            if ($header != Resources::EMPTY_STRING) {
                $value = $accessCondition->getValue();
                if ($value instanceof \DateTime) {
                    $value = gmdate(
                        Resources::AZURE_DATE_FORMAT,
                        $value->getTimestamp()
                    );
                }
                $headers[$header] = $value;
            }
        }

        return $headers;
    }

    /**
     * Adds optional header to headers if set
     *
     * @param array           $headers         The array of request headers.
     * @param AccessCondition $accessCondition The access condition object.
     *
     * @return array
     */
    public function addOptionalSourceAccessConditionHeader(
        $headers,
        $accessCondition
    ) {
        if (!is_null($accessCondition)) {
            $header     = $accessCondition->getHeader();
            $headerName = null;
            if (!empty($header)) {
                switch($header) {
                case Resources::IF_MATCH:
                    $headerName = Resources::X_MS_SOURCE_IF_MATCH;
                    break;

                case Resources::IF_UNMODIFIED_SINCE:
                    $headerName = Resources::X_MS_SOURCE_IF_UNMODIFIED_SINCE;
                    break;

                case Resources::IF_MODIFIED_SINCE:
                    $headerName = Resources::X_MS_SOURCE_IF_MODIFIED_SINCE;
                    break;

                case Resources::IF_NONE_MATCH:
                    $headerName = Resources::X_MS_SOURCE_IF_NONE_MATCH;
                    break;

                default:
                    throw new \Exception(Resources::INVALID_ACH_MSG);
                    break;
                }
            }
            $value = $accessCondition->getValue();
            if ($value instanceof \DateTime) {
                $value = gmdate(
                    Resources::AZURE_DATE_FORMAT,
                    $value->getTimestamp()
                );
            }

            $this->addOptionalHeader($headers, $headerName, $value);
        }

        return $headers;
    }

    /**
     * Adds HTTP POST parameter to the specified
     *
     * @param array  $postParameters An array of HTTP POST parameters.
     * @param string $key            The key of a HTTP POST parameter.
     * @param string $value          the value of a HTTP POST parameter.
     *
     * @return array
     */
    public function addPostParameter(
        $postParameters,
        $key,
        $value
    ) {
        Validate::isArray($postParameters, 'postParameters');
        $postParameters[$key] = $value;
        return $postParameters;
    }

    /**
     * Groups set of values into one value separated with Resources::SEPARATOR
     *
     * @param array $values array of values to be grouped.
     *
     * @return string
     */
    public function groupQueryValues($values)
    {
        Validate::isArray($values, 'values');
        $joined = Resources::EMPTY_STRING;

        foreach ($values as $value) {
            if (!is_null($value) && !empty($value)) {
                $joined .= $value . Resources::SEPARATOR;
            }
        }

        return trim($joined, Resources::SEPARATOR);
    }

    /**
     * Adds metadata elements to headers array
     *
     * @param array $headers  HTTP request headers
     * @param array $metadata user specified metadata
     *
     * @return array
     */
    protected function addMetadataHeaders($headers, $metadata)
    {
        $this->validateMetadata($metadata);

        $metadata = $this->generateMetadataHeaders($metadata);
        $headers  = array_merge($headers, $metadata);

        return $headers;
    }

    /**
     * Generates metadata headers by prefixing each element with 'x-ms-meta'.
     *
     * @param array $metadata user defined metadata.
     *
     * @return array.
     */
    public function generateMetadataHeaders($metadata)
    {
        $metadataHeaders = array();

        if (is_array($metadata) && !is_null($metadata)) {
            foreach ($metadata as $key => $value) {
                $headerName = Resources::X_MS_META_HEADER_PREFIX;
                if (   strpos($value, "\r") !== false
                    || strpos($value, "\n") !== false
                ) {
                    throw new \InvalidArgumentException(Resources::INVALID_META_MSG);
                }

                // Metadata name is case-presrved and case insensitive
                $headerName                     .= $key;
                $metadataHeaders[$headerName] = $value;
            }
        }

        return $metadataHeaders;
    }

    /**
     * Gets metadata array by parsing them from given headers.
     *
     * @param array $headers HTTP headers containing metadata elements.
     *
     * @return array.
     */
    public function getMetadataArray($headers)
    {
        $metadata = array();
        foreach ($headers as $key => $value) {
            $isMetadataHeader = Utilities::startsWith(
                strtolower($key),
                Resources::X_MS_META_HEADER_PREFIX
            );

            if ($isMetadataHeader) {
                // Metadata name is case-presrved and case insensitive
                $MetadataName = str_ireplace(
                    Resources::X_MS_META_HEADER_PREFIX,
                    Resources::EMPTY_STRING,
                    $key
                );
                $metadata[$MetadataName] = $value;
            }
        }

        return $metadata;
    }

    /**
     * Validates the provided metadata array.
     *
     * @param mix $metadata The metadata array.
     *
     * @return none
     */
    public function validateMetadata($metadata)
    {
        if (!is_null($metadata)) {
            Validate::isArray($metadata, 'metadata');
        } else {
            $metadata = array();
        }

        foreach ($metadata as $key => $value) {
            Validate::isString($key, 'metadata key');
            Validate::isString($value, 'metadata value');
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Base class for all REST services settings.
 * 
 * Derived classes must implement the following members:
 * 1- $isInitialized: A static property that indicates whether the class's static
 *    members have been initialized.
 * 2- init(): A protected static method that initializes static members.
 * 3- $validSettingKeys: A static property that contains valid setting keys for this 
 *    service.
 * 4- createFromConnectionString($connectionString): A public static function that
 *    takes a connection string and returns the created settings object.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
abstract class ServiceSettings
{
    /**
     * Throws an exception if the connection string format does not match any of the
     * available formats.
     * 
     * @param type $connectionString The invalid formatted connection string.
     * 
     * @return none
     * 
     * @throws \RuntimeException
     */
    protected static function noMatch($connectionString)
    {
        throw new \RuntimeException(
            sprintf(Resources::MISSING_CONNECTION_STRING_SETTINGS, $connectionString)
        );
    }
    
    /**
     * Parses the connection string and then validate that the parsed keys belong to
     * the $validSettingKeys
     * 
     * @param string $connectionString The user provided connection string.
     * 
     * @return array The tokenized connection string keys.
     * 
     * @throws \RuntimeException 
     */
    protected static function parseAndValidateKeys($connectionString)
    {
        // Initialize the static values if they are not initialized yet.
        if (!static::$isInitialized) {
            static::init();
            static::$isInitialized = true;
        }
        
        $tokenizedSettings = ConnectionStringParser::parseConnectionString(
            'connectionString',
            $connectionString
        );
        
        // Assure that all given keys are valid.
        foreach ($tokenizedSettings as $key => $value) {
            if (!Utilities::inArrayInsensitive($key, static::$validSettingKeys) ) {
                throw new \RuntimeException(
                    sprintf(
                        Resources::INVALID_CONNECTION_STRING_SETTING_KEY,
                        $key,
                        implode("\n", static::$validSettingKeys)
                    )
                );
            }
        }
        
        return $tokenizedSettings;
    }
    
    /**
     * Creates an anonymous function that acts as predicate.
     * 
     * @param array   $requirements The array of conditions to satisfy.
     * @param boolean $isRequired   Either these conditions are all required or all 
     * optional.
     * @param boolean $atLeastOne   Indicates that at least one requirement must
     * succeed.
     * 
     * @return callable
     */
    protected static function getValidator($requirements, $isRequired, $atLeastOne)
    {
        // @codingStandardsIgnoreStart
        
        return function ($userSettings)
         use ($requirements, $isRequired, $atLeastOne) {
            $oneFound = false;
            $result   = array_change_key_case($userSettings);
            foreach ($requirements as $requirement) {
                $settingName = strtolower($requirement[Resources::SETTING_NAME]);
                
                // Check if the setting name exists in the provided user settings.
                if (array_key_exists($settingName, $result)) {
                    // Check if the provided user setting value is valid.
                    $validationFunc = $requirement[Resources::SETTING_CONSTRAINT];
                    $isValid        = $validationFunc($result[$settingName]);
                    
                    if ($isValid) {
                        // Remove the setting as indicator for successful validation.
                        unset($result[$settingName]);
                        $oneFound = true;
                    }
                } else {
                    // If required then fail because the setting does not exist
                    if ($isRequired) {
                        return null;
                    }
                }
            }
            
            if ($atLeastOne) {
                // At least one requirement must succeed, otherwise fail.
                return $oneFound ? $result : null;
            } else {
                return $result;
            }
        };
        
        // @codingStandardsIgnoreEnd
    }
    
    /**
     * Creates at lease one succeed predicate for the provided list of requirements.
     * 
     * @return callable
     */
    protected static function atLeastOne()
    {
        $allSettings = func_get_args();
        return self::getValidator($allSettings, false, true);
    }
    
    /**
     * Creates an optional predicate for the provided list of requirements.
     * 
     * @return callable
     */
    protected static function optional()
    {
        $optionalSettings = func_get_args();
        return self::getValidator($optionalSettings, false, false);
    }
    
    /**
     * Creates an required predicate for the provided list of requirements.
     * 
     * @return callable
     */
    protected static function allRequired()
    {
        $requiredSettings = func_get_args();
        return self::getValidator($requiredSettings, true, false);
    }
    
    /**
     * Creates a setting value condition using the passed predicate.
     * 
     * @param string   $name      The setting key name.
     * @param callable $predicate The setting value predicate.
     * 
     * @return array 
     */
    protected static function settingWithFunc($name, $predicate)
    {
        $requirement                                = array();
        $requirement[Resources::SETTING_NAME]       = $name;
        $requirement[Resources::SETTING_CONSTRAINT] = $predicate;
        
        return $requirement;
    }
    
    /**
     * Creates a setting value condition that validates it is one of the
     * passed valid values.
     * 
     * @param string $name The setting key name.
     * 
     * @return array 
     */
    protected static function setting($name)
    {
        $validValues = func_get_args();
        
        // Remove $name argument.
        unset($validValues[0]);
        
        $validValuesCount = func_num_args();
        
        $predicate = function ($settingValue) use ($validValuesCount, $validValues) {
            if (empty($validValues)) {
                // No restrictions, succeed,
                return true;
            }
            
            // Check to find if the $settingValue is valid or not. The index must
            // start from 1 as unset deletes the value but does not update the array
            // indecies.
            for ($index = 1; $index < $validValuesCount; $index++) {
                if ($settingValue == $validValues[$index]) {
                    // $settingValue is found in valid values set, succeed.
                    return true;
                }
            }
            
            throw new \RuntimeException(
                sprintf(
                    Resources::INVALID_CONFIG_VALUE,
                    $settingValue,
                    implode("\n", $validValues)
                )
            );
            
            // $settingValue is missing in valid values set, fail.
            return false;
        };
        
        return self::settingWithFunc($name, $predicate);
    }
    
    /**
     * Tests to see if a given list of settings matches a set of filters exactly.
     * 
     * @param array $settings The settings to check.
     * 
     * @return boolean If any filter returns null, false. If there are any settings 
     * left over after all filters are processed, false. Otherwise true.
     */
    protected static function matchedSpecification($settings)
    {
        $constraints = func_get_args();
        
        // Remove first element which corresponds to $settings
        unset($constraints[0]);
        
        foreach ($constraints as $constraint) {
            $remainingSettings = $constraint($settings);
            
            if (is_null($remainingSettings)) {
                return false;
            } else {
                $settings = $remainingSettings;
            }
        }
        
        if (empty($settings)) {
            return true;
        }
        
        return false;
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Represents the settings used to sign and access a request against the storage 
 * service. For more information about storage service connection strings check this
 * page: http://msdn.microsoft.com/en-us/library/ee758697
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class StorageServiceSettings extends ServiceSettings
{
    /**
     * The storage service name.
     * 
     * @var string
     */
    private $_name;
    
    /**
     * A base64 representation.
     * 
     * @var string
     */
    private $_key;
    
    /**
     * The endpoint for the blob service.
     * 
     * @var string
     */
    private $_blobEndpointUri;
    
    /**
     * The endpoint for the queue service.
     * 
     * @var string
     */
    private $_queueEndpointUri;
    
    /**
     * The endpoint for the table service.
     * 
     * @var string
     */
    private $_tableEndpointUri;
    
    /**
     * @var StorageServiceSettings
     */
    private static $_devStoreAccount;
    
    /**
     * Validator for the UseDevelopmentStorage setting. Must be "true".
     * 
     * @var array
     */
    private static $_useDevelopmentStorageSetting;
    
    /**
     * Validator for the DevelopmentStorageProxyUri setting. Must be a valid Uri.
     * 
     * @var array
     */
    private static $_developmentStorageProxyUriSetting;
    
    /**
     * Validator for the DefaultEndpointsProtocol setting. Must be either "http" 
     * or "https".
     * 
     * @var array
     */
    private static $_defaultEndpointsProtocolSetting;
    
    /**
     * Validator for the AccountName setting. No restrictions.
     * 
     * @var array
     */
    private static $_accountNameSetting;
    
    /**
     * Validator for the AccountKey setting. Must be a valid base64 string.
     * 
     * @var array
     */
    private static $_accountKeySetting;
    
    /**
     * Validator for the BlobEndpoint setting. Must be a valid Uri.
     * 
     * @var array
     */
    private static $_blobEndpointSetting;
    
    /**
     * Validator for the QueueEndpoint setting. Must be a valid Uri.
     * 
     * @var array
     */
    private static $_queueEndpointSetting;
    
    /**
     * Validator for the TableEndpoint setting. Must be a valid Uri.
     * 
     * @var array
     */
    private static $_tableEndpointSetting;
    
    /**
     * @var boolean
     */
    protected static $isInitialized = false;
    
    /**
     * Holds the expected setting keys.
     * 
     * @var array
     */
    protected static $validSettingKeys = array();
    
    /**
     * Initializes static members of the class.
     * 
     * @return none
     */
    protected static function init()
    {
        self::$_useDevelopmentStorageSetting = self::setting(
            Resources::USE_DEVELOPMENT_STORAGE_NAME,
            'true'
        );
        
        self::$_developmentStorageProxyUriSetting = self::settingWithFunc(
            Resources::DEVELOPMENT_STORAGE_PROXY_URI_NAME,
            Validate::getIsValidUri()
        );
        
        self::$_defaultEndpointsProtocolSetting = self::setting(
            Resources::DEFAULT_ENDPOINTS_PROTOCOL_NAME,
            'http', 'https'
        );
        
        self::$_accountNameSetting = self::setting(Resources::ACCOUNT_NAME_NAME);
        
        self::$_accountKeySetting = self::settingWithFunc(
            Resources::ACCOUNT_KEY_NAME,
            // base64_decode will return false if the $key is not in base64 format.
            function ($key) {
                $isValidBase64String = base64_decode($key, true);
                if ($isValidBase64String) {
                    return true;
                } else {
                    throw new \RuntimeException(
                        sprintf(Resources::INVALID_ACCOUNT_KEY_FORMAT, $key)
                    );
                }
            }
        );
        
        self::$_blobEndpointSetting = self::settingWithFunc(
            Resources::BLOB_ENDPOINT_NAME,
            Validate::getIsValidUri()
        );
        
        self::$_queueEndpointSetting = self::settingWithFunc(
            Resources::QUEUE_ENDPOINT_NAME,
            Validate::getIsValidUri()
        );
        
        self::$_tableEndpointSetting = self::settingWithFunc(
            Resources::TABLE_ENDPOINT_NAME,
            Validate::getIsValidUri()
        );
        
        self::$validSettingKeys[] = Resources::USE_DEVELOPMENT_STORAGE_NAME;
        self::$validSettingKeys[] = Resources::DEVELOPMENT_STORAGE_PROXY_URI_NAME;
        self::$validSettingKeys[] = Resources::DEFAULT_ENDPOINTS_PROTOCOL_NAME;
        self::$validSettingKeys[] = Resources::ACCOUNT_NAME_NAME;
        self::$validSettingKeys[] = Resources::ACCOUNT_KEY_NAME;
        self::$validSettingKeys[] = Resources::BLOB_ENDPOINT_NAME;
        self::$validSettingKeys[] = Resources::QUEUE_ENDPOINT_NAME;
        self::$validSettingKeys[] = Resources::TABLE_ENDPOINT_NAME;
    }
    
    /**
     * Creates new storage service settings instance.
     * 
     * @param string $name             The storage service name.
     * @param string $key              The storage service key.
     * @param string $blobEndpointUri  The sotrage service blob endpoint.
     * @param string $queueEndpointUri The sotrage service queue endpoint.
     * @param string $tableEndpointUri The sotrage service table endpoint.
     */
    public function __construct(
        $name,
        $key,
        $blobEndpointUri,
        $queueEndpointUri,
        $tableEndpointUri
    ) {
        $this->_name             = $name;
        $this->_key              = $key;
        $this->_blobEndpointUri  = $blobEndpointUri;
        $this->_queueEndpointUri = $queueEndpointUri;
        $this->_tableEndpointUri = $tableEndpointUri;
    }
    
    /**
     * Returns a StorageServiceSettings with development storage credentials using 
     * the specified proxy Uri.
     * 
     * @param string $proxyUri The proxy endpoint to use.
     * 
     * @return StorageServiceSettings
     */
    private static function _getDevelopmentStorageAccount($proxyUri)
    {
        if (is_null($proxyUri)) {
            return self::developmentStorageAccount();
        }
        
        $scheme = parse_url($proxyUri, PHP_URL_SCHEME);
        $host   = parse_url($proxyUri, PHP_URL_HOST);
        $prefix = $scheme . "://" . $host;
        
        return new StorageServiceSettings(
            Resources::DEV_STORE_NAME,
            Resources::DEV_STORE_KEY,
            $prefix . ':10000/devstoreaccount1/',
            $prefix . ':10001/devstoreaccount1/',
            $prefix . ':10002/devstoreaccount1/'
        );
    }
    
    /**
     * Gets a StorageServiceSettings object that references the development storage 
     * account.
     * 
     * @return StorageServiceSettings 
     */
    public static function developmentStorageAccount()
    {
        if (is_null(self::$_devStoreAccount)) {
            self::$_devStoreAccount = self::_getDevelopmentStorageAccount(
                Resources::DEV_STORE_URI
            );
        }
        
        return self::$_devStoreAccount;
    }
    
    /**
     * Gets the default service endpoint using the specified protocol and account 
     * name.
     * 
     * @param array  $settings The service settings.
     * @param string $dns      The service DNS.
     * 
     * @return string
     */
    private static function _getDefaultServiceEndpoint($settings, $dns)
    {
        $scheme      = Utilities::tryGetValueInsensitive(
            Resources::DEFAULT_ENDPOINTS_PROTOCOL_NAME,
            $settings
        );
        $accountName = Utilities::tryGetValueInsensitive(
            Resources::ACCOUNT_NAME_NAME,
            $settings
        );
        
        return sprintf(Resources::SERVICE_URI_FORMAT, $scheme, $accountName, $dns);
    }
    
    /**
     * Creates StorageServiceSettings object given endpoints uri.
     * 
     * @param array  $settings         The service settings.
     * @param string $blobEndpointUri  The blob endpoint uri.
     * @param string $queueEndpointUri The queue endpoint uri.
     * @param string $tableEndpointUri The table endpoint uri.
     * 
     * @return \MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings
     */
    private static function _createStorageServiceSettings(
        $settings,
        $blobEndpointUri = null,
        $queueEndpointUri = null,
        $tableEndpointUri = null
    ) {
        $blobEndpointUri  = Utilities::tryGetValueInsensitive(
            Resources::BLOB_ENDPOINT_NAME,
            $settings,
            $blobEndpointUri
        );
        $queueEndpointUri = Utilities::tryGetValueInsensitive(
            Resources::QUEUE_ENDPOINT_NAME,
            $settings,
            $queueEndpointUri
        );
        $tableEndpointUri = Utilities::tryGetValueInsensitive(
            Resources::TABLE_ENDPOINT_NAME,
            $settings,
            $tableEndpointUri
        );
        $accountName      = Utilities::tryGetValueInsensitive(
            Resources::ACCOUNT_NAME_NAME,
            $settings
        );
        $accountKey       = Utilities::tryGetValueInsensitive(
            Resources::ACCOUNT_KEY_NAME,
            $settings
        );
            
        return new StorageServiceSettings(
            $accountName,
            $accountKey,
            $blobEndpointUri,
            $queueEndpointUri,
            $tableEndpointUri
        );
    }

    /**
     * Creates a StorageServiceSettings object from the given connection string.
     * 
     * @param string $connectionString The storage settings connection string.
     * 
     * @return StorageServiceSettings 
     */
    public static function createFromConnectionString($connectionString)
    {
        $tokenizedSettings = self::parseAndValidateKeys($connectionString);
        
        // Devstore case
        $matchedSpecs = self::matchedSpecification(
            $tokenizedSettings,
            self::allRequired(self::$_useDevelopmentStorageSetting),
            self::optional(self::$_developmentStorageProxyUriSetting)
        );
        if ($matchedSpecs) {
            $proxyUri = Utilities::tryGetValueInsensitive(
                Resources::DEVELOPMENT_STORAGE_PROXY_URI_NAME,
                $tokenizedSettings
            );
            
            return self::_getDevelopmentStorageAccount($proxyUri);
        }
        
        // Automatic case
        $matchedSpecs = self::matchedSpecification(
            $tokenizedSettings,
            self::allRequired(
                self::$_defaultEndpointsProtocolSetting,
                self::$_accountNameSetting,
                self::$_accountKeySetting
            ),
            self::optional(
                self::$_blobEndpointSetting,
                self::$_queueEndpointSetting,
                self::$_tableEndpointSetting
            )
        );
        if ($matchedSpecs) {
            return self::_createStorageServiceSettings(
                $tokenizedSettings,
                self::_getDefaultServiceEndpoint(
                    $tokenizedSettings,
                    Resources::BLOB_BASE_DNS_NAME
                ),
                self::_getDefaultServiceEndpoint(
                    $tokenizedSettings,
                    Resources::QUEUE_BASE_DNS_NAME
                ),
                self::_getDefaultServiceEndpoint(
                    $tokenizedSettings,
                    Resources::TABLE_BASE_DNS_NAME
                )
            );
        }
        
        // Explicit case
        $matchedSpecs = self::matchedSpecification(
            $tokenizedSettings,
            self::atLeastOne(
                self::$_blobEndpointSetting,
                self::$_queueEndpointSetting,
                self::$_tableEndpointSetting
            ),
            self::allRequired(
                self::$_accountNameSetting,
                self::$_accountKeySetting
            )
        );
        if ($matchedSpecs) {
            return self::_createStorageServiceSettings($tokenizedSettings);
        }
        
        self::noMatch($connectionString);
    }
    
    /**
     * Gets storage service name.
     * 
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }
    
    /**
     * Gets storage service key.
     * 
     * @return string
     */
    public function getKey()
    {
        return $this->_key;
    }
    
    /**
     * Gets storage service blob endpoint uri.
     * 
     * @return string
     */
    public function getBlobEndpointUri()
    {
        return $this->_blobEndpointUri;
    }
    
    /**
     * Gets storage service queue endpoint uri.
     * 
     * @return string
     */
    public function getQueueEndpointUri()
    {
        return $this->_queueEndpointUri;
    }

    /**
     * Gets storage service table endpoint uri.
     * 
     * @return string
     */
    public function getTableEndpointUri()
    {
        return $this->_tableEndpointUri;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal;

/**
 * Utilities for the project
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Utilities
{
    /**
     * Returns the specified value of the $key passed from $array and in case that
     * this $key doesn't exist, the default value is returned.
     *
     * @param array $array   The array to be used.
     * @param mix   $key     The array key.
     * @param mix   $default The value to return if $key is not found in $array.
     *
     * @static
     *
     * @return mix
     */
    public static function tryGetValue($array, $key, $default = null)
    {
        return is_array($array) && array_key_exists($key, $array)
            ? $array[$key]
            : $default;
    }

    /**
     * Adds a url scheme if there is no scheme.
     *
     * @param string $url    The URL.
     * @param string $scheme The scheme. By default HTTP
     *
     * @static
     *
     * @return string
     */
    public static function tryAddUrlScheme($url, $scheme = 'http')
    {
        $urlScheme = parse_url($url, PHP_URL_SCHEME);

        if (empty($urlScheme)) {
            $url = "$scheme://" . $url;
        }

        return $url;
    }
    
    /**
     * Parse storage account name from an endpoint url.
     * 
     * @param string $url The endpoint $url
     * 
     * @static
     * 
     * @return string
     */
    public static function tryParseAccountNameFromUrl($url)
    {
        $host = parse_url($url, PHP_URL_HOST);
        
        // first token of the url host is account name
        return explode('.', $host)[0];
    }
    
    /**
     * tries to get nested array with index name $key from $array.
     *
     * Returns empty array object if the value is NULL.
     *
     * @param string $key   The index name.
     * @param array  $array The array object.
     *
     * @static
     *
     * @return array
     */
    public static function tryGetArray($key, $array)
    {
        return Utilities::getArray(Utilities::tryGetValue($array, $key));
    }

    /**
     * Adds the given key/value pair into array if the value doesn't satisfy empty().
     *
     * This function just validates that the given $array is actually array. If it's
     * NULL the function treats it as array.
     *
     * @param string $key    The key.
     * @param string $value  The value.
     * @param array  &$array The array. If NULL will be used as array.
     *
     * @static
     *
     * @return none
     */
    public static function addIfNotEmpty($key, $value, &$array)
    {
        if (!is_null($array)) {
            Validate::isArray($array, 'array');
        }

        if (!empty($value)) {
            $array[$key] = $value;
        }
    }

    /**
     * Returns the specified value of the key chain passed from $array and in case
     * that key chain doesn't exist, null is returned.
     *
     * @param array $array Array to be used.
     *
     * @static
     *
     * @return mix
     */
    public static function tryGetKeysChainValue($array)
    {
        $arguments    = func_get_args();
        $numArguments = func_num_args();

        $currentArray = $array;
        for ($i = 1; $i < $numArguments; $i++) {
            if (is_array($currentArray)) {
                if (array_key_exists($arguments[$i], $currentArray)) {
                    $currentArray = $currentArray[$arguments[$i]];
                } else {
                    return null;
                }
            } else {
                return null;
            }
        }

        return $currentArray;
    }

    /**
     * Checks if the passed $string starts with $prefix
     *
     * @param string  $string     word to seaech in
     * @param string  $prefix     prefix to be matched
     * @param boolean $ignoreCase true to ignore case during the comparison;
     * otherwise, false
     *
     * @static
     *
     * @return boolean
     */
    public static function startsWith($string, $prefix, $ignoreCase = false)
    {
        if ($ignoreCase) {
            $string = strtolower($string);
            $prefix = strtolower($prefix);
        }
        return ($prefix == substr($string, 0, strlen($prefix)));
    }

    /**
     * Returns grouped items from passed $var
     *
     * @param array $var item to group
     *
     * @static
     *
     * @return array
     */
    public static function getArray($var)
    {
        if (is_null($var) || empty($var)) {
            return array();
        }

        foreach ($var as $value) {
            if ((gettype($value) == 'object')
                && (get_class($value) == 'SimpleXMLElement')
            ) {
                return (array) $var;
            } else if (!is_array($value)) {
                return array($var);
            }

        }

        return $var;
    }

    /**
     * Unserializes the passed $xml into array.
     *
     * @param string $xml XML to be parsed.
     *
     * @static
     *
     * @return array
     */
    public static function unserialize($xml)
    {
        $sxml = new \SimpleXMLElement($xml);

        return self::_sxml2arr($sxml);
    }

    /**
     * Converts a SimpleXML object to an Array recursively
     * ensuring all sub-elements are arrays as well.
     *
     * @param string $sxml SimpleXML object
     * @param array  $arr  Array into which to store results
     *
     * @static
     *
     * @return array
     */
    private static function _sxml2arr($sxml, $arr = null)
    {
        foreach ((array) $sxml as $key => $value) {
            if (is_object($value) || (is_array($value))) {
                $arr[$key] = self::_sxml2arr($value);
            } else {
                $arr[$key] = $value;
            }
        }

        return $arr;
    }

    /**
     * Serializes given array into xml. The array indices must be string to use
     * them as XML tags.
     *
     * @param array  $array      object to serialize represented in array.
     * @param string $rootName   name of the XML root element.
     * @param string $defaultTag default tag for non-tagged elements.
     * @param string $standalone adds 'standalone' header tag, values 'yes'/'no'
     *
     * @static
     *
     * @return string
     */
    public static function serialize($array, $rootName, $defaultTag = null,
        $standalone = null
    ) {
        $xmlVersion  = '1.0';
        $xmlEncoding = 'UTF-8';

        if (!is_array($array)) {
            return false;
        }

        $xmlw = new \XmlWriter();
        $xmlw->openMemory();
        $xmlw->startDocument($xmlVersion, $xmlEncoding, $standalone);

        $xmlw->startElement($rootName);

        self::_arr2xml($xmlw, $array, $defaultTag);

        $xmlw->endElement();

        return $xmlw->outputMemory(true);
    }

    /**
     * Takes an array and produces XML based on it.
     *
     * @param XMLWriter $xmlw       XMLWriter object that was previously instanted
     * and is used for creating the XML.
     * @param array     $data       Array to be converted to XML
     * @param string    $defaultTag Default XML tag to be used if none specified.
     *
     * @static
     *
     * @return void
     */
    private static function _arr2xml(\XMLWriter $xmlw, $data, $defaultTag = null)
    {
        foreach ($data as $key => $value) {
            if (strcmp($key, '@attributes') == 0) {
                foreach ($value as $attributeName => $attributeValue) {
                    $xmlw->writeAttribute($attributeName, $attributeValue);
                }
            } else if (is_array($value)) {
                if (!is_int($key)) {
                    if ($key != Resources::EMPTY_STRING) {
                        $xmlw->startElement($key);
                    } else {
                        $xmlw->startElement($defaultTag);
                    }
                }

                self::_arr2xml($xmlw, $value);

                if (!is_int($key)) {
                    $xmlw->endElement();
                }
                continue;
            } else {
                $xmlw->writeElement($key, $value);
            }
        }
    }

    /**
     * Converts string into boolean value.
     *
     * @param string $obj boolean value in string format.
     *
     * @static
     *
     * @return bool
     */
    public static function toBoolean($obj)
    {
        return filter_var($obj, FILTER_VALIDATE_BOOLEAN);
    }

    /**
     * Converts string into boolean value.
     *
     * @param bool $obj boolean value to convert.
     *
     * @static
     *
     * @return string
     */
    public static function booleanToString($obj)
    {
        return $obj ? 'true' : 'false';
    }

    /**
     * Converts a given date string into \DateTime object
     *
     * @param string $date windows azure date ins string represntation.
     *
     * @static
     *
     * @return \DateTime
     */
    public static function rfc1123ToDateTime($date)
    {
        $timeZone = new \DateTimeZone('GMT');
        $format   = Resources::AZURE_DATE_FORMAT;

        return \DateTime::createFromFormat($format, $date, $timeZone);
    }

    /**
     * Generate ISO 8601 compliant date string in UTC time zone
     *
     * @param int $timestamp The unix timestamp to convert
     *     (for DateTime check date_timestamp_get).
     *
     * @static
     *
     * @return string
     */
    public static function isoDate($timestamp = null)
    {
        $tz = date_default_timezone_get();
        date_default_timezone_set('UTC');

        if (is_null($timestamp)) {
            $timestamp = time();
        }

        $returnValue = str_replace(
            '+00:00', '.0000000Z', date('c', $timestamp)
        );
        date_default_timezone_set($tz);
        return $returnValue;
    }

    /**
     * Converts a DateTime object into an Edm.DaeTime value in UTC timezone,
     * represented as a string.
     *
     * @param \DateTime $value The datetime value.
     *
     * @static
     *
     * @return string
     */
    public static function convertToEdmDateTime($value)
    {
        if (empty($value)) {
            return $value;
        }

        if (is_string($value)) {
            $value =  self::convertToDateTime($value);
        }

        Validate::isDate($value);

        $cloned = clone $value;
        $cloned->setTimezone(new \DateTimeZone('UTC'));
        return str_replace('+0000', 'Z', $cloned->format("Y-m-d\TH:i:s.u0O"));
    }

    /**
     * Converts a string to a \DateTime object. Returns false on failure.
     *
     * @param string $value The string value to parse.
     *
     * @static
     *
     * @return \DateTime
     */
    public static function convertToDateTime($value)
    {
        if ($value instanceof \DateTime) {
            return $value;
        }

        if (substr($value, -1) == 'Z') {
            $value = substr($value, 0, strlen($value) - 1);
        }

        return new \DateTime($value, new \DateTimeZone('UTC'));
    }

    /**
     * Converts string to stream handle.
     *
     * @param type $string The string contents.
     *
     * @static
     *
     * @return resource
     */
    public static function stringToStream($string)
    {
        return fopen('data://text/plain,' . urlencode($string), 'rb');
    }

    /**
     * Sorts an array based on given keys order.
     *
     * @param array $array The array to sort.
     * @param array $order The keys order array.
     *
     * @return array
     */
    public static function orderArray($array, $order)
    {
        $ordered = array();

        foreach ($order as $key) {
            if (array_key_exists($key, $array)) {
                $ordered[$key] = $array[$key];
            }
        }

        return $ordered;
    }

    /**
     * Checks if a value exists in an array. The comparison is done in a case
     * insensitive manner.
     *
     * @param string $needle   The searched value.
     * @param array  $haystack The array.
     *
     * @static
     *
     * @return boolean
     */
    public static function inArrayInsensitive($needle, $haystack)
    {
        return in_array(strtolower($needle), array_map('strtolower', $haystack));
    }

    /**
     * Checks if the given key exists in the array. The comparison is done in a case
     * insensitive manner.
     *
     * @param string $key    The value to check.
     * @param array  $search The array with keys to check.
     *
     * @static
     *
     * @return boolean
     */
    public static function arrayKeyExistsInsensitive($key, $search)
    {
        return array_key_exists(strtolower($key), array_change_key_case($search));
    }

    /**
     * Returns the specified value of the $key passed from $array and in case that
     * this $key doesn't exist, the default value is returned. The key matching is
     * done in a case insensitive manner.
     *
     * @param string $key      The array key.
     * @param array  $haystack The array to be used.
     * @param mix    $default  The value to return if $key is not found in $array.
     *
     * @static
     *
     * @return mix
     */
    public static function tryGetValueInsensitive($key, $haystack, $default = null)
    {
        $array = array_change_key_case($haystack);
        return Utilities::tryGetValue($array, strtolower($key), $default);
    }

    /**
     * Returns a string representation of a version 4 GUID, which uses random
     * numbers.There are 6 reserved bits, and the GUIDs have this format:
     *     xxxxxxxx-xxxx-4xxx-[8|9|a|b]xxx-xxxxxxxxxxxx
     * where 'x' is a hexadecimal digit, 0-9a-f.
     *
     * See http://tools.ietf.org/html/rfc4122 for more information.
     *
     * Note: This function is available on all platforms, while the
     * com_create_guid() is only available for Windows.
     *
     * @static
     *
     * @return string A new GUID.
     */
    public static function getGuid()
    {
        // @codingStandardsIgnoreStart

        return sprintf(
            '%04x%04x-%04x-%04x-%02x%02x-%04x%04x%04x',
            mt_rand(0, 65535),
            mt_rand(0, 65535),          // 32 bits for "time_low"
            mt_rand(0, 65535),          // 16 bits for "time_mid"
            mt_rand(0, 4096) + 16384,   // 16 bits for "time_hi_and_version", with
                                        // the most significant 4 bits being 0100
                                        // to indicate randomly generated version
            mt_rand(0, 64) + 128,       // 8 bits  for "clock_seq_hi", with
                                        // the most significant 2 bits being 10,
                                        // required by version 4 GUIDs.
            mt_rand(0, 256),            // 8 bits  for "clock_seq_low"
            mt_rand(0, 65535),          // 16 bits for "node 0" and "node 1"
            mt_rand(0, 65535),          // 16 bits for "node 2" and "node 3"
            mt_rand(0, 65535)           // 16 bits for "node 4" and "node 5"
        );

        // @codingStandardsIgnoreEnd
    }

    /**
     * Creates a list of objects of type $class from the provided array using static
     * create method.
     *
     * @param array  $parsed The object in array representation
     * @param string $class  The class name. Must have static method create.
     *
     * @static
     *
     * @return array
     */
    public static function createInstanceList($parsed, $class)
    {
        $list = array();

        foreach ($parsed as $value) {
            $list[] = $class::create($value);
        }

        return $list;
    }

    /**
     * Takes a string and return if it ends with the specified character/string.
     *
     * @param string  $haystack   The string to search in.
     * @param string  $needle     postfix to match.
     * @param boolean $ignoreCase Set true to ignore case during the comparison;
     * otherwise, false
     *
     * @static
     *
     * @return boolean
     */
    public static function endsWith($haystack, $needle, $ignoreCase = false)
    {
        if ($ignoreCase) {
            $haystack = strtolower($haystack);
            $needle   = strtolower($needle);
        }
        $length = strlen($needle);
        if ($length == 0) {
            return true;
        }

        return (substr($haystack, -$length) === $needle);
    }

    /**
     * Get id from entity object or string.
     * If entity is object than validate type and return $entity->$method()
     * If entity is string than return this string
     *
     * @param object|string $entity Entity with id property
     * @param string        $type   Entity type to validate
     * @param string        $method Methods that gets id (getId by default)
     *
     * @return string
     */
    public static function getEntityId($entity, $type, $method = 'getId')
    {
        if (is_string($entity)) {
            return $entity;
        } else {
            Validate::isA($entity, $type, 'entity');
            Validate::methodExists($entity, $method, $type);

            return $entity->$method();
        }
    }

    /**
     * Generate a pseudo-random string of bytes using a cryptographically strong 
     * algorithm.
     *
     * @param int $length Length of the string in bytes
     *
     * @return string|boolean Generated string of bytes on success, or FALSE on 
     *                        failure.
     */
    public static function generateCryptoKey($length)
    {
        return openssl_random_pseudo_bytes($length);
    }
    
    
    /**
     * Encrypts $data with CTR encryption
     * 
     * @param string $data                 Data to be encrypted
     * @param string $key                  AES Encryption key
     * @param string $initializationVector Initialization vector
     * 
     * @return string Encrypted data
     */
    public static function ctrCrypt($data, $key, $initializationVector) 
    {
        Validate::isString($data, 'data');
        Validate::isString($key, 'key');
        Validate::isString($initializationVector, 'initializationVector');
        
        Validate::isTrue(
            (strlen($key) == 16 || strlen($key) == 24 || strlen($key) == 32), 
            sprintf(Resources::INVALID_STRING_LENGTH, 'key', '16, 24, 32')
        );
        
        Validate::isTrue(
            (strlen($initializationVector) == 16), 
            sprintf(Resources::INVALID_STRING_LENGTH, 'initializationVector', '16')
        );
        
        $blockCount = ceil(strlen($data) / 16);
    
        $ctrData = '';
        for ($i = 0; $i < $blockCount; ++$i) {
            $ctrData .= $initializationVector;
    
            // increment Initialization Vector
            $j = 15;
            do {
                $digit                    = ord($initializationVector[$j]) + 1;
                $initializationVector[$j] = chr($digit & 0xFF);
                
                $j--;
            } while (($digit == 0x100) && ($j >= 0));
        }
    
        $encryptCtrData = mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128, 
            $key, 
            $ctrData, 
            MCRYPT_MODE_ECB
        );
        
        return $data ^ $encryptCtrData;
    }
    
    /**
     * Convert base 256 number to decimal number. 
     * 
     * @param string $number Base 256 number
     * 
     * @return string Decimal number
     */
    public static function base256ToDec($number) 
    {
        Validate::isString($number, 'number');
        
        $result = 0;
        $base   = 1;
        for ($i = strlen($number) - 1; $i >= 0; $i--) {
            $result = bcadd($result, bcmul(ord($number[$i]), $base));
            $base   = bcmul($base, 256);
        }
    
        return $result;
    }

}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Validates aganist a condition and throws an exception in case of failure.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Validate
{
    /**
     * Throws exception if the provided variable type is not array.
     *
     * @param mix    $var  The variable to check.
     * @param string $name The parameter name.
     *
     * @throws InvalidArgumentTypeException.
     *
     * @return none
     */
    public static function isArray($var, $name)
    {
        if (!is_array($var)) {
            throw new InvalidArgumentTypeException(gettype(array()), $name);
        }
    }

    /**
     * Throws exception if the provided variable type is not string.
     *
     * @param mix    $var  The variable to check.
     * @param string $name The parameter name.
     *
     * @throws InvalidArgumentTypeException
     *
     * @return none
     */
    public static function isString($var, $name)
    {
        try {
            (string)$var;
        } catch (\Exception $e) {
            throw new InvalidArgumentTypeException(gettype(''), $name);
        }
    }

    /**
     * Throws exception if the provided variable type is not boolean.
     *
     * @param mix $var variable to check against.
     *
     * @throws InvalidArgumentTypeException
     *
     * @return none
     */
    public static function isBoolean($var)
    {
        (bool)$var;
    }

    /**
     * Throws exception if the provided variable is set to null.
     *
     * @param mix    $var  The variable to check.
     * @param string $name The parameter name.
     *
     * @throws \InvalidArgumentException
     *
     * @return none
     */
    public static function notNullOrEmpty($var, $name)
    {
        if (is_null($var) || empty($var)) {
            throw new \InvalidArgumentException(
                sprintf(Resources::NULL_OR_EMPTY_MSG, $name)
            );
        }
    }

    /**
     * Throws exception if the provided variable is not double.
     *
     * @param mix    $var  The variable to check.
     * @param string $name The parameter name.
     *
     * @throws \InvalidArgumentException
     *
     * @return none
     */
    public static function isDouble($var, $name)
    {
        if (!is_numeric($var)) {
            throw new InvalidArgumentTypeException('double', $name);
        }
    }

    /**
     * Throws exception if the provided variable type is not integer.
     *
     * @param mix    $var  The variable to check.
     * @param string $name The parameter name.
     *
     * @throws InvalidArgumentTypeException
     *
     * @return none
     */
    public static function isInteger($var, $name)
    {
        try {
            (int)$var;
        } catch (\Exception $e) {
            throw new InvalidArgumentTypeException(gettype(123), $name);
        }
    }

    /**
     * Returns whether the variable is an empty or null string.
     *
     * @param string $var value.
     *
     * @return boolean
     */
    public static function isNullOrEmptyString($var)
    {
        try {
            (string)$var;
        } catch (\Exception $e) {
            return false;
        }

        return (!isset($var) || trim($var)==='');
    }

    /**
     * Throws exception if the provided condition is not satisfied.
     *
     * @param bool   $isSatisfied    condition result.
     * @param string $failureMessage the exception message
     *
     * @throws \Exception
     *
     * @return none
     */
    public static function isTrue($isSatisfied, $failureMessage)
    {
        if (!$isSatisfied) {
            throw new \InvalidArgumentException($failureMessage);
        }
    }

    /**
     * Throws exception if the provided $date is not of type \DateTime
     *
     * @param mix $date variable to check against.
     *
     * @throws MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException
     *
     * @return none
     */
    public static function isDate($date)
    {
        if (gettype($date) != 'object' || get_class($date) != 'DateTime') {
            throw new InvalidArgumentTypeException('DateTime');
        }
    }

    /**
     * Throws exception if the provided variable is set to null.
     *
     * @param mix    $var  The variable to check.
     * @param string $name The parameter name.
     *
     * @throws \InvalidArgumentException
     *
     * @return none
     */
    public static function notNull($var, $name)
    {
        if (is_null($var)) {
            throw new \InvalidArgumentException(sprintf(Resources::NULL_MSG, $name));
        }
    }

    /**
     * Throws exception if the object is not of the specified class type.
     *
     * @param mixed  $objectInstance An object that requires class type validation.
     * @param mixed  $classInstance  The instance of the class the the
     * object instance should be.
     * @param string $name           The name of the object.
     *
     * @throws \InvalidArgumentException
     *
     * @return none
     */
    public static function isInstanceOf($objectInstance, $classInstance, $name)
    {
        Validate::notNull($classInstance, 'classInstance');
        if (is_null($objectInstance)) {
            return true;
        }

        $objectType = gettype($objectInstance);
        $classType  = gettype($classInstance);

        if ($objectType === $classType) {
            return true;
        } else {
            throw new \InvalidArgumentException(
                sprintf(
                    Resources::INSTANCE_TYPE_VALIDATION_MSG,
                    $name,
                    $objectType,
                    $classType
                )
            );
        }
    }

    /**
     * Creates a anonymous function that check if the given uri is valid or not.
     *
     * @return callable
     */
    public static function getIsValidUri()
    {
        return function ($uri) {
            return Validate::isValidUri($uri);
        };
    }

    /**
     * Throws exception if the string is not of a valid uri.
     *
     * @param string $uri String to check.
     *
     * @throws \InvalidArgumentException
     *
     * @return boolean
     */
    public static function isValidUri($uri)
    {
        $isValid = filter_var($uri, FILTER_VALIDATE_URL);

        if ($isValid) {
            return true;
        } else {
            throw new \RuntimeException(
                sprintf(Resources::INVALID_CONFIG_URI, $uri)
            );
        }
    }

    /**
     * Throws exception if the provided variable type is not object.
     *
     * @param mix    $var  The variable to check.
     * @param string $name The parameter name.
     *
     * @throws InvalidArgumentTypeException.
     *
     * @return boolean
     */
    public static function isObject($var, $name)
    {
        if (!is_object($var)) {
            throw new InvalidArgumentTypeException('object', $name);
        }

        return true;
    }

    /**
     * Throws exception if the object is not of the specified class type.
     *
     * @param mixed  $objectInstance An object that requires class type validation.
     * @param string $class          The class the object instance should be.
     * @param string $name           The parameter name.
     *
     * @throws \InvalidArgumentException
     *
     * @return boolean
     */
    public static function isA($objectInstance, $class, $name)
    {
        Validate::isString($class, 'class');
        Validate::notNull($objectInstance, 'objectInstance');
        Validate::isObject($objectInstance, 'objectInstance');

        $objectType = get_class($objectInstance);

        if (is_a($objectInstance, $class)) {
            return true;
        } else {
            throw new \InvalidArgumentException(
                sprintf(
                    Resources::INSTANCE_TYPE_VALIDATION_MSG,
                    $name,
                    $objectType,
                    $class
                )
            );
        }
    }

    /**
     * Validate if method exists in object
     *
     * @param object $objectInstance An object that requires method existing
     *                               validation
     * @param string $method         Method name
     * @param string $name           The parameter name
     *
     * @return boolean
     */
    public static function methodExists($objectInstance, $method, $name)
    {
        Validate::isString($method, 'method');
        Validate::notNull($objectInstance, 'objectInstance');
        Validate::isObject($objectInstance, 'objectInstance');

        if (method_exists($objectInstance, $method)) {
            return true;
        } else {
            throw new \InvalidArgumentException(
                sprintf(
                    Resources::ERROR_METHOD_NOT_FOUND,
                    $method,
                    $name
                )
            );
        }
    }

    /**
     * Validate if string is date formatted
     *
     * @param string $value Value to validate
     * @param string $name  Name of parameter to insert in erro message
     *
     * @throws \InvalidArgumentException
     *
     * @return boolean
     */
    public static function isDateString($value, $name)
    {
        Validate::isString($value, 'value');

        try {
            new \DateTime($value);
            return true;
        }
        catch (\Exception $e) {
            throw new \InvalidArgumentException(
                sprintf(
                    Resources::ERROR_INVALID_DATE_STRING,
                    $name,
                    $value
                )
            );
        }
    }

}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common\Models;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;

/**
 * Result from calling GetQueueProperties REST wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetServicePropertiesResult
{
    private $_serviceProperties;
    
    /**
     * Creates object from $parsedResponse.
     * 
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult
     */
    public static function create($parsedResponse)
    {
        $result                     = new GetServicePropertiesResult();
        $result->_serviceProperties = ServiceProperties::create($parsedResponse);
        
        return $result;
    }
    
    /**
     * Gets service properties object.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\ServiceProperties 
     */
    public function getValue()
    {
        return $this->_serviceProperties;
    }
    
    /**
     * Sets service properties object.
     * 
     * @param ServiceProperties $serviceProperties object to use.
     * 
     * @return none 
     */
    public function setValue($serviceProperties)
    {
        $this->_serviceProperties = clone $serviceProperties;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Models;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds elements of queue properties logging field.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Logging
{
    /**
     * The version of Storage Analytics to configure
     * 
     * @var string
     */
    private $_version;
    
    /**
     * Applies only to logging configuration. Indicates whether all delete requests 
     * should be logged.
     * 
     * @var bool
     */
    private $_delete;
    
    /**
     * Applies only to logging configuration. Indicates whether all read requests 
     * should be logged.
     * 
     * @var bool.
     */
    private $_read;
    
    /**
     * Applies only to logging configuration. Indicates whether all write requests 
     * should be logged.
     * 
     * @var bool 
     */
    private $_write;
    
    /**
     * @var MicrosoftAzure\Storage\Common\Models\RetentionPolicy
     */
    private $_retentionPolicy;
    
    /**
     * Creates object from $parsedResponse.
     * 
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\Logging
     */
    public static function create($parsedResponse)
    {
        $result = new Logging();
        $result->setVersion($parsedResponse['Version']);
        $result->setDelete(Utilities::toBoolean($parsedResponse['Delete']));
        $result->setRead(Utilities::toBoolean($parsedResponse['Read']));
        $result->setWrite(Utilities::toBoolean($parsedResponse['Write']));
        $result->setRetentionPolicy(
            RetentionPolicy::create($parsedResponse['RetentionPolicy'])
        );
        
        return $result;
    }
    
    /**
     * Gets retention policy
     * 
     * @return MicrosoftAzure\Storage\Common\Models\RetentionPolicy
     *  
     */
    public function getRetentionPolicy()
    {
        return $this->_retentionPolicy;
    }
    
    /**
     * Sets retention policy
     * 
     * @param RetentionPolicy $policy object to use
     * 
     * @return none.
     */
    public function setRetentionPolicy($policy)
    {
        $this->_retentionPolicy = $policy;
    }
    
    /**
     * Gets write
     * 
     * @return bool.
     */
    public function getWrite()
    {
        return $this->_write;
    }
    
    /**
     * Sets write
     * 
     * @param bool $write new value.
     * 
     * @return none.
     */
    public function setWrite($write)
    {
        $this->_write = $write;
    }
            
    /**
     * Gets read
     * 
     * @return bool.
     */
    public function getRead()
    {
        return $this->_read;
    }
    
    /**
     * Sets read
     * 
     * @param bool $read new value.
     * 
     * @return none.
     */
    public function setRead($read)
    {
        $this->_read = $read;
    }
    
    /**
     * Gets delete
     * 
     * @return bool.
     */
    public function getDelete()
    {
        return $this->_delete;
    }
    
    /**
     * Sets delete
     * 
     * @param bool $delete new value.
     * 
     * @return none.
     */
    public function setDelete($delete)
    {
        $this->_delete = $delete;
    }
    
    /**
     * Gets version
     * 
     * @return string.
     */
    public function getVersion()
    {
        return $this->_version;
    }
    
    /**
     * Sets version
     * 
     * @param string $version new value.
     * 
     * @return none.
     */
    public function setVersion($version)
    {
        $this->_version = $version;
    }
    
    /**
     * Converts this object to array with XML tags
     * 
     * @return array. 
     */
    public function toArray()
    {
        return array(
            'Version'         => $this->_version,
            'Delete'          => Utilities::booleanToString($this->_delete),
            'Read'            => Utilities::booleanToString($this->_read),
            'Write'           => Utilities::booleanToString($this->_write),
            'RetentionPolicy' => !empty($this->_retentionPolicy)
                ? $this->_retentionPolicy->toArray()
                : null
        );
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds elements of queue properties metrics field.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Metrics
{
    /**
     * The version of Storage Analytics to configure
     * 
     * @var string
     */
    private $_version;
    
    /**
     * Indicates whether metrics is enabled for the storage service
     * 
     * @var bool
     */
    private $_enabled;
    
    /**
     * Indicates whether a retention policy is enabled for the storage service
     * 
     * @var bool
     */
    private $_includeAPIs;
    
    /**
     * @var MicrosoftAzure\Storage\Common\Models\RetentionPolicy
     */
    private $_retentionPolicy;
    
    /**
     * Creates object from $parsedResponse.
     * 
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\Metrics
     */
    public static function create($parsedResponse)
    {
        $result = new Metrics();
        $result->setVersion($parsedResponse['Version']);
        $result->setEnabled(Utilities::toBoolean($parsedResponse['Enabled']));
        if ($result->getEnabled()) {
            $result->setIncludeAPIs(
                Utilities::toBoolean($parsedResponse['IncludeAPIs'])
            );
        }
        $result->setRetentionPolicy(
            RetentionPolicy::create($parsedResponse['RetentionPolicy'])
        );
        
        return $result;
    }
    
    /**
     * Gets retention policy
     * 
     * @return MicrosoftAzure\Storage\Common\Models\RetentionPolicy
     *  
     */
    public function getRetentionPolicy()
    {
        return $this->_retentionPolicy;
    }
    
    /**
     * Sets retention policy
     * 
     * @param RetentionPolicy $policy object to use
     * 
     * @return none.
     */
    public function setRetentionPolicy($policy)
    {
        $this->_retentionPolicy = $policy;
    }
    
    /**
     * Gets include APIs.
     * 
     * @return bool. 
     */
    public function getIncludeAPIs()
    {
        return $this->_includeAPIs;
    }
    
    /**
     * Sets include APIs.
     * 
     * @param $bool $includeAPIs value to use.
     * 
     * @return none. 
     */
    public function setIncludeAPIs($includeAPIs)
    {
        $this->_includeAPIs = $includeAPIs;
    }
    
    /**
     * Gets enabled.
     * 
     * @return bool. 
     */
    public function getEnabled()
    {
        return $this->_enabled;
    }
    
    /**
     * Sets enabled.
     * 
     * @param bool $enabled value to use.
     * 
     * @return none. 
     */
    public function setEnabled($enabled)
    {
        $this->_enabled = $enabled;
    }
    
    /**
     * Gets version
     * 
     * @return string.
     */
    public function getVersion()
    {
        return $this->_version;
    }
    
    /**
     * Sets version
     * 
     * @param string $version new value.
     * 
     * @return none.
     */
    public function setVersion($version)
    {
        $this->_version = $version;
    }
    
    /**
     * Converts this object to array with XML tags
     * 
     * @return array. 
     */
    public function toArray()
    {
        $array = array(
            'Version' => $this->_version,
            'Enabled' => Utilities::booleanToString($this->_enabled)
        );
        if ($this->_enabled) {
            $array['IncludeAPIs'] = Utilities::booleanToString($this->_includeAPIs);
        }
        $array['RetentionPolicy'] = !empty($this->_retentionPolicy)
            ? $this->_retentionPolicy->toArray()
            : null;
        
        return $array;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds elements of queue properties retention policy field.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class RetentionPolicy
{
    /**
     * Indicates whether a retention policy is enabled for the storage service
     * 
     * @var bool.
     */
    private $_enabled;
    
    /**
     * If $_enabled is true then this field indicates the number of days that metrics
     * or logging data should be retained. All data older than this value will be 
     * deleted. The minimum value you can specify is 1; 
     * the largest value is 365 (one year)
     * 
     * @var int
     */
    private $_days;
    
    /**
     * Creates object from $parsedResponse.
     * 
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\RetentionPolicy
     */
    public static function create($parsedResponse)
    {
        $result = new RetentionPolicy();
        $result->setEnabled(Utilities::toBoolean($parsedResponse['Enabled']));
        if ($result->getEnabled()) {
            $result->setDays(intval($parsedResponse['Days']));
        }
        
        return $result;
    }
    
    /**
     * Gets enabled.
     * 
     * @return bool. 
     */
    public function getEnabled()
    {
        return $this->_enabled;
    }
    
    /**
     * Sets enabled.
     * 
     * @param bool $enabled value to use.
     * 
     * @return none. 
     */
    public function setEnabled($enabled)
    {
        $this->_enabled = $enabled;
    }
    
    /**
     * Gets days field.
     * 
     * @return int
     */
    public function getDays()
    {
        return $this->_days;
    }
    
    /**
     * Sets days field.
     * 
     * @param int $days value to use.
     * 
     * @return none
     */
    public function setDays($days)
    {
        $this->_days = $days;
    }
    
    /**
     * Converts this object to array with XML tags
     * 
     * @return array. 
     */
    public function toArray()
    {
        $array = array('Enabled' => Utilities::booleanToString($this->_enabled));
        if (isset($this->_days)) {
            $array['Days'] = strval($this->_days);
        }
        
        return $array;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Models\Logging;
use MicrosoftAzure\Storage\Common\Models\Metrics;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * Encapsulates service properties
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServiceProperties
{
    private $_logging;
    private $_metrics;
    public static $xmlRootName = 'StorageServiceProperties';
    
    /**
     * Creates ServiceProperties object from parsed XML response.
     *
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\ServiceProperties.
     */
    public static function create($parsedResponse)
    {
        $result = new ServiceProperties();
        $result->setLogging(Logging::create($parsedResponse['Logging']));
        $result->setMetrics(Metrics::create($parsedResponse['HourMetrics']));
        
        return $result;
    }
    
    /**
     * Gets logging element.
     *
     * @return MicrosoftAzure\Storage\Common\Models\Logging.
     */
    public function getLogging()
    {
        return $this->_logging;
    }
    
    /**
     * Sets logging element.
     *
     * @param MicrosoftAzure\Storage\Common\Models\Logging $logging new element.
     * 
     * @return none.
     */
    public function setLogging($logging)
    {
        $this->_logging = clone $logging;
    }
    
    /**
     * Gets metrics element.
     *
     * @return MicrosoftAzure\Storage\Common\Models\Metrics.
     */
    public function getMetrics()
    {
        return $this->_metrics;
    }
    
    /**
     * Sets metrics element.
     *
     * @param MicrosoftAzure\Storage\Common\Models\Metrics $metrics new element.
     * 
     * @return none.
     */
    public function setMetrics($metrics)
    {
        $this->_metrics = clone $metrics;
    }
    
    /**
     * Converts this object to array with XML tags
     * 
     * @return array. 
     */
    public function toArray()
    {
        return array(
            'Logging' => !empty($this->_logging) ? $this->_logging->toArray() : null,
            'HourMetrics' => !empty($this->_metrics) ? $this->_metrics->toArray() : null
        );
    }
    
    /**
     * Converts this current object to XML representation.
     * 
     * @param XmlSerializer $xmlSerializer The XML serializer.
     * 
     * @return string
     */
    public function toXml($xmlSerializer)
    {
        $properties = array(XmlSerializer::ROOT_NAME => self::$xmlRootName);
        
        return $xmlSerializer->serialize($this->toArray(), $properties);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Common;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Fires when the response code is incorrect.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServiceException extends \LogicException
{
    private $_error;
    private $_reason;
    
    /**
     * Constructor
     *
     * @param string $errorCode status error code.
     * @param string $error     string value of the error code.
     * @param string $reason    detailed message for the error.
     * 
     * @return MicrosoftAzure\Storage\Common\ServiceException
     */
    public function __construct($errorCode, $error = null, $reason = null)
    {
        parent::__construct(
            sprintf(Resources::AZURE_ERROR_MSG, $errorCode, $error, $reason)
        );
        $this->code    = $errorCode;
        $this->_error  = $error;
        $this->_reason = $reason;
    }
    
    /**
     * Gets error text.
     *
     * @return string
     */
    public function getErrorText()
    {
        return $this->_error;
    }
    
    /**
     * Gets detailed error reason.
     *
     * @return string
     */
    public function getErrorReason()
    {
        return $this->_reason;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Common;
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Filters\DateFilter;
use MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter;
use MicrosoftAzure\Storage\Common\Internal\Filters\AuthenticationFilter;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;
use MicrosoftAzure\Storage\Common\Internal\Authentication\SharedKeyAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\Authentication\TableSharedKeyLiteAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;
use MicrosoftAzure\Storage\Queue\QueueRestProxy;
use MicrosoftAzure\Storage\Table\TableRestProxy;
use MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter;
use MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter;


/**
 * Builds azure service objects.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServicesBuilder
{
    /**
     * @var ServicesBuilder
     */
    private static $_instance = null;

    /**
     * Gets the serializer used in the REST services construction.
     *
     * @return MicrosoftAzure\Storage\Common\Internal\Serialization\ISerializer
     */
    protected function serializer()
    {
        return new XmlSerializer();
    }

    /**
     * Gets the MIME serializer used in the REST services construction.
     *
     * @return \MicrosoftAzure\Storage\Table\Internal\IMimeReaderWriter
     */
    protected function mimeSerializer()
    {
        return new MimeReaderWriter();
    }

    /**
     * Gets the Atom serializer used in the REST services construction.
     *
     * @return \MicrosoftAzure\Storage\Table\Internal\IAtomReaderWriter
     */
    protected function atomSerializer()
    {
        return new AtomReaderWriter();
    }

    /**
     * Gets the Queue authentication scheme.
     *
     * @param string $accountName The account name.
     * @param string $accountKey  The account key.
     *
     * @return \MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme
     */
    protected function queueAuthenticationScheme($accountName, $accountKey)
    {
        return new SharedKeyAuthScheme($accountName, $accountKey);
    }

    /**
     * Gets the Blob authentication scheme.
     *
     * @param string $accountName The account name.
     * @param string $accountKey  The account key.
     *
     * @return \MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme
     */
    protected function blobAuthenticationScheme($accountName, $accountKey)
    {
        return new SharedKeyAuthScheme($accountName, $accountKey);
    }

    /**
     * Gets the Table authentication scheme.
     *
     * @param string $accountName The account name.
     * @param string $accountKey  The account key.
     *
     * @return TableSharedKeyLiteAuthScheme
     */
    protected function tableAuthenticationScheme($accountName, $accountKey)
    {
        return new TableSharedKeyLiteAuthScheme($accountName, $accountKey);
    }

    /**
     * Builds a queue object.
     *
     * @param string $connectionString The configuration connection string.
     * @param array  $options          Array of options to pass to the service
     *
     * @return MicrosoftAzure\Storage\Queue\Internal\IQueue
     */
    public function createQueueService($connectionString, $options = [])
    {
        $settings = StorageServiceSettings::createFromConnectionString(
            $connectionString
        );

        $serializer = $this->serializer();
        $uri        = Utilities::tryAddUrlScheme(
            $settings->getQueueEndpointUri()
        );

        $queueWrapper = new QueueRestProxy(
            $uri,
            $settings->getName(),
            $serializer,
            $options
        );

        // Adding headers filter
        $headers = array(
            Resources::USER_AGENT => self::getUserAgent(),
        );

        $headers[Resources::X_MS_VERSION] = Resources::STORAGE_API_LATEST_VERSION;

        $headersFilter = new HeadersFilter($headers);
        $queueWrapper  = $queueWrapper->withFilter($headersFilter);

        // Adding date filter
        $dateFilter   = new DateFilter();
        $queueWrapper = $queueWrapper->withFilter($dateFilter);

        // Adding authentication filter
        $authFilter = new AuthenticationFilter(
            $this->queueAuthenticationScheme(
                $settings->getName(),
                $settings->getKey()
            )
        );

        $queueWrapper = $queueWrapper->withFilter($authFilter);

        return $queueWrapper;
    }

    /**
     * Builds a blob object.
     *
     * @param string $connectionString The configuration connection string.
     * @param array  $options          Array of options to pass to the service
     * @return MicrosoftAzure\Storage\Blob\Internal\IBlob
     */
    public function createBlobService($connectionString, $options = [])
    {
        $settings = StorageServiceSettings::createFromConnectionString(
            $connectionString
        );

        $serializer = $this->serializer();
        $uri        = Utilities::tryAddUrlScheme(
            $settings->getBlobEndpointUri()
        );

        $blobWrapper = new BlobRestProxy(
            $uri,
            $settings->getName(),
            $serializer,
            $options
        );

        // Adding headers filter
        $headers = array(
            Resources::USER_AGENT => self::getUserAgent(),
        );

        $headers[Resources::X_MS_VERSION] = Resources::STORAGE_API_LATEST_VERSION;

        $headersFilter = new HeadersFilter($headers);
        $blobWrapper   = $blobWrapper->withFilter($headersFilter);

        // Adding date filter
        $dateFilter  = new DateFilter();
        $blobWrapper = $blobWrapper->withFilter($dateFilter);

        $authFilter = new AuthenticationFilter(
            $this->blobAuthenticationScheme(
                $settings->getName(),
                $settings->getKey()
            )
        );

        $blobWrapper = $blobWrapper->withFilter($authFilter);

        return $blobWrapper;
    }

    /**
     * Builds a table object.
     *
     * @param string $connectionString The configuration connection string.
     * @param array  $options          Array of options to pass to the service
     *
     * @return MicrosoftAzure\Storage\Table\Internal\ITable
     */
    public function createTableService($connectionString, $options = [])
    {
        $settings = StorageServiceSettings::createFromConnectionString(
            $connectionString
        );

        $atomSerializer = $this->atomSerializer();
        $mimeSerializer = $this->mimeSerializer();
        $serializer     = $this->serializer();
        $uri            = Utilities::tryAddUrlScheme(
            $settings->getTableEndpointUri()
        );

        $tableWrapper = new TableRestProxy(
            $uri,
            $atomSerializer,
            $mimeSerializer,
            $serializer,
            $options
        );

        // Adding headers filter
        $headers               = array();
        $latestServicesVersion = Resources::STORAGE_API_LATEST_VERSION;
        $currentVersion        = Resources::DATA_SERVICE_VERSION_VALUE;
        $maxVersion            = Resources::MAX_DATA_SERVICE_VERSION_VALUE;
        $accept                = Resources::ACCEPT_HEADER_VALUE;
        $acceptCharset         = Resources::ACCEPT_CHARSET_VALUE;
        $userAgent             = self::getUserAgent();

        $headers[Resources::X_MS_VERSION]             = $latestServicesVersion;
        $headers[Resources::DATA_SERVICE_VERSION]     = $currentVersion;
        $headers[Resources::MAX_DATA_SERVICE_VERSION] = $maxVersion;
        $headers[Resources::MAX_DATA_SERVICE_VERSION] = $maxVersion;
        $headers[Resources::ACCEPT_HEADER]            = $accept;
        $headers[Resources::ACCEPT_CHARSET]           = $acceptCharset;
        $headers[Resources::USER_AGENT]               = $userAgent;

        $headersFilter = new HeadersFilter($headers);
        $tableWrapper  = $tableWrapper->withFilter($headersFilter);

        // Adding date filter
        $dateFilter   = new DateFilter();
        $tableWrapper = $tableWrapper->withFilter($dateFilter);

        // Adding authentication filter
        $authFilter = new AuthenticationFilter(
            $this->tableAuthenticationScheme(
                $settings->getName(),
                $settings->getKey()
            )
        );

        $tableWrapper = $tableWrapper->withFilter($authFilter);

        return $tableWrapper;
    }

    /**
     * Gets the user agent string used in request header.
     *
     * @return string
     */
    private static function getUserAgent()
    {
        // e.g. User-Agent: Azure-Storage/0.10.0 (PHP 5.5.32)
        return 'Azure-Storage/' . Resources::SDK_VERSION . ' (PHP ' . PHP_VERSION . ')';
    }

    /**
     * Gets the static instance of this class.
     *
     * @return ServicesBuilder
     */
    public static function getInstance()
    {
        if (!isset(self::$_instance)) {
            self::$_instance = new ServicesBuilder();
        }

        return self::$_instance;
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Queue\Internal;
use MicrosoftAzure\Storage\Common\Internal\FilterableService;

/**
 * This interface has all REST APIs provided by Windows Azure for queue service
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 * @see       http://msdn.microsoft.com/en-us/library/windowsazure/dd179363.aspx
 */
interface IQueue extends FilterableService
{
    /**
     * Gets the properties of the Queue service.
     * 
     * @param QueueServiceOptions $options The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult
     */
    public function getServiceProperties($options = null);

    /**
     * Sets the properties of the Queue service.
     * 
     * It's recommended to use getServiceProperties, alter the returned object and
     * then use setServiceProperties with this altered object.
     * 
     * @param array               $serviceProperties The new service properties.
     * @param QueueServiceOptions $options           The optional parameters.  
     * 
     * @return none
     */
    public function setServiceProperties($serviceProperties, $options = null);

    /**
     * Creates a new queue under the storage account.
     * 
     * @param string             $queueName The queue name.
     * @param QueueCreateOptions $options   The optional queue create options.
     * 
     * @return none
     */
    public function createQueue($queueName, $options = null);

    /**
     * Deletes a queue.
     * 
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     * 
     * @return none
     */
    public function deleteQueue($queueName, $options);

    /**
     * Lists all queues in the storage account.
     * 
     * @param ListQueuesOptions $options The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\ListQueuesResult
     */
    public function listQueues($options = null);

    /**
     * Returns queue properties, including user-defined metadata.
     * 
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\GetQueueMetadataResult
     */
    public function getQueueMetadata($queueName, $options = null);

    /**
     * Sets user-defined metadata on the queue. To delete queue metadata, call 
     * this API without specifying any metadata in $metadata.
     * 
     * @param string              $queueName The queue name.
     * @param array               $metadata  The metadata array.
     * @param QueueServiceOptions $options   The optional parameters.
     * 
     * @return none
     */
    public function setQueueMetadata($queueName, $metadata, $options = null);

    /**
     * Adds a message to the queue and optionally sets a visibility timeout 
     * for the message.
     * 
     * @param string               $queueName   The queue name.
     * @param string               $messageText The message contents.
     * @param CreateMessageOptions $options     The optional parameters.
     * 
     * @return none
     */
    public function createMessage($queueName, $messageText, $options = null);

    /**
     * Updates the visibility timeout of a message and/or the message contents.
     * 
     * @param string              $queueName                  The queue name.
     * @param string              $messageId                  The id of the message.
     * @param string              $popReceipt                 The valid pop receipt 
     * value returned from an earlier call to the Get Messages or Update Message
     * operation.
     * @param string              $messageText                The message contents.
     * @param int                 $visibilityTimeoutInSeconds Specifies the new 
     * visibility timeout value, in seconds, relative to server time. 
     * The new value must be larger than or equal to 0, and cannot be larger 
     * than 7 days. The visibility timeout of a message cannot be set to a value 
     * later than the expiry time. A message can be updated until it has been 
     * deleted or has expired.
     * @param QueueServiceOptions $options                    The optional 
     * parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\UpdateMessageResult
     */
    public function updateMessage($queueName, $messageId, $popReceipt, $messageText, 
        $visibilityTimeoutInSeconds, $options = null
    );

    /**
     * Deletes a specified message from the queue.
     * 
     * @param string              $queueName  The queue name.
     * @param string              $messageId  The id of the message.
     * @param string              $popReceipt The valid pop receipt value returned
     * from an earlier call to the Get Messages or Update Message operation.
     * @param QueueServiceOptions $options    The optional parameters.
     * 
     * @return none
     */
    public function deleteMessage($queueName, $messageId, $popReceipt, 
        $options = null
    );

    /**
     * Lists all messages in the queue.
     * 
     * @param string              $queueName The queue name.
     * @param ListMessagesOptions $options   The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\ListMessagesResult
     */
    public function listMessages($queueName, $options = null);

    /**
     * Retrieves a message from the front of the queue, without changing 
     * the message visibility.
     * 
     * @param string              $queueName The queue name.
     * @param PeekMessagesOptions $options   The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\PeekMessagesResult
     */
    public function peekMessages($queueName, $options = null);

    /**
     * Clears all messages from the queue.
     * 
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\PeekMessagesResult
     */
    public function clearMessages($queueName, $options = null);
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;

/**
 * Holds optional parameters for createMessage wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateMessageOptions extends QueueServiceOptions
{
    /**
     * If specified, the request must be made using an x-ms-version 
     * of 2011-08-18 or newer. If not specified, the default value is 0. 
     * Specifies the new visibility timeout value, in seconds, relative to server 
     * time. The new value must be larger than or equal to 0, and cannot be 
     * larger than 7 days. The visibility timeout of a message cannot be set to a 
     * value later than the expiry time. visibilitytimeout should be set to a 
     * value smaller than the time-to-live value.
     * 
     * @var integer
     */
    private $_visibilityTimeoutInSeconds;
    
    /**
     * Specifies the time-to-live interval for the message, in seconds. 
     * The maximum time-to-live allowed is 7 days. If this parameter is omitted, 
     * the default time-to-live is 7 days.
     * 
     * @var integer
     */
    private $_timeToLiveInSeconds;
    
    /**
     * Gets visibilityTimeoutInSeconds field.
     * 
     * @return integer
     */
    public function getVisibilityTimeoutInSeconds()
    {
        return $this->_visibilityTimeoutInSeconds;
    }
    
    /**
     * Sets visibilityTimeoutInSeconds field.
     * 
     * @param integer $visibilityTimeoutInSeconds value to use.
     * 
     * @return none
     */
    public function setVisibilityTimeoutInSeconds($visibilityTimeoutInSeconds)
    {
        $this->_visibilityTimeoutInSeconds = $visibilityTimeoutInSeconds;
    }
    
    /**
     * Gets timeToLiveInSeconds field.
     * 
     * @return integer
     */
    public function getTimeToLiveInSeconds()
    {
        return $this->_timeToLiveInSeconds;
    }
    
    /**
     * Sets timeToLiveInSeconds field.
     * 
     * @param integer $timeToLiveInSeconds value to use.
     * 
     * @return none
     */
    public function setTimeToLiveInSeconds($timeToLiveInSeconds)
    {
        $this->_timeToLiveInSeconds = $timeToLiveInSeconds;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;

/**
 * Optional parameters for Create Queue REST API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateQueueOptions extends QueueServiceOptions
{
    private $_metadata;
    
    /**
     * Gets user defined metadata.
     * 
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }
    
    /**
     * Sets user defined metadata. This metadata should be added without the header
     * prefix (x-ms-meta-*).
     * 
     * @param array $metadata user defined metadata object in array form.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
    
    /**
     * Adds new metadata element. This element should be added without the header
     * prefix (x-ms-meta-*).
     * 
     * @param string $key   metadata key element.
     * @param string $value metadata value element.
     * 
     * @return none.
     */
    public function addMetadata($key, $value)
    {
        $this->_metadata[$key] = $value;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;

/**
 * Holds result from calling GetQueueMetadata wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetQueueMetadataResult
{
    /**
     * Indicates the approximate number of messages in the queue
     * 
     * @var integer 
     */
    private $_approximateMessageCount;
    
    /**
     * A user-defined name/value pair
     * 
     * @var array 
     */
    private $_metadata;
    
    /**
     * Constructor
     * 
     * @param integer $approximateMessageCount Approximate number of queue messages.
     * @param array   $metadata                user defined metadata.
     */
    public function __construct($approximateMessageCount, $metadata)
    {
        $this->_approximateMessageCount = $approximateMessageCount;
        $this->_metadata                = is_null($metadata) ? array() : $metadata;
    }
    
    /**
     * Gets approximate message count.
     * 
     * @return integer
     */
    public function getApproximateMessageCount()
    {
        return $this->_approximateMessageCount;
    }
    
    /**
     * Sets approximate message count.
     * 
     * @param integer $approximateMessageCount value to use.
     * 
     * @return none
     */
    public function setApproximateMessageCount($approximateMessageCount)
    {
        $this->_approximateMessageCount = $approximateMessageCount;
    }
    
    /**
     * Sets metadata.
     * 
     * @return array
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }
    
    /**
     * Sets metadata.
     * 
     * @param array $metadata value to use.
     * 
     * @return none
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }

}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;

/**
 * Optional parameters for list messages wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListMessagesOptions extends QueueServiceOptions
{
    /**
     * A nonzero integer value that specifies the number of messages to retrieve 
     * from the queue, up to a maximum of 32. If fewer are visible, 
     * the visible messages are returned. By default, a single message is retrieved 
     * from the queue with this operation.
     * 
     * @var integer
     */
    private $_numberOfMessages;
    
    /**
     * Specifies the new visibility timeout value, in seconds, 
     * relative to server time. The new value must be larger than or equal to 
     * 1 second, and cannot be larger than 7 days, or larger than 2 hours on 
     * REST protocol versions prior to version 2011-08-18. 
     * The visibility timeout of a message can be set to a value later than the 
     * expiry time.
     * 
     * @var integer
     */
    private $_visibilityTimeoutInSeconds;
    
    /**
     * Gets visibilityTimeoutInSeconds field.
     * 
     * @return integer
     */
    public function getVisibilityTimeoutInSeconds()
    {
        return $this->_visibilityTimeoutInSeconds;
    }
    
    /**
     * Sets visibilityTimeoutInSeconds field.
     * 
     * @param integer $visibilityTimeoutInSeconds value to use.
     * 
     * @return none
     */
    public function setVisibilityTimeoutInSeconds($visibilityTimeoutInSeconds)
    {
        $this->_visibilityTimeoutInSeconds = $visibilityTimeoutInSeconds;
    }
    
    /**
     * Gets numberOfMessages field.
     * 
     * @return integer
     */
    public function getNumberOfMessages()
    {
        return $this->_numberOfMessages;
    }
    
    /**
     * Sets numberOfMessages field.
     * 
     * @param integer $numberOfMessages value to use.
     * 
     * @return none
     */
    public function setNumberOfMessages($numberOfMessages)
    {
        $this->_numberOfMessages = $numberOfMessages;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds results of listMessages wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListMessagesResult
{
    /**
     * Holds all message entries.
     * 
     * @var array.
     */
    private $_queueMessages;
    
    /**
     * Creates ListMessagesResult object from parsed XML response.
     *
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Queue\Models\ListMessagesResult.
     */
    public static function create($parsedResponse)
    {
        $result        = new ListMessagesResult();
        $queueMessages = array();
        
        if (!empty($parsedResponse)) {
            $rawMessages = Utilities::getArray($parsedResponse['QueueMessage']);
            foreach ($rawMessages as $value) {
                $message = MicrosoftAzureQueueMessage::createFromListMessages($value);
                
                $queueMessages[] = $message;
            }
        }
        $result->setQueueMessages($queueMessages);
        
        return $result;
    }
    
    /**
     * Gets queueMessages field.
     * 
     * @return array
     */
    public function getQueueMessages()
    {
        return $this->_queueMessages;
    }
    
    /**
     * Sets queueMessages field.
     * 
     * @param integer $queueMessages value to use.
     * 
     * @return none
     */
    public function setQueueMessages($queueMessages)
    {
        $this->_queueMessages = array();
        
        foreach ($queueMessages as $value) {
            $this->_queueMessages[] = clone $value;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions;
use \MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Options for listQueues API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListQueuesOptions extends QueueServiceOptions
{
    private $_prefix;
    private $_marker;
    private $_maxResults;
    private $_includeMetadata;

    /**
     * Gets prefix.
     *
     * @return string.
     */
    public function getPrefix()
    {
        return $this->_prefix;
    }

    /**
     * Sets prefix.
     *
     * @param string $prefix value.
     * 
     * @return none.
     */
    public function setPrefix($prefix)
    {
        Validate::isString($prefix, 'prefix');
        $this->_prefix = $prefix;
    }

    /**
     * Gets marker.
     * 
     * @return string.
     */
    public function getMarker()
    {
        return $this->_marker;
    }

    /**
     * Sets marker.
     *
     * @param string $marker value.
     * 
     * @return none.
     */
    public function setMarker($marker)
    {
        Validate::isString($marker, 'marker');
        $this->_marker = $marker;
    }

    /**
     * Gets max results.
     * 
     * @return string.
     */
    public function getMaxResults()
    {
        return $this->_maxResults;
    }

    /**
     * Sets max results.
     *
     * @param string $maxResults value.
     * 
     * @return none.
     */
    public function setMaxResults($maxResults)
    {
        Validate::isString($maxResults, 'maxResults');
        $this->_maxResults = $maxResults;
    }

    /**
     * Indicates if metadata is included or not.
     * 
     * @return boolean.
     */
    public function getIncludeMetadata()
    {
        return $this->_includeMetadata;
    }

    /**
     * Sets the include metadata flag.
     *
     * @param bool $includeMetadata value.
     * 
     * @return none.
     */
    public function setIncludeMetadata($includeMetadata)
    {
        Validate::isBoolean($includeMetadata);
        $this->_includeMetadata = $includeMetadata;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Queue\Models\Queue;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Container to hold list queue response object.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListQueuesResult
{
    private $_queues;
    private $_prefix;
    private $_marker;
    private $_nextMarker;
    private $_maxResults;
    private $_accountName;

    /**
     * Creates ListQueuesResult object from parsed XML response.
     *
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Queue\Models\ListQueuesResult.
     */
    public static function create($parsedResponse)
    {
        $result               = new ListQueuesResult();
        $serviceEndpoint      = Utilities::tryGetKeysChainValue(
            $parsedResponse,
            Resources::XTAG_ATTRIBUTES,
            Resources::XTAG_SERVICE_ENDPOINT
        );
        $result->_accountName = Utilities::tryParseAccountNameFromUrl(
            $serviceEndpoint
        );
        $result->_prefix      = Utilities::tryGetValue(
            $parsedResponse, Resources::QP_PREFIX
        );
        $result->_marker      = Utilities::tryGetValue(
            $parsedResponse, Resources::QP_MARKER
        );
        $result->_nextMarker  = Utilities::tryGetValue(
            $parsedResponse, Resources::QP_NEXT_MARKER
        );
        $result->_maxResults  = Utilities::tryGetValue(
            $parsedResponse, Resources::QP_MAX_RESULTS
        );
        $result->_queues      = array();
        $rawQueues            = array();
        
        if ( !empty($parsedResponse['Queues']) ) {
            $rawQueues = Utilities::getArray($parsedResponse['Queues']['Queue']);
        }
        
        foreach ($rawQueues as $value) {
            $queue    = new Queue($value['Name'], $serviceEndpoint . $value['Name']);
            $metadata = Utilities::tryGetValue($value, Resources::QP_METADATA);
            $queue->setMetadata(is_null($metadata) ? array() : $metadata);
            $result->_queues[] = $queue;
        }
        
        return $result;
    }

    /**
     * Gets queues.
     *
     * @return array.
     */
    public function getQueues()
    {
        return $this->_queues;
    }
    
    /**
     * Sets queues.
     *
     * @param array $queues list of queues
     * 
     * @return none.
     */
    public function setQueues($queues)
    {
        $this->_queues = array();
        foreach ($queues as $queue) {
            $this->_queues[] = clone $queue;
        }
    }

    /**
     * Gets prefix.
     *
     * @return string.
     */
    public function getPrefix()
    {
        return $this->_prefix;
    }

    /**
     * Sets prefix.
     *
     * @param string $prefix value.
     * 
     * @return none.
     */
    public function setPrefix($prefix)
    {
        $this->_prefix = $prefix;
    }

    /**
     * Gets marker.
     * 
     * @return string.
     */
    public function getMarker()
    {
        return $this->_marker;
    }

    /**
     * Sets marker.
     *
     * @param string $marker value.
     * 
     * @return none.
     */
    public function setMarker($marker)
    {
        $this->_marker = $marker;
    }

    /**
     * Gets max results.
     * 
     * @return string.
     */
    public function getMaxResults()
    {
        return $this->_maxResults;
    }

    /**
     * Sets max results.
     *
     * @param string $maxResults value.
     * 
     * @return none.
     */
    public function setMaxResults($maxResults)
    {
        $this->_maxResults = $maxResults;
    }

    /**
     * Gets next marker.
     * 
     * @return string.
     */
    public function getNextMarker()
    {
        return $this->_nextMarker;
    }

    /**
     * Sets next marker.
     *
     * @param string $nextMarker value.
     * 
     * @return none.
     */
    public function setNextMarker($nextMarker)
    {
        $this->_nextMarker = $nextMarker;
    }
    
    /**
     * Gets account name.
     * 
     * @return string
     */
    public function getAccountName()
    {
        return $this->_accountName;
    }

    /**
     * Sets account name.
     *
     * @param string $accountName value.
     * 
     * @return none
     */
    public function setAccountName($accountName)
    {
        $this->_accountName = $accountName;
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds data for single WindowsAzure queue message.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class MicrosoftAzureQueueMessage
{
    /**
     * GUID value that identifies the message in the queue
     * 
     * @var string
     */
    private $_messageId;
    
    /**
     * insertion date of the message.
     * 
     * @var \DateTime
     */
    private $_insertionDate;
    
    /**
     * expiration date of the message.
     * 
     * @var \DateTime
     */
    private $_expirationDate;
    
    /**
     * The value of PopReceipt is opaque to the client and its only purpose is to 
     * ensure that a message may be deleted with the delete message operation.
     * 
     * @var string
     */
    private $_popReceipt;
    
    /**
     * next visibility time of the message.
     * 
     * @var \DateTime
     */
    private $_timeNextVisible;
    
    /**
     * Dequeues count for this message. Note that this element is returned in the 
     * response body only if the queue was created with version 2009-09-19 of 
     * the Queue service.
     * 
     * @var integer
     */
    private $_dequeueCount;
    
    /**
     * message contents.
     * 
     * @var string
     */
    private $_messageText;
    
    /**
     * Creates MicrosoftAzureQueueMessage object from parsed XML response of 
     * ListMessages.
     *
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage.
     */
    public static function createFromListMessages($parsedResponse)
    {
        $timeNextVisible = $parsedResponse['TimeNextVisible'];
        
        $msg  = self::createFromPeekMessages($parsedResponse);
        $date = Utilities::rfc1123ToDateTime($timeNextVisible);
        $msg->setTimeNextVisible($date);
        $msg->setPopReceipt($parsedResponse['PopReceipt']);
        
        return $msg;
    }
    
    /**
     * Creates MicrosoftAzureQueueMessage object from parsed XML response of
     * PeekMessages.
     *
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage.
     */
    public static function createFromPeekMessages($parsedResponse)
    {
        $msg            = new MicrosoftAzureQueueMessage();
        $expirationDate = $parsedResponse['ExpirationTime'];
        $insertionDate  = $parsedResponse['InsertionTime'];
        
        $msg->setDequeueCount(intval($parsedResponse['DequeueCount']));
        
        $date = Utilities::rfc1123ToDateTime($expirationDate);
        $msg->setExpirationDate($date);
        
        $date = Utilities::rfc1123ToDateTime($insertionDate);
        $msg->setInsertionDate($date);
        
        $msg->setMessageId($parsedResponse['MessageId']);
        $msg->setMessageText($parsedResponse['MessageText']);
        
        return $msg;
    }
    
    /**
     * Gets message text field.
     * 
     * @return string.
     */
    public function getMessageText()
    {
        return $this->_messageText;
    }
    
    /**
     * Sets message text field.
     * 
     * @param string $messageText message contents.
     * 
     * @return none.
     */
    public function setMessageText($messageText)
    {
        $this->_messageText = $messageText;
    }
    
    /**
     * Gets messageId field.
     * 
     * @return integer.
     */
    public function getMessageId()
    {
        return $this->_messageId;
    }
    
    /**
     * Sets messageId field.
     * 
     * @param string $messageId message contents.
     * 
     * @return none.
     */
    public function setMessageId($messageId)
    {
        $this->_messageId = $messageId;
    }
    
    /**
     * Gets insertionDate field.
     * 
     * @return \DateTime.
     */
    public function getInsertionDate()
    {
        return $this->_insertionDate;
    }
    
    /**
     * Sets insertionDate field.
     * 
     * @param \DateTime $insertionDate message contents.
     * 
     * @return none.
     */
    public function setInsertionDate($insertionDate)
    {
        $this->_insertionDate = $insertionDate;
    }
    
    /**
     * Gets expirationDate field.
     * 
     * @return \DateTime.
     */
    public function getExpirationDate()
    {
        return $this->_expirationDate;
    }
    
    /**
     * Sets expirationDate field.
     * 
     * @param \DateTime $expirationDate the expiration date of the message.
     * 
     * @return none.
     */
    public function setExpirationDate($expirationDate)
    {
        $this->_expirationDate = $expirationDate;
    }
    
    /**
     * Gets timeNextVisible field.
     * 
     * @return \DateTime.
     */
    public function getTimeNextVisible()
    {
        return $this->_timeNextVisible;
    }
    
    /**
     * Sets timeNextVisible field.
     * 
     * @param \DateTime $timeNextVisible next visibile time for the message.
     * 
     * @return none.
     */
    public function setTimeNextVisible($timeNextVisible)
    {
        $this->_timeNextVisible = $timeNextVisible;
    }
    
    /**
     * Gets popReceipt field.
     * 
     * @return string.
     */
    public function getPopReceipt()
    {
        return $this->_popReceipt;
    }
    
    /**
     * Sets popReceipt field.
     * 
     * @param string $popReceipt used when deleting the message.
     * 
     * @return none.
     */
    public function setPopReceipt($popReceipt)
    {
        $this->_popReceipt = $popReceipt;
    }
    
    /**
     * Gets dequeueCount field.
     * 
     * @return integer.
     */
    public function getDequeueCount()
    {
        return $this->_dequeueCount;
    }
    
    /**
     * Sets dequeueCount field.
     * 
     * @param integer $dequeueCount number of dequeues for that message.
     * 
     * @return none.
     */
    public function setDequeueCount($dequeueCount)
    {
        $this->_dequeueCount = $dequeueCount;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;

/**
 * Short description
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PeekMessagesOptions extends QueueServiceOptions
{
    /**
     * A nonzero integer value that specifies the number of messages to peek from 
     * the queue, up to a maximum of 32. By default, a single message is peeked 
     * from the queue with this operation.
     * 
     * @var integer
     */
    private $_numberOfMessages;
    
    /**
     * Gets numberOfMessages field.
     * 
     * @return integer
     */
    public function getNumberOfMessages()
    {
        return $this->_numberOfMessages;
    }
    
    /**
     * Sets numberOfMessages field.
     * 
     * @param integer $numberOfMessages value to use.
     * 
     * @return none
     */
    public function setNumberOfMessages($numberOfMessages)
    {
        $this->_numberOfMessages = $numberOfMessages;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds results of listMessages wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PeekMessagesResult
{
    /**
     * Holds all message entries.
     * 
     * @var array.
     */
    private $_queueMessages;
    
    /**
     * Creates PeekMessagesResult object from parsed XML response.
     *
     * @param array $parsedResponse XML response parsed into array.
     * 
     * @return MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult.
     */
    public static function create($parsedResponse)
    {
        $result        = new PeekMessagesResult();
        $queueMessages = array();
        
        if (!empty($parsedResponse)) {
            $rawMessages = Utilities::getArray($parsedResponse['QueueMessage']);
            foreach ($rawMessages as $value) {
                $message = MicrosoftAzureQueueMessage::createFromPeekMessages($value);
                
                $queueMessages[] = $message;
            }
        }
        $result->setQueueMessages($queueMessages);
        
        return $result;
    }
    
    /**
     * Gets queueMessages field.
     * 
     * @return integer
     */
    public function getQueueMessages()
    {
        $clonedMessages = array();
        
        foreach ($this->_queueMessages as $value) {
            $clonedMessages[] = clone $value;
        }
        
        return $clonedMessages;
    }
    
    /**
     * Sets queueMessages field.
     * 
     * @param integer $queueMessages value to use.
     * 
     * @return none
     */
    public function setQueueMessages($queueMessages)
    {
        $this->_queueMessages = $queueMessages;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * WindowsAzure queue object.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Queue
{
    private $_name;
    private $_url;
    private $_metadata;

    /**
     * Constructor
     * 
     * @param string $name queue name.
     * @param string $url  queue url.
     * 
     * @return MicrosoftAzure\Storage\Queue\Models\Queue.
     */
    function __construct($name, $url)
    {
        $this->_name = $name;
        $this->_url  = $url;
    }

    /**
     * Gets queue name.
     *
     * @return string.
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Sets queue name.
     *
     * @param string $name value.
     * 
     * @return none.
     */
    public function setName($name)
    {
        $this->_name = $name;
    }

    /**
     * Gets queue url.
     *
     * @return string.
     */
    public function getUrl()
    {
        return $this->_url;
    }

    /**
     * Sets queue url.
     *
     * @param string $url value.
     * 
     * @return none.
     */
    public function setUrl($url)
    {
        $this->_url = $url;
    }

    /**
     * Gets queue metadata.
     *
     * @return array.
     */
    public function getMetadata()
    {
        return $this->_metadata;
    }

    /**
     * Sets queue metadata.
     *
     * @param string $metadata value.
     * 
     * @return none.
     */
    public function setMetadata($metadata)
    {
        $this->_metadata = $metadata;
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * Wrappers queue message text.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueueMessage
{
    private $_messageText;
    public static $xmlRootName = 'QueueMessage';
    
    /**
     * Gets message text field.
     * 
     * @return string.
     */
    public function getMessageText()
    {
        return $this->_messageText;
    }
    
    /**
     * Sets message text field.
     * 
     * @param string $messageText message contents.
     * 
     * @return string.
     */
    public function setMessageText($messageText)
    {
        $this->_messageText = $messageText;
    }
    
    /**
     * Converts this current object to XML representation.
     * 
     * @param XmlSerializer $xmlSerializer The XML serializer.
     * 
     * @return string. 
     */
    public function toXml($xmlSerializer)
    {
        $array      = array('MessageText' => $this->_messageText);
        $properties = array(XmlSerializer::ROOT_NAME => self::$xmlRootName);
        
        return $xmlSerializer->serialize($array, $properties);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;

/**
 * Queue service options.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueueServiceOptions
{
    private $_timeout;

    /**
     * Gets timeout.
     *
     * @return string.
     */
    public function getTimeout()
    {
        return $this->_timeout;
    }

    /**
     * Sets timeout.
     *
     * @param string $timeout value.
     * 
     * @return none.
     */
    public function setTimeout($timeout)
    {
        $this->_timeout = $timeout;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Queue\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Holds results of updateMessage wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class UpdateMessageResult
{
    /**
     * The value of PopReceipt is opaque to the client and its only purpose is to 
     * ensure that a message may be deleted with the delete message operation.
     * 
     * @var string
     */
    private $_popReceipt;
    
    /**
     * A UTC date/time value that represents when the message will be visible on the 
     * queue.
     * 
     * @var \DateTime
     */
    private $_timeNextVisible;
    
    /**
     * Gets timeNextVisible field.
     * 
     * @return \DateTime.
     */
    public function getTimeNextVisible()
    {
        return $this->_timeNextVisible;
    }
    
    /**
     * Sets timeNextVisible field.
     * 
     * @param \DateTime $timeNextVisible A UTC date/time value that represents when 
     * the message will be visible on the queue.
     * 
     * @return none.
     */
    public function setTimeNextVisible($timeNextVisible)
    {
        Validate::isDate($timeNextVisible);
        
        $this->_timeNextVisible = $timeNextVisible;
    }
    
    /**
     * Gets popReceipt field.
     * 
     * @return string.
     */
    public function getPopReceipt()
    {
        return $this->_popReceipt;
    }
    
    /**
     * Sets popReceipt field.
     * 
     * @param string $popReceipt The pop receipt of the queue message.
     * 
     * @return none.
     */
    public function setPopReceipt($popReceipt)
    {
        Validate::isString($popReceipt, 'popReceipt');
        $this->_popReceipt = $popReceipt;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Queue;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy;
use MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Queue\Internal\IQueue;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesResult;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;
use MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions;
use MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult;
use MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions;
use MicrosoftAzure\Storage\Queue\Models\QueueMessage;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesResult;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult;
use MicrosoftAzure\Storage\Queue\Models\UpdateMessageResult;
use MicrosoftAzure\Storage\Common\Internal\HttpFormatter;

/**
 * This class constructs HTTP requests and receive HTTP responses for queue 
 * service layer.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueueRestProxy extends ServiceRestProxy implements IQueue
{
    /**
     * Lists all queues in the storage account.
     * 
     * @param ListQueuesOptions $options The optional list queue options.
     * 
     * @return MicrosoftAzure\Storage\Queue\Models\ListQueuesResult
     */
    public function listQueues($options = null)
    {
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new ListQueuesOptions();
        }
        
        $timeout    = $options->getTimeout();
        $maxResults = $options->getMaxResults();
        $include    = $options->getIncludeMetadata();
        $include    = $include ? 'metadata' : null;
        $prefix     = $options->getPrefix();
        $marker     = $options->getMarker();
        
        $this->addOptionalQueryParam($queryParams, Resources::QP_TIMEOUT, $timeout);
        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'list');
        $this->addOptionalQueryParam($queryParams, Resources::QP_PREFIX, $prefix);
        $this->addOptionalQueryParam($queryParams, Resources::QP_MARKER, $marker);
        $this->addOptionalQueryParam($queryParams, Resources::QP_INCLUDE, $include);
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_MAX_RESULTS,
            $maxResults
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        $parsed   = $this->dataSerializer->unserialize($response->getBody());
        
        return ListQueuesResult::create($parsed);
    }

    /**
     * Clears all messages from the queue.
     * 
     * If a queue contains a large number of messages, Clear Messages may time out 
     * before all messages have been deleted. In this case the Queue service will 
     * return status code 500 (Internal Server Error), with the additional error 
     * code OperationTimedOut. If the operation times out, the client should 
     * continue to retry Clear Messages until it succeeds, to ensure that all 
     * messages have been deleted.
     * 
     * @param string              $queueName The name of the queue.
     * @param QueueServiceOptions $options   The optional parameters.
     * 
     * @return none
     */
    public function clearMessages($queueName, $options = null)
    {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        
        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName . '/messages';
        $body        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_NO_CONTENT;
        
        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode,
            $body
        );
    }

    /**
     * Adds a message to the queue and optionally sets a visibility timeout 
     * for the message.
     * 
     * @param string               $queueName   The name of the queue.
     * @param string               $messageText The message contents.
     * @param CreateMessageOptions $options     The optional parameters.
     * 
     * @return none
     */
    public function createMessage($queueName, $messageText,
        $options = null
    ) {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        Validate::isString($messageText, 'messageText');
        
        $method      = Resources::HTTP_POST;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName . '/messages';
        $body        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_CREATED;
        $message     = new QueueMessage();
        $message->setMessageText($messageText);
        $body = $message->toXml($this->dataSerializer);
        
        
        if (is_null($options)) {
            $options = new CreateMessageOptions();
        }
        
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::URL_ENCODED_CONTENT_TYPE
        );
        
        $visibility = $options->getVisibilityTimeoutInSeconds();
        $timeToLive = $options->getTimeToLiveInSeconds();
        $timeout    = $options->getTimeout();
        
        $this->addOptionalQueryParam($queryParams, Resources::QP_TIMEOUT, $timeout);
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_VISIBILITY_TIMEOUT,
            $visibility
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_MESSAGE_TTL,
            $timeToLive
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode, 
            $body
        );
    }

    /**
     * Creates a new queue under the storage account.
     * 
     * @param string             $queueName The queue name.
     * @param QueueCreateOptions $options   The Optional parameters.
     * 
     * @return none
     */
    public function createQueue($queueName, $options = null)
    {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName;
        $statusCode  = array(
            Resources::STATUS_CREATED,
            Resources::STATUS_NO_CONTENT
        );
        
        if (is_null($options)) {
            $options = new CreateQueueOptions();
        }

        $metadata = $options->getMetadata();
        $timeout  = $options->getTimeout();
        $headers  = $this->generateMetadataHeaders($metadata);
        
        $this->addOptionalQueryParam($queryParams, Resources::QP_TIMEOUT, $timeout);
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
    }

    /**
     * Deletes a specified message from the queue.
     * 
     * @param string              $queueName  The name of the queue.
     * @param string              $messageId  The id of the message.
     * @param string              $popReceipt The valid pop receipt value returned
     * from an earlier call to the Get Messages or Update Message operation.
     * @param QueueServiceOptions $options    The optional parameters.
     * 
     * @return none
     */
    public function deleteMessage($queueName, $messageId, $popReceipt, 
        $options = null
    ) {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        Validate::isString($messageId, 'messageId');
        Validate::notNullOrEmpty($messageId, 'messageId');
        Validate::isString($popReceipt, 'popReceipt');
        Validate::notNullOrEmpty($popReceipt, 'popReceipt');
        
        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName . '/messages/' . $messageId;
        $body        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_NO_CONTENT;
        
        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_POPRECEIPT,
            $popReceipt
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams,
            $postParams,
            $path, 
            $statusCode, 
            $body
        );
    }

    /**
     * Deletes a queue.
     * 
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     * 
     * @return none
     */
    public function deleteQueue($queueName, $options = null)
    {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        
        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName;
        $statusCode  = Resources::STATUS_NO_CONTENT;
        
        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
    }

    /**
     * Returns queue properties, including user-defined metadata.
     * 
     * @param string              $queueName The queue name.
     * @param QueueServiceOptions $options   The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\GetQueueMetadataResult
     */
    public function getQueueMetadata($queueName, $options = null)
    {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName;
        $body        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }
        
        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'metadata');
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode, 
            $body
        );
        
        $responseHeaders = HttpFormatter::formatHeaders($response->getHeaders());
        
        $metadata = $this->getMetadataArray($responseHeaders);
        $maxCount = intval(
            Utilities::tryGetValue($responseHeaders, Resources::X_MS_APPROXIMATE_MESSAGES_COUNT)
        );
        
        return new GetQueueMetadataResult($maxCount, $metadata);
    }

    /**
     * Gets the properties of the Queue service.
     * 
     * @param QueueServiceOptions $options The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult
     */
    public function getServiceProperties($options = null)
    {
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'service'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'properties'
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        $parsed   = $this->dataSerializer->unserialize($response->getBody());
        
        return GetServicePropertiesResult::create($parsed);
    }

    /**
     * Lists all messages in the queue.
     * 
     * @param string              $queueName The queue name.
     * @param ListMessagesOptions $options   The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\ListMessagesResult
     */
    public function listMessages($queueName, $options = null)
    {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $queueName . '/messages';
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new ListMessagesOptions();
        }
        
        $messagesCount = $options->getNumberOfMessages();
        $visibility    = $options->getVisibilityTimeoutInSeconds();
        $timeout       = $options->getTimeout();
        
        $this->addOptionalQueryParam($queryParams, Resources::QP_TIMEOUT, $timeout);
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NUM_OF_MESSAGES,
            $messagesCount
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_VISIBILITY_TIMEOUT,
            $visibility
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams,
            $postParams,
            $path, 
            $statusCode
        );

        $parsed = $this->dataSerializer->unserialize($response->getBody());
        
        return ListMessagesResult::create($parsed);
    }

    /**
     * Retrieves a message from the front of the queue, without changing 
     * the message visibility.
     * 
     * @param string              $queueName The queue name.
     * @param PeekMessagesOptions $options   The optional parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\PeekMessagesResult
     */
    public function peekMessages($queueName, $options = null)
    {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $queueName . '/messages';
        $statusCode  = Resources::STATUS_OK;
        
        if (is_null($options)) {
            $options = new PeekMessagesOptions();
        }
        
        $messagesCount = $options->getNumberOfMessages();
        $timeout       = $options->getTimeout();
        
        $this->addOptionalQueryParam($queryParams, Resources::QP_PEEK_ONLY, 'true');
        $this->addOptionalQueryParam($queryParams, Resources::QP_TIMEOUT, $timeout);
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NUM_OF_MESSAGES,
            $messagesCount
        );
        
        $response = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode
        );
        $parsed   = $this->dataSerializer->unserialize($response->getBody());
        
        return PeekMessagesResult::create($parsed);
    }

    /**
     * Sets user-defined metadata on the queue. To delete queue metadata, call 
     * this API without specifying any metadata in $metadata.
     * 
     * @param string              $queueName The queue name.
     * @param array               $metadata  The metadata array.
     * @param QueueServiceOptions $options   The optional parameters.
     * 
     * @return none
     */
    public function setQueueMetadata($queueName, $metadata, $options = null)
    {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        $this->validateMetadata($metadata);
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $queryParams = array();
        $postParams  = array();
        $path        = $queueName;
        $statusCode  = Resources::STATUS_NO_CONTENT;
        $body        = Resources::EMPTY_STRING;
        
        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }
        
        $this->addOptionalQueryParam($queryParams, Resources::QP_COMP, 'metadata');
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        
        $metadataHeaders = $this->generateMetadataHeaders($metadata);
        $headers         = $metadataHeaders;
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode, 
            $body
        );
    }

    /**
     * Sets the properties of the Queue service.
     * 
     * It's recommended to use getServiceProperties, alter the returned object and
     * then use setServiceProperties with this altered object.
     * 
     * @param array               $serviceProperties The new service properties.
     * @param QueueServiceOptions $options           The optional parameters.  
     * 
     * @return none
     */
    public function setServiceProperties($serviceProperties, $options = null)
    {
        Validate::isTrue(
            $serviceProperties instanceof ServiceProperties,
            Resources::INVALID_SVC_PROP_MSG
        );
                
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_ACCEPTED;
        $path        = Resources::EMPTY_STRING;
        $body        = $serviceProperties->toXml($this->dataSerializer);
        
        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }
    
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'service'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'properties'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::URL_ENCODED_CONTENT_TYPE
        );
        
        $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode, 
            $body
        );
    }

    /**
     * Updates the visibility timeout of a message and/or the message contents.
     * 
     * @param string              $queueName                  The queue name.
     * @param string              $messageId                  The id of the message.
     * @param string              $popReceipt                 The valid pop receipt 
     * value returned from an earlier call to the Get Messages or Update Message
     * operation.
     * @param string              $messageText                The message contents.
     * @param int                 $visibilityTimeoutInSeconds Specifies the new 
     * visibility timeout value, in seconds, relative to server time. 
     * The new value must be larger than or equal to 0, and cannot be larger 
     * than 7 days. The visibility timeout of a message cannot be set to a value 
     * later than the expiry time. A message can be updated until it has been 
     * deleted or has expired.
     * @param QueueServiceOptions $options                    The optional 
     * parameters.
     * 
     * @return MicrosoftAzure\Storage\Common\Models\UpdateMessageResult
     */
    public function updateMessage($queueName, $messageId, $popReceipt, $messageText, 
        $visibilityTimeoutInSeconds, $options = null
    ) {
        Validate::isString($queueName, 'queueName');
        Validate::notNullOrEmpty($queueName, 'queueName');
        Validate::isString($messageId, 'messageId');
        Validate::notNullOrEmpty($messageId, 'messageId');
        Validate::isString($popReceipt, 'popReceipt');
        Validate::notNullOrEmpty($popReceipt, 'popReceipt');
        Validate::isString($messageText, 'messageText');
        Validate::isInteger(
            $visibilityTimeoutInSeconds,
            'visibilityTimeoutInSeconds'
        );
        Validate::notNull(
            $visibilityTimeoutInSeconds,
            'visibilityTimeoutInSeconds'
        );
        
        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $path        = $queueName . '/messages' . '/' . $messageId;
        $body        = Resources::EMPTY_STRING;
        $statusCode  = Resources::STATUS_NO_CONTENT;
        
        if (is_null($options)) {
            $options = new QueueServiceOptions();
        }
        
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_VISIBILITY_TIMEOUT,
            $visibilityTimeoutInSeconds
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_POPRECEIPT,
            $popReceipt
        );
        
        if (!empty($messageText)) {
            $this->addOptionalHeader(
                $headers,
                Resources::CONTENT_TYPE,
                Resources::URL_ENCODED_CONTENT_TYPE
            );
        
            $message = new QueueMessage();
            $message->setMessageText($messageText);
            $body = $message->toXml($this->dataSerializer);
        }
        
        $response        = $this->send(
            $method, 
            $headers, 
            $queryParams, 
            $postParams, 
            $path, 
            $statusCode, 
            $body
        );
        
        $responseHeaders = HttpFormatter::formatHeaders($response->getHeaders());
        
        $popReceipt      = Utilities::tryGetValue($responseHeaders, Resources::X_MS_POPRECEIPT);
        $timeNextVisible = Utilities::tryGetValue($responseHeaders, Resources::X_MS_TIME_NEXT_VISIBLE);
        
        $date   = Utilities::rfc1123ToDateTime($timeNextVisible);
        $result = new UpdateMessageResult();
        $result->setPopReceipt($popReceipt);
        $result->setTimeNextVisible($date);
        
        return $result;
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Internal;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;

/**
 * Serializes and unserializes results from table wrapper calls
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AtomReaderWriter implements IAtomReaderWriter
{
    /**
     * @var string
     */
    private $_atomNamespaceName;
    
    /**
     * @var string
     */
    private $_dataServicesNamespaceName;
    
    /**
     * @var string 
     */
    private $_dataServicesMetadataNamespaceName;
    
    /**
     * @var string
     */
    private $_xmlVersion;
    
    /**
     * @var string
     */
    private $_xmlEncoding;
    
    /**
     * @var string
     */
    private $_dataServicesPrefix;
    
    /**
     * @var string
     */
    private $_dataServicesMetadataPrefix;
    
    /**
     * Generates the atom XML properties.
     * 
     * @param \XmlWriter $xmlw       The XML writer.
     * @param array      $properties The atom properties.
     * 
     * @return none
     */
    private function _generateProperties($xmlw, $properties)
    {
        foreach ($properties as $key => $value) {
            $content    = key($value);
            $attributes = $value[$content];
            $xmlw->startElementNS($this->_dataServicesPrefix, $key, null);
            if (!is_null($attributes)) {
                foreach ($attributes as $attribute => $attributeValue) {
                    $xmlw->writeAttributeNS(
                        $this->_dataServicesMetadataPrefix,
                        $attribute,
                        null,
                        $attributeValue
                    );
                }
            }
            $xmlw->text($content);
            $xmlw->endElement();
        }
    }

    /**
     * Serializes the atom into XML representation.
     * 
     * @param array $properties The atom properties.
     * 
     * @return string
     */
    private function _serializeAtom($properties)
    {
        $xmlw = new \XmlWriter();
        $xmlw->openMemory();
        $xmlw->setIndent(true);
        $xmlw->startDocument(
            strtoupper($this->_xmlVersion),
            $this->_xmlEncoding,
            'yes'
        );
        $xmlw->startElementNS(null, 'entry', $this->_atomNamespaceName);
        $xmlw->writeAttribute(
            "xmlns:$this->_dataServicesPrefix",
            $this->_dataServicesNamespaceName
        );
        $xmlw->writeAttribute(
            "xmlns:$this->_dataServicesMetadataPrefix",
            $this->_dataServicesMetadataNamespaceName
        );
        $xmlw->writeElement('title');
        $xmlw->writeElement('updated', Utilities::isoDate());
        $xmlw->startElement('author');
        $xmlw->writeElement('name');
        $xmlw->endElement();
        $xmlw->writeElement('id');
        $xmlw->startElement('content');
        $xmlw->writeAttribute('type', Resources::XML_CONTENT_TYPE);
        $xmlw->startElementNS(
            $this->_dataServicesMetadataPrefix,
            'properties',
            null
        );
        $this->_generateProperties($xmlw, $properties);
        $xmlw->endElement();
        $xmlw->endElement();
        $xmlw->endElement();
        
        return $xmlw->outputMemory(true);
    }
    
    /** 
     * Creates new SimpleXml from Atom XML and registers the namespaces prefixes.
     *
     * @param string $body Response from HTTP call.
     * 
     * @return \SimpleXml
     */
    private function _parseBody($body)
    {
        $xml = simplexml_load_string($body);

        if ($xml !== false) {
            $xml->registerXPathNamespace(
                $this->_dataServicesPrefix,
                $this->_dataServicesNamespaceName
            );
            $xml->registerXPathNamespace(
                $this->_dataServicesMetadataPrefix,
                $this->_dataServicesMetadataNamespaceName
            );
        }

        return $xml;
    }
    
    /**
     * Parses one table entry and returns the table name.
     * 
     * @param \SimpleXml $result The original XML body loaded in XML.
     * 
     * @return string
     */
    private function _parseOneTable($result)
    {        
        $query     = ".//$this->_dataServicesMetadataPrefix:properties/";
        $query    .= "$this->_dataServicesPrefix:TableName";
        $tableName = $result->xpath($query);
        $table     = (string)$tableName[0];
        
        return $table;
    }
    
    /**
     * Gets entry nodes from the XML body.
     * 
     * @param \SimpleXml $body The original XML body loaded in XML.
     * 
     * @return array
     */
    private function _getRawEntries($body)
    {
        $rawEntries = array();
        
        if (!is_null($body) && $body->entry) {
            $rawEntries = $body->entry;
        }
        
        return $rawEntries;
    }
    
    /**
     * Parses an entity entry from given SimpleXML object.
     * 
     * @param \SimpleXML $result The SimpleXML object representing the entity.
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\Entity
     */
    private function _parseOneEntity($result)
    {
        $prefix = $this->_dataServicesMetadataPrefix;
        $prop   = $result->content->xpath(".//$prefix:properties");
        $prop   = $prop[0]->children($this->_dataServicesNamespaceName);
        $entity = new Entity();
        
        // Set ETag
        $etag = $result->attributes($this->_dataServicesMetadataNamespaceName);
        $etag = $etag[Resources::ETAG];
        $entity->setETag((string)$etag);
        
        foreach ($prop as $key => $value) {
            $attributes = $value->attributes(
                $this->_dataServicesMetadataNamespaceName
            );
            $type       = $attributes['type'];
            $isnull     = $attributes['null'];
            $value      = EdmType::unserializeQueryValue((string)$type, $value);
            
            $entity->addProperty(
                (string)$key,
                is_null($type) ? EdmType::STRING : (string)$type,
                $isnull ? null : $value
            );
        }
        
        return $entity;
    }
    
    /**
     * Constructs new AtomReaderWriter object. 
     */
    public function __construct()
    {
        $this->_atomNamespaceName                 = Resources::ATOM_XML_NAMESPACE;
        $this->_dataServicesNamespaceName         = Resources::DS_XML_NAMESPACE;
        $this->_dataServicesMetadataNamespaceName = Resources::DSM_XML_NAMESPACE;
        $this->_dataServicesPrefix                = 'd';
        $this->_dataServicesMetadataPrefix        = 'm';
        $this->_xmlVersion                        = '1.0';
        $this->_xmlEncoding                       = 'UTF-8';
    }
    
    /**
     * Constructs XML representation for table entry.
     * 
     * @param string $name The name of the table.
     * 
     * @return string
     */
    public function getTable($name)
    {
        return $this->_serializeAtom(array('TableName' => array($name => null)));
    }
    
    /**
     * Parses one table entry.
     * 
     * @param string $body The HTTP response body.
     * 
     * @return string 
     */
    public function parseTable($body)
    {
        $result = $this->_parseBody($body);
        return $this->_parseOneTable($result);
    }
    
    /**
     * Constructs array of tables from HTTP response body.
     * 
     * @param string $body The HTTP response body.
     * 
     * @return array
     */
    public function parseTableEntries($body)
    {
        $tables     = array();
        $result     = $this->_parseBody($body);
        $rawEntries = $this->_getRawEntries($result);
        
        foreach ($rawEntries as $entry) {
            $tables[] = $this->_parseOneTable($entry);
        }
        
        return $tables;
    }
    
    /**
     * Constructs XML representation for entity.
     * 
     * @param Models\Entity $entity The entity instance.
     * 
     * @return string
     */
    public function getEntity($entity)
    {
        $entityProperties = $entity->getProperties();
        $properties       = array();
        
        foreach ($entityProperties as $name => $property) {
            $attributes = array();
            $edmType    = $property->getEdmType();
            $edmValue   = $property->getValue();
            if (!is_null($edmType)) {
                $attributes['type'] = $edmType;
            }
            if (is_null($edmValue)) {
                $attributes['null'] = 'true';
            }
            $value             = EdmType::serializeValue($edmType, $edmValue);
            $properties[$name] = array($value => $attributes);
        }
        
        return $this->_serializeAtom($properties);
    }
    
    /**
     * Constructs entity from HTTP response body.
     * 
     * @param string $body The HTTP response body.
     * 
     * @return Entity
     */
    public function parseEntity($body)
    {
        $result = $this->_parseBody($body);
        $entity = $this->_parseOneEntity($result);
        return $entity;
    }
    
    /**
     * Constructs array of entities from HTTP response body.
     * 
     * @param string $body The HTTP response body.
     * 
     * @return array
     */
    public function parseEntities($body)
    {
        $result     = $this->_parseBody($body);
        $entities   = array();
        $rawEntries = $this->_getRawEntries($result);
        
        foreach ($rawEntries as $entity) {
            $entities[] = $this->_parseOneEntity($entity);
        }
        
        return $entities;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Internal;

/**
 * Defines how to serialize and unserialize table wrapper xml
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
interface IAtomReaderWriter
{
    /**
     * Constructs XML representation for table entry.
     * 
     * @param string $name The name of the table.
     * 
     * @return string
     */
    public function getTable($name);
    
    /**
     * Parses one table entry.
     * 
     * @param string $body The HTTP response body.
     * 
     * @return string 
     */
    public function parseTable($body);
    
    /**
     * Constructs array of tables from HTTP response body.
     * 
     * @param string $body The HTTP response body.
     * 
     * @return array
     */
    public function parseTableEntries($body);
    
    /**
     * Constructs XML representation for entity.
     * 
     * @param Models\Entity $entity The entity instance.
     * 
     * @return string
     */
    public function getEntity($entity);
    
    /**
     * Constructs entity from HTTP response body.
     * 
     * @param string $body The HTTP response body.
     * 
     * @return Models\Entity
     */
    public function parseEntity($body);
    
    /**
     * Constructs array of entities from HTTP response body.
     * 
     * @param string $body The HTTP response body.
     * 
     * @return array
     */
    public function parseEntities($body);
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Internal;

/**
 * Interface for MIME reading and writing.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
interface IMimeReaderWriter
{
    /**
     * Given array of MIME parts in raw string, this function converts them into MIME
     * representation. 
     * 
     * @param array $bodyPartContents The MIME body parts.
     * 
     * @return array Returns array with two elements 'headers' and 'body' which
     * represents the MIME message.
     */
    public function encodeMimeMultipart($bodyPartContents);
    
    /**
     * Parses given mime HTTP response body into array. Each array element 
     * represents a change set result.
     * 
     * @param string $mimeBody The raw MIME body result.
     * 
     * @return array
     */
    public function decodeMimeMultipart($mimeBody);
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Internal;
use MicrosoftAzure\Storage\Common\Internal\FilterableService;

/**
 * This interface has all REST APIs provided by Windows Azure for Table service.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 * @see       http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx
 */
interface ITable extends FilterableService
{
    /**
    * Gets the properties of the Table service.
    * 
    * @param Models\TableServiceOptions $options optional table service options.
    * 
    * @return MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452238.aspx
    */
    public function getServiceProperties($options = null);

    /**
    * Sets the properties of the Table service.
    * 
    * @param ServiceProperties          $serviceProperties new service properties
    * @param Models\TableServiceOptions $options           optional parameters
    * 
    * @return none.
    * 
    * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452240.aspx
    */
    public function setServiceProperties($serviceProperties, $options = null);
    
    /**
     * Quries tables in the given storage account.
     * 
     * @param Models\QueryTablesOptions|string|Models\Filter $options Could be
     * optional parameters, table prefix or filter to apply.
     * 
     * @return Models\QueryTablesResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx
     */
    public function queryTables($options = null);
    
    /**
     * Creates new table in the storage account
     * 
     * @param string                     $table   The name of the table.
     * @param Models\TableServiceOptions $options optional parameters
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx
     */
    public function createTable($table, $options = null);
    
    /**
     * Gets the table.
     * 
     * @param string                     $table   The The name of the table..
     * @param Models\TableServiceOptions $options The optional parameters.
     * 
     * @return Models\GetTableResult
     */
    public function getTable($table, $options = null);
    
    /**
     * Deletes the specified table and any data it contains.
     * 
     * @param string                     $table   The name of the table.
     * @param Models\TableServiceOptions $options optional parameters
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx
     */
    public function deleteTable($table, $options = null);
    
    /**
     * Quries entities for the given table name
     * 
     * @param string                                           $table   The name of
     * the table.
     * @param Models\QueryEntitiesOptions|string|Models\Filter $options Coule be
     * optional parameters, query string or filter to apply.
     * 
     * @return Models\QueryEntitiesResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx
     */
    public function queryEntities($table, $options = null);
    
    /**
     * Inserts new entity to the table
     * 
     * @param string                     $table   name of the table
     * @param Models\Entity              $entity  table entity
     * @param Models\TableServiceOptions $options optional parameters
     * 
     * @return Models\InsertEntityResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179433.aspx
     */
    public function insertEntity($table, $entity, $options = null);
    
    /**
     * Updates an existing entity or inserts a new entity if it does not exist in the
     * table.
     * 
     * @param string                     $table   name of the table
     * @param Models\Entity              $entity  table entity
     * @param Models\TableServiceOptions $options optional parameters
     * 
     * @return Models\UpdateEntityResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452241.aspx
     */
    public function insertOrMergeEntity($table, $entity, $options = null);
    
    /**
     * Replaces an existing entity or inserts a new entity if it does not exist in
     * the table.
     * 
     * @param string                     $table   name of the table
     * @param Models\Entity              $entity  table entity
     * @param Models\TableServiceOptions $options optional parameters
     * 
     * @return Models\UpdateEntityResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452242.aspx
     */
    public function insertOrReplaceEntity($table, $entity, $options = null);
    
    /**
     * Updates an existing entity in a table. The Update Entity operation replaces 
     * the entire entity and can be used to remove properties.
     * 
     * @param string                     $table   The table name.
     * @param Models\Entity              $entity  The table entity.
     * @param Models\TableServiceOptions $options The optional parameters.
     * 
     * @return Models\UpdateEntityResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179427.aspx
     */
    public function updateEntity($table, $entity, $options = null);
    
    /**
     * Updates an existing entity by updating the entity's properties. This operation
     * does not replace the existing entity, as the updateEntity operation does.
     * 
     * @param string                     $table   The table name.
     * @param Models\Entity              $entity  The table entity.
     * @param Models\TableServiceOptions $options The optional parameters.
     * 
     * @return Models\UpdateEntityResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179392.aspx
     */
    public function mergeEntity($table, $entity, $options = null);
    
    /**
     * Deletes an existing entity in a table.
     * 
     * @param string                     $table        The name of the table.
     * @param string                     $partitionKey The entity partition key.
     * @param string                     $rowKey       The entity row key.
     * @param Models\DeleteEntityOptions $options      The optional parameters.
     * 
     * @return none
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135727.aspx
     */
    public function deleteEntity($table, $partitionKey, $rowKey, $options = null);
    
    /**
     * Gets table entity.
     * 
     * @param string                     $table        The name of the table.
     * @param string                     $partitionKey The entity partition key.
     * @param string                     $rowKey       The entity row key.
     * @param Models\TableServiceOptions $options      The optional parameters.
     * 
     * @return Models\GetEntityResult
     * 
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx
     */
    public function getEntity($table, $partitionKey, $rowKey, $options = null);
    
    /**
     * Does batch of operations on given table service.
     * 
     * @param BatchOperations            $operations the operations to apply
     * @param Models\TableServiceOptions $options    optional parameters
     * 
     * @return Models\BatchResult
     */
    public function batch($operations, $options = null);
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Internal;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Reads and writes MIME for batch API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class MimeReaderWriter implements IMimeReaderWriter
{
    /**
     * Given array of MIME parts in raw string, this function converts them into MIME
     * representation. 
     * 
     * @param array $bodyPartContents The MIME body parts.
     * 
     * @return array Returns array with two elements 'headers' and 'body' which
     * represents the MIME message.
     */
    public function encodeMimeMultipart($bodyPartContents)
    {
        $count         = count($bodyPartContents);
        $mimeType      = Resources::MULTIPART_MIXED_TYPE;
        $batchGuid     = Utilities::getGuid();
        $batchId       = sprintf('batch_%s', $batchGuid);
        $contentType1  = array('content_type' => "$mimeType");
        $changeSetGuid = Utilities::getGuid();
        $changeSetId   = sprintf('changeset_%s', $changeSetGuid);
        $contentType2  = array('content_type' => "$mimeType; boundary=$changeSetId");
        $options       = array(
            'encoding'     => 'binary',
            'content_type' => Resources::HTTP_TYPE
        );
        
        $eof = "\r\n";
        
        $result            = array();
        $result['body']    = Resources::EMPTY_STRING;
        $result['headers'] = array();
        
        $batchBody         =& $result['body'];
        $batchHeaders      =& $result['headers'];
        
        $batchHeaders['Content-Type'] = $mimeType . "; $eof boundary=\"$batchId\"";
        
        $batchBody .= "--" . $batchId . $eof;
        $batchBody .= "Content-Type: $mimeType; boundary=\"$changeSetId\"" . $eof;
        
        $batchBody .= $eof;
        for ($i = 0; $i < count($bodyPartContents); $i++) 
        {
            $batchBody .= "--" . $changeSetId . $eof;
            
            $batchBody .= "Content-Transfer-Encoding: binary" . $eof;
            $batchBody .= "Content-Type: " . Resources::HTTP_TYPE . $eof;
            
            $batchBody .= $eof . $bodyPartContents[$i] . $eof;
        }
        $batchBody .= "--" . $changeSetId . "--" . $eof;
        $batchBody .= $eof;
        $batchBody .= "--" . $batchId . "--" . $eof;

        return $result;
    }
    
    /**
     * Parses given mime HTTP response body into array. Each array element 
     * represents a change set result.
     * 
     * @param string $mimeBody The raw MIME body result.
     * 
     * @return array
     */
    public function decodeMimeMultipart($mimeBody)
    {
        // Find boundary
        $boundaryRegex = '~boundary=(changesetresponse_.*)~';
        preg_match($boundaryRegex, $mimeBody, $matches);
        
        $boundary = trim($matches[1]);
    
        // Split the requests
        $requests = explode('--' . $boundary, $mimeBody);
        
        // Get the body of each request
        $result = array();
         
        // The first and last element are not request
        for($i = 1; $i < count($requests) - 1; $i++)
        {
            // Split the request header and body
            preg_match("/^.*?\r?\n\r?\n(.*)/s", $requests[$i], $matches);
            $result[] = $matches[1];
        }
        
        return $result;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\ServiceException;

/**
 * Represents an error returned from call to batch API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchError
{
    /**
     * @var MicrosoftAzure\Storage\Common\ServiceException 
     */
    private $_error;
    
    /**
     * @var integer
     */
    private $_contentId;
    
    /**
     * Creates BatchError object.
     * 
     * @param MicrosoftAzure\Storage\Common\ServiceException $error   The error object.
     * @param array                                $headers The response headers.
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\BatchError 
     */
    public static function create($error, $headers)
    {
        Validate::isTrue(
            $error instanceof ServiceException,
            Resources::INVALID_EXC_OBJ_MSG
        );
        Validate::isArray($headers, 'headers');
        
        $result = new BatchError();
        $clean  = array_change_key_case($headers);
        
        $result->setError($error);
        $contentId = Utilities::tryGetValue($clean, Resources::CONTENT_ID);
        $result->setContentId(is_null($contentId) ? null : intval($contentId));
        
        return $result;
    }
    
    /**
     * Gets the error.
     * 
     * @return MicrosoftAzure\Storage\Common\ServiceException
     */
    public function getError()
    {
        return $this->_error;
    }
    
    /**
     * Sets the error.
     * 
     * @param MicrosoftAzure\Storage\Common\ServiceException $error The error object.
     * 
     * @return none
     */
    public function setError($error)
    {
        $this->_error = $error;
    }
    
    /**
     * Gets the contentId.
     * 
     * @return integer
     */
    public function getContentId()
    {
        return $this->_contentId;
    }
    
    /**
     * Sets the contentId.
     * 
     * @param integer $contentId The contentId object.
     * 
     * @return none
     */
    public function setContentId($contentId)
    {
        $this->_contentId = $contentId;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Represents one batch operation
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchOperation
{
    /**
     * @var string
     */
    private $_type;
    
    /**
     * @var array
     */
    private $_params;
    
    /**
     * Sets operation type.
     * 
     * @param string $type The operation type. Must be valid type.
     * 
     * @return none
     */
    public function setType($type)
    {
        Validate::isTrue(
            BatchOperationType::isValid($type),
            Resources::INVALID_BO_TYPE_MSG
        );
        
        $this->_type = $type;
    }
    
    /**
     * Gets operation type.
     * 
     * @return string
     */
    public function getType()
    {
        return $this->_type;
    }
    
    /**
     * Adds or sets parameter for the operation.
     * 
     * @param string $name  The param name. Must be valid name.
     * @param mix    $value The param value.
     * 
     * @return none
     */
    public function addParameter($name, $value)
    {
        Validate::isTrue(
            BatchOperationParameterName::isValid($name),
            Resources::INVALID_BO_PN_MSG
        );
        $this->_params[$name] = $value;
    }
    
    /**
     * Gets parameter value and if the name doesn't exist, return null.
     * 
     * @param string $name The parameter name.
     * 
     * @return mix
     */
    public function getParameter($name)
    {
        return Utilities::tryGetValue($this->_params, $name);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;

/**
 * Batch parameter names.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchOperationParameterName
{
    const BP_TABLE         = 'table';
    const BP_ENTITY        = 'entity';
    const BP_PARTITION_KEY = 'PartitionKey';
    const BP_ROW_KEY       = 'RowKey';
    const BP_ETAG          = 'etag';
    
    /**
     * Validates if $paramName is already defined.
     * 
     * @param string $paramName The batch operation parameter name.
     * 
     * @return boolean 
     */
    public static function isValid($paramName)
    {
        switch ($paramName) {
        case self::BP_TABLE:
        case self::BP_ENTITY:
        case self::BP_PARTITION_KEY:
        case self::BP_ROW_KEY:
        case self::BP_ETAG:
        return true;

        default:
        return false;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Holds batch operation change set.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchOperations
{
    /**
     * @var array
     */
    private $_operations;

    /**
     * Default constructor. 
     */
    public function __construct()
    {
        $this->_operations = array();
    }
    
    /**
     * Gets the batch operations.
     * 
     * @return array
     */
    public function getOperations()
    {
        return $this->_operations;
    }
    
    /**
     * Sets the batch operations.
     * 
     * @param array $operations The batch operations.
     * 
     * @return none
     */
    public function setOperations($operations)
    {
        $this->_operations = array();
        foreach ($operations as $operation) {
            $this->addOperation($operation);
        }
    }
    
    /**
     * Adds operation to the batch operations.
     * 
     * @param mix $operation The operation to add.
     * 
     * @return none
     */
    public function addOperation($operation)
    {
        Validate::isTrue(
            $operation instanceof BatchOperation,
            Resources::INVALID_BO_TYPE_MSG
        );
        
        $this->_operations[] = $operation;
    }
    
    /**
     * Adds insertEntity operation.
     * 
     * @param string $table  The table name.
     * @param Entity $entity The entity instance.
     * 
     * @return none
     */
    public function addInsertEntity($table, $entity)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        
        $operation = new BatchOperation();
        $type      = BatchOperationType::INSERT_ENTITY_OPERATION;
        $operation->setType($type);
        $operation->addParameter(BatchOperationParameterName::BP_TABLE, $table);
        $operation->addParameter(BatchOperationParameterName::BP_ENTITY, $entity);
        $this->addOperation($operation);
    }
    
    /**
     * Adds updateEntity operation.
     * 
     * @param string $table  The table name.
     * @param Entity $entity The entity instance.
     * 
     * @return none
     */
    public function addUpdateEntity($table, $entity)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        
        $operation = new BatchOperation();
        $type      = BatchOperationType::UPDATE_ENTITY_OPERATION;
        $operation->setType($type);
        $operation->addParameter(BatchOperationParameterName::BP_TABLE, $table);
        $operation->addParameter(BatchOperationParameterName::BP_ENTITY, $entity);
        $this->addOperation($operation);
    }
    
    /**
     * Adds mergeEntity operation.
     * 
     * @param string $table  The table name.
     * @param Entity $entity The entity instance.
     * 
     * @return none
     */
    public function addMergeEntity($table, $entity)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        
        $operation = new BatchOperation();
        $type      = BatchOperationType::MERGE_ENTITY_OPERATION;
        $operation->setType($type);
        $operation->addParameter(BatchOperationParameterName::BP_TABLE, $table);
        $operation->addParameter(BatchOperationParameterName::BP_ENTITY, $entity);
        $this->addOperation($operation);
    }
    
    /**
     * Adds insertOrReplaceEntity operation.
     * 
     * @param string $table  The table name.
     * @param Entity $entity The entity instance.
     * 
     * @return none
     */
    public function addInsertOrReplaceEntity($table, $entity)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        
        $operation = new BatchOperation();
        $type      = BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION;
        $operation->setType($type);
        $operation->addParameter(BatchOperationParameterName::BP_TABLE, $table);
        $operation->addParameter(BatchOperationParameterName::BP_ENTITY, $entity);
        $this->addOperation($operation);
    }
    
    /**
     * Adds insertOrMergeEntity operation.
     * 
     * @param string $table  The table name.
     * @param Entity $entity The entity instance.
     * 
     * @return none
     */
    public function addInsertOrMergeEntity($table, $entity)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        
        $operation = new BatchOperation();
        $type      = BatchOperationType::INSERT_MERGE_ENTITY_OPERATION;
        $operation->setType($type);
        $operation->addParameter(BatchOperationParameterName::BP_TABLE, $table);
        $operation->addParameter(BatchOperationParameterName::BP_ENTITY, $entity);
        $this->addOperation($operation);
    }
    
    /**
     * Adds deleteEntity operation.
     * 
     * @param string $table        The table name.
     * @param string $partitionKey The entity partition key.
     * @param string $rowKey       The entity row key.
     * @param string $etag         The entity etag.
     * 
     * @return none
     */
    public function addDeleteEntity($table, $partitionKey, $rowKey, $etag = null)
    {
        Validate::isString($table, 'table');
        Validate::isTrue(!is_null($partitionKey), Resources::NULL_TABLE_KEY_MSG);
        Validate::isTrue(!is_null($rowKey), Resources::NULL_TABLE_KEY_MSG);
        
        $operation = new BatchOperation();
        $type      = BatchOperationType::DELETE_ENTITY_OPERATION;
        $operation->setType($type);
        $operation->addParameter(BatchOperationParameterName::BP_TABLE, $table);
        $operation->addParameter(BatchOperationParameterName::BP_ROW_KEY, $rowKey);
        $operation->addParameter(BatchOperationParameterName::BP_ETAG, $etag);
        $operation->addParameter(
            BatchOperationParameterName::BP_PARTITION_KEY,
            $partitionKey
        );
        $this->addOperation($operation);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;

/**
 * Supported batch operations.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchOperationType
{
    const INSERT_ENTITY_OPERATION         = 'InsertEntityOperation';
    const UPDATE_ENTITY_OPERATION         = 'UpdateEntityOperation';
    const DELETE_ENTITY_OPERATION         = 'DeleteEntityOperation';
    const MERGE_ENTITY_OPERATION          = 'MergeEntityOperation';
    const INSERT_REPLACE_ENTITY_OPERATION = 'InsertOrReplaceEntityOperation';
    const INSERT_MERGE_ENTITY_OPERATION   = 'InsertOrMergeEntityOperation';
    
    /**
     * Validates if $type is already defined.
     * 
     * @param string $type The operation type.
     * 
     * @return boolean 
     */
    public static function isValid($type)
    {
        switch ($type) {
        case self::INSERT_ENTITY_OPERATION:
        case self::UPDATE_ENTITY_OPERATION:
        case self::DELETE_ENTITY_OPERATION:
        case self::MERGE_ENTITY_OPERATION:
        case self::INSERT_REPLACE_ENTITY_OPERATION:
        case self::INSERT_MERGE_ENTITY_OPERATION:
        return true;
                
        default:
        return false;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\HttpFormatter;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy;
use MicrosoftAzure\Storage\Table\Models\BatchError;
use MicrosoftAzure\Storage\Table\Models\InsertEntityResult;
use MicrosoftAzure\Storage\Table\Models\UpdateEntityResult;

/**
 * Holds results from batch API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchResult
{
    /**
     * Each entry represents change set result.
     * 
     * @var array
     */
    private $_entries;
    
    /**
     * Creates a array of responses from the batch response body.
     * 
     * @param string            $body           The HTTP response body.
     * @param IMimeReaderWriter $mimeSerializer The MIME reader and writer.
     * 
     * @return array
     */
    private static function _constructResponses($body, $mimeSerializer)
    {
        $responses = array();
        $parts     = $mimeSerializer->decodeMimeMultipart($body);
        // Decrease the count of parts to remove the batch response body and just
        // include change sets response body. We may need to undo this action in
        // case that batch response body has useful info.
        $count = count($parts);
        
        for ($i = 0; $i < $count; $i++) {
            $response = new \stdClass();
            
            // Split lines
            $lines    = explode("\r\n", $parts[$i]);
            
            // Version Status Reason
            $statusTokens = explode(' ', $lines[0], 3);
            $response->version = $statusTokens[0];
            $response->statusCode = $statusTokens[1];
            $response->reason = $statusTokens[2];
            
            $headers = array();
            $j       = 1;
            do {
                $headerLine = $lines[$j++];
                $headerTokens = explode(':', $headerLine);
                $headers[trim($headerTokens[0])] = 
                    isset($headerTokens[1]) ? trim($headerTokens[1]) : null;   
            } while (Resources::EMPTY_STRING != $headerLine);
            $response->headers = $headers;
            $response->body = implode("\r\n", array_slice($lines, $j));
            $responses[] = $response;
        }
        
        return $responses;
    }
    
    /**
     * Compares between two responses by Content-ID header.
     * 
     * @param \HTTP_Request2_Response $r1 The first response object.
     * @param \HTTP_Request2_Response $r2 The second response object.
     * 
     * @return boolean
     */
    private static function _compareUsingContentId($r1, $r2)
    {
        $h1 = array_change_key_case($r1->headers);
        $h2 = array_change_key_case($r2->headers);
        $c1 = Utilities::tryGetValue($h1, Resources::CONTENT_ID, 0);
        $c2 = Utilities::tryGetValue($h2, Resources::CONTENT_ID, 0);
        
        return intval($c1) >= intval($c2);
    }

    /**
     * Creates BatchResult object.
     * 
     * @param string            $body           The HTTP response body.
     * @param array             $operations     The batch operations.
     * @param array             $contexts       The batch operations context.
     * @param IAtomReaderWriter $atomSerializer The Atom reader and writer.
     * @param IMimeReaderWriter $mimeSerializer The MIME reader and writer.
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\BatchResult
     * 
     * @throws \InvalidArgumentException 
     */
    public static function create($body, $operations, $contexts, $atomSerializer, 
        $mimeSerializer
    ) {
        $result       = new BatchResult();
        $responses    = self::_constructResponses($body, $mimeSerializer);
        $callbackName = __CLASS__ . '::_compareUsingContentId';
        $count        = count($responses);
        $entries      = array();
        
        // Sort $responses based on Content-ID so they match order of $operations.
        uasort($responses, $callbackName);
        
        for ($i = 0; $i < $count; $i++) {
            $context   = $contexts[$i];
            $response  = $responses[$i];
            $operation = $operations[$i];
            $type      = $operation->getType();
            $body      = $response->body;
            $headers   = HttpFormatter::formatHeaders($response->headers);
            
            try {
                ServiceRestProxy::throwIfError(
                    $response->statusCode,
                    $response->reason,
                    $response->body,
                    $context->getStatusCodes()
                );
            
                switch ($type) {
                case BatchOperationType::INSERT_ENTITY_OPERATION:
                    $entries[] = InsertEntityResult::create(
                        $body,
                        $headers,
                        $atomSerializer
                    );
                    break;
                case BatchOperationType::UPDATE_ENTITY_OPERATION:
                case BatchOperationType::MERGE_ENTITY_OPERATION:
                case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
                case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
                    $entries[] = UpdateEntityResult::create($headers);
                    break;

                case BatchOperationType::DELETE_ENTITY_OPERATION:
                    $entries[] = Resources::BATCH_ENTITY_DEL_MSG;
                    break;

                default:
                    throw new \InvalidArgumentException();
                }
            } catch (ServiceException $e) {
                $entries[] = BatchError::create($e, $response->headers);
            }
        }
        $result->setEntries($entries);
        
        return $result;
    }
    
    /**
     * Gets batch call result entries.
     * 
     * @return array
     */
    public function getEntries()
    {
        return $this->_entries;
    }
    
    /**
     * Sets batch call result entries.
     * 
     * @param array $entries The batch call result entries.
     * 
     * @return none
     */
    public function setEntries($entries)
    {
        $this->_entries = $entries;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Holds optional parameters for deleteEntity API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DeleteEntityOptions extends TableServiceOptions
{
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * Gets entity etag.
     *
     * @return string
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets entity etag.
     *
     * @param string $etag The entity ETag.
     *
     * @return none
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Basic Windows Azure EDM Types used for table entity properties.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class EdmType
{
    // @codingStandardsIgnoreStart
    
    const DATETIME = 'Edm.DateTime';
    const BINARY   = 'Edm.Binary';
    const BOOLEAN  = 'Edm.Boolean';
    const DOUBLE   = 'Edm.Double';
    const GUID     = 'Edm.Guid';
    const INT32    = 'Edm.Int32';
    const INT64    = 'Edm.Int64';
    const STRING   = 'Edm.String';
    
    /**
     * Converts the type to string if it's empty and validates the type.
     * 
     * @param string $type The Edm type
     * 
     * @return string
     */
    public static function processType($type)
    {
        $type = empty($type) ? self::STRING : $type;
        Validate::isTrue(self::isValid($type), Resources::INVALID_EDM_MSG);
        
        return $type;
    }
    
    /**
     * Validates that the value associated with the EDM type is valid.
     * 
     * @param string $type       The EDM type.
     * @param mix    $value      The EDM value.
     * @param string &$condition The error message.
     * 
     * @return boolean
     * 
     * @throws \InvalidArgumentException 
     */
    public static function validateEdmValue($type, $value, &$condition = null)
    {
        // Having null value means that the user wants to remove the property name
        // associated with this value. Leave the value as null so this hold.
        if (is_null($value)) {
            return true;
        } else {
            switch ($type) {
            case EdmType::GUID:
            case EdmType::BINARY:
            case EdmType::STRING:
            case EdmType::INT64:
            case null:
                // NULL also is treated as EdmType::STRING
                $condition = 'is_string';
                return is_string($value);

            case EdmType::DOUBLE:
                $condition = 'is_double';
                return is_double($value);
                
            case EdmType::INT32:
                $condition = 'is_int';
                return is_int($value);

            case EdmType::DATETIME:
                $condition = 'instanceof \DateTime';
                return $value instanceof \DateTime;

            case EdmType::BOOLEAN:
                $condition = 'is_bool';
                return is_bool($value);

            default:
                throw new \InvalidArgumentException();
            }
        }
    }
    
    /**
     * Serializes EDM value into proper value for sending it to Windows Azure.
     * 
     * @param string $type  The EDM type.
     * @param mix    $value The EDM value.
     * 
     * @return string
     * 
     * @throws \InvalidArgumentException 
     */
    public static function serializeValue($type, $value)
    {
        switch ($type) {
        case EdmType::DOUBLE:
        case EdmType::INT32:
        case EdmType::INT64:
        case EdmType::GUID:
        case EdmType::STRING:
        case null:
            // NULL also is treated as EdmType::STRING
            return strval($value);
            
        case EdmType::BINARY:
            return base64_encode($value);
            
        case EdmType::DATETIME:
            return Utilities::convertToEdmDateTime($value);

        case EdmType::BOOLEAN:
            return (is_null($value) ? '' : ($value == true ? '1' : '0'));

        default:
            throw new \InvalidArgumentException();
        }
    }
    
    /**
     * Serializes EDM value into proper value to be used in query.
     * 
     * @param string $type  The EDM type.
     * @param mix    $value The EDM value.
     * 
     * @return string
     * 
     * @throws \InvalidArgumentException 
     */
    public static function serializeQueryValue($type, $value)
    {
        switch ($type) {
        case EdmType::DATETIME:
            $edmDate = Utilities::convertToEdmDateTime($value);
            return 'datetime\'' . $edmDate . '\'';

        case EdmType::BINARY:
            return 'X\'' . implode('', unpack("H*", $value)) . '\'';

        case EdmType::BOOLEAN:
            return ($value ? 'true' : 'false');

        case EdmType::DOUBLE:
        case EdmType::INT32:
            return $value;
            
        case EdmType::INT64:
            return $value . 'L';

        case EdmType::GUID:
            return 'guid\'' . $value . '\'';

        case null:
        case EdmType::STRING:
            // NULL also is treated as EdmType::STRING
            return '\'' . str_replace('\'', '\'\'', $value) . '\'';

        default:
            throw new \InvalidArgumentException();
        }
    }
    
    /**
     * Converts the value into its proper type.
     * 
     * @param string $type  The edm type.
     * @param string $value The edm value.
     * 
     * @return mix
     * 
     * @throws \InvalidArgumentException
     */
    public static function unserializeQueryValue($type, $value)
    {
        // Having null value means that the user wants to remove the property name
        // associated with this value. Leave the value as null so this hold.
        if (is_null($value)) {
            return null;
        } else {
            switch ($type) {
            case self::GUID:
            case self::STRING:
            case self::INT64:
            case null:
                // NULL also is treated as EdmType::STRING
                return strval($value);

            case self::BINARY:
                return base64_decode($value);

            case self::DATETIME:
                return Utilities::convertToDateTime($value);

            case self::BOOLEAN:
                return Utilities::toBoolean($value);

            case self::DOUBLE:
                return doubleval($value);
                
            case self::INT32:
                return intval($value);

            default:
                throw new \InvalidArgumentException();
            }
        }
    }
    
    /**
     * Check if the $type belongs to valid header types.
     * 
     * @param string $type The type string to check.
     * 
     * @return boolean 
     */
    public static function isValid($type)
    {
        switch($type) {
        case $type == self::DATETIME:
        case $type == self::BINARY:
        case $type == self::BOOLEAN:
        case $type == self::DOUBLE:
        case $type == self::GUID:
        case $type == self::INT32:
        case $type == self::INT64:
        case $type == self::STRING:
        case $type == null:
            // NULL also is treated as EdmType::STRING
            return true;
        
        default:
            return false;
                
        }
    }
    
    // @codingStandardsIgnoreEnd
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Validate;

/**
 * Represents entity object used in tables
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Entity
{
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * @var array
     */
    private $_properties;
    
    /**
     * Validates if properties is valid or not.
     * 
     * @param mix $properties The properties array.
     * 
     * @return none
     */
    private function _validateProperties($properties)
    {
        Validate::isArray($properties, 'entity properties');
        
        foreach ($properties as $key => $value) {
            Validate::isString($key, 'key');
            Validate::isTrue(
                $value instanceof Property,
                Resources::INVALID_PROP_MSG
            );
            Validate::isTrue(
                EdmType::validateEdmValue(
                    $value->getEdmType(),
                    $value->getValue(),
                    $condition
                ),
                sprintf(Resources::INVALID_PROP_VAL_MSG, $key, $condition)
            );
        }
    }
    
    /**
     * Gets property value and if the property name is not found return null.
     * 
     * @param string $name The property name.
     * 
     * @return mix
     */
    public function getPropertyValue($name)
    {
        $p = Utilities::tryGetValue($this->_properties, $name);
        return is_null($p) ? null : $p->getValue();
    }
    
    /**
     * Sets property value.
     * 
     * Note that if the property doesn't exist, it doesn't add it. Use addProperty
     * to add new properties.
     * 
     * @param string $name  The property name.
     * @param mix    $value The property value. 
     * 
     * @return mix
     */
    public function setPropertyValue($name, $value)
    {
        $p = Utilities::tryGetValue($this->_properties, $name);
        if (!is_null($p)) {
            $p->setValue($value);
        }
    }
    
    /**
     * Gets entity etag.
     *
     * @return string
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets entity etag.
     *
     * @param string $etag The entity ETag value.
     *
     * @return none
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
    
    /**
     * Gets entity PartitionKey.
     *
     * @return string
     */
    public function getPartitionKey()
    {
        return $this->getPropertyValue('PartitionKey');
    }

    /**
     * Sets entity PartitionKey.
     *
     * @param string $partitionKey The entity PartitionKey value.
     *
     * @return none
     */
    public function setPartitionKey($partitionKey)
    {
        $this->addProperty('PartitionKey', null, $partitionKey);
    }
    
    /**
     * Gets entity RowKey.
     *
     * @return string
     */
    public function getRowKey()
    {
        return $this->getPropertyValue('RowKey');
    }

    /**
     * Sets entity RowKey.
     *
     * @param string $rowKey The entity RowKey value.
     *
     * @return none
     */
    public function setRowKey($rowKey)
    {
        $this->addProperty('RowKey', null, $rowKey);
    }
    
    /**
     * Gets entity Timestamp.
     *
     * @return \DateTime
     */
    public function getTimestamp()
    {
        return $this->getPropertyValue('Timestamp');
    }

    /**
     * Sets entity Timestamp.
     *
     * @param \DateTime $timestamp The entity Timestamp value.
     *
     * @return none
     */
    public function setTimestamp($timestamp)
    {
        $this->addProperty('Timestamp', EdmType::DATETIME, $timestamp);
    }
    
    /**
     * Gets the entity properties array.
     * 
     * @return array
     */
    public function getProperties()
    {
        return $this->_properties;
    }
    
    /**
     * Sets the entity properties array.
     * 
     * @param array $properties The entity properties.
     * 
     * @return none
     */
    public function setProperties($properties)
    {
        $this->_validateProperties($properties);
        $this->_properties = $properties;
    }
    
    /**
     * Gets property object from the entity properties.
     * 
     * @param string $name The property name.
     * 
     * @return Property
     */
    public function getProperty($name)
    {
        return Utilities::tryGetValue($this->_properties, $name);
    }
    
    /**
     * Sets entity property.
     * 
     * @param string   $name     The property name.
     * @param Property $property The property object.
     * 
     * @return none
     */
    public function setProperty($name, $property)
    {
        Validate::isTrue($property instanceof Property, Resources::INVALID_PROP_MSG);
        $this->_properties[$name] = $property;
    }
    
    /**
     * Creates new entity property.
     * 
     * @param string $name    The property name.
     * @param string $edmType The property edm type.
     * @param mix    $value   The property value.
     * 
     * @return none
     */
    public function addProperty($name, $edmType, $value)
    {        
        $p = new Property();
        $p->setEdmType($edmType);
        $p->setValue($value);
        $this->setProperty($name, $p);
    }
    
    /**
     * Checks if the entity object is valid or not.
     * Valid means the partition and row key exists for this entity along with the
     * timestamp.
     * 
     * @param string &$msg The error message.
     * 
     * @return boolean
     */
    public function isValid(&$msg = null)
    {
        try {
            $this->_validateProperties($this->_properties);
        } catch (\Exception $exc) {
            $msg = $exc->getMessage();
            return false;
        }

        if (   is_null($this->getPartitionKey())
            || is_null($this->getRowKey())
        ) {
            $msg = Resources::NULL_TABLE_KEY_MSG;
            return false;
        } else {
            return true;
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models\Filters;

/**
 * Binary filter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BinaryFilter extends Filter
{
    /**
     * @var string 
     */
    private $_operator;
    
    /**
     * @var Filter
     */
    private $_left;
    
    /**
     * @var Filter
     */
    private $_right;
    
    /**
     * Constructor.
     * 
     * @param Filter $left     The left operand.
     * @param string $operator The operator.
     * @param Filter $right    The right operand.
     */
    public function __construct($left, $operator, $right)
    {
        $this->_left     = $left;
        $this->_operator = $operator;
        $this->_right    = $right;
    }
    
    /**
     * Gets operator
     * 
     * @return string 
     */
    public function getOperator() 
    {
        return $this->_operator;
    }

    /**
     * Gets left
     * 
     * @return Filter 
     */
    public function getLeft()
    {
        return $this->_left;
    }

    /**
     * Gets right
     * 
     * @return Filter 
     */
    public function getRight()
    {
        return $this->_right;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models\Filters;
use MicrosoftAzure\Storage\Table\Models\EdmType;

/**
 * Constant filter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ConstantFilter extends Filter
{
    /**
     * @var mix
     */
    private $_value;
    
    /**
     * @var string
     */
    private $_edmType;
    
    /**
     * Constructor.
     * 
     * @param string $edmType The EDM type.
     * @param string $value   The EDM value.
     */
    public function __construct($edmType, $value)
    {
        $this->_edmType = EdmType::processType($edmType);
        $this->_value   = $value;
    }

    /**
     * Gets value
     * 
     * @return mix 
     */
    public function getValue()
    {
        return $this->_value;
    }
    
    /**
     * Gets the type of the constant.
     * 
     * @return string
     */
    public function getEdmType()
    {
        return $this->_edmType;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models\Filters;

/**
 * Filter operations
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Filter
{
    /**
     * Apply and operation between two filters
     * 
     * @param Filter $left  The left filter
     * @param Filter $right The right filter
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter 
     */
    public static function applyAnd($left, $right)
    {
        $filter = new BinaryFilter($left, 'and', $right);
        return $filter;
    }
   
    /**
     * Applies not operation on $operand
     * 
     * @param Filter $operand The operand
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter 
     */
    public static function applyNot($operand)
    {
        $filter = new UnaryFilter('not', $operand);
        return $filter;
    }

    /**
     * Apply or operation on the passed filers
     * 
     * @param Filter $left  The left operand
     * @param Filter $right The right operand
     * 
     * @return BinaryFilter
     */
    public static function applyOr($left, $right)
    {
        $filter = new BinaryFilter($left, 'or', $right);
        return $filter;
    }

    /**
     * Apply eq operation on the passed filers
     * 
     * @param Filter $left  The left operand
     * @param Filter $right The right operand
     * 
     * @return BinaryFilter
     */
    public static function applyEq($left, $right)
    {
        $filter = new BinaryFilter($left, 'eq', $right);
        return $filter;
    }

    /**
     * Apply ne operation on the passed filers
     * 
     * @param Filter $left  The left operand
     * @param Filter $right The right operand
     * 
     * @return BinaryFilter
     */
    public static function applyNe($left, $right)
    {
        $filter = new BinaryFilter($left, 'ne', $right);
        return $filter;
    }

    /**
     * Apply ge operation on the passed filers
     * 
     * @param Filter $left  The left operand
     * @param Filter $right The right operand
     * 
     * @return BinaryFilter
     */
    public static function applyGe($left, $right)
    {
        $filter = new BinaryFilter($left, 'ge', $right);
        return $filter;
    }

    /**
     * Apply gt operation on the passed filers
     * 
     * @param Filter $left  The left operand
     * @param Filter $right The right operand
     * 
     * @return BinaryFilter
     */
    public static function applyGt($left, $right)
    {
        $filter = new BinaryFilter($left, 'gt', $right);
        return $filter;
    }

    /**
     * Apply lt operation on the passed filers
     * 
     * @param Filter $left  The left operand
     * @param Filter $right The right operand
     * 
     * @return BinaryFilter
     */
    public static function applyLt($left, $right)
    {
        $filter = new BinaryFilter($left, 'lt', $right);
        return $filter;
    }

    /**
     * Apply le operation on the passed filers
     * 
     * @param Filter $left  The left operand
     * @param Filter $right The right operand
     * 
     * @return BinaryFilter
     */
    public static function applyLe($left, $right)
    {
        $filter = new BinaryFilter($left, 'le', $right);
        return $filter;
    }

    /**
     * Apply constant filter on value.
     * 
     * @param mix    $value   The filter value
     * @param string $edmType The value EDM type.
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter 
     */
    public static function applyConstant($value, $edmType = null)
    {
        $filter = new ConstantFilter($edmType, $value);
        return $filter;
    }

    /**
     * Apply propertyName filter on $value
     * 
     * @param string $value The filter value
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\Filters\PropertyNameFilter 
     */
    public static function applyPropertyName($value)
    {
        $filter = new PropertyNameFilter($value);
        return $filter;
    }

    /**
     * Takes raw string filter
     * 
     * @param string $value The raw string filter expression
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\Filters\QueryStringFilter 
     */
    public static function applyQueryString($value)
    {
        $filter = new QueryStringFilter($value);
        return $filter;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models\Filters;

/**
 * Constant filter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PropertyNameFilter extends Filter
{
    /**
     * @var string
     */
    private $_propertyName;
    
    /**
     * Constructor.
     * 
     * @param string $propertyName The propertyName.
     */
    public function __construct($propertyName)
    {
        $this->_propertyName = $propertyName;
    }
    
    /**
     * Gets propertyName
     * 
     * @return string 
     */
    public function getPropertyName()
    {
        return $this->_propertyName;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models\Filters;

/**
 * Constant filter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryStringFilter extends Filter
{
    /**
     * @var string
     */
    private $_queryString;
    
    /**
     * Constructor.
     * 
     * @param string $queryString The OData query string.
     */
    public function __construct($queryString)
    {
        $this->_queryString = $queryString;
    }


    /**
     * Gets raw string filter
     * 
     * @return string 
     */
    public function getQueryString()
    {
        return $this->_queryString;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models\Filters;

/**
 * Unary filter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class UnaryFilter extends Filter
{
    /**
     * @var string 
     */
    private $_operator;
    
    /**
     * @var Filter
     */
    private $_operand;
    
    /**
     * Constructor.
     * 
     * @param string $operator The operator.
     * @param Filter $operand  The operand filter.
     */
    public function __construct($operator, $operand)
    {
        $this->_operand  = $operand;
        $this->_operator = $operator;
    }
    
    /**
     * Gets operator
     * 
     * @return string 
     */
    public function getOperator()
    {
        return $this->_operator;
    }

    /**
     * Gets operand
     * 
     * @return Filter 
     */
    public function getOperand()
    {
        return $this->_operand;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;

/**
 * Holds result of calling getEntity wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetEntityResult
{
    /**
     * @var Entity
     */
    private $_entity;
    
    /**
     * Gets table entity.
     * 
     * @return Entity
     */
    public function getEntity()
    {
        return $this->_entity;
    }
    
    /**
     * Sets table entity.
     * 
     * @param Entity $entity The table entity instance.
     * 
     * @return none
     */
    public function setEntity($entity)
    {
        $this->_entity = $entity;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Holds result of getTable API.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetTableResult
{
    /**
     * @var string
     */
    private $_name;
    
    /**
     * Creates GetTableResult from HTTP response body.
     * 
     * @param string           $body           The HTTP response body.
     * @param AtomReaderWriter $atomSerializer The Atom reader and writer.
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\GetTableResult
     */
    public static function create($body, $atomSerializer)
    {
        $result = new GetTableResult();
        
        $name = $atomSerializer->parseTable($body);
        $result->setName($name);
        
        return $result;
    }
    
    /**
     * Gets the name.
     * 
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }
    
    /**
     * Sets the name.
     * 
     * @param string $name The table name.
     * 
     * @return none
     */
    public function setName($name)
    {
        $this->_name = $name;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Holds result of calling insertEntity wrapper
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class InsertEntityResult
{
    /**
     * @var Entity
     */
    private $_entity;
    
    /**
     * Create InsertEntityResult object from HTTP response parts.
     * 
     * @param string            $body           The HTTP response body.
     * @param array             $headers        The HTTP response headers.
     * @param IAtomReaderWriter $atomSerializer The atom reader and writer.
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\InsertEntityResult
     * 
     * @static
     */
    public static function create($body, $headers, $atomSerializer)
    {
        $result = new InsertEntityResult();
        $entity = $atomSerializer->parseEntity($body);
        $entity->setETag(Utilities::tryGetValue($headers, Resources::ETAG));
        $result->setEntity($entity);
        
        return $result;
    }
    
    /**
     * Gets table entity.
     * 
     * @return Entity
     */
    public function getEntity()
    {
        return $this->_entity;
    }
    
    /**
     * Sets table entity.
     * 
     * @param Entity $entity The table entity instance.
     * 
     * @return none
     */
    public function setEntity($entity)
    {
        $this->_entity = $entity;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Tests\Unit\Table\Models\EdmTypeTest;

/**
 * Represents entity property.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Property
{
    /**
     * @var string
     */
    private $_edmType;
    
    /**
     * @var mix
     */
    private $_value;
    
    /**
     * Gets the type of the property.
     * 
     * @return string
     */
    public function getEdmType()
    {
        return $this->_edmType;
    }
    
    /**
     * Sets the value of the property.
     * 
     * @param string $edmType The property type.
     * 
     * @return none
     */
    public function setEdmType($edmType)
    {
        EdmType::isValid($edmType);
        $this->_edmType = $edmType;
    }
    
    /**
     * Gets the value of the property.
     * 
     * @return string
     */
    public function getValue()
    {
        return $this->_value;
    }
    
    /**
     * Sets the property value.
     * 
     * @param mix $value The value of property.
     * 
     * @return none
     */
    public function setValue($value)
    {
        $this->_value = $value;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;

/**
 * Query to be performed on a table
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class Query
{
    /**
     * @var array
     */
    private $_selectFields;
    
    /**
     * @var Filters\Filter
     */
    private $_filter;
    
    /**
     * @var integer
     */
    private $_top;
    
    /**
     * Gets filter.
     *
     * @return Filters\Filter
     */
    public function getFilter()
    {
        return $this->_filter;
    }

    /**
     * Sets filter.
     *
     * @param Filters\Filter $filter value.
     * 
     * @return none.
     */
    public function setFilter($filter)
    {
        $this->_filter = $filter;
    }
    
    /**
     * Gets top.
     *
     * @return integer.
     */
    public function getTop()
    {
        return $this->_top;
    }

    /**
     * Sets top.
     *
     * @param integer $top value.
     * 
     * @return none.
     */
    public function setTop($top)
    {
        $this->_top = $top;
    }
    
    /**
     * Adds a field to select fields.
     * 
     * @param string $field The value of the field.
     * 
     * @return none.
     */
    public function addSelectField($field)
    {
        $this->_selectFields[] = $field;
    }
    
    /**
     * Gets selectFields.
     *
     * @return array.
     */
    public function getSelectFields()
    {
        return $this->_selectFields;
    }

    /**
     * Sets selectFields.
     *
     * @param array $selectFields value.
     * 
     * @return none.
     */
    public function setSelectFields($selectFields)
    {
        $this->_selectFields = $selectFields;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;

/**
 * Holds optional parameters for queryEntities API
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryEntitiesOptions extends TableServiceOptions
{
    /**
     * @var Query
     */
    private $_query;
    
    /**
     * @var string
     */
    private $_nextPartitionKey;
    
    /**
     * @var string
     */
    private $_nextRowKey;
    
    /**
     * Constructs new QueryEntitiesOptions object.
     */
    public function __construct()
    {
        $this->_query = new Query();
    }
    
    /**
     * Gets query.
     * 
     * @return Query
     */
    public function getQuery()
    {
        return $this->_query;
    }
    
    /**
     * Sets query.
     * 
     * You can either sets the whole query *or* use the individual query functions
     * like (setTop).
     * 
     * @param string $query The query instance.
     * 
     * @return none
     */
    public function setQuery($query)
    {
        $this->_query = $query;
    }
    
    /**
     * Gets entity next partition key.
     *
     * @return string
     */
    public function getNextPartitionKey()
    {
        return $this->_nextPartitionKey;
    }

    /**
     * Sets entity next partition key.
     *
     * @param string $nextPartitionKey The entity next partition key value.
     *
     * @return none
     */
    public function setNextPartitionKey($nextPartitionKey)
    {
        $this->_nextPartitionKey = $nextPartitionKey;
    }
    
    /**
     * Gets entity next row key.
     *
     * @return string
     */
    public function getNextRowKey()
    {
        return $this->_nextRowKey;
    }

    /**
     * Sets entity next row key.
     *
     * @param string $nextRowKey The entity next row key value.
     *
     * @return none
     */
    public function setNextRowKey($nextRowKey)
    {
        $this->_nextRowKey = $nextRowKey;
    }
    
    /**
     * Gets filter.
     *
     * @return Filters\Filter
     */
    public function getFilter()
    {
        return $this->_query->getFilter();
    }

    /**
     * Sets filter.
     *
     * You can either use this individual function or use setQuery to set the whole
     * query object.
     * 
     * @param Filters\Filter $filter value.
     * 
     * @return none.
     */
    public function setFilter($filter)
    {
        $this->_query->setFilter($filter);
    }
    
    /**
     * Gets top.
     *
     * @return integer.
     */
    public function getTop()
    {
        return $this->_query->getTop();
    }

    /**
     * Sets top.
     *
     * You can either use this individual function or use setQuery to set the whole
     * query object.
     * 
     * @param integer $top value.
     * 
     * @return none.
     */
    public function setTop($top)
    {
        $this->_query->setTop($top);
    }
    
    /**
     * Adds a field to select fields.
     * 
     * You can either use this individual function or use setQuery to set the whole
     * query object.
     * 
     * @param string $field The value of the field.
     * 
     * @return none.
     */
    public function addSelectField($field)
    {
        $this->_query->addSelectField($field);
    }
    
    /**
     * Gets selectFields.
     *
     * @return array.
     */
    public function getSelectFields()
    {
        return $this->_query->getSelectFields();
    }

    /**
     * Sets selectFields.
     *
     * You can either use this individual function or use setQuery to set the whole
     * query object.
     * 
     * @param array $selectFields value.
     * 
     * @return none.
     */
    public function setSelectFields($selectFields)
    {
        $this->_query->setSelectFields($selectFields);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Holds results of calling queryEntities API
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryEntitiesResult
{
    /**
     * @var Query
     */
    private $_nextRowKey;
    
    /**
     * @var string
     */
    private $_nextPartitionKey;
    
    /**
     * @var array
     */
    private $_entities;
    
    /**
     * Creates new QueryEntitiesResult instance.
     * 
     * @param array $headers  The HTTP response headers.
     * @param array $entities The entities.
     * 
     * @return QueryEntitiesResult
     */
    public static function create($headers, $entities)
    {
        $result  = new QueryEntitiesResult();
        $headers = array_change_key_case($headers);
        $nextPK  = Utilities::tryGetValue(
            $headers, Resources::X_MS_CONTINUATION_NEXTPARTITIONKEY
        );
        $nextRK  = Utilities::tryGetValue(
            $headers, Resources::X_MS_CONTINUATION_NEXTROWKEY
        );
        
        $result->setEntities($entities);
        $result->setNextPartitionKey($nextPK);
        $result->setNextRowKey($nextRK);
        
        return $result;
    }
    
    /**
     * Gets entities.
     * 
     * @return array
     */
    public function getEntities()
    {
        return $this->_entities;
    }
    
    /**
     * Sets entities.
     * 
     * @param array $entities The entities array.
     * 
     * @return none
     */
    public function setEntities($entities)
    {
        $this->_entities = $entities;
    }
    
    /**
     * Gets entity next partition key.
     *
     * @return string
     */
    public function getNextPartitionKey()
    {
        return $this->_nextPartitionKey;
    }

    /**
     * Sets entity next partition key.
     *
     * @param string $nextPartitionKey The entity next partition key value.
     *
     * @return none
     */
    public function setNextPartitionKey($nextPartitionKey)
    {
        $this->_nextPartitionKey = $nextPartitionKey;
    }
    
    /**
     * Gets entity next row key.
     *
     * @return string
     */
    public function getNextRowKey()
    {
        return $this->_nextRowKey;
    }

    /**
     * Sets entity next row key.
     *
     * @param string $nextRowKey The entity next row key value.
     *
     * @return none
     */
    public function setNextRowKey($nextRowKey)
    {
        $this->_nextRowKey = $nextRowKey;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;

/**
 * Optional parameters for queryTables wrapper.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryTablesOptions extends TableServiceOptions
{
    /**
     * @var string
     */
    private $_nextTableName;
    
    /**
     * @var Query
     */
    private $_query;
    
    /**
     * @var string
     */
    private $_prefix;
    
    /**
     * Constructs new QueryTablesOptions object.
     */
    public function __construct()
    {
        $this->_query = new Query();
    }
    
    /**
     * Gets nextTableName
     * 
     * @return string
     */
    public function getNextTableName()
    {
        return $this->_nextTableName;
    }
    
    /**
     * Sets nextTableName
     * 
     * @param string $nextTableName value
     * 
     * @return none
     */
    public function setNextTableName($nextTableName)
    {
        $this->_nextTableName = $nextTableName;
    }
    
    /**
     * Gets prefix
     * 
     * @return string
     */
    public function getPrefix()
    {
        return $this->_prefix;
    }
    
    /**
     * Sets prefix
     * 
     * @param string $prefix value
     * 
     * @return none
     */
    public function setPrefix($prefix)
    {
        $this->_prefix = $prefix;
    }
    
    /**
     * Gets top.
     *
     * @return integer.
     */
    public function getTop()
    {
        return $this->_query->getTop();
    }

    /**
     * Sets top.
     *
     * @param integer $top value.
     * 
     * @return none.
     */
    public function setTop($top)
    {
        $this->_query->setTop($top);
    }
    
    /**
     * Gets query.
     * 
     * @return Query
     */
    public function getQuery()
    {
        return $this->_query;
    }
    
    /**
     * Gets filter.
     *
     * @return Filters\Filter
     */
    public function getFilter()
    {
        return $this->_query->getFilter();
    }

    /**
     * Sets filter.
     *
     * @param Filters\Filter $filter value.
     * 
     * @return none.
     */
    public function setFilter($filter)
    {
        $this->_query->setFilter($filter);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * QueryTablesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryTablesResult
{
    /**
     * @var string 
     */
    private $_nextTableName;
    
    /**
     * @var array
     */
    private $_tables;
    
    /**
     * Creates new QueryTablesResult object
     * 
     * @param array $headers The HTTP response headers
     * @param array $entries The table entriess
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\QueryTablesResult 
     */
    public static function create($headers, $entries)
    {
        $result  = new QueryTablesResult();
        $headers = array_change_key_case($headers);
        
        $result->setNextTableName(
            Utilities::tryGetValue(
                $headers, Resources::X_MS_CONTINUATION_NEXTTABLENAME
            )
        );
        $result->setTables($entries);
        
        return $result;
    }
    
    /**
     * Gets nextTableName
     * 
     * @return string
     */
    public function getNextTableName()
    {
        return $this->_nextTableName;
    }
    
    /**
     * Sets nextTableName
     * 
     * @param string $nextTableName value
     * 
     * @return none
     */
    public function setNextTableName($nextTableName)
    {
        $this->_nextTableName = $nextTableName;
    }
    
    /**
     * Gets tables
     * 
     * @return array
     */
    public function getTables()
    {
        return $this->_tables;
    }
    
    /**
     * Sets tables
     * 
     * @param array $tables value
     * 
     * @return none
     */
    public function setTables($tables)
    {
        $this->_tables = $tables;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;

/**
 * Table service options.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class TableServiceOptions
{
    private $_timeout;

    /**
     * Gets timeout.
     *
     * @return string.
     */
    public function getTimeout()
    {
        return $this->_timeout;
    }

    /**
     * Sets timeout.
     *
     * @param string $timeout value.
     * 
     * @return none.
     */
    public function setTimeout($timeout)
    {
        $this->_timeout = $timeout;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Table\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Holds result of updateEntity and mergeEntity APIs
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class UpdateEntityResult
{
    /**
     * @var string
     */
    private $_etag;
    
    /**
     * Creates UpdateEntityResult from HTTP response headers.
     * 
     * @param array $headers The HTTP response headers.
     * 
     * @return \MicrosoftAzure\Storage\Table\Models\UpdateEntityResult 
     */
    public static function create($headers)
    {
        $result = new UpdateEntityResult();
        $clean  = array_change_key_case($headers);
        $result->setETag($clean[Resources::ETAG]);
        
        return $result;
    }
    
    /**
     * Gets entity etag.
     *
     * @return string
     */
    public function getETag()
    {
        return $this->_etag;
    }

    /**
     * Sets entity etag.
     *
     * @param string $etag The entity ETag.
     *
     * @return none
     */
    public function setETag($etag)
    {
        $this->_etag = $etag;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Table;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy;
use MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult;
use MicrosoftAzure\Storage\Table\Internal\ITable;
use MicrosoftAzure\Storage\Table\Models\TableServiceOptions;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Filters;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\Filters\PropertyNameFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\QueryStringFilter;
use MicrosoftAzure\Storage\Table\Models\GetTableResult;
use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions;
use MicrosoftAzure\Storage\Table\Models\QueryTablesResult;
use MicrosoftAzure\Storage\Table\Models\InsertEntityResult;
use MicrosoftAzure\Storage\Table\Models\UpdateEntityResult;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult;
use MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions;
use MicrosoftAzure\Storage\Table\Models\GetEntityResult;
use MicrosoftAzure\Storage\Table\Models\BatchOperationType;
use MicrosoftAzure\Storage\Table\Models\BatchOperationParameterName;
use MicrosoftAzure\Storage\Table\Models\BatchResult;
use MicrosoftAzure\Storage\Common\Internal\HttpFormatter;

/**
 * This class constructs HTTP requests and receive HTTP responses for table
 * service layer.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class TableRestProxy extends ServiceRestProxy implements ITable
{
    /**
     * @var Utilities\IAtomReaderWriter
     */
    private $_atomSerializer;

    /**
     *
     * @var Utilities\IMimeReaderWriter
     */
    private $_mimeSerializer;

    /**
     * Creates contexts for batch operations.
     *
     * @param array $operations The batch operations array.
     *
     * @return array
     *
     * @throws \InvalidArgumentException
     */
    private function _createOperationsContexts($operations)
    {
        $contexts = array();

        foreach ($operations as $operation) {
            $context = null;
            $type    = $operation->getType();

            switch ($type) {
            case BatchOperationType::INSERT_ENTITY_OPERATION:
            case BatchOperationType::UPDATE_ENTITY_OPERATION:
            case BatchOperationType::MERGE_ENTITY_OPERATION:
            case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
            case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
                $table   = $operation->getParameter(
                    BatchOperationParameterName::BP_TABLE
                );
                $entity  = $operation->getParameter(
                    BatchOperationParameterName::BP_ENTITY
                );
                $context = $this->_getOperationContext($table, $entity, $type);
                break;

            case BatchOperationType::DELETE_ENTITY_OPERATION:
                $table        = $operation->getParameter(
                    BatchOperationParameterName::BP_TABLE
                );
                $partitionKey = $operation->getParameter(
                    BatchOperationParameterName::BP_PARTITION_KEY
                );
                $rowKey       = $operation->getParameter(
                    BatchOperationParameterName::BP_ROW_KEY
                );
                $etag         = $operation->getParameter(
                    BatchOperationParameterName::BP_ETAG
                );
                $options      = new DeleteEntityOptions();
                $options->setETag($etag);
                $context = $this->_constructDeleteEntityContext(
                    $table, $partitionKey, $rowKey, $options
                );
                break;

            default:
                throw new \InvalidArgumentException();
            }

            $contexts[] = $context;
        }

        return $contexts;
    }

    /**
     * Creates operation context for the API.
     *
     * @param string        $table  The table name.
     * @param Models\Entity $entity The entity object.
     * @param string        $type   The API type.
     *
     * @return MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext
     *
     * @throws \InvalidArgumentException
     */
    private function _getOperationContext($table, $entity, $type)
    {
        switch ($type) {
        case BatchOperationType::INSERT_ENTITY_OPERATION:
        return $this->_constructInsertEntityContext($table, $entity, null);

        case BatchOperationType::UPDATE_ENTITY_OPERATION:
        return $this->_constructPutOrMergeEntityContext(
            $table,
            $entity,
            Resources::HTTP_PUT,
            true,
            null
        );

        case BatchOperationType::MERGE_ENTITY_OPERATION:
        return $this->_constructPutOrMergeEntityContext(
            $table,
            $entity,
            Resources::HTTP_MERGE,
            true,
            null
        );

        case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
        return $this->_constructPutOrMergeEntityContext(
            $table,
            $entity,
            Resources::HTTP_PUT,
            false,
            null
        );

        case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
        return $this->_constructPutOrMergeEntityContext(
            $table,
            $entity,
            Resources::HTTP_MERGE,
            false,
            null
        );

        default:
            throw new \InvalidArgumentException();
        }
    }

    /**
     * Creates MIME part body for batch API.
     *
     * @param array $operations The batch operations.
     * @param array $contexts   The contexts objects.
     *
     * @return array
     *
     * @throws \InvalidArgumentException
     */
    private function _createBatchRequestBody($operations, $contexts)
    {
        $mimeBodyParts = array();
        $contentId     = 1;
        $count         = count($operations);

        Validate::isTrue(
            count($operations) == count($contexts),
            Resources::INVALID_OC_COUNT_MSG
        );

        for ($i = 0; $i < $count; $i++) {
            $operation = $operations[$i];
            $context   = $contexts[$i];
            $type      = $operation->getType();

            switch ($type) {
            case BatchOperationType::INSERT_ENTITY_OPERATION:
            case BatchOperationType::UPDATE_ENTITY_OPERATION:
            case BatchOperationType::MERGE_ENTITY_OPERATION:
            case BatchOperationType::INSERT_REPLACE_ENTITY_OPERATION:
            case BatchOperationType::INSERT_MERGE_ENTITY_OPERATION:
                $contentType  = $context->getHeader(Resources::CONTENT_TYPE);
                $body         = $context->getBody();
                $contentType .= ';type=entry';
                $context->addOptionalHeader(Resources::CONTENT_TYPE, $contentType);
                // Use mb_strlen instead of strlen to get the length of the string
                // in bytes instead of the length in chars.
                $context->addOptionalHeader(
                    Resources::CONTENT_LENGTH,
                    strlen($body)
                );
                break;

            case BatchOperationType::DELETE_ENTITY_OPERATION:
                break;

            default:
                throw new \InvalidArgumentException();
            }

            $context->addOptionalHeader(Resources::CONTENT_ID, $contentId);
            $mimeBodyPart    = $context->__toString();
            $mimeBodyParts[] = $mimeBodyPart;
            $contentId++;
        }

        return $this->_mimeSerializer->encodeMimeMultipart($mimeBodyParts);
    }

    /**
     * Constructs HTTP call context for deleteEntity API.
     *
     * @param string                     $table        The name of the table.
     * @param string                     $partitionKey The entity partition key.
     * @param string                     $rowKey       The entity row key.
     * @param Models\DeleteEntityOptions $options      The optional parameters.
     *
     * @return HttpCallContext
     */
    private function _constructDeleteEntityContext($table, $partitionKey, $rowKey,
        $options
    ) {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');
        Validate::isTrue(!is_null($partitionKey), Resources::NULL_TABLE_KEY_MSG);
        Validate::isTrue(!is_null($rowKey), Resources::NULL_TABLE_KEY_MSG);

        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_NO_CONTENT;
        $path        = $this->_getEntityPath($table, $partitionKey, $rowKey);

        if (is_null($options)) {
            $options = new DeleteEntityOptions();
        }

        $etagObj = $options->getETag();
        $ETag    = !is_null($etagObj);
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::IF_MATCH,
            $ETag ? $etagObj : Resources::ASTERISK
        );

        $context = new HttpCallContext();
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setPath($path);
        $context->setQueryParameters($queryParams);
        $context->addStatusCode($statusCode);
        $context->setUri($this->getUri());
        $context->setBody('');

        return $context;
    }

    /**
     * Constructs HTTP call context for updateEntity, mergeEntity,
     * insertOrReplaceEntity and insertOrMergeEntity.
     *
     * @param string                     $table   The table name.
     * @param Models\Entity              $entity  The entity instance to use.
     * @param string                     $verb    The HTTP method.
     * @param boolean                    $useETag The flag to include etag or not.
     * @param Models\TableServiceOptions $options The optional parameters.
     *
     * @return HttpCallContext
     */
    private function _constructPutOrMergeEntityContext($table, $entity, $verb,
        $useETag, $options
    ) {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        Validate::isTrue($entity->isValid($msg), $msg);

        $method       = $verb;
        $headers      = array();
        $queryParams  = array();
        $statusCode   = Resources::STATUS_NO_CONTENT;
        $partitionKey = $entity->getPartitionKey();
        $rowKey       = $entity->getRowKey();
        $path         = $this->_getEntityPath($table, $partitionKey, $rowKey);
        $body         = $this->_atomSerializer->getEntity($entity);

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        if ($useETag) {
            $etag         = $entity->getETag();
            $ifMatchValue = is_null($etag) ? Resources::ASTERISK : $etag;

            $this->addOptionalHeader($headers, Resources::IF_MATCH, $ifMatchValue);
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::XML_ATOM_CONTENT_TYPE
        );

        $context = new HttpCallContext();
        $context->setBody($body);
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setPath($path);
        $context->setQueryParameters($queryParams);
        $context->addStatusCode($statusCode);
        $context->setUri($this->getUri());

        return $context;
    }

    /**
     * Constructs HTTP call context for insertEntity API.
     *
     * @param string                     $table   The name of the table.
     * @param Models\Entity              $entity  The table entity.
     * @param Models\TableServiceOptions $options The optional parameters.
     *
     * @return HttpCallContext
     */
    private function _constructInsertEntityContext($table, $entity, $options)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');
        Validate::notNullOrEmpty($entity, 'entity');
        Validate::isTrue($entity->isValid($msg), $msg);

        $method      = Resources::HTTP_POST;
        $context     = new HttpCallContext();
        $headers     = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_CREATED;
        $path        = $table;
        $body        = $this->_atomSerializer->getEntity($entity);

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::XML_ATOM_CONTENT_TYPE
        );

        $context->setBody($body);
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setPath($path);
        $context->setQueryParameters($queryParams);
        $context->addStatusCode($statusCode);
        $context->setUri($this->getUri());

        return $context;
    }

    /**
     * Constructs URI path for entity.
     *
     * @param string $table        The table name.
     * @param string $partitionKey The entity's partition key.
     * @param string $rowKey       The entity's row key.
     *
     * @return string
     */
    private function _getEntityPath($table, $partitionKey, $rowKey)
    {
        $encodedPK = $this->_encodeODataUriValue($partitionKey);
        $encodedRK = $this->_encodeODataUriValue($rowKey);

        return "$table(PartitionKey='$encodedPK',RowKey='$encodedRK')";
    }

    /**
     * Does actual work for update and merge entity APIs.
     *
     * @param string                     $table   The table name.
     * @param Models\Entity              $entity  The entity instance to use.
     * @param string                     $verb    The HTTP method.
     * @param boolean                    $useETag The flag to include etag or not.
     * @param Models\TableServiceOptions $options The optional parameters.
     *
     * @return Models\UpdateEntityResult
     */
    private function _putOrMergeEntityImpl($table, $entity, $verb, $useETag,
        $options
    ) {
        $context = $this->_constructPutOrMergeEntityContext(
            $table,
            $entity,
            $verb,
            $useETag,
            $options
        );

        $response = $this->sendContext($context);

        return UpdateEntityResult::create(HttpFormatter::formatHeaders($response->getHeaders()));
    }

    /**
     * Builds filter expression
     *
     * @param Filter $filter The filter object
     *
     * @return string
     */
    private function _buildFilterExpression($filter)
    {
        $e = Resources::EMPTY_STRING;
        $this->_buildFilterExpressionRec($filter, $e);

        return $e;
    }

    /**
     * Builds filter expression
     *
     * @param Filter $filter The filter object
     * @param string &$e     The filter expression
     *
     * @return string
     */
    private function _buildFilterExpressionRec($filter, &$e)
    {
        if (is_null($filter)) {
            return;
        }

        if ($filter instanceof PropertyNameFilter) {
            $e .= $filter->getPropertyName();
        } else if ($filter instanceof ConstantFilter) {
            $value = $filter->getValue();
            // If the value is null we just append null regardless of the edmType.
            if (is_null($value)) {
                $e .= 'null';
            } else {
                $type = $filter->getEdmType();
                $e   .= EdmType::serializeQueryValue($type, $value);
            }
        } else if ($filter instanceof UnaryFilter) {
            $e .= $filter->getOperator();
            $e .= '(';
            $this->_buildFilterExpressionRec($filter->getOperand(), $e);
            $e .= ')';
        } else if ($filter instanceof Filters\BinaryFilter) {
            $e .= '(';
            $this->_buildFilterExpressionRec($filter->getLeft(), $e);
            $e .= ' ';
            $e .= $filter->getOperator();
            $e .= ' ';
            $this->_buildFilterExpressionRec($filter->getRight(), $e);
            $e .= ')';
        } else if ($filter instanceof QueryStringFilter) {
            $e .= $filter->getQueryString();
        }

        return $e;
    }

    /**
     * Adds query object to the query parameter array
     *
     * @param array        $queryParam The URI query parameters
     * @param Models\Query $query      The query object
     *
     * @return array
     */
    private function _addOptionalQuery($queryParam, $query)
    {
        if (!is_null($query)) {
            $selectedFields = $query->getSelectFields();
            if (!empty($selectedFields)) {
                $final = $this->_encodeODataUriValues($selectedFields);

                $this->addOptionalQueryParam(
                    $queryParam,
                    Resources::QP_SELECT,
                    implode(',', $final)
                );
            }

            if (!is_null($query->getTop())) {
                $final = strval($this->_encodeODataUriValue($query->getTop()));

                $this->addOptionalQueryParam(
                    $queryParam,
                    Resources::QP_TOP,
                    $final
                );
            }

            if (!is_null($query->getFilter())) {
                $final = $this->_buildFilterExpression($query->getFilter());

                $this->addOptionalQueryParam(
                    $queryParam,
                    Resources::QP_FILTER,
                    $final
                );
            }
        }

        return $queryParam;
    }

    /**
     * Encodes OData URI values
     *
     * @param array $values The OData URL values
     *
     * @return array
     */
    private function _encodeODataUriValues($values)
    {
        $list = array();

        foreach ($values as $value) {
            $list[] = $this->_encodeODataUriValue($value);
        }

        return $list;
    }

    /**
     * Encodes OData URI value
     *
     * @param string $value The OData URL value
     *
     * @return string
     */
    private function _encodeODataUriValue($value)
    {
        // Replace each single quote (') with double single quotes ('') not doudle
        // quotes (")
        $value = str_replace('\'', '\'\'', $value);

        // Encode the special URL characters
        $value = rawurlencode($value);

        return $value;
    }

    /**
     * Initializes new TableRestProxy object.
     *
     * @param string            $uri            The storage account uri.
     * @param IAtomReaderWriter $atomSerializer The atom serializer.
     * @param IMimeReaderWriter $mimeSerializer The MIME serializer.
     * @param ISerializable     $dataSerializer The data serializer.
     * @param array             $options        Array of options to pass to the service
     */
    public function __construct($uri, $atomSerializer, $mimeSerializer,
        $dataSerializer, $options = []
    ) {
        parent::__construct(
            $uri,
            Resources::EMPTY_STRING,
            $dataSerializer,
            $options
        );
        $this->_atomSerializer = $atomSerializer;
        $this->_mimeSerializer = $mimeSerializer;
    }

    /**
     * Gets the properties of the Table service.
     *
     * @param Models\TableServiceOptions $options optional table service options.
     *
     * @return MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452238.aspx
     */
    public function getServiceProperties($options = null)
    {
        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $context = new HttpCallContext();
        $timeout = $options->getTimeout();
        $context->setMethod(Resources::HTTP_GET);
        $context->addOptionalQueryParameter(Resources::QP_REST_TYPE, 'service');
        $context->addOptionalQueryParameter(Resources::QP_COMP, 'properties');
        $context->addOptionalQueryParameter(Resources::QP_TIMEOUT, $timeout);
        $context->addStatusCode(Resources::STATUS_OK);

        $response = $this->sendContext($context);
        $parsed   = $this->dataSerializer->unserialize($response->getBody());

        return GetServicePropertiesResult::create($parsed);
    }

    /**
     * Sets the properties of the Table service.
     *
     * It's recommended to use getServiceProperties, alter the returned object and
     * then use setServiceProperties with this altered object.
     *
     * @param ServiceProperties          $serviceProperties new service properties
     * @param Models\TableServiceOptions $options           optional parameters
     *
     * @return none.
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452240.aspx
     */
    public function setServiceProperties($serviceProperties, $options = null)
    {
        Validate::isTrue(
            $serviceProperties instanceof ServiceProperties,
            Resources::INVALID_SVC_PROP_MSG
        );

        $method      = Resources::HTTP_PUT;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_ACCEPTED;
        $path        = Resources::EMPTY_STRING;
        $body        = Resources::EMPTY_STRING;

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_REST_TYPE,
            'service'
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_COMP,
            'properties'
        );

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::XML_ATOM_CONTENT_TYPE
        );
        $body = $serviceProperties->toXml($this->dataSerializer);

        $this->send(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            $statusCode,
            $body
        );
    }

    /**
     * Quries tables in the given storage account.
     *
     * @param Models\QueryTablesOptions|string|Models\Filter $options Could be
     * optional parameters, table prefix or filter to apply.
     *
     * @return Models\QueryTablesResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx
     */
    public function queryTables($options = null)
    {
        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_OK;
        $path        = 'Tables';

        if (is_null($options)) {
            $options = new QueryTablesOptions();
        } else if (is_string($options)) {
            $prefix  = $options;
            $options = new QueryTablesOptions();
            $options->setPrefix($prefix);
        } else if ($options instanceof Filter) {
            $filter  = $options;
            $options = new QueryTablesOptions();
            $options->setFilter($filter);
        }

        $query   = $options->getQuery();
        $next    = $options->getNextTableName();
        $prefix  = $options->getPrefix();
        $timeout = $options->getTimeout();

        if (!empty($prefix)) {
            // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> upperBound
            // is prefix + '{'
            $prefixFilter = Filter::applyAnd(
                Filter::applyGe(
                    Filter::applyPropertyName('TableName'),
                    Filter::applyConstant($prefix, EdmType::STRING)
                ),
                Filter::applyLe(
                    Filter::applyPropertyName('TableName'),
                    Filter::applyConstant($prefix . '{', EdmType::STRING)
                )
            );

            if (is_null($query)) {
                $query = new Models\Query();
            }

            if (is_null($query->getFilter())) {
                // use the prefix filter if the query filter is null
                $query->setFilter($prefixFilter);
            } else {
                // combine and use the prefix filter if the query filter exists
                $combinedFilter = Filter::applyAnd(
                    $query->getFilter(), $prefixFilter
                );
                $query->setFilter($combinedFilter);
            }
        }

        $queryParams = $this->_addOptionalQuery($queryParams, $query);

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NEXT_TABLE_NAME,
            $next
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $timeout
        );

        // One can specify the NextTableName option to get table entities starting
        // from the specified name. However, there appears to be an issue in the
        // Azure Table service where this does not engage on the server unless
        // $filter appears in the URL. The current behavior is to just ignore the
        // NextTableName options, which is not expected or easily detectable.
        if (   array_key_exists(Resources::QP_NEXT_TABLE_NAME, $queryParams)
            && !array_key_exists(Resources::QP_FILTER, $queryParams)
        ) {
            $queryParams[Resources::QP_FILTER] = Resources::EMPTY_STRING;
        }

        $response = $this->send(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            $statusCode
        );
        $tables   = $this->_atomSerializer->parseTableEntries($response->getBody());

        return QueryTablesResult::create(HttpFormatter::formatHeaders($response->getHeaders()), $tables);
    }

    /**
     * Creates new table in the storage account
     *
     * @param string                     $table   The name of the table.
     * @param Models\TableServiceOptions $options The optional parameters.
     *
     * @return none
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx
     */
    public function createTable($table, $options = null)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');

        $method      = Resources::HTTP_POST;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_CREATED;
        $path        = 'Tables';
        $body        = $this->_atomSerializer->getTable($table);

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::XML_ATOM_CONTENT_TYPE
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );

        $this->send(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            $statusCode,
            $body
        );
    }

    /**
     * Gets the table.
     *
     * @param string                     $table   The name of the table.
     * @param Models\TableServiceOptions $options The optional parameters.
     *
     * @return Models\GetTableResult
     */
    public function getTable($table, $options = null)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_OK;
        $path        = "Tables('$table')";

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::XML_ATOM_CONTENT_TYPE
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );

        $response = $this->send(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            $statusCode
        );

        return GetTableResult::create($response->getBody(), $this->_atomSerializer);
    }

    /**
     * Deletes the specified table and any data it contains.
     *
     * @param string                     $table   The name of the table.
     * @param Models\TableServiceOptions $options optional parameters
     *
     * @return none
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx
     */
    public function deleteTable($table, $options = null)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');

        $method      = Resources::HTTP_DELETE;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_NO_CONTENT;
        $path        = "Tables('$table')";

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );

        $this->send(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            $statusCode
        );
    }

    /**
     * Quries entities for the given table name
     *
     * @param string                                           $table   The name of
     * the table.
     * @param Models\QueryEntitiesOptions|string|Models\Filter $options Coule be
     * optional parameters, query string or filter to apply.
     *
     * @return Models\QueryEntitiesResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx
     */
    public function queryEntities($table, $options = null)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_OK;
        $path        = $table;

        if (is_null($options)) {
            $options = new QueryEntitiesOptions();
        } else if (is_string($options)) {
            $queryString = $options;
            $options     = new QueryEntitiesOptions();
            $options->setFilter(Filter::applyQueryString($queryString));
        } else if ($options instanceof Filter) {
            $filter  = $options;
            $options = new QueryEntitiesOptions();
            $options->setFilter($filter);
        }

        $queryParams = $this->_addOptionalQuery($queryParams, $options->getQuery());

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NEXT_PK,
            $options->getNextPartitionKey()
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_NEXT_RK,
            $options->getNextRowKey()
        );

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::XML_ATOM_CONTENT_TYPE
        );

        if (!is_null($options->getQuery())) {
            $dsHeader   = Resources::DATA_SERVICE_VERSION;
            $maxdsValue = Resources::MAX_DATA_SERVICE_VERSION_VALUE;
            $fields     = $options->getQuery()->getSelectFields();
            $hasSelect  = !empty($fields);
            if ($hasSelect) {
                $this->addOptionalHeader($headers, $dsHeader, $maxdsValue);
            }
        }

        $response = $this->send(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            $statusCode
        );

        $entities = $this->_atomSerializer->parseEntities($response->getBody());

        return QueryEntitiesResult::create(HttpFormatter::formatHeaders($response->getHeaders()), $entities);
    }

    /**
     * Inserts new entity to the table.
     *
     * @param string                     $table   name of the table.
     * @param Models\Entity              $entity  table entity.
     * @param Models\TableServiceOptions $options optional parameters.
     *
     * @return Models\InsertEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179433.aspx
     */
    public function insertEntity($table, $entity, $options = null)
    {
        $context = $this->_constructInsertEntityContext($table, $entity, $options);

        $response = $this->sendContext($context);
        $body     = $response->getBody();
        $headers  = HttpFormatter::formatHeaders($response->getHeaders());

        return InsertEntityResult::create($body, $headers, $this->_atomSerializer);
    }

    /**
     * Updates an existing entity or inserts a new entity if it does not exist in the
     * table.
     *
     * @param string                     $table   name of the table
     * @param Models\Entity              $entity  table entity
     * @param Models\TableServiceOptions $options optional parameters
     *
     * @return Models\UpdateEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452241.aspx
     */
    public function insertOrMergeEntity($table, $entity, $options = null)
    {
        return $this->_putOrMergeEntityImpl(
            $table,
            $entity,
            Resources::HTTP_MERGE,
            false,
            $options
        );
    }

    /**
     * Replaces an existing entity or inserts a new entity if it does not exist in
     * the table.
     *
     * @param string                     $table   name of the table
     * @param Models\Entity              $entity  table entity
     * @param Models\TableServiceOptions $options optional parameters
     *
     * @return Models\UpdateEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/hh452242.aspx
     */
    public function insertOrReplaceEntity($table, $entity, $options = null)
    {
        return $this->_putOrMergeEntityImpl(
            $table,
            $entity,
            Resources::HTTP_PUT,
            false,
            $options
        );
    }

    /**
     * Updates an existing entity in a table. The Update Entity operation replaces
     * the entire entity and can be used to remove properties.
     *
     * @param string                     $table   The table name.
     * @param Models\Entity              $entity  The table entity.
     * @param Models\TableServiceOptions $options The optional parameters.
     *
     * @return Models\UpdateEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179427.aspx
     */
    public function updateEntity($table, $entity, $options = null)
    {
        return $this->_putOrMergeEntityImpl(
            $table,
            $entity,
            Resources::HTTP_PUT,
            true,
            $options
        );
    }

    /**
     * Updates an existing entity by updating the entity's properties. This operation
     * does not replace the existing entity, as the updateEntity operation does.
     *
     * @param string                     $table   The table name.
     * @param Models\Entity              $entity  The table entity.
     * @param Models\TableServiceOptions $options The optional parameters.
     *
     * @return Models\UpdateEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179392.aspx
     */
    public function mergeEntity($table, $entity, $options = null)
    {
        return $this->_putOrMergeEntityImpl(
            $table,
            $entity,
            Resources::HTTP_MERGE,
            true,
            $options
        );
    }

    /**
     * Deletes an existing entity in a table.
     *
     * @param string                     $table        The name of the table.
     * @param string                     $partitionKey The entity partition key.
     * @param string                     $rowKey       The entity row key.
     * @param Models\DeleteEntityOptions $options      The optional parameters.
     *
     * @return none
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd135727.aspx
     */
    public function deleteEntity($table, $partitionKey, $rowKey, $options = null)
    {
        $context = $this->_constructDeleteEntityContext(
            $table,
            $partitionKey,
            $rowKey,
            $options
        );

        $this->sendContext($context);
    }

    /**
     * Gets table entity.
     *
     * @param string                     $table        The name of the table.
     * @param string                     $partitionKey The entity partition key.
     * @param string                     $rowKey       The entity row key.
     * @param Models\TableServiceOptions $options      The optional parameters.
     *
     * @return Models\GetEntityResult
     *
     * @see http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx
     */
    public function getEntity($table, $partitionKey, $rowKey, $options = null)
    {
        Validate::isString($table, 'table');
        Validate::notNullOrEmpty($table, 'table');
        Validate::isTrue(!is_null($partitionKey), Resources::NULL_TABLE_KEY_MSG);
        Validate::isTrue(!is_null($rowKey), Resources::NULL_TABLE_KEY_MSG);

        $method      = Resources::HTTP_GET;
        $headers     = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_OK;
        $path        = $this->_getEntityPath($table, $partitionKey, $rowKey);

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalHeader(
            $headers,
            Resources::CONTENT_TYPE,
            Resources::XML_ATOM_CONTENT_TYPE
        );
        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );

        $context = new HttpCallContext();
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setPath($path);
        $context->setQueryParameters($queryParams);
        $context->addStatusCode($statusCode);

        $response = $this->sendContext($context);
        $entity   = $this->_atomSerializer->parseEntity($response->getBody());
        $result   = new GetEntityResult();
        $result->setEntity($entity);

        return $result;
    }

    /**
     * Does batch of operations on the table service.
     *
     * @param Models\BatchOperations     $batchOperations The operations to apply.
     * @param Models\TableServiceOptions $options         The optional parameters.
     *
     * @return Models\BatchResult
     */
    public function batch($batchOperations, $options = null)
    {
        Validate::notNullOrEmpty($batchOperations, 'batchOperations');

        $method      = Resources::HTTP_POST;
        $operations  = $batchOperations->getOperations();
        $contexts    = $this->_createOperationsContexts($operations);
        $mime        = $this->_createBatchRequestBody($operations, $contexts);
        $body        = $mime['body'];
        $headers     = $mime['headers'];
        $postParams  = array();
        $queryParams = array();
        $statusCode  = Resources::STATUS_ACCEPTED;
        $path        = '$batch';

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->addOptionalQueryParam(
            $queryParams,
            Resources::QP_TIMEOUT,
            $options->getTimeout()
        );

        $response = $this->send(
            $method,
            $headers,
            $queryParams,
            $postParams,
            $path,
            $statusCode,
            $body
        );

        return BatchResult::create(
            $response->getBody(),
            $operations,
            $contexts,
            $this->_atomSerializer,
            $this->_mimeSerializer
        );
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Framework;
use MicrosoftAzure\Storage\Tests\Framework\ServiceRestProxyTestBase;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\ListContainersOptions;
use MicrosoftAzure\Storage\Common\ServiceException;

/**
 * TestBase class for each unit test class.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobServiceRestProxyTestBase extends ServiceRestProxyTestBase
{
    protected $_createdContainers;
    
    public function setUp()
    {
        parent::setUp();
        $blobRestProxy = $this->builder->createBlobService($this->connectionString);
        parent::setProxy($blobRestProxy);
        $this->_createdContainers = array();
    }

    public function createContainer($containerName, $options = null)
    {
        if (is_null($options)) {
            $options = new CreateContainerOptions();
            $options->setPublicAccess('container');
        }

        $this->restProxy->createContainer($containerName, $options);
        $this->_createdContainers[] = $containerName;
    }

    public function createContainerWithRetry($containerName, $options = null, $retryCount = 6)
    {
        // Containers cannot be recreated within a minute of them being
        // deleted; the service will give response of 409:Conflict.
        // So, if get that error, wait a bit then retry.

        $ok = false;
        $counter = 0;
        do {
            try {
                $this->createContainer($containerName, $options);
                $ok = true;
            } catch (ServiceException $e) {
                if ($e->getCode() != TestResources::STATUS_CONFLICT ||
                        $counter > $retryCount) {
                    throw $e;
                }
                sleep(10);
                $counter++;
            }
        } while (!$ok);
    }

    public function createContainers($containerList, $containerPrefix = null)
    {
        $containers = $this->listContainers($containerPrefix);
        foreach($containerList as $container) {
            if (array_search($container, $containers) === FALSE) {
                $this->createContainer($container);
            } else {
                $listResults = $this->restProxy->listBlobs($container);
                $blobs = $listResults->getBlobs();
                foreach($blobs as $blob)  {
                    try {
                        $this->restProxy->deleteBlob($container, $blob->getName());
                    } catch (\Exception $e) {
                        // Ignore exception and continue.
                        error_log($e->getMessage());
                    }
                }
            }
        }
    }

    public function deleteContainer($containerName)
    {
        $this->restProxy->deleteContainer($containerName);
    }

    public function deleteContainers($containerList, $containerPrefix = null)
    {
        $containers = $this->listContainers($containerPrefix);
        foreach($containerList as $container)  {
            if (!(array_search($container, $containers) === FALSE)) {
                $this->deleteContainer($container);
            }
        }
    }

    public function listContainers($containerPrefix = null)
    {
        $result = array();
        $opts = new ListContainersOptions();
        if (!is_null($containerPrefix)) {
            $opts->setPrefix($containerPrefix);
        }

        $list = $this->restProxy->listContainers($opts);
        foreach($list->getContainers() as $item)  {
            array_push($result, $item->getName());
        }

        return $result;
    }

    protected function tearDown()
    {
        parent::tearDown();

        foreach ($this->_createdContainers as $value) {
            try {
                $this->deleteContainer($value);
            } catch (\Exception $e) {
                // Ignore exception and continue, will assume that this container doesn't exist in the sotrage account
                error_log($e->getMessage());
            }
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Framework;
use MicrosoftAzure\Storage\Tests\Framework\ServiceRestProxyTestBase;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;

/**
 * TestBase class for each unit test class.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueueServiceRestProxyTestBase extends ServiceRestProxyTestBase
{
    private $_createdQueues;
    
    public function setUp()
    {
        parent::setUp();
        $queueRestProxy = $this->builder->createQueueService($this->connectionString);
        parent::setProxy($queueRestProxy);
        $this->_createdQueues = array();
    }
    
    public function createQueue($queueName, $options = null)
    {
        $this->restProxy->createQueue($queueName, $options);
        $this->_createdQueues[] = $queueName;
    }
    
    public function deleteQueue($queueName, $options = null)
    {
        $this->restProxy->deleteQueue($queueName, $options);
    }
    
    public function safeDeleteQueue($queueName)
    {
        try
        {
            $this->deleteQueue($queueName);
        }
        catch (\Exception $e)
        {
            // Ignore exception and continue, will assume that this queue doesn't exist in the sotrage account
            error_log($e->getMessage());
        }
    }
    
    protected function tearDown()
    {
        parent::tearDown();
        
        foreach ($this->_createdQueues as $value) {
            $this->safeDeleteQueue($value);
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Tests\Framework;
use MicrosoftAzure\Storage\Common\Internal\Logger;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;
use MicrosoftAzure\Storage\Common\ServicesBuilder;

/**
 * Testbase for all REST proxy tests.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class RestProxyTestBase extends \PHPUnit_Framework_TestCase
{
    protected $restProxy;
    protected $xmlSerializer;
    protected $builder;
    
    protected function getTestName()
    {
        return sprintf('onesdkphp%04x', mt_rand(0, 65535));
    }
    
    public static function assertHandler($file, $line, $code)
    {
        echo "Assertion Failed:\n
            File '$file'\n
            Line '$line'\n
            Code '$code'\n";
    }
    
    public function __construct()
    {
        $this->xmlSerializer = new XmlSerializer();
        $this->builder = new ServicesBuilder();
        Logger::setLogFile('C:\log.txt');
        
        // Enable PHP asserts
        assert_options(ASSERT_ACTIVE, 1);
        assert_options(ASSERT_WARNING, 0);
        assert_options(ASSERT_QUIET_EVAL, 1);
        assert_options(ASSERT_CALLBACK, 'MicrosoftAzure\Storage\Tests\Framework\RestProxyTestBase::assertHandler');
    }
    
    public function setProxy($serviceRestProxy)
    {
        $this->restProxy = $serviceRestProxy;
    }
    
    protected function onNotSuccessfulTest(\Exception $e)
    {
        parent::onNotSuccessfulTest($e);
        
        $this->tearDown();
        throw $e;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Framework;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * TestBase class for Storage Services test classes.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServiceRestProxyTestBase extends RestProxyTestBase
{
    protected $propertiesChanged;
    protected $defaultProperties;
    protected $connectionString;

    const NOT_SUPPORTED                     = 'The storage emulator doesn\'t support this API';
    const TAKE_TOO_LONG                     = 'This test takes long time, skip.';
    const SKIPPED_AFTER_SEVERAL_ATTEMPTS    = 'Test skipped after several fails.';

    protected function skipIfEmulated()
    {
        if ($this->isEmulated()) {
            $this->markTestSkipped(self::NOT_SUPPORTED);
        }
    }

    protected function isEmulated()
    {
        return (strpos($this->connectionString, Resources::USE_DEVELOPMENT_STORAGE_NAME) !== false);
    }

    public function __construct()
    {
        parent::__construct();
        $this->connectionString = TestResources::getWindowsAzureStorageServicesConnectionString();
    }

    public function setUp()
    {
        parent::setUp();
        $this->_createDefaultProperties();
    }

    private function _createDefaultProperties()
    {
        $this->propertiesChanged = false;
        $propertiesArray = array();
        $propertiesArray['Logging']['Version'] = '1.0';
        $propertiesArray['Logging']['Delete'] = 'false';
        $propertiesArray['Logging']['Read'] = 'false';
        $propertiesArray['Logging']['Write'] = 'false';
        $propertiesArray['Logging']['RetentionPolicy']['Enabled'] = 'false';
        $propertiesArray['HourMetrics']['Version'] = '1.0';
        $propertiesArray['HourMetrics']['Enabled'] = 'false';
        $propertiesArray['HourMetrics']['IncludeAPIs'] = 'false';
        $propertiesArray['HourMetrics']['RetentionPolicy']['Enabled'] = 'false';
        $this->defaultProperties = ServiceProperties::create($propertiesArray);
    }

    public function setServiceProperties($properties, $options = null)
    {
        $this->restProxy->setServiceProperties($properties, $options);
        $this->propertiesChanged = true;
    }

    protected function tearDown()
    {
        parent::tearDown();

        if ($this->propertiesChanged) {
            $this->restProxy->setServiceProperties($this->defaultProperties);
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Framework;
use MicrosoftAzure\Storage\Tests\Framework\ServiceRestProxyTestBase;

/**
 * TestBase class for each unit test class.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class TableServiceRestProxyTestBase extends ServiceRestProxyTestBase
{
    protected $_createdTables;
    
    public function setUp()
    {
        parent::setUp();
        $tableRestProxy = $this->builder->createTableService($this->connectionString);
        parent::setProxy($tableRestProxy);
        $this->_createdTables = array();
    }

    public function createTable($tableName, $options = null)
    {
        $this->restProxy->createTable($tableName, $options);
        $this->_createdTables[] = $tableName;
    }
    
    public function deleteTable($tableName)
    {
        $this->restProxy->deleteTable($tableName);
    }
    
    public function safeDeleteTable($tableName)
    {
        try
        {
            $this->deleteTable($tableName);
        }
        catch (\Exception $e)
        {
            // Ignore exception and continue, will assume that this table doesn't exist in the sotrage account
            error_log($e->getMessage());
        }
    }

    protected function tearDown()
    {
        parent::tearDown();
        
        foreach ($this->_createdTables as $value) {
            $this->safeDeleteTable($value);
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Framework;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Resources for testing framework.
 *
 * @package    MicrosoftAzure\Storage\Tests\Framework
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class TestResources
{
    const QUEUE1_NAME   = 'Queue1';
    const QUEUE2_NAME   = 'Queue2';
    const QUEUE3_NAME   = 'Queue3';
    const KEY1          = 'key1';
    const KEY2          = 'key2';
    const KEY3          = 'key3';
    const KEY4          = 'AhlzsbLRkjfwObuqff3xrhB2yWJNh1EMptmcmxFJ6fvPTVX3PZXwrG2YtYWf5DPMVgNsteKStM5iBLlknYFVoA==';
    const VALUE1        = 'value1';
    const VALUE2        = 'value2';
    const VALUE3        = 'value3';
    const ACCOUNT_NAME  = 'myaccount';
    const QUEUE_URI     = '.queue.core.windows.net';
    const URI1          = "http://myaccount.queue.core.windows.net/myqueue";
    const URI2          = "http://myaccount.queue.core.windows.net/?comp=list";
    const DATE1         = 'Sat, 18 Feb 2012 16:25:21 GMT';
    const DATE2         = 'Mon, 20 Feb 2012 17:12:31 GMT';
    const VALID_URL     = 'http://www.example.com';
    const HEADER1       = 'testheader1';
    const HEADER2       = 'testheader2';
    const HEADER1_VALUE = 'HeaderValue1';
    const HEADER2_VALUE = 'HeaderValue2';

    // Media services
    const MEDIA_SERVICES_ASSET_NAME             = 'TestAsset';
    const MEDIA_SERVICES_OUTPUT_ASSET_NAME      = 'TestOutputAsset';
    const MEDIA_SERVICES_ACCESS_POLICY_NAME     = 'TestAccessPolicy';
    const MEDIA_SERVICES_LOCATOR_NAME           = 'TestLocator';
    const MEDIA_SERVICES_JOB_NAME               = 'TestJob';
    const MEDIA_SERVICES_JOB_ID_PREFIX          = 'nb:jid:UUID:';
    const MEDIA_SERVICES_JOB_TEMPLATE_NAME      = 'TestJobTemplate';
    const MEDIA_SERVICES_JOB_TEMPLATE_ID_PREFIX = 'nb:jtid:UUID:';
    const MEDIA_SERVICES_TASK_COFIGURATION      = 'H.264 HD 720p VBR';
    const MEDIA_SERVICES_PROCESSOR_NAME         = 'Windows Azure Media Encoder';
    const MEDIA_SERVICES_DECODE_PROCESSOR_NAME  = 'Storage Decryption';
    const MEDIA_SERVICES_PROCESSOR_ID_PREFIX    = 'nb:mpid:UUID:';
    const MEDIA_SERVICES_DUMMY_FILE_NAME        = 'simple.avi';
    const MEDIA_SERVICES_DUMMY_FILE_CONTENT     = 'test file content';
    const MEDIA_SERVICES_DUMMY_FILE_NAME_1      = 'other.avi';
    const MEDIA_SERVICES_DUMMY_FILE_CONTENT_1   = 'other file content';
    const MEDIA_SERVICES_ISM_FILE_NAME          = 'small.ism';
    const MEDIA_SERVICES_ISMC_FILE_NAME         = 'small.ismc';
    const MEDIA_SERVICES_STREAM_APPEND          = 'Manifest';
    const MEDIA_SERVICES_INGEST_MANIFEST        = 'TestIngestManifest';
    const MEDIA_SERVICES_INGEST_MANIFEST_ASSET  = 'TestIngestManifestAsset';
    const MEDIA_SERVICES_CONTENT_KEY_AUTHORIZATION_POLICY_NAME     = 'TestContentKeyAuthorizationPolicy';
    const MEDIA_SERVICES_CONTENT_KEY_AUTHORIZATION_OPTIONS_NAME    = 'TestContentKeyAuthorizationPolicyOption';
    const MEDIA_SERVICES_CONTENT_KEY_AUTHORIZATION_POLICY_RESTRICTION_NAME = 'TestContentKeyAuthorizationPolicyRestriction';
    const MEDIA_SERVICES_ASSET_DELIVERY_POLICY_NAME = 'AssetDeliveryPolicyName';

    // See https://tools.ietf.org/html/rfc2616
    const STATUS_NOT_MODIFIED          = 304;
    const STATUS_BAD_REQUEST           = 400;
    const STATUS_UNAUTHORIZED          = 401;
    const STATUS_FORBIDDEN             = 403;
    const STATUS_NOT_FOUND             = 404;
    const STATUS_CONFLICT              = 409;
    const STATUS_PRECONDITION_FAILED   = 412;
    const STATUS_INTERNAL_SERVER_ERROR = 500;

    public static function getWindowsAzureStorageServicesConnectionString()
    {
        $connectionString = getenv('AZURE_STORAGE_CONNECTION_STRING');

        if (empty($connectionString)) {
            throw new \Exception('AZURE_STORAGE_CONNECTION_STRING envionment variable is missing');
        }

        return $connectionString;
    }

    public static function getEmulatorStorageServicesConnectionString()
    {
        $developmentStorageConnectionString = 'UseDevelopmentStorage=true';

        return $developmentStorageConnectionString;
    }

    public static function getServiceManagementConnectionString()
    {
        $connectionString = getenv('AZURE_SERVICE_MANAGEMENT_CONNECTION_STRING');

        if (empty($connectionString)) {
            throw new \Exception('AZURE_SERVICE_MANAGEMENT_CONNECTION_STRING envionment variable is missing');
        }

        return $connectionString;
    }

    public static function getServiceBusConnectionString()
    {
        $connectionString = getenv('AZURE_SERVICE_BUS_CONNECTION_STRING');

        if (empty($connectionString)) {
            throw new \Exception('AZURE_SERVICE_BUS_CONNECTION_STRING enviroment variable is missing.');
        }

        return $connectionString;
    }

    public static function simplePackageUrl()
    {
        $name = getenv('SERVICE_MANAGEMENT_SIMPLE_PACKAGE_URL');

        if (empty($name)) {
            throw new \Exception('SERVICE_MANAGEMENT_SIMPLE_PACKAGE_URL envionment variable is missing');
        }

        return $name;
    }

    public static function simplePackageConfiguration()
    {
        $name = getenv('SERVICE_MANAGEMENT_SIMPLE_PACKAGE_CONFIGURATION');

        if (empty($name)) {
            throw new \Exception('SERVICE_MANAGEMENT_SIMPLE_PACKAGE_CONFIGURATION envionment variable is missing');
        }

        return $name;
    }

    public static function complexPackageUrl()
    {
        $name = getenv('SERVICE_MANAGEMENT_COMPLEX_PACKAGE_URL');

        if (empty($name)) {
            throw new \Exception('SERVICE_MANAGEMENT_COMPLEX_PACKAGE_URL envionment variable is missing');
        }

        return $name;
    }

    public static function complexPackageConfiguration()
    {
        $name = getenv('SERVICE_MANAGEMENT_COMPLEX_PACKAGE_CONFIGURATION');

        if (empty($name)) {
            throw new \Exception('SERVICE_MANAGEMENT_COMPLEX_PACKAGE_CONFIGURATION envionment variable is missing');
        }

        return $name;
    }

    public static function getMediaServicesConnectionParameters()
    {
        return array(
            'accountName'       => self::getEnvironmentVariable('AZURE_MEDIA_SERVICES_ACCOUNT_NAME'),
            'accessKey'         => self::getEnvironmentVariable('AZURE_MEDIA_SERVICES_ACCESS_KEY'),
            'endpointUri'       => self::getEnvironmentVariable('AZURE_MEDIA_SERVICES_ENDPOINT_URI', false),
            'oauthEndopointUri' => self::getEnvironmentVariable('AZURE_MEDIA_SERVICES_OAUTH_ENDPOINT_URI', false),
        );
    }

    private static function getEnvironmentVariable($name, $required = true)
    {
        $value = getenv($name);

        if (empty($value) && $required) {
            throw new \Exception("{$name} enviroment variable is missing.");
        }

        return $value;
    }

    public static function getServicePropertiesSample()
    {
        $sample = array();
        $sample['Logging']['Version'] = '1.0';
        $sample['Logging']['Delete'] = 'true';
        $sample['Logging']['Read'] = 'false';
        $sample['Logging']['Write'] = 'true';
        $sample['Logging']['RetentionPolicy']['Enabled'] = 'true';
        $sample['Logging']['RetentionPolicy']['Days'] = '20';
        $sample['HourMetrics']['Version'] = '1.0';
        $sample['HourMetrics']['Enabled'] = 'true';
        $sample['HourMetrics']['IncludeAPIs'] = 'false';
        $sample['HourMetrics']['RetentionPolicy']['Enabled'] = 'true';
        $sample['HourMetrics']['RetentionPolicy']['Days'] = '20';

        return $sample;
    }

    public static function setServicePropertiesSample()
    {
        $sample = array();
        $sample['Logging']['Version'] = '1.0';
        $sample['Logging']['Delete'] = 'true';
        $sample['Logging']['Read'] = 'false';
        $sample['Logging']['Write'] = 'true';
        $sample['Logging']['RetentionPolicy']['Enabled'] = 'true';
        $sample['Logging']['RetentionPolicy']['Days'] = '10';
        $sample['HourMetrics']['Version'] = '1.0';
        $sample['HourMetrics']['Enabled'] = 'true';
        $sample['HourMetrics']['IncludeAPIs'] = 'false';
        $sample['HourMetrics']['RetentionPolicy']['Enabled'] = 'true';
        $sample['HourMetrics']['RetentionPolicy']['Days'] = '10';

        return $sample;
    }

    public static function listMessagesSample()
    {
        $sample = array();
        $sample['QueueMessage']['MessageId']       = '5974b586-0df3-4e2d-ad0c-18e3892bfca2';
        $sample['QueueMessage']['InsertionTime']   = 'Fri, 09 Oct 2009 21:04:30 GMT';
        $sample['QueueMessage']['ExpirationTime']  = 'Fri, 16 Oct 2009 21:04:30 GMT';
        $sample['QueueMessage']['PopReceipt']      = 'YzQ4Yzg1MDItYTc0Ny00OWNjLTkxYTUtZGM0MDFiZDAwYzEw';
        $sample['QueueMessage']['TimeNextVisible'] = 'Fri, 09 Oct 2009 23:29:20 GMT';
        $sample['QueueMessage']['DequeueCount']    = '1';
        $sample['QueueMessage']['MessageText']     = 'PHRlc3Q+dGhpcyBpcyBhIHRlc3QgbWVzc2FnZTwvdGVzdD4=';

        return $sample;
    }

    public static function listMessagesMultipleMessagesSample()
    {
        $sample = array();
        $sample['QueueMessage'][0]['MessageId']       = '5974b586-0df3-4e2d-ad0c-18e3892bfca2';
        $sample['QueueMessage'][0]['InsertionTime']   = 'Fri, 09 Oct 2009 21:04:30 GMT';
        $sample['QueueMessage'][0]['ExpirationTime']  = 'Fri, 16 Oct 2009 21:04:30 GMT';
        $sample['QueueMessage'][0]['PopReceipt']      = 'YzQ4Yzg1MDItYTc0Ny00OWNjLTkxYTUtZGM0MDFiZDAwYzEw';
        $sample['QueueMessage'][0]['TimeNextVisible'] = 'Fri, 09 Oct 2009 23:29:20 GMT';
        $sample['QueueMessage'][0]['DequeueCount']    = '1';
        $sample['QueueMessage'][0]['MessageText']     = 'PHRlc3Q+dGhpcyBpcyBhIHRlc3QgbWVzc2FnZTwvdGVzdD4=';

        $sample['QueueMessage'][1]['MessageId']       = '1234c20-0df3-4e2d-ad0c-18e3892bfca2';
        $sample['QueueMessage'][1]['InsertionTime']   = 'Sat, 10 Feb 2010 21:04:30 GMT';
        $sample['QueueMessage'][1]['ExpirationTime']  = 'Sat, 05 Jun 2010 21:04:30 GMT';
        $sample['QueueMessage'][1]['PopReceipt']      = 'QzW4Szf1MDItYTc0Ny00OWNjLTkxYTUtZGM0MDFiZDAwYzEw';
        $sample['QueueMessage'][1]['TimeNextVisible'] = 'Sun, 09 Oct 2009 23:29:20 GMT';
        $sample['QueueMessage'][1]['DequeueCount']    = '4';
        $sample['QueueMessage'][1]['MessageText']     = 'QWEFGlsc3Q+dGhpcyBpcyBhIHRlc3QgbWVzc2FnZTwvdGVzdD4=';

        return $sample;
    }

    public static function listQueuesEmpty()
    {
        $sample = array();
        $sample['Queues'] = '';
        $sample['NextMarker'] = '';

        return $sample;
    }

    public static function listQueuesOneEntry()
    {
        $sample = array();
        $sample['@attributes']['ServiceEndpoint'] = 'http://myaccount.blob.core.windows.net/';
        $sample['Marker'] = '/account/listqueueswithnextmarker3';
        $sample['MaxResults'] = '2';
        $sample['Queues'] = array('Queue' => array('Name' => 'myqueue'));
        $sample['NextMarker'] = '';

        return $sample;
    }

    public static function listQueuesMultipleEntries()
    {
        $sample = array();
        $sample['@attributes']['ServiceEndpoint'] = 'http://myaccount.blob.core.windows.net/';
        $sample['MaxResults'] = '2';
        $sample['Queues'] = array ('Queue' => array(
          0 => array('Name' => 'myqueue1'),
          1 => array('Name' => 'myqueue2')
        ));
        $sample['NextMarker'] = '/account/myqueue3';

        return $sample;
    }

    public static function listContainersEmpty()
    {
        $sample = array();
        $sample['Containers'] = '';
        $sample['NextMarker'] = '';

        return $sample;
    }

    public static function listContainersOneEntry()
    {
        $sample = array();
        $sample['@attributes']['ServiceEndpoint'] = 'http://myaccount.blob.core.windows.net/';
        $sample['Marker'] = '/account/listqueueswithnextmarker3';
        $sample['MaxResults'] = '2';
        $sample['Containers'] = array('Container' => array(
            'Name' => 'audio',
            'Properties' => array(
                'Last-Modified' => 'Wed, 12 Aug 2009 20:39:39 GMT',
                'Etag' => '0x8CACB9BD7C6B1B2'
            )
            ));
        $sample['NextMarker'] = '';

        return $sample;
    }

    public static function listContainersMultipleEntries()
    {
        $sample = array();
        $sample['@attributes']['ServiceEndpoint'] = 'http://myaccount.blob.core.windows.net/';
        $sample['MaxResults'] = '3';
        $sample['Containers'] = array ('Container' => array(
          0 => array(
            'Name' => 'audio',
            'Properties' => array(
                'Last-Modified' => 'Wed, 12 Aug 2009 20:39:39 GMT',
                'Etag' => '0x8CACB9BD7C6B1B2'
            )
            ),
          1 => array(
            'Name' => 'images',
            'Properties' => array(
                'Last-Modified' => 'Wed, 12 Aug 2009 20:39:39 GMT',
                'Etag' => '0x8CACB9BD7C1EEEC'
            )
            )
        ));
        $sample['NextMarker'] = 'video';

        return $sample;
    }

    public static function getContainerAclOneEntrySample()
    {
        $sample = array();
        $sample['SignedIdentifiers'] = array('SignedIdentifier' => array (
            'Id' => 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=',
            'AccessPolicy' => array(
                'Start' => '2009-09-28T08%3A49%3A37.0000000Z',
                'Expiry' => '2009-09-29T08%3A49%3A37.0000000Z',
                'Permission' => 'rwd')
            ));

        return $sample;
    }

    public static function getContainerAclMultipleEntriesSample()
    {
        $sample = array();
        $sample['SignedIdentifiers'] = array( 'SignedIdentifier' => array (
            0 => array ('Id' => 'HYQzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=',
            'AccessPolicy' => array(
                'Start' => '2010-09-28T08%3A49%3A37.0000000Z',
                'Expiry' => '2010-09-29T08%3A49%3A37.0000000Z',
                'Permission' => 'wd')),
            1 => array ('Id' => 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=',
            'AccessPolicy' => array(
                'Start' => '2009-09-28T08%3A49%3A37.0000000Z',
                'Expiry' => '2009-09-29T08%3A49%3A37.0000000Z',
                'Permission' => 'rwd'))
            ));

        return $sample;
    }

    public static function listBlobsEmpty()
    {
        $sample = array();
        $sample['Blobs'] = '';
        $sample['NextMarker'] = '';

        return $sample;
    }

    public static function listBlobsOneEntry()
    {
        $sample = array();
        $sample['@attributes']['ServiceEndpoint'] = 'http://myaccount.blob.core.windows.net/';
        $sample['Marker'] = '/account/listblobswithnextmarker3';
        $sample['MaxResults'] = '2';
        $sample['Delimiter'] = 'mydelimiter';
        $sample['Prefix'] = 'myprefix';
        $sample['Blobs'] = array(
            'BlobPrefix' => array('Name' => 'myblobprefix'),
            'Blob' => array(
                'Name' => 'myblob',
                'Snapshot' => '10-12-2011',
                'Metadata' => array('Name1' => 'Value1', 'Name2' => 'Value2'),
                'Properties' => array(
                    'Last-Modified' => 'Sat, 04 Sep 2011 12:43:08 GMT',
                    'Etag' => '0x8CAFB82EFF70C46',
                    'Content-Length' => '10',
                    'Content-Type' => 'type',
                    'Content-Encoding' => 'encoding',
                    'Content-Language' => 'language',
                    'Content-MD5' => 'md5',
                    'Cache-Control' => 'cachecontrol',
                    'x-ms-blob-sequence-number' => '0',
                    'BlobType' => 'BlockBlob',
                    'LeaseStatus' => 'locked'
                )
            )
        );

        $sample['NextMarker'] = '';

        return $sample;
    }

    public static function listBlobsMultipleEntries()
    {
        $sample = array();
        $sample['@attributes']['ServiceEndpoint'] = 'http://myaccount.blob.core.windows.net/';
        $sample['Marker'] = '/account/listblobswithnextmarker3';
        $sample['MaxResults'] = '2';
        $sample['Blobs'] = array(
            'BlobPrefix' => array(
                0 => array('Name' => 'myblobprefix'),
                1 => array('Name' => 'myblobprefix2')),
            'Blob' => array( 0 => array(
                'Name' => 'myblob',
                'Snapshot' => '10-12-2011',
                'Metadata' => array('Name1' => 'Value1', 'Name2' => 'Value2'),
                'Properties' => array(
                    'Last-Modified' => 'Sat, 04 Sep 2011 12:43:08 GMT',
                    'Etag' => '0x8CAFB82EFF70C46',
                    'Content-Length' => '10',
                    'Content-Type' => 'type',
                    'Content-Encoding' => 'encoding',
                    'Content-Language' => 'language',
                    'Content-MD5' => 'md5',
                    'Cache-Control' => 'cachecontrol',
                    'x-ms-blob-sequence-number' => '0',
                    'BlobType' => 'BlockBlob',
                    'LeaseStatus' => 'locked'
                )
            ),

            1 => array(
                'Name' => 'myblob2',
                'Snapshot' => '10-12-2011',
                'Metadata' => array('Name1' => 'Value1', 'Name2' => 'Value2'),
                'Properties' => array(
                    'Last-Modified' => 'Sun, 26 Feb 2010 12:43:08 GMT',
                    'Etag' => '0x7CQWER2EFF70C46',
                    'Content-Length' => '20',
                    'Content-Type' => 'type2',
                    'Content-Encoding' => 'encoding2',
                    'Content-Language' => 'language2',
                    'Content-MD5' => 'md52',
                    'Cache-Control' => 'cachecontrol2',
                    'x-ms-blob-sequence-number' => '1',
                    'BlobType' => 'PageBlob',
                    'LeaseStatus' => 'unlocked'
                )
            )));

        $sample['NextMarker'] = 'value';

        return $sample;
    }

    public static function getTestEntity($partitionKey, $rowKey)
    {
        $entity = new Entity();
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey($rowKey);
        $entity->addProperty('CustomerId', EdmType::INT32, 890);
        $entity->addProperty('CustomerName', null, 'John');
        $entity->addProperty('IsNew', EdmType::BOOLEAN, true);
        $entity->addProperty('JoinDate', EdmType::DATETIME, Utilities::convertToDateTime('2012-01-26T18:26:19.0000473Z'));

        return $entity;
    }
    
    public static function getExpectedTestEntity($partitionKey, $rowKey)
    {
        $entity = new Entity();
        $entity->addProperty('PartitionKey', EdmType::STRING, $partitionKey);
        $entity->addProperty('RowKey', EdmType::STRING, $rowKey);
        $entity->addProperty('CustomerId', EdmType::INT32, 890);
        $entity->addProperty('CustomerName', EdmType::STRING, 'John');
        $entity->addProperty('IsNew', EdmType::BOOLEAN, true);
        $entity->addProperty('JoinDate', EdmType::DATETIME, Utilities::convertToDateTime('2012-01-26T18:26:19.0000473Z'));
    
        return $entity;
    }

    public static function getSimpleJson()
    {
        $data['dataArray'] = array('test1','test2','test3');
        $data['jsonArray'] = '["test1","test2","test3"]';

        $data['dataObject'] = array('k1' => 'test1', 'k2' => 'test2', 'k3' => 'test3');
        $data['jsonObject'] = '{"k1":"test1","k2":"test2","k3":"test3"}';

        return $data;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Tests\Framework;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use org\bovigo\vfs\vfsStreamWrapper;

/**
 * Represents virtual file system for testing purpose.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Framework
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class VirtualFileSystem
{
    public static function newFile($contents, $fileName = null, $root = null)
    {
        $root = is_null($root) ? 'root' : $root;
        $fileName = is_null($fileName) ? 'test.txt' : $fileName;

        vfsStreamWrapper::register();
        vfsStreamWrapper::setRoot(new vfsStreamDirectory($root));
        
        $file = vfsStream::newFile($fileName);
        $file->setContent($contents);
        
        vfsStreamWrapper::getRoot()->addChild($file);
        $virtualPath = vfsStream::url($root . '/' . $fileName);
        
        return $virtualPath;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Blob;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions;
use MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\DeleteContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Blob\Models\ListContainersOptions;
use MicrosoftAzure\Storage\Blob\Models\PublicAccessType;
use MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataOptions;
use MicrosoftAzure\Storage\Blob\Models\SetContainerMetadataOptions;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

class BlobServiceFunctionalTest extends FunctionalTestBase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
     */
    public function testGetServicePropertiesNoOptions()
    {
        $serviceProperties = BlobServiceFunctionalTestData::getDefaultServiceProperties();
        $shouldReturn = false;
        try {
            $this->restProxy->setServiceProperties($serviceProperties);
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        $this->getServicePropertiesWorker(null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
     */
    public function testGetServiceProperties()
    {
        $serviceProperties = BlobServiceFunctionalTestData::getDefaultServiceProperties();

        $shouldReturn = false;
        try {
            $this->restProxy->setServiceProperties($serviceProperties);
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        // Now look at the combos.
        $interestingTimeouts = BlobServiceFunctionalTestData::getInterestingTimeoutValues();
        foreach($interestingTimeouts as $timeout)  {
            $options = new BlobServiceOptions();
            $options->setTimeout($timeout);
            $this->getServicePropertiesWorker($options);
        }
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
     */
    private function getServicePropertiesWorker($options)
    {
        $effOptions = (is_null($options) ? new BlobServiceOptions() : $options);
        try {
            $ret = (is_null($options) ? $this->restProxy->getServiceProperties() : $this->restProxy->getServiceProperties($effOptions));

            if (!is_null($effOptions->getTimeout()) && $effOptions->getTimeout() < 1) {
                $this->True('Expect negative timeouts in $options to throw', false);
            } else {
                $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
            }
            $this->verifyServicePropertiesWorker($ret, null);
        } catch (ServiceException $e) {
            if ($this->isEmulated()) {
                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                } else {
                    // Expect failure in emulator, as v1.6 doesn't support this method
                    $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                }
            } else {
                if (is_null($effOptions->getTimeout()) || $effOptions->getTimeout() >= 1) {
                    throw $e;
                } else {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                }
            }
        }
    }

    private function verifyServicePropertiesWorker($ret, $serviceProperties)
    {
        if (is_null($serviceProperties)) {
            $serviceProperties = BlobServiceFunctionalTestData::getDefaultServiceProperties();
        }

        $sp = $ret->getValue();
        $this->assertNotNull($sp, 'getValue should be non-null');

        $l = $sp->getLogging();
        $this->assertNotNull($l, 'getValue()->getLogging() should be non-null');
        $this->assertEquals($serviceProperties->getLogging()->getVersion(), $l->getVersion(), 'getValue()->getLogging()->getVersion');
        $this->assertEquals($serviceProperties->getLogging()->getDelete(), $l->getDelete(), 'getValue()->getLogging()->getDelete');
        $this->assertEquals($serviceProperties->getLogging()->getRead(), $l->getRead(), 'getValue()->getLogging()->getRead');
        $this->assertEquals($serviceProperties->getLogging()->getWrite(), $l->getWrite(), 'getValue()->getLogging()->getWrite');

        $r = $l->getRetentionPolicy();
        $this->assertNotNull($r, 'getValue()->getLogging()->getRetentionPolicy should be non-null');
        $this->assertEquals($serviceProperties->getLogging()->getRetentionPolicy()->getDays(), $r->getDays(), 'getValue()->getLogging()->getRetentionPolicy()->getDays');

        $m = $sp->getMetrics();
        $this->assertNotNull($m, 'getValue()->getMetrics() should be non-null');
        $this->assertEquals($serviceProperties->getMetrics()->getVersion(), $m->getVersion(), 'getValue()->getMetrics()->getVersion');
        $this->assertEquals($serviceProperties->getMetrics()->getEnabled(), $m->getEnabled(), 'getValue()->getMetrics()->getEnabled');
        $this->assertEquals($serviceProperties->getMetrics()->getIncludeAPIs(), $m->getIncludeAPIs(), 'getValue()->getMetrics()->getIncludeAPIs');

        $r = $m->getRetentionPolicy();
        $this->assertNotNull($r, 'getValue()->getMetrics()->getRetentionPolicy should be non-null');
        $this->assertEquals($serviceProperties->getMetrics()->getRetentionPolicy()->getDays(), $r->getDays(), 'getValue()->getMetrics()->getRetentionPolicy()->getDays');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setServiceProperties
     */
    public function testSetServicePropertiesNoOptions()
    {
        $serviceProperties = BlobServiceFunctionalTestData::getDefaultServiceProperties();
        $this->setServicePropertiesWorker($serviceProperties, null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setServiceProperties
     */
    public function testSetServiceProperties()
    {
        $interestingServiceProperties = BlobServiceFunctionalTestData::getInterestingServiceProperties();
        foreach($interestingServiceProperties as $serviceProperties)  {
            $interestingTimeouts = BlobServiceFunctionalTestData::getInterestingTimeoutValues();
            foreach($interestingTimeouts as $timeout)  {
                $options = new BlobServiceOptions();
                $options->setTimeout($timeout);
                $this->setServicePropertiesWorker($serviceProperties, $options);
            }
        }

        if (!$this->isEmulated()) {
            $this->restProxy->setServiceProperties($interestingServiceProperties[0]);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setServiceProperties
     */
    private function setServicePropertiesWorker($serviceProperties, $options)
    {
        try {
            if (is_null($options)) {
                $this->restProxy->setServiceProperties($serviceProperties);
            } else {
                $this->restProxy->setServiceProperties($serviceProperties, $options);
            }

            if (is_null($options)) {
                $options = new BlobServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            } else {
                $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
            }

            $ret = (is_null($options) ? $this->restProxy->getServiceProperties() : $this->restProxy->getServiceProperties($options));
            $this->verifyServicePropertiesWorker($ret, $serviceProperties);
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new BlobServiceOptions();
            }

            if ($this->isEmulated()) {
                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                } else {
                    $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                }
            } else {
                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                } else {
                    throw $e;
                }
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testListContainersNoOptions()
    {
        $this->listContainersWorker(null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testListContainers()
    {
        $interestingListContainersOptions = BlobServiceFunctionalTestData::getInterestingListContainersOptions();
        foreach($interestingListContainersOptions as $options)  {
            $this->listContainersWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    private function listContainersWorker($options)
    {
        $finished = false;
        while (!$finished) {
            try {
                $ret = (is_null($options) ? $this->restProxy->listContainers() : $this->restProxy->listContainers($options));

                if (is_null($options)) {
                    $options = new ListContainersOptions();
                }

                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
                }
                $this->verifyListContainersWorker($ret, $options);

                if (strlen($ret->getNextMarker()) == 0) {
                    $finished = true;
                } else {
                    $options->setMarker($ret->getNextMarker());
                }
            } catch (ServiceException $e) {
                $finished = true;
                if (is_null($options->getTimeout()) || $options->getTimeout() >= 1) {
                    throw $e;
                } else {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                }
            }
        }
    }

    private function verifyListContainersWorker($ret, $options)
    {
        // Cannot really check the next marker. Just make sure it is not null.
        $this->assertEquals($options->getMarker(), $ret->getMarker(), 'getMarker');
        $this->assertEquals($options->getMaxResults(), $ret->getMaxResults(), 'getMaxResults');
        $this->assertEquals($options->getPrefix(), $ret->getPrefix(), 'getPrefix');

        $this->assertNotNull($ret->getContainers(), 'getBlobs');
        if ($options->getMaxResults() == 0) {
            $this->assertEquals(0, strlen($ret->getNextMarker()), 'When MaxResults is 0, expect getNextMarker (' . strlen($ret->getNextMarker()) . ')to be  ');

            if (!is_null($options->getPrefix()) && $options->getPrefix() == (BlobServiceFunctionalTestData::$nonExistBlobPrefix)) {
                $this->assertEquals(0, count($ret->getContainers()), 'when MaxResults=0 and Prefix=(\'' . $options->getPrefix() . '\'), then Blobs length');
            } else if (!is_null($options->getPrefix()) && $options->getPrefix() == (BlobServiceFunctionalTestData::$testUniqueId)) {
                $this->assertEquals(count(BlobServiceFunctionalTestData::$testContainerNames), count($ret->getContainers()), 'when MaxResults=0 and Prefix=(\'' . $options->getPrefix() . '\'), then Blobs length');
            } else {
                // Do not know how many there should be
            }
        } else if (strlen($ret->getNextMarker()) == 0) {
            $this->assertTrue(count($ret->getContainers()) <= $options->getMaxResults(), 'when NextMarker (\'' . $ret->getNextMarker() . '\')==\'\', Blobs length (' . count($ret->getContainers()) . ') should be <= MaxResults (' . $options->getMaxResults() . ')');

            if (BlobServiceFunctionalTestData::$nonExistBlobPrefix == $options->getPrefix()) {
                $this->assertEquals(0, count($ret->getContainers()), 'when no next marker and Prefix=(\'' . $options->getPrefix() . '\'), then Blobs length');
            } else if (BlobServiceFunctionalTestData::$testUniqueId ==$options->getPrefix()) {
                // Need to futz with the mod because you are allowed to get MaxResults items returned.
                $expectedCount = count(BlobServiceFunctionalTestData::$testContainerNames) % $options->getMaxResults();
                if (!$this->isEmulated()) {
                    $expectedCount += 1;
                }
                $this->assertEquals(
                        $expectedCount,
                        count($ret->getContainers()),
                        'when no next marker and Prefix=(\'' . $options->getPrefix() . '\'), then Blobs length');
            } else {
                // Do not know how many there should be
            }
        } else {
            $this->assertEquals(
                    count($ret->getContainers()),
                    $options->getMaxResults(),
                    'when NextMarker (' . $ret->getNextMarker() . ')!=\'\', Blobs length (' . count($ret->getContainers()) . ') should be == MaxResults (' . $options->getMaxResults() . ')');

            if (!is_null($options->getPrefix()) && $options->getPrefix() == BlobServiceFunctionalTestData::$nonExistBlobPrefix) {
                $this->assertTrue(false, 'when a next marker and Prefix=(\'' . $options->getPrefix() . '\'), impossible');
            }
        }

    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testCreateContainerNoOptions()
    {
        $this->createContainerWorker(null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testCreateContainer()
    {
        $interestingCreateContainerOptions = BlobServiceFunctionalTestData::getInterestingCreateContainerOptions();
        foreach($interestingCreateContainerOptions as $options)  {
            $this->createContainerWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    private function createContainerWorker($options)
    {
        $container = BlobServiceFunctionalTestData::getInterestingContainerName();
        $created = false;

        try {
            if (is_null($options)) {
                $this->restProxy->createContainer($container);
            } else {
                $this->restProxy->createContainer($container, $options);
            }
            $created = true;

            if (is_null($options)) {
                $options = new CreateContainerOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            // Now check that the $container was $created correctly.

            // Make sure that the list of all applicable containers is correctly updated.
            $opts = new ListContainersOptions();
            $opts->setPrefix(BlobServiceFunctionalTestData::$testUniqueId);
            $qs = $this->restProxy->listContainers($opts);
            $this->assertEquals(count($qs->getContainers()), count(BlobServiceFunctionalTestData::$testContainerNames) + 1, 'After adding one, with Prefix=(\'' . BlobServiceFunctionalTestData::$testUniqueId . '\'), then Containers length');

            // Check the metadata on the container
            $ret = $this->restProxy->getContainerMetadata($container);
            $this->verifyCreateContainerWorker($ret, $options);
            $this->restProxy->deleteContainer($container);
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new CreateContainerOptions();
            }

            if (is_null($options->getTimeout()) || $options->getTimeout() >= 1) {
                throw $e;
            } else {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            }
        }

        if ($created) {
            try {
                $this->restProxy->deleteContainer($container);
            } catch (ServiceException $e) {
                // Ignore.
            }
        }
    }

    private function verifyCreateContainerWorker($ret, $options)
    {
        if (is_null($options->getMetadata())) {
            $this->assertNotNull($ret->getMetadata(), 'container Metadata');
            $this->assertEquals(0, count($ret->getMetadata()), 'count container Metadata');
        } else {
            $this->assertNotNull($ret->getMetadata(), 'container Metadata');
            $this->assertEquals(count($options->getMetadata()), count($ret->getMetadata()), 'Metadata');
            $retMetadata = $ret->getMetadata();
            foreach($options->getMetadata() as $key => $value)  {
                $this->assertEquals($value, $retMetadata[$key], 'Metadata(' . $key . ')');
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testDeleteContainerNoOptions()
    {
        $this->deleteContainerWorker(null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testDeleteContainer()
    {
        $interestingDeleteContainerOptions = BlobServiceFunctionalTestData::getInterestingDeleteContainerOptions();
        foreach($interestingDeleteContainerOptions as $options)  {
            $this->deleteContainerWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    private function deleteContainerWorker($options)
    {
        $container = BlobServiceFunctionalTestData::getInterestingContainerName();

        // Make sure there is something to delete.
        $this->restProxy->createContainer($container);

        // Make sure that the list of all applicable containers is correctly updated.
        $opts = new ListContainersOptions();
        $opts->setPrefix(BlobServiceFunctionalTestData::$testUniqueId);
        $qs = $this->restProxy->listContainers($opts);
        $this->assertEquals(
                count($qs->getContainers()),
                count(BlobServiceFunctionalTestData::$testContainerNames) + 1,
                'After adding one, with Prefix=(\'' . BlobServiceFunctionalTestData::$testUniqueId . '\'), then Containers length');

        $deleted = false;
        try {
            if (is_null($options)) {
                $this->restProxy->deleteContainer($container);
            } else {
                $this->restProxy->deleteContainer($container, $options);
            }

            $deleted = true;

            if (is_null($options)) {
                $options = new DeleteContainerOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }
            if (!$this->isEmulated() &&
                    !BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Failing access condition should throw');
            }

            // Make sure that the list of all applicable containers is correctly updated.
            $opts = new ListContainersOptions();
            $opts->setPrefix(BlobServiceFunctionalTestData::$testUniqueId);
            $qs = $this->restProxy->listContainers($opts);
            $this->assertEquals(
                    count($qs->getContainers()),
                    count(BlobServiceFunctionalTestData::$testContainerNames),
                    'After adding then deleting one, with Prefix=(\'' . BlobServiceFunctionalTestData::$testUniqueId . '\'), then Containers length');

            // Nothing else interesting to check for the $options.
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new DeleteContainerOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else if (!$this->isEmulated() && !BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }

        if (!$deleted) {
            // Try again. If it does not work, not much else to try.
            $this->restProxy->deleteContainer($container);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    public function testGetContainerMetadataNoOptions()
    {
        $metadata = BlobServiceFunctionalTestData::getNiceMetadata();
        $this->getContainerMetadataWorker(null, $metadata);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    public function testGetContainerMetadata()
    {
        $interestingTimeouts = BlobServiceFunctionalTestData::getInterestingTimeoutValues();
        $metadata = BlobServiceFunctionalTestData::getNiceMetadata();

        foreach($interestingTimeouts as $timeout)  {
            $options = new BlobServiceOptions();
            $options->setTimeout($timeout);
            $this->getContainerMetadataWorker($options, $metadata);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    private function getContainerMetadataWorker($options, $metadata)
    {
        $container = BlobServiceFunctionalTestData::getInterestingContainerName();

        // Make sure there is something to test
        $this->restProxy->createContainer($container);
        $this->restProxy->setContainerMetadata($container, $metadata);

        try {
            $res = (is_null($options) ? $this->restProxy->getContainerMetadata($container) : $this->restProxy->getContainerMetadata($container, $options));

            if (is_null($options)) {
                $options = new BlobServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() <= 0) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            $this->verifyGetContainerMetadataWorker($res, $metadata);
        } catch (ServiceException $e) {
            if (is_null($options->getTimeout()) || $options->getTimeout() > 0) {
                throw $e;
            } else {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            }
        }
        // Clean up.
        $this->restProxy->deleteContainer($container);
    }

    private function verifyGetContainerMetadataWorker($ret, $metadata)
    {
        $this->assertNotNull($ret->getMetadata(), 'container Metadata');
        $this->assertNotNull($ret->getETag(), 'container getETag');
        $this->assertNotNull($ret->getLastModified(), 'container getLastModified');

        $this->assertEquals(count($metadata), count($ret->getMetadata()), 'Metadata');
        $md = $ret->getMetadata();
        foreach($metadata as $key => $value)  {
            $this->assertEquals($value, $md[$key], 'Metadata(' . $key . ')');
        }

        // Make sure the last modified date is within 10 seconds
        $now = new \DateTime();
        $this->assertTrue(
                BlobServiceFunctionalTestData::diffInTotalSeconds($ret->getLastModified(), $now) < 10,
                'Last modified date (' . $ret->getLastModified()->format(\DateTime::RFC1123) . ')'.
                ' should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    public function testSetContainerMetadataNoOptions()
    {
        $interestingMetadata = BlobServiceFunctionalTestData::getInterestingMetadata();
        foreach($interestingMetadata as $metadata) {
            $this->setContainerMetadataWorker(null, $metadata);
        }
    }

//     /**
//      * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
//      * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
//      * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
//      * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
//      */
//     public function testSetContainerMetadata()
//     {
//         $interestingSetContainerMetadataOptions = BlobServiceFunctionalTestData::getSetContainerMetadataOptions();
//         $interestingMetadata = BlobServiceFunctionalTestData::getInterestingMetadata();

//         foreach($interestingSetContainerMetadataOptions as $options)  {
//             foreach($interestingMetadata as $metadata) {
//                 $this->setContainerMetadataWorker($options, $metadata);
//             }
//         }
//     }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    private function setContainerMetadataWorker($options, $metadata)
    {
        $container = BlobServiceFunctionalTestData::getInterestingContainerName();

        // Make sure there is something to test
        $this->restProxy->createContainer($container);

        $firstkey = '';
        if (!is_null($metadata) && count($metadata) > 0) {
            $firstkey = array_keys($metadata);
            $firstkey = $firstkey[0];
        }

        try {
            // And put in some metadata
            if (is_null ( $options )) {
                $this->restProxy->setContainerMetadata ( $container, $metadata );
            } else {
                $this->restProxy->setContainerMetadata ( $container, $metadata, $options );
            }
            
            if (is_null ( $options )) {
                $options = new SetContainerMetadataOptions ();
            }
            
            $this->assertFalse ( Utilities::startsWith ( $firstkey, '<' ), 'Should get HTTP request error if the metadata is invalid' );
            
            if (! is_null ( $options->getTimeout () ) && $options->getTimeout () < 1) {
                $this->assertTrue ( false, 'Expect negative timeouts in $options to throw' );
            }
            
            // setMetadata only honors If-Modified-Since
            if (! $this->isEmulated () && ! BlobServiceFunctionalTestData::passTemporalAccessCondition ( $options->getAccessCondition () ) && (! is_null ( $options->getAccessCondition () ) && $options->getAccessCondition ()->getHeader () != Resources::IF_UNMODIFIED_SINCE)) {
                $this->assertTrue ( false, 'Expect failing access condition to throw' );
            }
            
            $res = $this->restProxy->getContainerMetadata ( $container );
            $this->verifyGetContainerMetadataWorker ( $res, $metadata );
        } catch (ServiceException $e) {
            if (Utilities::startsWith($firstkey, '<')) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else if (!$this->isEmulated() &&
                    !BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition()) &&
                    (!is_null($options->getAccessCondition()) &&
                    $options->getAccessCondition()->getHeader() != Resources::IF_UNMODIFIED_SINCE)) {
                // setMetadata only honors If-Modified-Since
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'getCode');
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }

        // Clean up.
        $this->restProxy->deleteContainer($container);
    }

    /**
      * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
      * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
      * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerProperties
      * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
      */
    public function testGetContainerPropertiesNoOptions()
    {
        $metadata = BlobServiceFunctionalTestData::getNiceMetadata();
        $this->getContainerPropertiesWorker(null, $metadata);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    public function testGetContainerProperties()
    {

        $interestingTimeouts = BlobServiceFunctionalTestData::getInterestingTimeoutValues();
        $metadata = BlobServiceFunctionalTestData::getNiceMetadata();
        foreach($interestingTimeouts as $timeout)  {
            $options = new BlobServiceOptions();
            $options->setTimeout($timeout);
            $this->getContainerPropertiesWorker($options, $metadata);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    private function getContainerPropertiesWorker($options, $metadata)
    {
        $container = BlobServiceFunctionalTestData::getInterestingContainerName();

        // Make sure there is something to test
        $this->restProxy->createContainer($container);
        $this->restProxy->setContainerMetadata($container, $metadata);

        try {
            $res = (is_null($options) ? $this->restProxy->getContainerProperties($container) : $this->restProxy->getContainerProperties($container, $options));

            if (is_null($options)) {
                $options = new BlobServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            $this->verifyGetContainerMetadataWorker($res, $metadata);
        } catch (ServiceException $e) {
            if (is_null($options->getTimeout()) || $options->getTimeout() >= 1) {
                throw $e;
            } else {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            }
        }

        // Clean up.
        $this->restProxy->deleteContainer($container);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerACL
     */
    public function testGetContainerACLNoOptions()
    {
        $this->getContainerACLWorker(null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerACL
     */
    public function testGetContainerACL()
    {

        $interestingTimeouts = BlobServiceFunctionalTestData::getInterestingTimeoutValues();
        foreach($interestingTimeouts as $timeout)  {
            $options = new BlobServiceOptions();
            $options->setTimeout($timeout);
            $this->getContainerACLWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerACL
     */
    private function getContainerACLWorker($options)
    {
        $container = BlobServiceFunctionalTestData::getInterestingContainerName();

        // Make sure there is something to test
        $this->restProxy->createContainer($container);

        try {
            $res = (is_null($options) ? $this->restProxy->getContainerACL($container) : $this->restProxy->getContainerACL($container, $options));

            if (is_null($options)) {
                $options = new BlobServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            $this->verifyGetContainerACLWorker($res);
        } catch (ServiceException $e) {
            if (is_null($options->getTimeout()) || $options->getTimeout() >= 1) {
                throw $e;
            } else {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            }
        }

        // Clean up.
        $this->restProxy->deleteContainer($container);
    }

    private function verifyGetContainerACLWorker($ret)
    {
        $this->assertNotNull($ret->getContainerACL(), '$ret->getContainerACL');
        $this->assertNotNull($ret->getETag(), '$ret->getETag');
        $this->assertNotNull($ret->getLastModified(), '$ret->getLastModified');
        $this->assertNull($ret->getContainerACL()->getPublicAccess(), '$ret->getContainerACL->getPublicAccess');
        $this->assertNotNull($ret->getContainerACL()->getSignedIdentifiers(), '$ret->getContainerACL->getSignedIdentifiers');

        // Make sure the last modified date is within 10 seconds
        $now = new \DateTime();
        $this->assertTrue(BlobServiceFunctionalTestData::diffInTotalSeconds(
                $ret->getLastModified(),
                $now) < 10000,
                'Last modified date (' . $ret->getLastModified()->format(\DateTime::RFC1123) . ') ' .
                'should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerACL
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerACL
     */
    public function testSetContainerACLNoOptions()
    {
        $interestingACL = BlobServiceFunctionalTestData::getInterestingACL();
        foreach($interestingACL as $acl)  {
            $this->setContainerACLWorker(null, $acl);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerACL
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerACL
     */
    public function testSetContainerACL()
    {
        $interestingACL = BlobServiceFunctionalTestData::getInterestingACL();
        $interestingTimeouts = BlobServiceFunctionalTestData::getInterestingTimeoutValues();
        foreach($interestingTimeouts as $timeout)  {
            foreach($interestingACL as $acl)  {
                $options = new BlobServiceOptions();
                $options->setTimeout($timeout);
                $this->setContainerACLWorker($options, $acl);
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerACL
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerACL
     */
    private function setContainerACLWorker($options, $acl)
    {
        $container = BlobServiceFunctionalTestData::getInterestingContainerName();

        // Make sure there is something to test
        $this->restProxy->createContainer($container);
        $blobContent = uniqid();
        $this->restProxy->createBlockBlob($container, 'test', $blobContent);

        try {
            if (is_null($options)) {
                $this->restProxy->setContainerACL($container, $acl);
                $this->restProxy->setContainerACL($container, $acl);
            } else {
                $this->restProxy->setContainerACL($container, $acl, $options);
                $this->restProxy->setContainerACL($container, $acl, $options);
            }

            if (is_null($options)) {
                $options = new BlobServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            $res = $this->restProxy->getContainerACL($container);
            $this->verifySetContainerACLWorker($res, $container, $acl, $blobContent);
        } catch (ServiceException $e) {
            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }

        // Clean up.
        $this->restProxy->deleteContainer($container);
    }

    private function verifySetContainerACLWorker($ret, $container, $acl, $blobContent)
    {
        $this->assertNotNull($ret->getContainerACL(), '$ret->getContainerACL');
        $this->assertNotNull($ret->getETag(), '$ret->getContainerACL->getETag');
        $now = new \DateTime();
        $this->assertTrue(BlobServiceFunctionalTestData::diffInTotalSeconds(
                $ret->getLastModified(),
                $now) < 10000,
                'Last modified date (' . $ret->getLastModified()->format(\DateTime::RFC1123) . ') ' .
                'should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');

        $this->assertNotNull($ret->getContainerACL()->getSignedIdentifiers(), '$ret->getContainerACL->getSignedIdentifiers');

        $this->assertEquals((is_null($acl->getPublicAccess()) ? '' : $acl->getPublicAccess()), $ret->getContainerACL()->getPublicAccess(), '$ret->getContainerACL->getPublicAccess');
        $expIds = $acl->getSignedIdentifiers();
        $actIds = $ret->getContainerACL()->getSignedIdentifiers();
        $this->assertEquals(count($expIds), count($actIds), '$ret->getContainerACL->getSignedIdentifiers');

        for ($i = 0; $i < count($expIds); $i++) {
            $expId = $expIds[$i];
            $actId = $actIds[$i];
            $this->assertEquals($expId->getId(), $actId->getId(), 'SignedIdentifiers['+$i+']->getId');
            $this->assertEquals(
                    $expId->getAccessPolicy()->getPermission(),
                    $actId->getAccessPolicy()->getPermission(),
                    'SignedIdentifiers['+$i+']->getAccessPolicy->getPermission');
            $this->assertTrue(BlobServiceFunctionalTestData::diffInTotalSeconds(
                    $expId->getAccessPolicy()->getStart(),
                    $actId->getAccessPolicy()->getStart()) < 1,
                    'SignedIdentifiers['+$i+']->getAccessPolicy->getStart should match within 1 second, ' .
                    'exp=' . $expId->getAccessPolicy()->getStart()->format(\DateTime::RFC1123) . ', ' .
                    'act=' . $actId->getAccessPolicy()->getStart()->format(\DateTime::RFC1123));
            $this->assertTrue(BlobServiceFunctionalTestData::diffInTotalSeconds(
                    $expId->getAccessPolicy()->getExpiry(),
                    $actId->getAccessPolicy()->getExpiry()) < 1,
                    'SignedIdentifiers['+$i+']->getAccessPolicy->getExpiry should match within 1 second, ' .
                    'exp=' . $expId->getAccessPolicy()->getExpiry()->format(\DateTime::RFC1123) . ', ' .
                    'act=' . $actId->getAccessPolicy()->getExpiry()->format(\DateTime::RFC1123));
        }

        if (!$this->isEmulated()) {
            $settings = StorageServiceSettings::createFromConnectionString($this->connectionString);
            $containerAddress = $settings->getBlobEndpointUri() . '/' . $container;
            $blobListAddress = $containerAddress . '?restype=container&comp=list';
            $blobAddress = $containerAddress . '/test';

            $canDownloadBlobList = $this->canDownloadFromUrl($blobListAddress,
                    "<?xml version=\"1.0\" encoding=\"utf-8\"?" . "><EnumerationResults");
            $canDownloadBlob = $this->canDownloadFromUrl($blobAddress, $blobContent);

            if (!is_null($acl->getPublicAccess()) && $acl->getPublicAccess() == PublicAccessType::CONTAINER_AND_BLOBS) {
                // Full public read access: Container and blob data can be read via anonymous request.
                // Clients can enumerate blobs within the $container via anonymous request,
                // but cannot enumerate containers within the storage account.
                $this->assertTrue($canDownloadBlobList, '$canDownloadBlobList when ' . $acl->getPublicAccess());
                $this->assertTrue($canDownloadBlob, '$canDownloadBlob when ' . $acl->getPublicAccess());
            } else if (!is_null($acl->getPublicAccess()) && $acl->getPublicAccess() == PublicAccessType::BLOBS_ONLY) {
                // Public read access for blobs only: Blob data within this container
                // can be read via anonymous request, but $container data is not available.
                // Clients cannot enumerate blobs within the $container via anonymous request.
                $this->assertFalse($canDownloadBlobList, '$canDownloadBlobList when ' . $acl->getPublicAccess());
                $this->assertTrue($canDownloadBlob, '$canDownloadBlob when ' . $acl->getPublicAccess());
            } else {
                // No public read access: Container and blob data can be read by the account owner only.
                $this->assertFalse($canDownloadBlobList, '$canDownloadBlobList when ' . $acl->getPublicAccess());
                $this->assertFalse($canDownloadBlob, '$canDownloadBlob when ' . $acl->getPublicAccess());
            }
        }
    }

    private function canDownloadFromUrl($blobAddress, $expectedStartingValue)
    {
        $url = parse_url($blobAddress);
        $host = $url['host'];
        $fp = fsockopen($host, '80');
        $request = 'GET ' . $blobAddress . ' HTTP/1.1' . "\r\n" . 'Host: ' . $host ."\r\n\r\n";
        fputs($fp, $request);
        $value = fread($fp, 1000);
        fclose($fp);
        return strpos($value, $expectedStartingValue) !== false;
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testListBlobsNoOptions()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $this->listBlobsWorker($container, null);
    }

    // This fails because the service returns the container list
    // instead of the blob list. In principle, the service can
    // distinguish between the two, because this is of the
    // format:
    //     /?restype=container&comp=list
    // whereas the container list has this format:
    //     /?comp=list

//    /**
//     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
//     */
//    public function testListBlobsNoOptionsRoot()
//    {
//        $container = null;
//        $this->listBlobsWorker($container, null);
//    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testListBlobsNoOptionsExplicitRoot()
    {
        $container = '$root';
        $this->listBlobsWorker($container, null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testListBlobs()
    {
        $interestingListBlobsOptions = BlobServiceFunctionalTestData::getInterestingListBlobsOptions();
        $container = BlobServiceFunctionalTestData::getContainerName();
        foreach($interestingListBlobsOptions as $options)  {
            $this->listBlobsWorker($container, $options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    private function listBlobsWorker($container, $options)
    {
        $finished = false;
        while (!$finished) {
            try {
                $ret = (is_null($options) ? $this->restProxy->listBlobs($container) : $this->restProxy->listBlobs($container, $options));

                if (is_null($options)) {
                    $options = new ListBlobsOptions();
                }

                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
                }
                $this->verifyListBlobsWorker($ret, $options);

                if (strlen($ret->getNextMarker()) == 0) {
                    $finished = true;
                } else {
                    $options->setMarker($ret->getNextMarker());
                }
            } catch (ServiceException $e) {
                $finished = true;
                if (is_null($options->getTimeout()) || $options->getTimeout() >= 1) {
                    throw $e;
                } else {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                }
            }
        }
    }

    private function verifyListBlobsWorker($ret, $options)
    {
        $this->assertEquals($options->getMarker(), $ret->getMarker(), 'getMarker');
        $this->assertEquals($options->getMaxResults(), $ret->getMaxResults(), 'getMaxResults');
        $this->assertEquals($options->getPrefix(), $ret->getPrefix(), 'getPrefix');

        $this->assertNotNull($ret->getBlobs(), 'getBlobs');
        if ($options->getMaxResults() == 0) {
            $this->assertEquals(0, strlen($ret->getNextMarker()), 'When MaxResults is 0, expect getNextMarker (' . strlen($ret->getNextMarker()) . ')to be  ');

            if (!is_null($options->getPrefix()) && $options->getPrefix() == (BlobServiceFunctionalTestData::$nonExistBlobPrefix)) {
                $this->assertEquals(0, count($ret->getBlobs()), 'when MaxResults=0 and Prefix=(\'' . $options->getPrefix() . '\'), then Blobs length');
            } else if (!is_null($options->getPrefix()) && $options->getPrefix() == (BlobServiceFunctionalTestData::$testUniqueId)) {
                $this->assertEquals(0, count($ret->getBlobs()), 'when MaxResults=0 and Prefix=(\'' . $options->getPrefix() . '\'), then Blobs length');
            } else {
                // Do not know how many there should be
            }
        } else if (strlen($ret->getNextMarker()) == 0) {
            $this->assertTrue(
                    count($ret->getBlobs()) <= $options->getMaxResults(),
                    'when NextMarker (\'' . $ret->getNextMarker() . '\')==\'\', Blobs length (' . count($ret->getBlobs()) . ') should be <= MaxResults (' . $options->getMaxResults() . ')');

            if (BlobServiceFunctionalTestData::$nonExistBlobPrefix == $options->getPrefix()) {
                $this->assertEquals(
                        0,
                        count($ret->getBlobs()),
                        'when no next marker and Prefix=(\'' . $options->getPrefix() . '\'), then Blobs length');
            } else if (BlobServiceFunctionalTestData::$testUniqueId == $options->getPrefix()) {
                // Need to futz with the mod because you are allowed to get MaxResults items returned.
                $this->assertEquals(
                        0,
                        count($ret->getBlobs()) % $options->getMaxResults(),
                        'when no next marker and Prefix=(\'' . $options->getPrefix() . '\'), then Blobs length');
            } else {
                // Do not know how many there should be
            }
        } else {
            $this->assertEquals(count($ret->getBlobs()), $options->getMaxResults(), 'when NextMarker (' . $ret->getNextMarker() . ')!=\'\', Blobs length (' . count($ret->getBlobs()) . ') should be == MaxResults (' . $options->getMaxResults() . ')');

            if (!is_null($options->getPrefix()) && $options->getPrefix() == (BlobServiceFunctionalTestData::$nonExistBlobPrefix)) {
                $this->assertTrue(false, 'when a next marker and Prefix=(\'' . $options->getPrefix() . '\'), impossible');
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobMetadataNoOptions()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $this->getBlobMetadataWorker($container, null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobMetadataNoOptionsRoot()
    {
        $container = null;
        $this->getBlobMetadataWorker($container, null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobMetadataNoOptionsExplicitRoot()
    {
        $container = '$root';
        $this->getBlobMetadataWorker($container, null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobMetadata()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $interestingTimeouts = BlobServiceFunctionalTestData::getInterestingTimeoutValues();

        foreach($interestingTimeouts as $timeout)  {
            $options = new GetBlobMetadataOptions();
            $options->setTimeout($timeout);
            $this->getBlobMetadataWorker($container, $options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    private function getBlobMetadataWorker($container, $options)
    {
        $blob = BlobServiceFunctionalTestData::getInterestingBlobName($container);

        // Make sure there is something to test
        $createBlockBlobResult = $this->restProxy->createBlockBlob($container, $blob, "");

        $properties = BlobServiceFunctionalTestData::getNiceMetadata();
        $this->restProxy->setBlobMetadata($container, $blob, $properties);

        if (!is_null($options)) {
            BlobServiceFunctionalTestData::fixETagAccessCondition($options->getAccessCondition(), $createBlockBlobResult->getETag());
        }

        try {
            $res = (is_null($options) ? $this->restProxy->getBlobMetadata($container, $blob) : $this->restProxy->getBlobMetadata($container, $blob, $options));

            if (is_null($options)) {
                $options = new GetBlobMetadataOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Failing temporal access condition should throw');
            }
            if (!BlobServiceFunctionalTestData::passETagAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Failing etag access condition should throw');
            }

            $this->verifyGetBlobMetadataWorker($res, $properties);
        } catch (ServiceException $e) {
            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'bad timeout: getCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad temporal access condition: getCode');
            } else if (!BlobServiceFunctionalTestData::passETagAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad etag access condition: getCode');
            } else {
                throw $e;
            }
        }

        // Clean up.
        $this->restProxy->deleteBlob($container, $blob);
    }

    private function verifyGetBlobMetadataWorker($res, $metadata)
    {
        $this->assertNotNull($res->getMetadata(), 'blob Metadata');
        $this->assertNotNull($res->getETag(), 'blob getETag');
        $this->assertNotNull($res->getLastModified(), 'blob getLastModified');

        $this->assertEquals(count($metadata), count($res->getMetadata()), 'Metadata');
        $retMetadata = $res->getMetadata();
        foreach($metadata as $key => $value)  {
            $this->assertEquals($value, $retMetadata[$key], 'Metadata(' . $key . ')');
        }

        // Make sure the last modified date is within 10 seconds
        $now = new \DateTime();
        $this->assertTrue(BlobServiceFunctionalTestData::diffInTotalSeconds(
                $res->getLastModified(),
                $now) < 10000,
                'Last modified date (' . $res->getLastModified()->format(\DateTime::RFC1123) . ') ' .
                'should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');
     }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testSetBlobMetadataNoOptions()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $interestingMetadata = BlobServiceFunctionalTestData::getInterestingMetadata();
        foreach($interestingMetadata as $properties) {
            $this->setBlobMetadataWorker($container, null, $properties);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testSetBlobMetadataNoOptionsRoot()
    {
        $container = null;
        $interestingMetadata = BlobServiceFunctionalTestData::getInterestingMetadata();
        foreach($interestingMetadata as $properties) {
            $this->setBlobMetadataWorker($container, null, $properties);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testSetBlobMetadataNoOptionsExplicitRoot()
    {
        $container = '$root';
        $interestingMetadata = BlobServiceFunctionalTestData::getInterestingMetadata();
        foreach($interestingMetadata as $properties) {
            $this->setBlobMetadataWorker($container, null, $properties);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testSetBlobMetadata()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $interestingSetBlobMetadataOptions = BlobServiceFunctionalTestData::getSetBlobMetadataOptions();
        $interestingMetadata = BlobServiceFunctionalTestData::getInterestingMetadata();

        foreach($interestingSetBlobMetadataOptions as $options)  {
            foreach($interestingMetadata as $properties) {
                $this->setBlobMetadataWorker($container, $options, $properties);
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    private function setBlobMetadataWorker($container, $options, $metadata)
    {
        $blob = BlobServiceFunctionalTestData::getInterestingBlobName($container);

        // Make sure there is something to test
        $createBlockBlobResult = $this->restProxy->createBlockBlob($container, $blob, "");
        if (!is_null($options)) {
            BlobServiceFunctionalTestData::fixETagAccessCondition($options->getAccessCondition(), $createBlockBlobResult->getETag());
        }

        $firstkey = '';
        if (!is_null($metadata) && count($metadata) > 0) {
            $firstkey = array_keys($metadata);
            $firstkey = $firstkey[0];
        }

        try {
                // And put in some properties
                $res = (is_null($options) ? $this->restProxy->setBlobMetadata($container, $blob, $metadata) : $this->restProxy->setBlobMetadata($container, $blob, $metadata, $options));
                if (is_null($options)) {
                    $options = new SetBlobMetadataOptions();
                }

                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
                }
                if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                    $this->assertTrue(false, 'Failing access condition should throw');
                }
                if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                    $this->assertTrue(false, 'Expect failing access condition to throw');
                }
                if (Utilities::startsWith($firstkey, '<')) {
                    $this->assertTrue(false, 'Should get HTTP request error if the metadata is invalid');
                }

                $this->verifySetBlobMetadataWorker($res);

                $res2 = $this->restProxy->getBlobMetadata($container, $blob);
                $this->verifyGetBlobMetadataWorker($res2, $metadata);
        } catch (ServiceException $e) {
            if (Utilities::startsWith($firstkey, '<')) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'bad timeout: getCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad temporal access condition: getCode');
            } else if (!BlobServiceFunctionalTestData::passETagAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad etag access condition: getCode');
            } else {
                throw $e;
            }
        }

        // Clean up.
        $this->restProxy->deleteBlob($container, $blob);
    }

    private function verifySetBlobMetadataWorker($res)
    {
        $this->assertNotNull($res->getETag(), 'blob getETag');
        $this->assertNotNull($res->getLastModified(), 'blob getLastModified');

        // Make sure the last modified date is within 10 seconds
        $now = new \DateTime();
        $this->assertTrue(BlobServiceFunctionalTestData::diffInTotalSeconds(
                $res->getLastModified(),
                $now) < 10000,
                'Last modified date (' . $res->getLastModified()->format(\DateTime::RFC1123) . ') ' .
                'should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobPropertiesNoOptions()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $this->getBlobPropertiesWorker($container, null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobPropertiesNoOptionsRoot()
    {
        $container = null;
        $this->getBlobPropertiesWorker($container, null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobPropertiesNoOptionsExplicitRoot()
    {
        $container = '$root';
        $this->getBlobPropertiesWorker($container, null);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobProperties()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $interestingGetBlobPropertiesOptions = BlobServiceFunctionalTestData::getGetBlobPropertiesOptions();

        foreach($interestingGetBlobPropertiesOptions as $options)  {
            $this->getBlobPropertiesWorker($container, $options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    private function getBlobPropertiesWorker($container, $options)
    {
        $blob = BlobServiceFunctionalTestData::getInterestingBlobName($container);

        // Make sure there is something to test
        $createPageBlobResult = $this->restProxy->createPageBlob($container, $blob, 512);

        $metadata = BlobServiceFunctionalTestData::getNiceMetadata();
        $this->restProxy->setBlobMetadata($container, $blob, $metadata);
        // Do not set the properties, there should be default properties.

        if (!is_null($options)) {
            BlobServiceFunctionalTestData::fixETagAccessCondition($options->getAccessCondition(), $createPageBlobResult->getETag());
        }

        try {
            $res = (is_null($options) ? $this->restProxy->getBlobProperties($container, $blob) : $this->restProxy->getBlobProperties($container, $blob, $options));

            if (is_null($options)) {
                $options = new GetBlobPropertiesOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Failing temporal access condition should throw');
            }

            $this->verifyGetBlobPropertiesWorker($res, $metadata, null);
        } catch (ServiceException $e) {
            if (!is_null($options->getAccessCondition()) &&
                    !$this->hasSecureEndpoint() &&
                    $e->getCode() == TestResources::STATUS_FORBIDDEN) {
                // Proxies can eat the access condition headers of
                // unsecured (http) requests, which causes the authentication
                // to fail, with a 403:Forbidden. There is nothing much that
                // can be done about this, other than ignore it.
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'bad timeout: getCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                if ($options->getAccessCondition()->getHeader() == Resources::IF_MODIFIED_SINCE) {
                    $this->assertEquals(TestResources::STATUS_NOT_MODIFIED, $e->getCode(), 'bad temporal access condition IF_MODIFIED_SINCE: getCode');
                } else {
                    $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad temporal access condition: getCode');
                }
            } else {
                throw $e;
            }
        }

        // Clean up.
        $this->restProxy->deleteBlob($container, $blob);
    }

    private function verifyGetBlobPropertiesWorker($res, $metadata, $properties)
    {
        /* The semantics for updating a blob's properties are as follows:
         *
         *  * A page blob's sequence number is updated only if the request meets either of the
         *    following conditions:
         *
         *     * The request sets the x-ms-sequence-number-action to max or update, and also
         *       specifies a value for the x-ms-blob-sequence-number header.
         *     * The request sets the x-ms-sequence-number-action to increment, indicating that
         *       the service should increment the sequence number by one.
         *
         *  * A page blob's size is modified only if the request specifies a value for the
         *    x-ms-content-length header.
         *
         *  * If a request sets only x-ms-blob-sequence-number and/or x-ms-content-length, and
         *    no other properties, then none of the blob's other properties are modified.
         *
         *  * If any one or more of the following properties is set in the request, then all of
         *    these properties are set together. If a value is not provided for a given property
         *    when at least one of the properties listed below is set, then that property will be
         *    cleared for the blob.
         *
         *     * x-ms-blob-cache-control
         *     * x-ms-blob-content-type
         *     * x-ms-blob-content-md5
         *     * x-ms-blob-content-encoding
         *     * x-ms-blob-content-language
         */

        $this->assertNotNull($res->getMetadata(), 'blob Metadata');
        if (is_null($metadata)) {
            $this->assertEquals(0, count($res->getMetadata()), 'Metadata');
        } else {
            $this->assertEquals(count($metadata), count($res->getMetadata()), 'Metadata');
            $resMetadata = $res->getMetadata();
            foreach($metadata as $key => $value)  {
                $this->assertEquals($value, $resMetadata[$key], 'Metadata(' . $key . ')');
            }
        }

        $this->assertNotNull($res->getProperties(), 'blob Properties');
        $this->assertNotNull($res->getProperties()->getETag(), 'blob getProperties->getETag');
        $this->assertNotNull($res->getProperties()->getLastModified(), 'blob getProperties->getLastModified');
        $this->assertEquals('PageBlob', $res->getProperties()->getBlobType(), 'blob getProperties->getBlobType');
        $this->assertEquals('unlocked', $res->getProperties()->getLeaseStatus(), 'blob getProperties->getLeaseStatus');

        if (is_null($properties) || !is_null($properties->getBlobContentLength()) || !is_null($properties->getSequenceNumber())) {
            $this->assertNull($res->getProperties()->getCacheControl(), 'blob getProperties->getCacheControl');
            $this->assertEquals('application/octet-stream', $res->getProperties()->getContentType(), 'blob getProperties->getContentType');
            $this->assertNull($res->getProperties()->getContentMD5(), 'blob getProperties->getContentMD5');
            $this->assertNull($res->getProperties()->getContentEncoding(), 'blob getProperties->getContentEncoding');
            $this->assertNull($res->getProperties()->getContentLanguage(), 'blob getProperties->getContentLanguage');
        } else {
            $this->assertEquals($properties->getBlobCacheControl(), $res->getProperties()->getCacheControl(), 'blob getProperties->getCacheControl');
            $this->assertEquals($properties->getBlobContentType(), $res->getProperties()->getContentType(), 'blob getProperties->getContentType');
            $this->assertEquals($properties->getBlobContentMD5(), $res->getProperties()->getContentMD5(), 'blob getProperties->getContentMD5');
            $this->assertEquals($properties->getBlobContentEncoding(), $res->getProperties()->getContentEncoding(), 'blob getProperties->getContentEncoding');
            $this->assertEquals($properties->getBLobContentLanguage(), $res->getProperties()->getContentLanguage(), 'blob getProperties->getContentLanguage');
        }

        if (is_null($properties) || is_null($properties->getBlobContentLength())) {
            $this->assertEquals(512, $res->getProperties()->getContentLength(), 'blob getProperties->getContentLength');
        } else {
            $this->assertEquals($properties->getBlobContentLength(), $res->getProperties()->getContentLength(), 'blob getProperties->getContentLength');
        }

        if (is_null($properties) || is_null($properties->getSequenceNumber())) {
            $this->assertEquals(0, $res->getProperties()->getSequenceNumber(), 'blob getProperties->getSequenceNumber');
        } else {
            $this->assertEquals($properties->getSequenceNumber(), $res->getProperties()->getSequenceNumber(), 'blob getProperties->getSequenceNumber');

        }

        // Make sure the last modified date is within 10 seconds
        $now = new \DateTime();
        $this->assertTrue(BlobServiceFunctionalTestData::diffInTotalSeconds(
                $res->getProperties()->getLastModified(),
                $now) < 10000,
                'Last modified date (' . $res->getProperties()->getLastModified()->format(\DateTime::RFC1123) . ') ' .
                'should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobProperties
     */
    public function testSetBlobProperties()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $interestingSetBlobPropertiesOptions = BlobServiceFunctionalTestData::getSetBlobPropertiesOptions();

        foreach($interestingSetBlobPropertiesOptions as $properties)  {
            $this->setBlobPropertiesWorker($container, $properties);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobProperties
     */
    public function testSetBlobPropertiesRoot()
    {
        $container = null;
        $interestingSetBlobPropertiesOptions = BlobServiceFunctionalTestData::getSetBlobPropertiesOptions();
        $this->setBlobPropertiesWorker($container, $interestingSetBlobPropertiesOptions[2]);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobProperties
     */
    public function testSetBlobPropertiesExplicitRoot()
    {
        $container = '$root';
        $interestingSetBlobPropertiesOptions = BlobServiceFunctionalTestData::getSetBlobPropertiesOptions();
        $this->setBlobPropertiesWorker($container, $interestingSetBlobPropertiesOptions[2]);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobProperties
     */
    private function setBlobPropertiesWorker($container, $properties)
    {
        $blob = BlobServiceFunctionalTestData::getInterestingBlobName($container);

        // Make sure there is something to test
        $createPageBlobResult = $this->restProxy->createPageBlob($container, $blob, 512);
        BlobServiceFunctionalTestData::fixETagAccessCondition($properties->getAccessCondition(), $createPageBlobResult->getETag());

        try {
            // And put in some properties
            $res = $this->restProxy->setBlobProperties($container, $blob, $properties);

            if (!is_null($properties->getTimeout()) && $properties->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in options to throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($properties->getAccessCondition())) {
                $this->assertTrue(false, 'Failing access condition should throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($properties->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing access condition to throw');
            }

            $this->verifySetBlobPropertiesWorker($res);

            $res2 = $this->restProxy->getBlobProperties($container, $blob);
            $this->verifyGetBlobPropertiesWorker($res2, null, $properties);
        } catch (ServiceException $e) {
            if (!is_null($properties->getTimeout()) && $properties->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'bad timeout: getCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($properties->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad temporal access condition: getCode');
            } else if (!BlobServiceFunctionalTestData::passETagAccessCondition($properties->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad etag access condition: getCode');
            } else {
            }
        }

        // Clean up.
        $this->restProxy->deleteBlob($container, $blob);
    }

    private function verifySetBlobPropertiesWorker($res)
    {
        $this->assertNotNull($res->getETag(), 'blob getETag');
        $this->assertNotNull($res->getLastModified(), 'blob getLastModified');
        $this->assertNotNull($res->getSequenceNumber(), 'blob getSequenceNumber');
        $this->assertEquals(0, $res->getSequenceNumber(), 'blob getSequenceNumber');

        // Make sure the last modified date is within 10 seconds
        $now = new \DateTime();
        $this->assertTrue(BlobServiceFunctionalTestData::diffInTotalSeconds(
                $res->getLastModified(),
                $now) < 10000,
                'Last modified date (' . $res->getLastModified()->format(\DateTime::RFC1123) . ') ' .
                'should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlob_NoOptions()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $this->getBlobWorker(null, $container);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlob_NoOptionsExplicitRoot()
    {
        $this->getBlobWorker(null, '$root');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlob_NoOptionsRoot()
    {
        $this->getBlobWorker(null, '');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlob_AllOptions()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $interestingGetBlobOptions = BlobServiceFunctionalTestData::getGetBlobOptions();
        foreach($interestingGetBlobOptions as $options)  {
            $this->getBlobWorker($options, $container);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    private function getBlobWorker($options, $container)
    {
        $blob = BlobServiceFunctionalTestData::getInterestingBlobName($container);

        // Make sure there is something to test
        $dataSize = 512;
        $this->restProxy->createPageBlob($container, $blob, $dataSize);

        $metadata = BlobServiceFunctionalTestData::getNiceMetadata();
        $sbmd = $this->restProxy->setBlobMetadata($container, $blob, $metadata);

        $snapshot = $this->restProxy->createBlobSnapshot($container, $blob);
        $this->restProxy->createBlobSnapshot($container, $blob);

        if (!is_null($options)) {
            BlobServiceFunctionalTestData::fixETagAccessCondition($options->getAccessCondition(), $sbmd->getETag());
            $options->setSnapshot(is_null($options->getSnapshot()) ? null : $snapshot->getSnapshot());
        }

        try {
            $res = (is_null($options) ? $this->restProxy->getBlob($container, $blob) : $this->restProxy->getBlob($container, $blob, $options));

            if (is_null($options)) {
                $options = new GetBlobOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing temporal access condition should throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing etag access condition to throw');
            }
            if ($options->getComputeRangeMD5() && is_null($options->getRangeStart())) {
                $this->assertTrue(false, 'Expect compute range MD5 to fail if range not set');
            }

            $this->verifyGetBlobWorker($res, $options, $dataSize, $metadata);
        } catch (ServiceException $e) {
            if (!is_null($options->getAccessCondition()) &&
                    !$this->hasSecureEndpoint() &&
                    $e->getCode() == TestResources::STATUS_FORBIDDEN) {
                // Proxies can eat the access condition headers of
                // unsecured (http) requests, which causes the authentication
                // to fail, with a 403:Forbidden. There is nothing much that
                // can be done about this, other than ignore it.
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'bad timeout: getCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                if ($options->getAccessCondition()->getHeader() == Resources::IF_MODIFIED_SINCE) {
                    $this->assertEquals(TestResources::STATUS_NOT_MODIFIED, $e->getCode(), 'bad temporal access condition IF_MODIFIED_SINCE: getCode');
                } else {
                    $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad temporal access condition IF_UNMODIFIED_SINCE: getCode');
                }
            } else if (!BlobServiceFunctionalTestData::passETagAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad etag access condition: getCode');
            } else if ($options->getComputeRangeMD5() && is_null($options->getRangeStart())) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'Expect compute range MD5 to fail when range not set: getCode');
            } else {
                throw $e;
            }
        }

        // Clean up.
        $this->restProxy->deleteBlob($container, $blob);
    }

    private function verifyGetBlobWorker($res, $options, $dataSize, $metadata)
    {
        $this->assertNotNull($res, 'result');

        $content =  stream_get_contents($res->getContentStream());

        $rangeSize = $dataSize;
        if (!is_null($options->getRangeEnd())) {
            $rangeSize = (int) $options->getRangeEnd() + 1;
        }
        if (!is_null($options->getRangeStart())) {
            $rangeSize -= $options->getRangeStart();
        } else {
            // One might expect that not specifying the start would just take the
            // first $rangeEnd bytes, but instead the Azure service ignores
            // the malformed Range headers.
            $rangeSize = $dataSize;
        }

        $this->assertEquals($rangeSize, strlen($content), '$content length and range');

        if ($options->getComputeRangeMD5()) {
            // Compute the MD5 from the stream.
            $md5 = base64_encode(md5($content, true));
            $this->assertEquals($md5, $res->getProperties()->getContentMD5(), 'asked for MD5, result->getProperties()->getContentMD5');
        } else {
            $this->assertNull($res->getProperties()->getContentMD5(), 'did not ask for MD5, result->getProperties()->getContentMD5');
        }

        $this->assertNotNull($res->getMetadata(), 'blob Metadata');
        $resMetadata = $res->getMetadata();
        $this->assertEquals(count($metadata), count($resMetadata), 'Metadata');
        foreach($metadata as $key => $value)  {
            $this->assertEquals($value, $resMetadata[$key], 'Metadata(' . $key . ')');
        }

        // Rest of the properties are tested elsewhere.
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testDeleteBlobNoOptions()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $this->deleteBlobWorker(null, $container);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testDeleteBlobNoOptionsExplicitRoot()
    {
        $this->deleteBlobWorker(null, '$root');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testDeleteBlobNoOptionsRoot()
    {
        $this->deleteBlobWorker(null, '');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testDeleteBlob()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $interestingDeleteBlobOptions = BlobServiceFunctionalTestData::getDeleteBlobOptions();
        foreach($interestingDeleteBlobOptions as $options)  {
            $this->deleteBlobWorker($options, $container);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    private function deleteBlobWorker($options, $container)
    {
        $blob = BlobServiceFunctionalTestData::getInterestingBlobName($container);

        // Make sure there is something to test
        $dataSize = 512;
        $this->restProxy->createPageBlob($container, $blob, $dataSize);
        $snapshot = $this->restProxy->createBlobSnapshot($container, $blob);
        $this->restProxy->createBlobSnapshot($container, $blob);

        $blobinfo = $this->restProxy->getBlob($container, $blob);
        if (!is_null($options)) {
            BlobServiceFunctionalTestData::fixETagAccessCondition($options->getAccessCondition(), $blobinfo->getProperties()->getETag());
            $options->setSnapshot(is_null($options->getSnapshot()) ? null : $snapshot->getSnapshot());
        }

        $deleted = false;
        try {
            if (is_null($options)) {
                $this->restProxy->deleteBlob($container, $blob);
            } else {
                $this->restProxy->deleteBlob($container, $blob, $options);
            }
            $deleted = true;

            if (is_null($options)) {
                $options = new DeleteBlobOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing temporal access condition should throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing etag access condition to throw');
            }

            $listOptions = new ListBlobsOptions();
            $listOptions->setIncludeSnapshots(true);
            $listOptions->setPrefix($blob);
            $listBlobsResult = $this->restProxy->listBlobs($container == '' ? '$root' : $container, $listOptions);
            $blobs = $listBlobsResult->getBlobs();

            $this->verifyDeleteBlobWorker($options, $blobs);
        } catch (ServiceException $e) {
            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'bad timeout: deleteHttpStatusCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad temporal access condition IF_UNMODIFIED_SINCE: deleteHttpStatusCode');
            } else if (!BlobServiceFunctionalTestData::passETagAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad etag access condition: deleteHttpStatusCode');
            } else {
                throw $e;
            }
        }

        // Clean up.
        if (!$deleted) {
            $this->restProxy->deleteBlob($container, $blob);
        }
    }

    private function verifyDeleteBlobWorker($options, $blobs)
    {
        if (!is_null($options->getSnapshot())) {
            $this->assertEquals(2, count($blobs), 'when give a snapshot, $blobs with same name as main blob');
        } else if ($options->getDeleteSnaphotsOnly()) {
            $this->assertEquals(1, count($blobs), 'when getDeleteSnaphotsOnly=true, $blobs with same name as main blob');
        } else {
            $this->assertEquals(0, count($blobs), 'when getDeleteSnaphotsOnly=false, blob with same name as main blob');
        }
    }


    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testCreateBlobSnapshotNoOptionsContainer()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $this->createBlobSnapshotWorker(null, $container);
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testCreateBlobSnapshotNoOptionsExplicitRoot()
    {
        $this->createBlobSnapshotWorker(null, '$root');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testCreateBlobSnapshotNoOptionsRoot()
    {
        $this->createBlobSnapshotWorker(null, '');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testCreateBlobSnapshotAllOptions()
    {
        $container = BlobServiceFunctionalTestData::getContainerName();
        $interestingCreateBlobSnapshotOptions = BlobServiceFunctionalTestData::getCreateBlobSnapshotOptions();
        foreach($interestingCreateBlobSnapshotOptions as $options)  {
            $this->createBlobSnapshotWorker($options, $container);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    private function createBlobSnapshotWorker($options, $container)
    {
        $blob = BlobServiceFunctionalTestData::getInterestingBlobName($container);

        // Make sure there is something to test
        $dataSize = 512;
        $this->restProxy->createPageBlob($container, $blob, $dataSize);
        $snapshot1 = $this->restProxy->createBlobSnapshot($container, $blob);
        if (!is_null($options)) {
            BlobServiceFunctionalTestData::fixETagAccessCondition($options->getAccessCondition(), $snapshot1->getETag());
        }

        try {
            $res = (is_null($options) ? $this->restProxy->createBlobSnapshot($container, $blob) : $this->restProxy->createBlobSnapshot($container, $blob, $options));

            if (is_null($options)) {
                $options = new CreateBlobSnapshotOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing temporal access condition should throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing etag access condition to throw');
            }

            $listOptions = new ListBlobsOptions();
            $listOptions->setIncludeSnapshots(true);
            $listOptions->setPrefix($blob);
            $listBlobsResult = $this->restProxy->listBlobs($container == '' ? '$root' : $container, $listOptions);
            $blobs = $listBlobsResult->getBlobs();

            $getBlobOptions = new GetBlobOptions();
            $getBlobOptions->setSnapshot($res->getSnapshot());
            $getBlobResult = $this->restProxy->getBlob($container, $blob, $getBlobOptions);

            $this->verifyCreateBlobSnapshotWorker($res, $options, $blobs, $getBlobResult);
        } catch (ServiceException $e) {
            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'bad timeout: deleteHttpStatusCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad temporal access condition IF_UNMODIFIED_SINCE: deleteHttpStatusCode');
            } else if (!BlobServiceFunctionalTestData::passETagAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'bad etag access condition: deleteHttpStatusCode');
            } else {
                throw $e;
            }
        }

        // Clean up.
        $this->restProxy->deleteBlob($container, $blob);
    }

    private function verifyCreateBlobSnapshotWorker($res, $options, $blobs, $getBlobResult)
    {
        $now = new \DateTime();

        $this->assertNotNull($res->getETag(), 'result etag');

        $snapshotDate = new \DateTime($res->getSnapshot());

        // Make sure the last modified date is within 10 seconds
        $this->assertTrue(
                BlobServiceFunctionalTestData::diffInTotalSeconds($snapshotDate, $now) < 10,
                'Last modified date (' . $snapshotDate->format(\DateTime::RFC1123) . ')'.
                ' should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');

        // Make sure the last modified date is within 10 seconds
        $this->assertTrue(
                BlobServiceFunctionalTestData::diffInTotalSeconds($res->getLastModified(), $now) < 10,
                'Last modified date (' . $res->getLastModified()->format(\DateTime::RFC1123) . ')'.
                ' should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');

        $this->assertEquals(3, count($blobs), 'Should end up with 3 $blobs with same name as main blob');

        $this->assertNotNull($getBlobResult->getMetadata(), 'blob Metadata');
        $this->assertEquals(count($options->getMetadata()), count($getBlobResult->getMetadata()), 'Metadata');
        $retMetadata = $getBlobResult->getMetadata();
        if (!is_null($options->getMetadata())) {
            foreach($options->getMetadata() as $key => $value)  {
                $this->assertEquals($value, $retMetadata[$key], 'Metadata(' . $key . ')');
            }
        }
    }


    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::copyBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testCopyBlobNoOptions()
    {
        $sourceContainers = array(
            BlobServiceFunctionalTestData::$testContainerNames[0],
            '$root',
            '');

        $destContainers = array(
            BlobServiceFunctionalTestData::$testContainerNames[1],
            '$root',
            '');

        foreach($sourceContainers as $sourceContainer)  {
            foreach($destContainers as $destContainer)  {
                $this->copyBlobWorker(null, $sourceContainer, $destContainer);
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::copyBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testCopyBlobAllOptions()
    {
        $sourceContainer = BlobServiceFunctionalTestData::$testContainerNames[0];
        $destContainer = BlobServiceFunctionalTestData::$testContainerNames[1];

        $interestingCopyBlobOptions = BlobServiceFunctionalTestData::getCopyBlobOptions();
        foreach($interestingCopyBlobOptions as $options)  {
            $this->copyBlobWorker($options, $sourceContainer, $destContainer);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::copyBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    private function copyBlobWorker($options, $sourceContainer, $destContainer)
    {
        $sourceBlob = BlobServiceFunctionalTestData::getInterestingBlobName($sourceContainer);
        $destBlob = BlobServiceFunctionalTestData::getInterestingBlobName($destContainer);

        // Make sure there is something to test
        $sourceDataSize = 512;
        $this->restProxy->createPageBlob($sourceContainer, $sourceBlob, $sourceDataSize);

        $destDataSize = 2048;
        $destBlobInfo = $this->restProxy->createPageBlob($destContainer, $destBlob, $destDataSize);
        $this->restProxy->createBlobSnapshot($destContainer, $destBlob);

        $metadata = BlobServiceFunctionalTestData::getNiceMetadata();
        $this->restProxy->setBlobMetadata($sourceContainer, $sourceBlob, $metadata);
        $snapshot = $this->restProxy->createBlobSnapshot($sourceContainer, $sourceBlob);
        if (!is_null($options)) {
            BlobServiceFunctionalTestData::fixETagAccessCondition($options->getSourceAccessCondition(), $snapshot->getETag());
            BlobServiceFunctionalTestData::fixETagAccessCondition($options->getAccessCondition(), $destBlobInfo->getETag());
            $options->setSourceSnapshot(is_null($options->getSourceSnapshot()) ? null : $snapshot->getSnapshot());
        }

        try {
            if (is_null($options)) {
                $this->restProxy->copyBlob($destContainer, $destBlob, $sourceContainer, $sourceBlob);
            } else {
                $this->restProxy->copyBlob($destContainer, $destBlob, $sourceContainer, $sourceBlob, $options);
            }

            if (is_null($options)) {
                $options = new CopyBlobOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getSourceAccessCondition())) {
                $this->assertTrue(false, 'Expect failing source temporal access condition should throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getSourceAccessCondition())) {
                $this->assertTrue(false, 'Expect failing source etag access condition to throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing dest temporal access condition should throw');
            }
            if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertTrue(false, 'Expect failing dest etag access condition to throw');
            }

            $listOptions = new ListBlobsOptions();
            $listOptions->setIncludeSnapshots(true);
            $listOptions->setPrefix($destBlob);
            $listBlobsResult = $this->restProxy->listBlobs($destContainer == '' ? '$root' : $destContainer, $listOptions);
            $blobs = $listBlobsResult->getBlobs();

            $getBlobResult = $this->restProxy->getBlob($destContainer, $destBlob);

            $this->verifyCopyBlobWorker($options, $blobs, $getBlobResult, $sourceDataSize, $metadata);
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new CopyBlobOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(500, $e->getCode(), 'bad timeout: deleteHttpStatusCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getSourceAccessCondition())) {
                $this->assertEquals(412, $e->getCode(), 'bad source temporal access condition IF_UNMODIFIED_SINCE: deleteHttpStatusCode');
            } else if (!BlobServiceFunctionalTestData::passETagAccessCondition($options->getSourceAccessCondition())) {
                $this->assertEquals(412, $e->getCode(), 'bad source etag access condition: deleteHttpStatusCode');
            } else if (!BlobServiceFunctionalTestData::passTemporalAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(412, $e->getCode(), 'bad dest temporal access condition IF_UNMODIFIED_SINCE: deleteHttpStatusCode');
            } else if (!BlobServiceFunctionalTestData::passETagAccessCondition($options->getAccessCondition())) {
                $this->assertEquals(412, $e->getCode(), 'bad dest etag access condition: deleteHttpStatusCode');
            } else {
                throw $e;
            }
        }

        // Clean up.
        $this->restProxy->deleteBlob($sourceContainer, $sourceBlob);
        $this->restProxy->deleteBlob($destContainer, $destBlob);
    }

    private function verifyCopyBlobWorker($options, $blobs, $getBlobResult, $sourceDataSize, $metadata)
    {
        $this->assertEquals(2, count($blobs), 'Should end up with 2 blob with same name as dest blob, snapshot and copied blob');
        $this->assertEquals($sourceDataSize, $getBlobResult->getProperties()->getContentLength(), 'Dest length should be the same as the source length');

        $this->assertNotNull($getBlobResult->getMetadata(), 'blob Metadata');
        $expectedMetadata = (count($options->getMetadata()) == 0 ? $metadata : $options->getMetadata());
        $resMetadata = $getBlobResult->getMetadata();
        $this->assertEquals(count($expectedMetadata), count($resMetadata), 'Metadata');
        foreach($expectedMetadata as $key => $value)  {
            $this->assertEquals($value, $resMetadata[strtolower($key)], 'Metadata(' . $key . ')');
        }

        // Make sure the last modified date is within 10 seconds
        $now = new \DateTime();
        $this->assertTrue(
                BlobServiceFunctionalTestData::diffInTotalSeconds($getBlobResult->getProperties()->getLastModified(), $now) < 10,
                'Last modified date (' . $getBlobResult->getProperties()->getLastModified()->format(\DateTime::RFC1123) . ')'.
                ' should be within 10 seconds of $now (' . $now->format(\DateTime::RFC1123) . ')');
    }

    //    createBlockBlob
    //    createBlobBlock
    //    commitBlobBlocks
    //    listBlobBlocks

    //    createPageBlob
    //    createBlobPages
    //    clearBlobPages
    //    listBlobRegions

    //    acquireLease
    //    renewLease
    //    releaseLease
    //    breakLease

}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Blob;

use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\ContainerAcl;
use MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\DeleteContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Blob\Models\ListContainersOptions;
use MicrosoftAzure\Storage\Blob\Models\PublicAccessType;
use MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataOptions;
use MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Blob\Models\SetContainerMetadataOptions;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Models\Logging;
use MicrosoftAzure\Storage\Common\Models\Metrics;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;

class BlobServiceFunctionalTestData
{
    public static $testUniqueId;
    public static $tempBlobCounter = 1;
    public static $nonExistContainerPrefix;
    public static $nonExistBlobPrefix;
    public static $testContainerNames;
    public static $testBlobNames;
    private static $_accountName;
    private static $badETag = '0x123456789ABCDEF';

    public static function setupData($accountName)
    {
        $rint = mt_rand(0, 1000000);
        self::$_accountName = $accountName;
        self::$testUniqueId = 'qa-' . $rint . '-';
        self::$nonExistContainerPrefix = 'qa-' . ($rint . 1) . '-';
        self::$nonExistBlobPrefix = 'qa-' . ($rint . 2) . '-';
        self::$testContainerNames = array( self::$testUniqueId . 'a1', self::$testUniqueId . 'a2', self::$testUniqueId . 'b1' );
        self::$testBlobNames = array( 'b' . self::$testUniqueId . 'a1', 'b' . self::$testUniqueId . 'a2', 'b' . self::$testUniqueId . 'b1' );
    }

    public static function getInterestingContainerName()
    {
        // http://msdn.microsoft.com/en-us/library/windowsazure/dd135715.aspx
        // 1. Container names must start with a letter or number, and can
        //    contain only letters, numbers, and the dash (-) character.
        // 2. Consecutive dashes are not permitted in container names.
        // 3. All letters in a container name must be lowercase.
        // 4. Container names must be from 3 through 63 characters long.
        // 5. Container names cannot contain control characters: 0x00 to 0x1F

        return self::$testUniqueId . 'con-' . (self::$tempBlobCounter++);
    }

    public static function getInterestingBlobName($container)
    {
        // http://msdn.microsoft.com/en-us/library/windowsazure/dd135715.aspx
        // 1. A blob name cannot contain control characters: 0x00 to 0x1F
        // 2. A blob name can contain any combination of characters, but
        //    reserved URL characters must be properly escaped.
        // 3. A blob name must be at least one character long and cannot be
        //    more than 1,024 characters long.
        // 4. Blob names are case-sensitive.
        // 5. Avoid blob names that end with a dot (.) or a forward slash (/);
        //    they are removed by the server
        // 6. Avoid blob names containing a dot-slash sequence (./);
        //    the dot is removed by the server.

        $uB2E4 = chr(0xEB) . chr(0x8B) . chr(0xA4); // UTF8 encoding of \uB2E4
        $blobname = self::$testUniqueId . '/*\"\'&.({[<+ ' . chr(0x7D) . $uB2E4 . '_' . (self::$tempBlobCounter++);
        if (empty($container) || $container == '$root') {
            $blobname = str_replace('/', 'X', $blobname);
            $blobname = str_replace('\\', 'X', $blobname);
        }
        return $blobname;
    }

    public static function getSimpleMessageText()
    {
        return 'simple message text #' . (self::$tempBlobCounter++);
    }

    public static function getInterestingTimeoutValues()
    {
        $ret = array();
        array_push($ret, null);
        array_push($ret, -1);
        array_push($ret,  0);
        array_push($ret,  1);
        array_push($ret,-2147483648);
        array_push($ret, 2147483647);
        return $ret;
    }

    public static function diffInTotalSeconds($date1, $date2)
    {
        $diff = $date1->diff($date2);
        $sec = $diff->s
                + 60 * ( $diff->i
                + 60 * ( $diff->h
                + 24 * ( $diff->d
                + 30 * ( $diff->m
                + 12 * ( $diff->y )))));
        return abs($sec);
    }

    public static function passTemporalAccessCondition($ac)
    {
        if (is_null($ac)) {
            return true;
        }

        $now = new \DateTime();

        if ($ac->getHeader() == Resources::IF_UNMODIFIED_SINCE) {
            return $ac->getValue() > $now;
        } else if ($ac->getHeader() == Resources::IF_MODIFIED_SINCE) {
            return $ac->getValue() < $now;
        } else {
            return true;
        }
    }

    public static function passETagAccessCondition($ac)
    {
        if (is_null($ac)) {
            return true;
        } else if ($ac->getHeader() == Resources::IF_MATCH) {
            return self::$badETag != $ac->getValue();
        } else if ($ac->getHeader() == Resources::IF_NONE_MATCH) {
            return self::$badETag == $ac->getValue();
        } else {
            return true;
        }
    }

    public static function fixETagAccessCondition($ac, $etag)
    {
        if (!is_null($ac)) {
            if ($ac->getHeader() == Resources::IF_MATCH || $ac->getHeader() == Resources::IF_NONE_MATCH) {
                if (is_null($ac->getValue()) || self::$badETag != $ac->getValue()) {
                    $ac->setValue($etag);
                }
            }
        }
    }

    private static function getTemporalAccessConditions()
    {
        $ret = array();

        $past = new \DateTime("01/01/2010");
        $future = new \DateTime("01/01/2020");

        array_push($ret, AccessCondition::ifModifiedSince($past));
        array_push($ret, AccessCondition::ifNotModifiedSince($past));
        array_push($ret, AccessCondition::ifModifiedSince($future));
        array_push($ret, AccessCondition::ifNotModifiedSince($future));

        return $ret;
    }

    private static function getAllAccessConditions()
    {
        $ret = self::getTemporalAccessConditions();

        array_push($ret, AccessCondition::ifMatch(null));
        array_push($ret, AccessCondition::ifMatch(self::$badETag));
        array_push($ret, AccessCondition::ifNoneMatch(null));
        array_push($ret, AccessCondition::ifNoneMatch(self::$badETag));

        return $ret;
    }

    public static function getDefaultServiceProperties()
    {
        // This is the default that comes from the server.
        $rp = new RetentionPolicy();
        $l = new Logging();
        $l->setRetentionPolicy($rp);
        $l->setVersion('1.0');
        $l->setDelete(false);
        $l->setRead(false);
        $l->setWrite(false);

        $m = new Metrics();
        $m->setRetentionPolicy($rp);
        $m->setVersion('1.0');
        $m->setEnabled(false);
        $m->setIncludeAPIs(null);

        $sp = new ServiceProperties();
        $sp->setLogging($l);
        $sp->setMetrics($m);

        return $sp;
    }

    public static function getContainerName()
    {
        return self::$testContainerNames[0];
    }

    public static function getInterestingServiceProperties()
    {
        $ret = array();

        {
            // This is the default that comes from the server.
            array_push($ret, self::getDefaultServiceProperties());
        }

        {
            $rp = new RetentionPolicy();
            $rp->setEnabled(true);
            $rp->setDays(10);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(true);
            $l->setRead(true);
            $l->setWrite(true);

            $m = new Metrics();
            $m->setRetentionPolicy($rp);
            $m->setVersion('1.0');
            $m->setEnabled(true);
            $m->setIncludeAPIs(true);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret,$sp);
        }

        {
            $rp = new RetentionPolicy();
            // The service does not accept setting days when enabled is false.
            $rp->setEnabled(false);
            $rp->setDays(null);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(false);
            $l->setRead(false);
            $l->setWrite(false);

            $m = new Metrics();
            $m->setRetentionPolicy($rp);
            $m->setVersion('1.0');
            $m->setEnabled(true);
            $m->setIncludeAPIs(true);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret,$sp);
        }

        {
            $rp = new RetentionPolicy();
            $rp->setEnabled(true);
            // Days has to be 0 < days <= 365
            $rp->setDays(364);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(false);
            $l->setRead(false);
            $l->setWrite(false);

            $m = new Metrics();
            $m->setVersion('1.0');
            $m->setEnabled(false);
            $m->setIncludeAPIs(null);
            $m->setRetentionPolicy($rp);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret,$sp);
        }

        return $ret;
    }

    public static function getInterestingListContainersOptions()
    {
        $ret = array();


        $options = new ListContainersOptions();
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $marker = '/' . self::$_accountName . '/' . self::$testContainerNames[1];
        $options->setMarker($marker);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $marker = '/' . self::$_accountName . '/' . self::$nonExistContainerPrefix;
        $options->setMarker($marker);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $maxResults = 2;
        $options->setMaxResults($maxResults);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $prefix = self::$testUniqueId;
        $options->setPrefix($prefix);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $prefix = self::$nonExistContainerPrefix;
        $options->setPrefix($prefix);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $prefix = self::$testContainerNames[1];
        $options->setPrefix($prefix);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $timeout = -1;
        $options->setTimeout($timeout);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $timeout = 60;
        $options->setTimeout($timeout);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $includeMetadata = true;
        $options->setIncludeMetadata($includeMetadata);
        array_push($ret, $options);

        $options = new ListContainersOptions();
        $marker = '/' . self::$_accountName . '/' . self::$testContainerNames[1];
        $maxResults = 2;
        $prefix = self::$testUniqueId;
        $timeout = 60;
        $includeMetadata = true;
        $options->setMarker($marker);
        $options->setMaxResults($maxResults);
        $options->setPrefix($prefix);
        $options->setTimeout($timeout);
        $options->setIncludeMetadata($includeMetadata);
        array_push($ret, $options);

        return $ret;
    }

    public static function getInterestingMetadata()
    {
        $ret = array();

        $metadata = array();
        array_push($ret, $metadata);

        array_push($ret, self::getNiceMetadata());

        // Some metadata that HTTP will not like.
        $metadata = array('<>000' => '::::value');
        array_push($ret, $metadata);

        return $ret;
    }

    public static function getNiceMetadata()
    {
        return array(
            'key' => 'value',
            'foo' => 'bar',
            'baz' => 'boo');
    }

    public static function getInterestingCreateBlobOptions()
    {
        $ret = array();

        $options = new CreateBlobOptions();
        array_push($ret, $options);

        $options = new CreateBlobOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new CreateBlobOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        $options = new CreateBlobOptions();
        $metadata = array(
            'foo' => 'bar',
            'foo2' => 'bar2',
            'foo3' => 'bar3');
        $options->setMetadata($metadata);
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new CreateBlobOptions();
        $metadata = array('foo' => 'bar');
        $options->setMetadata($metadata);
        $options->setTimeout(-10);
        array_push($ret, $options);

        return $ret;
    }

    public static function getInterestingListBlobsOptions()
    {
        $ret = array();

        $options = new ListBlobsOptions();
        array_push($ret, $options);

        $options = new ListBlobsOptions();
        $options->setMaxResults(2);
        array_push($ret, $options);

        $options = new ListBlobsOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new ListBlobsOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        $options = new ListBlobsOptions();
        $options->setPrefix(self::$nonExistBlobPrefix);
        array_push($ret, $options);

        $options = new ListBlobsOptions();
        $options->setPrefix(self::$testUniqueId);
        array_push($ret, $options);

        $options = new ListBlobsOptions();
        // Cannot set Marker to arbitrary values. Must only use if the previous request returns a NextMarker.
        //            $options->setMarker('abc');
        // So, add logic in listBlobsWorker to loop and setMarker if there is a NextMarker.
        $options->setMaxResults(2);
        $options->setPrefix(self::$testUniqueId);
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new ListBlobsOptions();
        $options->setMaxResults(3);
        $options->setPrefix(self::$testUniqueId);
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new ListBlobsOptions();
        $options->setMaxResults(4);
        $options->setPrefix(self::$testUniqueId);
        $options->setTimeout(10);
        array_push($ret, $options);

        return $ret;
    }

    public static function getInterestingCreateContainerOptions()
    {
        $ret = array();

        $options = new CreateContainerOptions();
        array_push($ret, $options);

        $options = new CreateContainerOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new CreateContainerOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        $options = new CreateContainerOptions();
        $options->setPublicAccess('container');
        array_push($ret, $options);

        $options = new CreateContainerOptions();
        $options->setPublicAccess('blob');
        array_push($ret, $options);

        $options = new CreateContainerOptions();
        $metadata = array(
            'foo' => 'bar',
            'boo' => 'baz',
        );
        $options->setMetadata($metadata);
        array_push($ret, $options);

        return $ret;
    }

    public static function getInterestingDeleteContainerOptions()
    {
        $ret = array();

             $past = new \DateTime("01/01/2010");
        $future = new \DateTime("01/01/2020");

        $options = new DeleteContainerOptions();
        array_push($ret, $options);

        $options = new DeleteContainerOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new DeleteContainerOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        $options = new DeleteContainerOptions();
        $options->setAccessCondition(AccessCondition::ifModifiedSince($past));
        array_push($ret, $options);

        $options = new DeleteContainerOptions();
        $options->setAccessCondition(AccessCondition::ifNotModifiedSince($past));
        array_push($ret, $options);

        $options = new DeleteContainerOptions();
        $options->setAccessCondition(AccessCondition::ifModifiedSince($future));
        array_push($ret, $options);

        $options = new DeleteContainerOptions();
        $options->setAccessCondition(AccessCondition::ifNotModifiedSince($future));
        array_push($ret, $options);

        return $ret;
    }

    public static function getSetContainerMetadataOptions()
    {
        $ret = array();

        $options = new SetContainerMetadataOptions();
        array_push($ret, $options);

        $options = new SetContainerMetadataOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new SetContainerMetadataOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        // Set Container Metadata only supports the If-Modified-Since access condition.
        // But easier to special-case If-Unmodified-Since in the test.
        foreach(self::getTemporalAccessConditions() as $ac)  {
            $options = new SetContainerMetadataOptions();
            $options->setAccessCondition($ac);
            array_push($ret, $options);
        }

        return $ret;
    }

    public static function getSetBlobMetadataOptions()
    {
        $ret = array();

        $options = new SetBlobMetadataOptions();
        array_push($ret, $options);

        $options = new SetBlobMetadataOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new SetBlobMetadataOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        foreach(self::getAllAccessConditions() as $ac)  {
            $options = new SetBlobMetadataOptions();
            $options->setAccessCondition($ac);
            array_push($ret, $options);
        }

        // TODO: Make sure the lease id part is tested in the leasing part.
        //        $options = new SetBlobMetadataOptions();
        //        $options->setLeaseId(leaseId)
        //        array_push($ret, $options);

        return $ret;
    }

    public static function getGetBlobPropertiesOptions()
    {
        $ret = array();

        $options = new GetBlobPropertiesOptions();
        array_push($ret, $options);

        $options = new GetBlobPropertiesOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new GetBlobPropertiesOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        // Get Blob Properties only supports the temporal access conditions.
        foreach(self::getTemporalAccessConditions() as $ac)  {
            $options = new GetBlobPropertiesOptions();
            $options->setAccessCondition($ac);
            array_push($ret, $options);
        }

        return $ret;
    }

    public static function getSetBlobPropertiesOptions()
    {
        $ret = array();

        $options = new SetBlobPropertiesOptions();
        array_push($ret, $options);

        $options = new SetBlobPropertiesOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new SetBlobPropertiesOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        // Get Blob Properties only supports the temporal access conditions.
        foreach(self::getTemporalAccessConditions() as $ac)  {
            $options = new SetBlobPropertiesOptions();
            $options->setAccessCondition($ac);
            array_push($ret, $options);
        }

        $options = new SetBlobPropertiesOptions();
        $options->setBlobCacheControl('setBlobCacheControl');
        array_push($ret, $options);

        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentEncoding('setBlobContentEncoding');
        array_push($ret, $options);

        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentLanguage('setBlobContentLanguage');
        array_push($ret, $options);

        // Note: This is not allowed on block blobs
        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentLength(2048);
        array_push($ret, $options);

        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentMD5('d41d8cd98f00b204e9800998ecf8427e');
        array_push($ret, $options);

        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentType('setContentType');
        array_push($ret, $options);

        // TODO: Handle Lease ID
        //        $options = new SetBlobPropertiesOptions();
        //        $options->setLeaseId('setLeaseId');
        //        array_push($ret, $options);

        // Note: This is not allowed on block blobs
        $options = new SetBlobPropertiesOptions();
        $options->setSequenceNumber(0);
        $options->setSequenceNumberAction('update');
        array_push($ret, $options);

        return $ret;
    }

    public static function getInterestingACL()
    {
        $ret = array();

        $past = new \DateTime("01/01/2010");
        $future = new \DateTime("01/01/2020");

        $acl = new ContainerAcl();
        array_push($ret, $acl);

        $acl = new ContainerAcl();
        $acl->setPublicAccess(PublicAccessType::NONE);
        array_push($ret, $acl);

        $acl = new ContainerAcl();
        $acl->setPublicAccess(PublicAccessType::BLOBS_ONLY);
        array_push($ret, $acl);

        $acl = new ContainerAcl();
        $acl->setPublicAccess(PublicAccessType::CONTAINER_AND_BLOBS);
        array_push($ret, $acl);

        $acl = new ContainerAcl();
        $acl->addSignedIdentifier('123', $past, $future, 'rw');
        array_push($ret, $acl);

        return $ret;
    }

    public static function getGetBlobOptions()
    {
        $ret = array();

        $options = new GetBlobOptions();
        array_push($ret, $options);

        $options = new GetBlobOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new GetBlobOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        // Get Blob only supports the temporal access conditions.
        foreach(self::getTemporalAccessConditions() as $ac)  {
            $options = new GetBlobOptions();
            $options->setAccessCondition($ac);
            array_push($ret, $options);
        }

        $options = new GetBlobOptions();
        $options->setRangeStart(50);
        $options->setRangeEnd(200);
        array_push($ret, $options);

        $options = new GetBlobOptions();
        $options->setRangeStart(50);
        $options->setRangeEnd(200);
        $options->setComputeRangeMD5(true);
        array_push($ret, $options);

        $options = new GetBlobOptions();
        $options->setRangeStart(50);
        array_push($ret, $options);

        $options = new GetBlobOptions();
        $options->setComputeRangeMD5(true);
        array_push($ret, $options);

        $options = new GetBlobOptions();
        $options->setRangeEnd(200);
        $options->setComputeRangeMD5(true);
        array_push($ret, $options);

        $options = new GetBlobOptions();
        $options->setRangeEnd(200);
        array_push($ret, $options);

        $options = new GetBlobOptions();
        $options->setSnapshot('placeholder');
        array_push($ret, $options);

        // TODO: Handle Lease ID
        //        $options = new GetBlobOptions();
        //        $options->setLeaseId('setLeaseId');
        //        array_push($ret, $options);

        return $ret;
    }

    public static function getDeleteBlobOptions()
    {
        $ret = array();

        $options = new DeleteBlobOptions();
        array_push($ret, $options);

        $options = new DeleteBlobOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new DeleteBlobOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        foreach(self::getAllAccessConditions() as $ac)  {
            $options = new DeleteBlobOptions();
            $options->setAccessCondition($ac);
            array_push($ret, $options);
        }

        $options = new DeleteBlobOptions();
        $options->setDeleteSnaphotsOnly(true);
        array_push($ret, $options);

        $options = new DeleteBlobOptions();
        $options->setDeleteSnaphotsOnly(false);
        array_push($ret, $options);

        $options = new DeleteBlobOptions();
        $options->setSnapshot('placeholder');
        array_push($ret, $options);

        // TODO: Handle Lease ID
        //        $options = new DeleteBlobOptions();
        //        $options->setLeaseId('setLeaseId');
        //        array_push($ret, $options);

        return $ret;
    }

    public static function getCreateBlobSnapshotOptions()
    {
        $ret = array();

        $options = new CreateBlobSnapshotOptions();
        array_push($ret, $options);

        $options = new CreateBlobSnapshotOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new CreateBlobSnapshotOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        foreach(self::getAllAccessConditions() as $ac)  {
            $options = new CreateBlobSnapshotOptions();
            $options->setAccessCondition($ac);
            array_push($ret, $options);
        }

        $options = new CreateBlobSnapshotOptions();
        $options->setMetadata(self::getNiceMetadata());
        array_push($ret, $options);

        // TODO: Handle Lease ID
        //        $options = new CreateBlobSnapshotOptions();
        //        $options->setLeaseId('setLeaseId');
        //        array_push($ret, $options);

        return $ret;
    }

    public static function getCopyBlobOptions()
    {
        $ret = array();

        $options = new CopyBlobOptions();
        array_push($ret, $options);

        $options = new CopyBlobOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new CopyBlobOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        foreach(self::getAllAccessConditions() as $ac)  {
            $options = new CopyBlobOptions();
            $options->setSourceAccessCondition($ac);
            array_push($ret, $options);
        }

        foreach(self::getAllAccessConditions() as $ac)  {
            $options = new CopyBlobOptions();
            $options->setAccessCondition($ac);
            array_push($ret, $options);
        }

        $options = new CopyBlobOptions();
        $metadata = array(
            'Xkey' => 'Avalue',
            'Yfoo' => 'Bbar',
            'Zbaz' => 'Cboo');
        $options->setMetadata($metadata);
        array_push($ret, $options);

        $options = new CopyBlobOptions();
        $options->setSourceSnapshot('placeholder');
        array_push($ret, $options);

        // TODO: Handle Lease ID
        //        $options = new CopyBlobOptions();
        //        $options->setLeaseId('setLeaseId');
        //        array_push($ret, $options);
        //
        //        $options = new CopyBlobOptions();
        //        $options->setSourceLeaseId('setSourceLeaseId');
        //        array_push($ret, $options);

        return $ret;
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Blob;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\BlobBlockType;
use MicrosoftAzure\Storage\Blob\Models\Block;
use MicrosoftAzure\Storage\Blob\Models\BlockList;
use MicrosoftAzure\Storage\Blob\Models\ContainerACL;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Blob\Models\ListContainersOptions;
use MicrosoftAzure\Storage\Blob\Models\PageRange;
use MicrosoftAzure\Storage\Blob\Models\PublicAccessType;
use MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

class BlobServiceIntegrationTest extends IntegrationTestBase
{
    private static $_testContainersPrefix = 'sdktest-';
    private static $_createableContainersPrefix = 'csdktest-';
    private static $_blob_for_root_container = 'sdktestroot';
    private static $_creatable_container_1;
    private static $_creatable_container_2;
    private static $_creatable_container_3;
    private static $_creatable_container_4;
    private static $_test_container_for_blobs;
    private static $_test_container_for_blobs_2;
    private static $_test_container_for_listing;
    private static $_creatableContainers;
    private static $_testContainers;

    private static $isOneTimeSetup = false;

    public function setUp()
    {
        parent::setUp();
        if (!self::$isOneTimeSetup) {
            $this->doOneTimeSetup();
            self::$isOneTimeSetup = true;
        }
    }

    private function doOneTimeSetup()
    {
        // Setup container names array (list of container names used by
        // integration tests)
        $rint = mt_rand(0, 1000000);
        self::$_testContainers = array();
        for ($i = 0; $i < 10; $i++) {
            self::$_testContainers[$i] = self::$_testContainersPrefix . ($rint + $i);
        }

        self::$_creatableContainers = array();
        for ($i = 0; $i < 10; $i++) {
            self::$_creatableContainers[$i] = self::$_createableContainersPrefix . ($rint + $i);
        }

        self::$_creatable_container_1 = self::$_creatableContainers[0];
        self::$_creatable_container_2 = self::$_creatableContainers[1];
        self::$_creatable_container_3 = self::$_creatableContainers[2];
        self::$_creatable_container_4 = self::$_creatableContainers[3];

        self::$_test_container_for_blobs = self::$_testContainers[0];
        self::$_test_container_for_blobs_2 = self::$_testContainers[1];
        self::$_test_container_for_listing = self::$_testContainers[2];

        // Create all test containers and their content
        $this->createContainers(self::$_testContainers, self::$_testContainersPrefix);
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            $inst = new IntegrationTestBase();
            $inst->setUp();
            $inst->deleteContainers(self::$_testContainers, self::$_testContainersPrefix);
            $inst->deleteContainers(self::$_creatableContainers,self::$_createableContainersPrefix);
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }
    
    protected function tearDown()
    {
        // tearDown of parent will delete the container created in setUp
        // Do nothing here
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
     */
    public function testGetServicePropertiesWorks()
    {
        // Act
        $shouldReturn = false;
        try {
            $props = $this->restProxy->getServiceProperties()->getValue();
            $this->assertTrue(!$this->isEmulated(), 'Should succeed if and only if not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        // Assert
        $this->assertNotNull($props, '$props');
        $this->assertNotNull($props->getLogging(), '$props->getLogging');
        $this->assertNotNull($props->getLogging()->getRetentionPolicy(), '$props->getLogging()->getRetentionPolicy');
        $this->assertNotNull($props->getLogging()->getVersion(), '$props->getLogging()->getVersion');
        $this->assertNotNull($props->getMetrics()->getRetentionPolicy(), '$props->getMetrics()->getRetentionPolicy');
        $this->assertNotNull($props->getMetrics()->getVersion(), '$props->getMetrics()->getVersion');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setServiceProperties
     */
    public function testSetServicePropertiesWorks()
    {
        // Act
        $shouldReturn = false;
        try {
            $props = $this->restProxy->getServiceProperties()->getValue();
            $this->assertTrue(!$this->isEmulated(), 'Should succeed if and only if not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        $props->getLogging()->setRead(true);
        $this->restProxy->setServiceProperties($props);

        $props = $this->restProxy->getServiceProperties()->getValue();

        // Assert
        $this->assertNotNull($props, '$props');
        $this->assertNotNull($props->getLogging(), '$props->getLogging');
        $this->assertNotNull($props->getLogging()->getRetentionPolicy(), '$props->getLogging()->getRetentionPolicy');
        $this->assertNotNull($props->getLogging()->getVersion(), '$props->getLogging()->getVersion');
        $this->assertTrue($props->getLogging()->getRead(), '$props->getLogging()->getRead');
        $this->assertNotNull($props->getMetrics()->getRetentionPolicy(), '$props->getMetrics()->getRetentionPolicy');
        $this->assertNotNull($props->getMetrics()->getVersion(), '$props->getMetrics()->getVersion');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testCreateContainerWorks()
    {
        // Act
        $this->restProxy->createContainer(self::$_creatable_container_1);

        // Assert
        $opts = new ListContainersOptions();
        $opts->setPrefix(self::$_creatable_container_1);
        $results = $this->restProxy->listContainers($opts);

        $this->assertNotNull($results, '$results');
        $this->assertEquals(1, count($results->getContainers()), 'count($results->getContainers())');
        $container0 = $results->getContainers();
        $container0 = $container0[0];
        $this->assertEquals(self::$_creatable_container_1, $container0->getName(), '$results->getContainers()[0]->getName');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerACL
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testCreateContainerWithMetadataWorks()
    {
        // Act
        $opts = new CreateContainerOptions();
        $opts->setPublicAccess('blob');
        $opts->addMetadata('test', 'bar');
        $opts->addMetadata('blah', 'bleah');
        $this->restProxy->createContainer(self::$_creatable_container_2, $opts);

        $prop = $this->restProxy->getContainerMetadata(self::$_creatable_container_2);
        $prop2 = $this->restProxy->getContainerProperties(self::$_creatable_container_2);
        $acl = $this->restProxy->getContainerACL(self::$_creatable_container_2)->getContainerACL();

        $opts = new ListContainersOptions();
        $opts->setPrefix(self::$_creatable_container_2);
        $opts->setIncludeMetadata(true);
        $results2 = $this->restProxy->listContainers($opts);

        $this->restProxy->deleteContainer(self::$_creatable_container_2);

        // Assert
        $this->assertNotNull($prop, '$prop');
        $this->assertNotNull($prop->getETag(), '$prop->getETag()');
        $this->assertNotNull($prop->getLastModified(), '$prop->getLastModified()');
        $this->assertNotNull($prop->getMetadata(), '$prop->getMetadata()');
        $this->assertEquals(2, count($prop->getMetadata()), 'count($prop->getMetadata())');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('test', $prop->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'test\', $prop->getMetadata())');
        $this->assertTrue(!(array_search('bar', $prop->getMetadata()) === FALSE), '!(array_search(\'bar\', $prop->getMetadata()) === FALSE)');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('blah', $prop->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'blah\', $prop->getMetadata())');
        $this->assertTrue(!(array_search('bleah', $prop->getMetadata()) === FALSE), '!(array_search(\'bleah\', $prop->getMetadata()) === FALSE)');

        $this->assertNotNull($prop2, '$prop2');
        $this->assertNotNull($prop2->getETag(), '$prop2->getETag()');
        $this->assertNotNull($prop2->getLastModified(), '$prop2->getLastModified()');
        $this->assertNotNull($prop2->getMetadata(), '$prop2->getMetadata()');
        $this->assertEquals(2, count($prop2->getMetadata()), 'count($prop2->getMetadata())');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('test', $prop2->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'test\', $prop2->getMetadata())');
        $this->assertTrue(!(array_search('bar', $prop2->getMetadata()) === FALSE), '!(array_search(\'bar\', $prop2->getMetadata()) === FALSE)');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('blah', $prop2->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'blah\', $prop2->getMetadata())');
        $this->assertTrue(!(array_search('bleah', $prop2->getMetadata()) === FALSE), '!(array_search(\'bleah\', $prop2->getMetadata()) === FALSE)');

        $this->assertNotNull($results2, '$results2');
        $this->assertEquals(1, count($results2->getContainers()), 'count($results2->getContainers())');
        $container0 = $results2->getContainers();
        $container0 = $container0[0];
        // The capitalizaion gets changed.
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('test', $container0->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'test\', $container0->getMetadata())');
        $this->assertTrue(!(array_search('bar', $container0->getMetadata()) === FALSE), '!(array_search(\'bar\', $container0->getMetadata()) === FALSE)');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('blah', $container0->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'blah\', $container0->getMetadata())');
        $this->assertTrue(!(array_search('bleah', $container0->getMetadata()) === FALSE), '!(array_search(\'bleah\', $container0->getMetadata()) === FALSE)');

        $this->assertNotNull($acl, '$acl');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    public function testSetContainerMetadataWorks()
    {
        // Act
        $this->restProxy->createContainer(self::$_creatable_container_3);

        $metadata = array(
            'test' => 'bar',
            'blah' => 'bleah');
        $this->restProxy->setContainerMetadata(self::$_creatable_container_3, $metadata);
        $prop = $this->restProxy->getContainerMetadata(self::$_creatable_container_3);

        // Assert
        $this->assertNotNull($prop, '$prop');
        $this->assertNotNull($prop->getETag(), '$prop->getETag()');
        $this->assertNotNull($prop->getLastModified(), '$prop->getLastModified()');
        $this->assertNotNull($prop->getMetadata(), '$prop->getMetadata()');
        $this->assertEquals(2, count($prop->getMetadata()), 'count($prop->getMetadata())');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('test', $prop->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'test\', $prop->getMetadata())');
        $this->assertTrue(!(array_search('bar', $prop->getMetadata()) === FALSE), '!(array_search(\'bar\', $prop->getMetadata()) === FALSE)');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('blah', $prop->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'blah\', $prop->getMetadata())');
        $this->assertTrue(!(array_search('bleah', $prop->getMetadata()) === FALSE), '!(array_search(\'bleah\', $prop->getMetadata()) === FALSE)');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerACL
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerACL
     */
    public function testSetContainerACLWorks()
    {
        // Arrange
        $container = self::$_creatable_container_4;

        $expiryStartDate = new \DateTime;
        $expiryStartDate->setDate(2010, 1, 1);
        $expiryEndDate = new \DateTime;
        $expiryEndDate->setDate(2020, 1, 1);

        // Act
        $this->restProxy->createContainer($container);
        $acl = new ContainerACL();
        $acl->setPublicAccess(PublicAccessType::BLOBS_ONLY);

        $acl->addSignedIdentifier('test', $expiryStartDate, $expiryEndDate, 'rwd');
        $this->restProxy->setContainerACL($container, $acl);

        $res = $this->restProxy->getContainerACL($container);
        $acl2 = $res->getContainerACL();
        $this->restProxy->deleteContainer($container);

        // Assert
        $this->assertNotNull($acl2, '$acl2');
        $this->assertNotNull($res->getETag(), '$res->getETag()');
        $this->assertNotNull($res->getLastModified(), '$res->getLastModified()');
        $this->assertNotNull($acl2->getPublicAccess(), '$acl2->getPublicAccess()');
        $this->assertEquals(PublicAccessType::BLOBS_ONLY, $acl2->getPublicAccess(), '$acl2->getPublicAccess()');
        $this->assertEquals(1, count($acl2->getSignedIdentifiers()), 'count($acl2->getSignedIdentifiers())');
        $signedids = $acl2->getSignedIdentifiers();
        $this->assertEquals('test', $signedids[0]->getId(), '$signedids[0]->getId()');
        $expiryStartDate = $expiryStartDate->setTimezone(new \DateTimeZone('UTC'));
        $expiryEndDate = $expiryEndDate->setTimezone(new \DateTimeZone('UTC'));
        $this->assertEquals(
                Utilities::convertToDateTime($expiryStartDate),
                Utilities::convertToDateTime($signedids[0]->getAccessPolicy()->getStart()),
                '$signedids[0]->getAccessPolicy()->getStart()');
        $this->assertEquals(
                Utilities::convertToDateTime($expiryEndDate),
                Utilities::convertToDateTime($signedids[0]->getAccessPolicy()->getExpiry()),
                '$signedids[0]->getAccessPolicy()->getExpiry()');
        $this->assertEquals('rwd', $signedids[0]->getAccessPolicy()->getPermission(), '$signedids[0]->getAccessPolicy()->getPermission()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testListContainersWorks()
    {
        // Act
        $results = $this->restProxy->listContainers();

        // Assert
        $this->assertNotNull($results, '$results');
        $this->assertTrue(count(self::$_testContainers) <= count($results->getContainers()), 'count(self::$_testContainers) <= count($results->getContainers())');
        $container0 = $results->getContainers();
        $container0 = $container0[0];
        $this->assertNotNull($container0->getName(), '$container0->getName()');
        $this->assertNotNull($container0->getMetadata(), '$container0->getMetadata()');
        $this->assertNotNull($container0->getProperties(), '$container0->getProperties()');
        $this->assertNotNull($container0->getProperties()->getETag(), '$container0->getProperties()->getETag()');
        $this->assertNotNull($container0->getProperties()->getLastModified(), '$container0->getProperties()->getLastModified()');
        $this->assertNotNull($container0->getUrl(), '$container0->getUrl()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testListContainersWithPaginationWorks()
    {
        // Act
        $opts = new ListContainersOptions();
        $opts->setMaxResults(3);
        $results = $this->restProxy->listContainers($opts);
        $opts2 = new ListContainersOptions();
        $opts2->setMarker($results ->getNextMarker());
        $results2 = $this->restProxy->listContainers($opts2);

        // Assert
        $this->assertNotNull($results, '$results');
        $this->assertEquals(3, count($results->getContainers()), 'count($results->getContainers())');
        $this->assertNotNull($results->getNextMarker(), '$results->getNextMarker()');
        $this->assertEquals(3, $results->getMaxResults(), '$results->getMaxResults()');

        $this->assertNotNull($results2, '$results2');
        $this->assertTrue(count(self::$_testContainers) - 3 <= count($results2->getContainers()), 'count(self::$_testContainers) - 3 <= count($results2->getContainers())');
        $this->assertEquals('', $results2->getNextMarker(), '$results2->getNextMarker()');
        $this->assertEquals(0, $results2->getMaxResults(), '$results2->getMaxResults()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function testListContainersWithPrefixWorks()
    {
        // Act
        $opts = new ListContainersOptions();
        $opts->setPrefix(self::$_testContainersPrefix);
        $opts->setMaxResults(3);
        $results = $this->restProxy->listContainers($opts);
        // Assert
        $this->assertNotNull($results, '$results');
        $this->assertEquals(3, count($results->getContainers()), 'count($results->getContainers())');
        $this->assertNotNull($results->getNextMarker(), '$results->getNextMarker()');
        $this->assertEquals(3, $results->getMaxResults(), '$results->getMaxResults()');

        // Act
        $opts = new ListContainersOptions();
        $opts->setPrefix( self::$_testContainersPrefix);
        $opts->setMarker($results->getNextMarker());
        $results2 = $this->restProxy->listContainers($opts);

        // Assert
        $this->assertNotNull($results2, '$results2');
        $this->assertNull($results2->getNextMarker(), '$results2->getNextMarker()');
        $this->assertEquals(0, $results2->getMaxResults(), '$results2->getMaxResults()');

        // Act
        $opts = new ListContainersOptions();
        $opts->setPrefix(self::$_testContainersPrefix);
        $results3 = $this->restProxy->listContainers($opts);

        // Assert
        $this->assertEquals(count($results->getContainers()) + count($results2->getContainers()), count($results3->getContainers()), 'count($results3->getContainers())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testWorkingWithRootContainersWorks()
    {
        // Ensure root container exists
        $this->createContainerWithRetry('$root', new CreateContainerOptions());

        // Work with root container explicitly ('$root')
        {
            // Act
            $this->restProxy->createPageBlob('$root', self::$_blob_for_root_container, 512);
            $list = $this->restProxy->listBlobs('$root');
            $properties = $this->restProxy->getBlobProperties('$root', self::$_blob_for_root_container);
            $metadata = $this->restProxy->getBlobMetadata('$root', self::$_blob_for_root_container);

            // Assert
            $this->assertNotNull($list, '$list');
            $this->assertTrue(1 <= count($list->getBlobs()), '1 <= count($list->getBlobs())');
            $this->assertNotNull($properties, '$properties');
            $this->assertNotNull($metadata, '$metadata');

            // Act
            $this->restProxy->deleteBlob('$root', self::$_blob_for_root_container);
        }

        // Work with root container implicitly ('')
        {
            // Act
            $this->restProxy->createPageBlob('', self::$_blob_for_root_container, 512);
            // '$root' must be explicit when listing blobs in the root container
            $list = $this->restProxy->listBlobs('$root');
            $properties = $this->restProxy->getBlobProperties('', self::$_blob_for_root_container);
            $metadata = $this->restProxy->getBlobMetadata('', self::$_blob_for_root_container);

            // Assert
            $this->assertNotNull($list, '$list');
            $this->assertTrue(1 <= count($list->getBlobs()), '1 <= count($list->getBlobs())');
            $this->assertNotNull($properties, '$properties');
            $this->assertNotNull($metadata, '$metadata');

            // Act
            $this->restProxy->deleteBlob('', self::$_blob_for_root_container);
        }

        // Cleanup.
        $this->restProxy->deleteContainer('$root');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testListBlobsWorks()
    {
        // Arrange
        $blobNames = array( 'myblob1', 'myblob2', 'other-blob1', 'other-blob2' );
        foreach($blobNames as $blob)  {
            $this->restProxy->createPageBlob(self::$_test_container_for_listing, $blob, 512);
        }

        // Act
        $results = $this->restProxy->listBlobs(self::$_test_container_for_listing);

        foreach($blobNames as $blob)  {
            $this->restProxy->deleteBlob(self::$_test_container_for_listing, $blob);
        }

        // Assert
        $this->assertNotNull($results, '$results');
        $this->assertEquals(4, count($results->getBlobs()), 'count($results->getBlobs())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testListBlobsWithPrefixWorks()
    {
        // Arrange
        $blobNames = array( 'myblob1', 'myblob2', 'otherblob1', 'otherblob2' );
        foreach($blobNames as $blob)  {
            $this->restProxy->createPageBlob(self::$_test_container_for_listing, $blob, 512);
        }

        // Act
        $opts = new ListBlobsOptions();
        $opts->setPrefix('myblob');
        $results = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);
        $opts = new ListBlobsOptions();
        $opts->setPrefix('o');
        $results2 = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);

        foreach($blobNames as $blob)  {
            $this->restProxy->deleteBlob(self::$_test_container_for_listing, $blob);
        }

        // Assert
        $this->assertNotNull($results, '$results');
        $this->assertEquals(2, count($results->getBlobs()), 'count($results->getBlobs())');
        $blobs = $results->getBlobs();
        $this->assertEquals('myblob1', $blobs[0]->getName(), '$blobs[0]->getName()');
        $this->assertEquals('myblob2', $blobs[1]->getName(), '$blobs[1]->getName()');

        $this->assertNotNull($results2, '$results2');
        $this->assertEquals(2, count($results2->getBlobs()), 'count($results2->getBlobs())');
        $blobs = $results2->getBlobs();
        $this->assertEquals('otherblob1', $blobs[0]->getName(), '$blobs[0]->getName()');
        $this->assertEquals('otherblob2', $blobs[1]->getName(), '$blobs[1]->getName()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testListBlobsWithOptionsWorks()
    {
        // Arrange
        $blobNames = array( 'myblob1', 'myblob2', 'otherblob1', 'otherblob2' );
        foreach($blobNames as $blob)  {
            $this->restProxy->createPageBlob(self::$_test_container_for_listing, $blob, 512);
        }

        // Act
        $opts = new ListBlobsOptions();
        $opts->setIncludeMetadata(true);
        $opts->setIncludeSnapshots(true);
        $results = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);

        foreach($blobNames as $blob)  {
            $this->restProxy->deleteBlob(self::$_test_container_for_listing, $blob);
        }

        // Assert
        $this->assertNotNull($results, '$results');
        $this->assertEquals(4, count($results->getBlobs()), 'count($results->getBlobs())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testListBlobsWithDelimiterWorks()
    {
        // Arrange
        $blobNames = array( 'myblob1', 'myblob2', 'dir1-blob1', 'dir1-blob2', 'dir2-dir21-blob3', 'dir2-dir22-blob3' );
        foreach($blobNames as $blob)  {
            $this->restProxy->createPageBlob(self::$_test_container_for_listing, $blob, 512);
        }

        // Act
        $opts = new ListBlobsOptions();
        $opts->setDelimiter('-');
        $results = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);
        $opts->setPrefix('dir1-');
        $results2 = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);
        $opts->setPrefix('dir2-');
        $results3 = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);
        $opts->setPrefix('dir2-dir21-');
        $results4 = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);
        $opts->setPrefix('dir2-dir22-');
        $results5 = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);
        $opts->setPrefix('dir2-dir44-');
        $results6 = $this->restProxy->listBlobs(self::$_test_container_for_listing, $opts);

        foreach($blobNames as $blob)  {
            $this->restProxy->deleteBlob(self::$_test_container_for_listing, $blob);
        }

        // Assert
        $this->assertNotNull($results, '$results');
        $this->assertEquals(2, count($results->getBlobs()), 'count($results->getBlobs())');
        $this->assertEquals(2, count($results->getBlobPrefixes()), 'count($results->getBlobPrefixes())');

        $this->assertEquals(2, count($results2->getBlobs()), 'count($results2->getBlobs())');
        $this->assertEquals(0, count($results2->getBlobPrefixes()), 'count($results2->getBlobPrefixes())');

        $this->assertEquals(0, count($results3->getBlobs()), 'count($results3->getBlobs())');
        $this->assertEquals(2, count($results3->getBlobPrefixes()), 'count($results3->getBlobPrefixes())');

        $this->assertEquals(1, count($results4->getBlobs()), 'count($results4->getBlobs())');
        $this->assertEquals(0, count($results4->getBlobPrefixes()), 'count($results4->getBlobPrefixes())');

        $this->assertEquals(1, count($results5->getBlobs()), 'count($results5->getBlobs())');
        $this->assertEquals(0, count($results5->getBlobPrefixes()), 'count($results5->getBlobPrefixes())');

        $this->assertEquals(0, count($results6->getBlobs()), 'count($results6->getBlobs())');
        $this->assertEquals(0, count($results6->getBlobPrefixes()), 'count($results6->getBlobPrefixes())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     */
    public function testCreatePageBlobWorks()
    {
        // Act
        $this->restProxy->createPageBlob(self::$_test_container_for_blobs, 'test', 512);

        // Assert
        $this->assertTrue(true, 'success');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     */
    public function testCreatePageBlobWithOptionsWorks()
    {
        // Act
        $opts = new CreateBlobOptions();
        $opts->setBlobCacheControl('test');
        $opts->setBlobContentEncoding('UTF-8');
        $opts->setBlobContentLanguage('en-us');
        // opts->setBlobContentMD5('1234');
        $opts->setBlobContentType('text/plain');
        $opts->setCacheControl('test');
        $opts->setContentEncoding('UTF-8');
        // $opts->setContentMD5('1234');
        $opts->setContentType('text/plain');
        $this->restProxy->createPageBlob(self::$_test_container_for_blobs, 'test', 512, $opts);

        $result = $this->restProxy->getBlobProperties(self::$_test_container_for_blobs, 'test');

        // Assert
        $this->assertNotNull($result, '$result');

        $this->assertNotNull($result->getMetadata(), '$result->getMetadata()');
        $this->assertEquals(0, count($result->getMetadata()), 'count($result->getMetadata())');

        $props = $result->getProperties();
        $this->assertNotNull($props, '$props');
        $this->assertEquals('test', $props->getCacheControl(), '$props->getCacheControl()');
        $this->assertEquals('UTF-8', $props->getContentEncoding(), '$props->getContentEncoding()');
        $this->assertEquals('en-us', $props->getContentLanguage(), '$props->getContentLanguage()');
        $this->assertEquals('text/plain', $props->getContentType(), '$props->getContentType()');
        $this->assertEquals(512, $props->getContentLength(), '$props->getContentLength()');
        $this->assertNotNull($props->getETag(), '$props->getETag()');
        $this->assertNull($props->getContentMD5(), '$props->getContentMD5()');
        $this->assertNotNull($props->getLastModified(), '$props->getLastModified()');
        $this->assertEquals('PageBlob', $props->getBlobType(), '$props->getBlobType()');
        $this->assertEquals('unlocked', $props->getLeaseStatus(), '$props->getLeaseStatus()');
        $this->assertEquals(0, $props->getSequenceNumber(), '$props->getSequenceNumber()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::clearBlobPages
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     */
    public function testClearBlobPagesWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test';
        $this->restProxy->createPageBlob($container, $blob, 512);

        $result = $this->restProxy->clearBlobPages($container, $blob, new PageRange(0, 511));

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNull($result->getContentMD5(), '$result->getContentMD5()');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertEquals(0, $result->getSequenceNumber(), '$result->getSequenceNumber()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobPages
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     */
    public function testCreateBlobPagesWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test';
        $content = str_pad('', 512);
        $this->restProxy->createPageBlob($container, $blob, 512);

        $result = $this->restProxy->createBlobPages($container, $blob, new PageRange(0, 511), $content);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getContentMD5(), '$result->getContentMD5()');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertEquals(0, $result->getSequenceNumber(), '$result->getSequenceNumber()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobPages
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listPageBlobRanges
     */
    public function testListBlobRegionsWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test';
        $content = str_pad('', 512);
        $this->restProxy->createPageBlob($container, $blob, 16384 + 512);

        $this->restProxy->createBlobPages($container, $blob, new PageRange(0, 511), $content);
        $this->restProxy->createBlobPages($container, $blob, new PageRange(1024, 1024 + 511), $content);
        $this->restProxy->createBlobPages($container, $blob, new PageRange(8192, 8192 + 511), $content);
        $this->restProxy->createBlobPages($container, $blob, new PageRange(16384, 16384 + 511), $content);

//        $result = $this->restProxy->listBlobRegions($container, $blob);
        $result = $this->restProxy->listPageBlobRanges($container, $blob);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertEquals(16384 + 512, $result->getContentLength(), '$result->getContentLength()');
        $this->assertNotNull($result->getPageRanges(), '$result->getPageRanges()');
        $this->assertEquals(4, count($result->getPageRanges()), 'count($result->getPageRanges())');
        $ranges = $result->getPageRanges();
        $this->assertEquals(0, $ranges[0]->getStart(), '$ranges[0]->getStart()');
        $this->assertEquals(511, $ranges[0]->getEnd(), '$ranges[0]->getEnd()');
        $this->assertEquals(1024, $ranges[1]->getStart(), '$ranges[1]->getStart()');
        $this->assertEquals(1024 + 511, $ranges[1]->getEnd(), '$ranges[1]->getEnd()');
        $this->assertEquals(8192, $ranges[2]->getStart(), '$ranges[2]->getStart()');
        $this->assertEquals(8192 + 511, $ranges[2]->getEnd(), '$ranges[2]->getEnd()');
        $this->assertEquals(16384, $ranges[3]->getStart(), '$ranges[3]->getStart()');
        $this->assertEquals(16384 + 511, $ranges[3]->getEnd(), '$ranges[3]->getEnd()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobBlocks
     */
    public function testListBlobBlocksOnEmptyBlobWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test13';
        $content = str_pad('', 512);
        $this->restProxy->createBlockBlob($container, $blob, $content);

        $result = $this->restProxy->listBlobBlocks($container, $blob);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertEquals(512, $result->getContentLength(), '$result->getContentLength()');
        $this->assertNotNull($result->getCommittedBlocks(), '$result->getCommittedBlocks()');
        $this->assertEquals(0, count($result->getCommittedBlocks()), 'count($result->getCommittedBlocks())');
        $this->assertNotNull($result->getUncommittedBlocks(), '$result->getUncommittedBlocks()');
        $this->assertEquals(0, count($result->getUncommittedBlocks()), 'count($result->getUncommittedBlocks())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobBlock
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobBlocks
     */
    public function testListBlobBlocksWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test14';
        $this->restProxy->createBlockBlob($container, $blob, '');
        $this->restProxy->createBlobBlock($container, $blob, base64_encode('123'), str_pad('', 256));
        $this->restProxy->createBlobBlock($container, $blob, base64_encode('124'), str_pad('', 512));
        $this->restProxy->createBlobBlock($container, $blob, base64_encode('125'), str_pad('', 195));

        $opts = new ListBlobBlocksOptions();
        $opts->setIncludeCommittedBlobs(true);
        $opts->setIncludeUncommittedBlobs(true);
        $result = $this->restProxy->listBlobBlocks($container, $blob, $opts);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertEquals(0, $result->getContentLength(), '$result->getContentLength()');
        $this->assertNotNull($result->getCommittedBlocks(), '$result->getCommittedBlocks()');
        $this->assertEquals(0, count($result->getCommittedBlocks()), 'count($result->getCommittedBlocks())');
        $this->assertNotNull($result->getUncommittedBlocks(), '$result->getUncommittedBlocks()');
        $this->assertEquals(3, count($result->getUncommittedBlocks()), 'count($result->getUncommittedBlocks())');
        $uncom = $result->getUncommittedBlocks();
        $keys = array_keys($uncom);
        $this->assertEquals(base64_encode('123'), $keys[0], '$keys[0]');
        $this->assertEquals(256, $uncom[$keys[0]], '$uncom[$keys[0]]');
        $this->assertEquals(base64_encode('124'), $keys[1], '$keys[1]');
        $this->assertEquals(512, $uncom[$keys[1]], '$uncom[$keys[1]]');
        $this->assertEquals(base64_encode('125'), $keys[2], '$keys[2]');
        $this->assertEquals(195, $uncom[$keys[2]], '$uncom[$keys[2]]');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::commitBlobBlocks
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobBlock
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobBlocks
     */
    public function testListBlobBlocksWithOptionsWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test14';
        $this->restProxy->createBlockBlob($container, $blob, '');
        $this->restProxy->createBlobBlock($container, $blob, base64_encode('123'), str_pad('', 256));

        $blockList = new BlockList();
        $blockList->addUncommittedEntry(base64_encode('123'));
        $this->restProxy->commitBlobBlocks($container, $blob, $blockList);

        $this->restProxy->createBlobBlock($container, $blob, base64_encode('124'), str_pad('', 512));
        $this->restProxy->createBlobBlock($container, $blob, base64_encode('125'), str_pad('', 195));

        $opts = new ListBlobBlocksOptions();
        $opts->setIncludeCommittedBlobs(true);
        $opts->setIncludeUncommittedBlobs(true);
        $result1 = $this->restProxy->listBlobBlocks($container, $blob, $opts);
        $opts = new ListBlobBlocksOptions();
        $opts->setIncludeCommittedBlobs(true);
        $result2 = $this->restProxy->listBlobBlocks($container, $blob, $opts);
        $opts = new ListBlobBlocksOptions();
        $opts->setIncludeUncommittedBlobs(true);
        $result3 = $this->restProxy->listBlobBlocks($container, $blob, $opts);

        // Assert
        $this->assertEquals(1, count($result1->getCommittedBlocks()), 'count($result1->getCommittedBlocks())');
        $this->assertEquals(2, count($result1->getUncommittedBlocks()), 'count($result1->getUncommittedBlocks())');

        $this->assertEquals(1, count($result2->getCommittedBlocks()), 'count($result2->getCommittedBlocks())');
        $this->assertEquals(0, count($result2->getUncommittedBlocks()), 'count($result2->getUncommittedBlocks())');

        $this->assertEquals(0, count($result3->getCommittedBlocks()), 'count($result3->getCommittedBlocks())');
        $this->assertEquals(2, count($result3->getUncommittedBlocks()), 'count($result3->getUncommittedBlocks())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::commitBlobBlocks
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobBlock
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobBlocks
     */
    public function testCommitBlobBlocksWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test14';
        $blockId1 = base64_encode('1fedcba');
        $blockId2 = base64_encode('2abcdef');
        $blockId3 = base64_encode('3zzzzzz');
        $this->restProxy->createBlockBlob($container, $blob, '');
        $this->restProxy->createBlobBlock($container, $blob, $blockId1, str_pad('', 256));
        $this->restProxy->createBlobBlock($container, $blob, $blockId2, str_pad('', 512));
        $this->restProxy->createBlobBlock($container, $blob, $blockId3, str_pad('', 195));

        $blockList = new BlockList();
        $blockList->addUncommittedEntry($blockId1);
        $blockList->addLatestEntry($blockId3);

        $this->restProxy->commitBlobBlocks($container, $blob, $blockList);

        $opts = new ListBlobBlocksOptions();
        $opts->setIncludeCommittedBlobs(true);
        $opts->setIncludeUncommittedBlobs(true);
        $result = $this->restProxy->listBlobBlocks($container, $blob, $opts);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertEquals(256 + 195, $result->getContentLength(), '$result->getContentLength()');

        $this->assertNotNull($result->getCommittedBlocks(), '$result->getCommittedBlocks()');
        $this->assertEquals(2, count($result->getCommittedBlocks()), 'count($result->getCommittedBlocks())');
        $comblk = $result->getCommittedBlocks();
        $keys = array_keys($comblk);
        $this->assertEquals($blockId1, $keys[0], '$keys[0]');
        $this->assertEquals(256, $comblk[$keys[0]], '$comblk[$keys[0]]');
        $this->assertEquals($blockId3, $keys[1], '$keys[1]');
        $this->assertEquals(195, $comblk[$keys[1]], '$comblk[$keys[1]]');

        $this->assertNotNull($result->getUncommittedBlocks(), '$result->getUncommittedBlocks()');
        $this->assertEquals(0, count($result->getUncommittedBlocks()), 'count($result->getUncommittedBlocks())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::commitBlobBlocks
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobBlock
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobBlocks
     */
    public function testCommitBlobBlocksWithArrayWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test14a';
        $blockId1 = base64_encode('1fedcba');
        $blockId2 = base64_encode('2abcdef');
        $blockId3 = base64_encode('3zzzzzz');
        $this->restProxy->createBlockBlob($container, $blob, '');
        $this->restProxy->createBlobBlock($container, $blob, $blockId1, str_pad('', 256));
        $this->restProxy->createBlobBlock($container, $blob, $blockId2, str_pad('', 512));
        $this->restProxy->createBlobBlock($container, $blob, $blockId3, str_pad('', 195));

        $block1 = new Block();
        $block1->setBlockId($blockId1);
        $block1->setType(BlobBlockType::UNCOMMITTED_TYPE);
        $block3 = new Block();
        $block3->setBlockId($blockId3);
        $block3->setType(BlobBlockType::LATEST_TYPE);
        $blockList = array($block1, $block3);

        $this->restProxy->commitBlobBlocks($container, $blob, $blockList);

        $opts = new ListBlobBlocksOptions();
        $opts->setIncludeCommittedBlobs(true);
        $opts->setIncludeUncommittedBlobs(true);
        $result = $this->restProxy->listBlobBlocks($container, $blob, $opts);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertEquals(256 + 195, $result->getContentLength(), '$result->getContentLength()');

        $this->assertNotNull($result->getCommittedBlocks(), '$result->getCommittedBlocks()');
        $this->assertEquals(2, count($result->getCommittedBlocks()), 'count($result->getCommittedBlocks())');
        $comblk = $result->getCommittedBlocks();
        $keys = array_keys($comblk);
        $this->assertEquals($blockId1, $keys[0], '$keys[0]');
        $this->assertEquals(256, $comblk[$keys[0]], '$comblk[$keys[0]]');
        $this->assertEquals($blockId3, $keys[1], '$keys[1]');
        $this->assertEquals(195, $comblk[$keys[1]], '$comblk[$keys[1]]');

        $this->assertNotNull($result->getUncommittedBlocks(), '$result->getUncommittedBlocks()');
        $this->assertEquals(0, count($result->getUncommittedBlocks()), 'count($result->getUncommittedBlocks())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobBlock
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     */
    public function testCreateBlobBlockWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test13';
        $content = str_pad('', 512);
        $this->restProxy->createBlockBlob($container, $blob, $content);
        $this->restProxy->createBlobBlock($container, $blob, base64_encode('123'), $content);
        $this->restProxy->createBlobBlock($container, $blob, base64_encode('124'), $content);

        // Assert
        $this->assertTrue(true, 'success');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     */
    public function testCreateBlockBlobWorks()
    {
        // Act
        $this->restProxy->createBlockBlob(self::$_test_container_for_blobs, 'test2', 'some content');

        // Assert
        $this->assertTrue(true, 'success');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     */
    public function testCreateBlockBlobWithOptionsWorks()
    {
        // Act
        $content = 'some $content';
        $opts = new CreateBlobOptions();
        $opts->setBlobCacheControl('test');
        $opts->setBlobContentEncoding('UTF-8');
        $opts->setBlobContentLanguage('en-us');
        // $opts->setBlobContentMD5('1234');
        $opts->setBlobContentType('text/plain');
        $opts->setCacheControl('test');
        $opts->setContentEncoding('UTF-8');
        // $opts->setContentMD5('1234');
        $opts->setContentType('text/plain');
        $this->restProxy->createBlockBlob(self::$_test_container_for_blobs, 'test2', $content, $opts);

        $result = $this->restProxy->getBlobProperties(self::$_test_container_for_blobs, 'test2');

        // Assert
        $expectedMD5 = base64_encode(md5($content, true));
        
        $this->assertNotNull($result, '$result');

        $this->assertNotNull($result->getMetadata(), '$result->getMetadata()');
        $this->assertEquals(0, count($result->getMetadata()), 'count($result->getMetadata())');

        $props = $result->getProperties();
        $this->assertNotNull($props, '$props');
        $this->assertEquals('test', $props->getCacheControl(), '$props->getCacheControl()');
        $this->assertEquals('UTF-8', $props->getContentEncoding(), '$props->getContentEncoding()');
        $this->assertEquals('en-us', $props->getContentLanguage(), '$props->getContentLanguage()');
        $this->assertEquals('text/plain', $props->getContentType(), '$props->getContentType()');
        $this->assertEquals(strlen($content), $props->getContentLength(), '$props->getContentLength()');
        $this->assertNotNull($props->getETag(), '$props->getETag()');
        $this->assertEquals($expectedMD5, $props->getContentMD5(), '$props->getContentMD5()');
        $this->assertNotNull($props->getLastModified(), '$props->getLastModified()');
        $this->assertEquals('BlockBlob', $props->getBlobType(), '$props->getBlobType()');
        $this->assertEquals('unlocked', $props->getLeaseStatus(), '$props->getLeaseStatus()');
        $this->assertEquals(0, $props->getSequenceNumber(), '$props->getSequenceNumber()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     */
    public function testCreateBlobSnapshotWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test3';
        $this->restProxy->createBlockBlob($container, $blob, 'some content');
        $snapshot = $this->restProxy->createBlobSnapshot($container, $blob);

        // Assert
        $this->assertNotNull($snapshot, '$snapshot');
        $this->assertNotNull($snapshot->getETag(), '$snapshot->getETag()');
        $this->assertNotNull($snapshot->getLastModified(), '$snapshot->getLastModified()');
        $this->assertNotNull($snapshot->getSnapshot(), '$snapshot->getSnapshot()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     */
    public function testCreateBlobSnapshotWithOptionsWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test3';
        $this->restProxy->createBlockBlob($container, $blob, 'some content');
        $opts = new CreateBlobSnapshotOptions();
        $metadata = array(
            'test' => 'bar',
            'blah' => 'bleah');
        $opts->setMetadata($metadata);
        $snapshot = $this->restProxy->createBlobSnapshot($container, $blob, $opts);

        $opts = new GetBlobPropertiesOptions();
        $opts->setSnapshot($snapshot->getSnapshot());
        $result = $this->restProxy->getBlobProperties($container, $blob, $opts);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals($snapshot->getETag(), $result->getProperties()->getETag(), '$result->getProperties()->getETag()');
        $this->assertEquals($snapshot->getLastModified(), $result->getProperties()->getLastModified(), '$result->getProperties()->getLastModified()');
        // The capitalizaion gets changed.
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('test', $result->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'test\', $result->getMetadata())');
        $this->assertTrue(!(array_search('bar', $result->getMetadata()) === FALSE), '!(array_search(\'bar\', $result->getMetadata()) === FALSE)');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('blah', $result->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'blah\', $result->getMetadata())');
        $this->assertTrue(!(array_search('bleah', $result->getMetadata()) === FALSE), '!(array_search(\'bleah\', $result->getMetadata()) === FALSE)');
        }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     */
    public function testGetBlockBlobWorks()
    {
        // Act
        $content = 'some $content';
        $opts = new CreateBlobOptions();
        $opts->setBlobCacheControl('test');
        $opts->setBlobContentEncoding('UTF-8');
        $opts->setBlobContentLanguage('en-us');
        // $opts->setBlobContentMD5('1234');
        $opts->setBlobContentType('text/plain');
        $opts->setCacheControl('test');
        $opts->setContentEncoding('UTF-8');
        // $opts->setContentMD5('1234');
        $opts->setContentType('text/plain');
        $this->restProxy->createBlockBlob(self::$_test_container_for_blobs, 'test2', $content, $opts);

        $result = $this->restProxy->getBlob(self::$_test_container_for_blobs, 'test2');

        // Assert
        $expectedMD5 = base64_encode(md5($content, true));
        
        $this->assertNotNull($result, '$result');

        $this->assertNotNull($result->getMetadata(), '$result->getMetadata()');
        $this->assertEquals(0, count($result->getMetadata()), 'count($result->getMetadata())');

        $props = $result->getProperties();
        $this->assertNotNull($props, '$props');
        $this->assertEquals('test', $props->getCacheControl(), '$props->getCacheControl()');
        $this->assertEquals('UTF-8', $props->getContentEncoding(), '$props->getContentEncoding()');
        $this->assertEquals('en-us', $props->getContentLanguage(), '$props->getContentLanguage()');
        $this->assertEquals('text/plain', $props->getContentType(), '$props->getContentType()');
        $this->assertEquals(strlen($content), $props->getContentLength(), '$props->getContentLength()');
        $this->assertNotNull($props->getETag(), '$props->getETag()');
        $this->assertEquals($expectedMD5, $props->getContentMD5(), '$props->getContentMD5()');
        $this->assertNotNull($props->getLastModified(), '$props->getLastModified()');
        $this->assertEquals('BlockBlob', $props->getBlobType(), '$props->getBlobType()');
        $this->assertEquals('unlocked', $props->getLeaseStatus(), '$props->getLeaseStatus()');
        $this->assertEquals(0, $props->getSequenceNumber(), '$props->getSequenceNumber()');
        $this->assertEquals($content, stream_get_contents($result->getContentStream()), '$result->getContentStream()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     */
    public function testGetPageBlobWorks()
    {
        // Act
        $opts = new CreateBlobOptions();
        $opts->setBlobCacheControl('test');
        $opts->setBlobContentEncoding('UTF-8');
        $opts->setBlobContentLanguage('en-us');
        // $opts->setBlobContentMD5('1234');
        $opts->setBlobContentType('text/plain');
        $opts->setCacheControl('test');
        $opts->setContentEncoding('UTF-8');
        // $opts->setContentMD5('1234');
        $opts->setContentType('text/plain');
        $this->restProxy->createPageBlob(self::$_test_container_for_blobs, 'test', 4096, $opts);

        $result = $this->restProxy->getBlob(self::$_test_container_for_blobs, 'test');

        // Assert
        $this->assertNotNull($result, '$result');

        $this->assertNotNull($result->getMetadata(), '$result->getMetadata()');
        $this->assertEquals(0, count($result->getMetadata()), 'count($result->getMetadata())');

        $props = $result->getProperties();
        $this->assertEquals('test', $props->getCacheControl(), '$props->getCacheControl()');
        $this->assertEquals('UTF-8', $props->getContentEncoding(), '$props->getContentEncoding()');
        $this->assertEquals('en-us', $props->getContentLanguage(), '$props->getContentLanguage()');
        $this->assertEquals('text/plain', $props->getContentType(), '$props->getContentType()');
        $this->assertEquals(4096, $props->getContentLength(), '$props->getContentLength()');
        $this->assertNotNull($props->getETag(), '$props->getETag()');
        $this->assertNull($props->getContentMD5(), '$props->getContentMD5()');
        $this->assertNotNull($props->getLastModified(), '$props->getLastModified()');
        $this->assertEquals('PageBlob', $props->getBlobType(), '$props->getBlobType()');
        $this->assertEquals('unlocked', $props->getLeaseStatus(), '$props->getLeaseStatus()');
        $this->assertEquals(0, $props->getSequenceNumber(), '$props->getSequenceNumber()');
        $this->assertEquals(4096, strlen(stream_get_contents($result->getContentStream())), 'strlen($result->getContentStream())');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     */
    public function testGetBlobWithIfMatchETagAccessConditionWorks()
    {
        // Act
        $this->restProxy->createPageBlob(self::$_test_container_for_blobs, 'test', 4096);
        try {
            $opts = new GetBlobOptions();
            $opts->setAccessCondition(AccessCondition::ifMatch('123'));
            $this->restProxy->getBlob(self::$_test_container_for_blobs, 'test', $opts);
            $this->fail('getBlob should throw an exception');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'got the expected exception');
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     */
    public function testGetBlobWithIfNoneMatchETagAccessConditionWorks()
    {
        // Act
        $this->restProxy->createPageBlob(self::$_test_container_for_blobs, 'test', 4096);
        $props = $this->restProxy->getBlobProperties(self::$_test_container_for_blobs, 'test');
        try {
            $opts = new GetBlobOptions();
            $opts->setAccessCondition(AccessCondition::ifNoneMatch($props->getProperties()->getETag()));
            $this->restProxy->getBlob(self::$_test_container_for_blobs, 'test', $opts);
            $this->fail('getBlob should throw an exception');
        } catch (ServiceException $e) {
            if (!$this->hasSecureEndpoint() && $e->getCode() == TestResources::STATUS_FORBIDDEN) {
                // Proxies can eat the access condition headers of
                // unsecured (http) requests, which causes the authentication
                // to fail, with a 403:Forbidden. There is nothing much that
                // can be done about this, other than ignore it.
                $this->markTestSkipped('Appears that a proxy ate your access condition');
            } else {
                $this->assertEquals(TestResources::STATUS_NOT_MODIFIED, $e->getCode(), 'got the expected exception');
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     */
    public function testGetBlobWithIfModifiedSinceAccessConditionWorks()
    {
        // Act
        $this->restProxy->createPageBlob(self::$_test_container_for_blobs, 'test', 4096);
        $props = $this->restProxy->getBlobProperties(self::$_test_container_for_blobs, 'test');
        try {
            $opts = new GetBlobOptions();
            $lastMod = $props->getProperties()->getLastModified();
            $opts->setAccessCondition(AccessCondition::ifModifiedSince($lastMod));
            $this->restProxy->getBlob(self::$_test_container_for_blobs, 'test', $opts);
            $this->fail('getBlob should throw an exception');
        } catch (ServiceException $e) {
            if (!$this->hasSecureEndpoint() && $e->getCode() == TestResources::STATUS_FORBIDDEN) {
                // Proxies can eat the access condition headers of
                // unsecured (http) requests, which causes the authentication
                // to fail, with a 403:Forbidden. There is nothing much that
                // can be done about this, other than ignore it.
                $this->markTestSkipped('Appears that a proxy ate your access condition');
            } else {
                $this->assertEquals(TestResources::STATUS_NOT_MODIFIED, $e->getCode(), 'got the expected exception');
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testGetBlobWithIfNotModifiedSinceAccessConditionWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test';
        $this->restProxy->createPageBlob($container, $blob, 4096);
        $props = $this->restProxy->getBlobProperties($container, $blob);

        // To test for "IfNotModifiedSince", we need to make updates to the blob
        // until at least 1 second has passed since the blob creation
        $lastModifiedBase = $props->getProperties()->getLastModified();

        // +1 second
        $lastModifiedNext = clone $lastModifiedBase;
        $lastModifiedNext = $lastModifiedNext->modify("+1 sec");

        while (true) {
            $metadata = array('test' => 'test1');
            $result = $this->restProxy->setBlobMetadata($container, $blob, $metadata);
            if ($result->getLastModified() >= $lastModifiedNext) break;
        }
        try {
            $opts = new GetBlobOptions();
            $opts->setAccessCondition(AccessCondition::ifNotModifiedSince($lastModifiedBase));
            $this->restProxy->getBlob($container, $blob, $opts);
            $this->fail('getBlob should throw an exception');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'got the expected exception');
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     */
    public function testGetBlobPropertiesWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test';
        $this->restProxy->createPageBlob($container, $blob, 4096);
        $result = $this->restProxy->getBlobProperties($container, $blob);

        // Assert
        $this->assertNotNull($result, '$result');

        $this->assertNotNull($result->getMetadata(), '$result->getMetadata()');
        $this->assertEquals(0, count($result->getMetadata()), 'count($result->getMetadata())');

        $props = $result->getProperties();
        $this->assertNotNull($props, '$props');
        $this->assertNull($props->getCacheControl(), '$props->getCacheControl()');
        $this->assertNull($props->getContentEncoding(), '$props->getContentEncoding()');
        $this->assertNull($props->getContentLanguage(), '$props->getContentLanguage()');
        $this->assertEquals('application/octet-stream', $props->getContentType(), '$props->getContentType()');
        $this->assertEquals(4096, $props->getContentLength(), '$props->getContentLength()');
        $this->assertNotNull($props->getETag(), '$props->getETag()');
        $this->assertNull($props->getContentMD5(), '$props->getContentMD5()');
        $this->assertNotNull($props->getLastModified(), '$props->getLastModified()');
        $this->assertEquals('PageBlob', $props->getBlobType(), '$props->getBlobType()');
        $this->assertEquals('unlocked', $props->getLeaseStatus(), '$props->getLeaseStatus()');
        $this->assertEquals(0, $props->getSequenceNumber(), '$props->getSequenceNumber()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     */
    public function testGetBlobMetadataWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test';
        $opts = new CreateBlobOptions();
        $metadata = $opts->getMetadata();
        $metadata['test'] = 'bar';
        $metadata['blah'] = 'bleah';
        $opts->setMetadata($metadata);
        $this->restProxy->createPageBlob($container, $blob, 4096, $opts);
        $props = $this->restProxy->getBlobMetadata($container, $blob);

        // Assert
        $this->assertNotNull($props, '$props');
        $this->assertNotNull($props->getETag(), '$props->getETag()');
        $this->assertNotNull($props->getMetadata(), '$props->getMetadata()');
        $this->assertEquals(2, count($props->getMetadata()), 'count($props->getMetadata())');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('test', $props->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'test\', $props->getMetadata())');
        $this->assertTrue(!(array_search('bar', $props->getMetadata()) === FALSE), '!(array_search(\'bar\', $props->getMetadata()) === FALSE)');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('blah', $props->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'blah\', $props->getMetadata())');
        $this->assertTrue(!(array_search('bleah', $props->getMetadata()) === FALSE), '!(array_search(\'bleah\', $props->getMetadata()) === FALSE)');
        $this->assertNotNull($props->getLastModified(), '$props->getLastModified()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobProperties
     */
    public function testSetBlobPropertiesWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test10';
        $this->restProxy->createPageBlob($container, $blob, 4096);
        $opts = new SetBlobPropertiesOptions();
        $opts->setBlobCacheControl('test');
        $opts->setBlobContentEncoding('UTF-8');
        $opts->setBlobContentLanguage('en-us');
        $opts->setBlobContentLength(512);
        $opts->setBlobContentMD5(null);
        $opts->setBlobContentType('text/plain');
        $opts->setSequenceNumberAction('increment');
        $result = $this->restProxy->setBlobProperties($container, $blob, $opts);

        $getResult = $this->restProxy->getBlobProperties($container, $blob);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');
        $this->assertNotNull($result->getSequenceNumber(), '$result->getSequenceNumber()');
        $this->assertEquals(1, $result->getSequenceNumber(), '$result->getSequenceNumber()');

        $this->assertNotNull($getResult, '$getResult');

        $this->assertNotNull($getResult->getMetadata(), '$getResult->getMetadata()');
        $this->assertEquals(0, count($getResult->getMetadata()), 'count($getResult->getMetadata())');

        $props = $getResult->getProperties();
        $this->assertNotNull($props, '$props');
        $this->assertEquals('test', $props->getCacheControl(), '$props->getCacheControl()');
        $this->assertEquals('UTF-8', $props->getContentEncoding(), '$props->getContentEncoding()');
        $this->assertEquals('en-us', $props->getContentLanguage(), '$props->getContentLanguage()');
        $this->assertEquals('text/plain', $props->getContentType(), '$props->getContentType()');
        $this->assertEquals(512, $props->getContentLength(), '$props->getContentLength()');
        $this->assertNull($props->getContentMD5(), '$props->getContentMD5()');
        $this->assertNotNull($props->getLastModified(), '$props->getLastModified()');
        $this->assertEquals('PageBlob', $props->getBlobType(), '$props->getBlobType()');
        $this->assertEquals('unlocked', $props->getLeaseStatus(), '$props->getLeaseStatus()');
        $this->assertEquals(1, $props->getSequenceNumber(), '$props->getSequenceNumber()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     */
    public function testSetBlobMetadataWorks()
    {
        // Act
        $container = self::$_test_container_for_blobs;
        $blob = 'test11';
        $metadata = array(
            'test' => 'bar',
            'blah' => 'bleah');

        $this->restProxy->createPageBlob($container, $blob, 4096);
        $result = $this->restProxy->setBlobMetadata($container, $blob, $metadata);
        $props = $this->restProxy->getBlobProperties($container, $blob);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getETag(), '$result->getETag()');
        $this->assertNotNull($result->getLastModified(), '$result->getLastModified()');

        $this->assertNotNull($props, '$props');
        $this->assertNotNull($props->getMetadata(), '$props->getMetadata()');
        $this->assertEquals(2, count($props->getMetadata()), 'count($props->getMetadata())');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('test', $props->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'test\', $props->getMetadata())');
        $this->assertTrue(!(array_search('bar', $props->getMetadata()) === FALSE), '!(array_search(\'bar\', $props->getMetadata()) === FALSE)');
        $this->assertTrue(Utilities::arrayKeyExistsInsensitive('blah', $props->getMetadata()), 'Utilities::arrayKeyExistsInsensitive(\'blah\', $props->getMetadata())');
        $this->assertTrue(!(array_search('bleah', $props->getMetadata()) === FALSE), '!(array_search(\'bleah\', $props->getMetadata()) === FALSE)');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     */
    public function testDeleteBlobWorks()
    {
        // Act
        $content = 'some $content';
        $this->restProxy->createBlockBlob(self::$_test_container_for_blobs, 'test2', $content);

        $this->restProxy->deleteBlob(self::$_test_container_for_blobs, 'test2');

        // Assert
        $this->assertTrue(true, 'success');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::copyBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     */
    public function testCopyBlobWorks()
    {
        // Act
        $content = 'some content2';
        $this->restProxy->createBlockBlob(self::$_test_container_for_blobs, 'test6', $content);
        $this->restProxy->copyBlob(self::$_test_container_for_blobs_2, 'test5', self::$_test_container_for_blobs, 'test6');

        $result = $this->restProxy->getBlob(self::$_test_container_for_blobs_2, 'test5');

        // Assert
        $expectedMD5 = base64_encode(md5($content, true));
        
        $this->assertNotNull($result, '$result');

        $this->assertNotNull($result->getMetadata(), '$result->getMetadata()');
        $this->assertEquals(0, count($result->getMetadata()), 'count($result->getMetadata())');

        $props = $result->getProperties();
        $this->assertNotNull($props, '$props');
        $this->assertEquals(strlen($content), $props->getContentLength(), '$props->getContentLength()');
        $this->assertNotNull($props->getETag(), '$props->getETag()');
        $this->assertEquals($expectedMD5, $props->getContentMD5(), '$props->getContentMD5()');
        $this->assertNotNull($props->getLastModified(), '$props->getLastModified()');
        $this->assertEquals('BlockBlob', $props->getBlobType(), '$props->getBlobType()');
        $this->assertEquals('unlocked', $props->getLeaseStatus(), '$props->getLeaseStatus()');
        $this->assertEquals(0, $props->getSequenceNumber(), '$props->getSequenceNumber()');
        $this->assertEquals($content, stream_get_contents($result->getContentStream()), '$result->getContentStream()');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::acquireLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::releaseLease
     */
    public function testAcquireLeaseWorks()
    {
        // Act
        $content = 'some content2';
        $this->restProxy->createBlockBlob(self::$_test_container_for_blobs, 'test6', $content);
        $leaseId = $this->restProxy->acquireLease(self::$_test_container_for_blobs, 'test6')->getLeaseId();
        $this->restProxy->releaseLease(self::$_test_container_for_blobs, 'test6', $leaseId);

        // Assert
        $this->assertNotNull($leaseId, '$leaseId');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::acquireLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::releaseLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::renewLease
     */
    public function testRenewLeaseWorks()
    {
        // Act
        $content = 'some content2';
        $this->restProxy->createBlockBlob(self::$_test_container_for_blobs, 'test6', $content);
        $leaseId = $this->restProxy->acquireLease(self::$_test_container_for_blobs, 'test6')->getLeaseId();
        $leaseId2 = $this->restProxy->renewLease(self::$_test_container_for_blobs, 'test6', $leaseId)->getLeaseId();
        $this->restProxy->releaseLease(self::$_test_container_for_blobs, 'test6', $leaseId);

        // Assert
        $this->assertNotNull($leaseId, '$leaseId');
        $this->assertNotNull($leaseId2, '$leaseId2');
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::acquireLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::breakLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::releaseLease
     */
    public function testBreakLeaseWorks()
    {
        // Act
        $content = 'some content2';
        $this->restProxy->createBlockBlob(self::$_test_container_for_blobs, 'test6', $content);
        $leaseId = $this->restProxy->acquireLease(self::$_test_container_for_blobs, 'test6')->getLeaseId();
        $this->restProxy->breakLease(self::$_test_container_for_blobs, 'test6', $leaseId);
        $this->restProxy->releaseLease(self::$_test_container_for_blobs, 'test6', $leaseId);

        // Assert
        $this->assertNotNull($leaseId, '$leaseId');
    }

    // Extra tests from Java
    //    public function testRetryPolicyWorks() { }
    //    public function testRetryPolicyCompositionWorks() { }
    //    public function testRetryPolicyThrowsOnInvalidInputStream() { }
    //    public function testRetryPolicyCallsResetOnValidInputStream() { }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Blob;

use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;

class FunctionalTestBase extends IntegrationTestBase
{
    private static $isOneTimeSetup = false;

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     */
    public function setUp()
    {
        parent::setUp();
        $settings = StorageServiceSettings::createFromConnectionString($this->connectionString);
        $accountName = $settings->getBlobEndpointUri();
        $firstSlash = strpos($accountName, '/');
        $accountName = substr($accountName, $firstSlash + 2);
        $firstDot = strpos($accountName, '.');
        $accountName = substr($accountName, 0, $firstDot);

        BlobServiceFunctionalTestData::setupData($accountName);

        $hasRoot = false;
        foreach($this->restProxy->listContainers()->getContainers() as $container) {
            if ($container->getName() == '$root') {
                $hasRoot = true;
                $this->safeDeleteContainerContents('$root');
            } else {
                $this->safeDeleteContainer($container->getName());
            }
        }

        foreach(BlobServiceFunctionalTestData::$testContainerNames as $name)  {
            $this->safeCreateContainer($name);
        }

        if (!$hasRoot) {
            $this->safeCreateContainer('$root');
        }

        if (!self::$isOneTimeSetup) {
            self::$isOneTimeSetup = true;
        }
    }

    public function tearDown()
    {
        foreach(BlobServiceFunctionalTestData::$testContainerNames as $name)  {
            $this->safeDeleteContainer($name);
        }
        parent::tearDown();
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            $tmp = new FunctionalTestBase();
            $tmp->setUp();
            $tmp->safeDeleteContainer('$root');
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    private function safeDeleteContainerContents($name)
    {
        $blobListResult = $this->restProxy->listBlobs($name);
        foreach($blobListResult->getBlobs() as $blob)  {
            try {
                $this->restProxy->deleteBlob($name, $blob->getName());
            } catch (ServiceException $e) {
                error_log($e->getMessage());
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
     */
    private function safeDeleteContainer($name)
    {
        try {
            $this->restProxy->deleteContainer($name);
        } catch (ServiceException $e) {
            error_log($e->getMessage());
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
     */
    private function safeCreateContainer($name)
    {
        try {
            $this->restProxy->createContainer($name);
        } catch (ServiceException $e) {
            error_log($e->getMessage());
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Blob;

use MicrosoftAzure\Storage\Tests\Framework\BlobServiceRestProxyTestBase;
use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

class IntegrationTestBase extends BlobServiceRestProxyTestBase
{
    private static $isOneTimeSetup = false;

    public function setUp()
    {
        parent::setUp();
        if (!self::$isOneTimeSetup) {
            self::$isOneTimeSetup = true;
        }
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            $integrationTestBase = new IntegrationTestBase();
            $integrationTestBase->setUp();
            if ($integrationTestBase->isEmulated()) {
                $serviceProperties = BlobServiceFunctionalTestData::getDefaultServiceProperties();
                $integrationTestBase->restProxy->setServiceProperties($serviceProperties);
            }
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }

    protected function hasSecureEndpoint()
    {
        $settings = StorageServiceSettings::createFromConnectionString($this->connectionString);
        $uri = $settings->getBlobEndpointUri();
        return Utilities::startsWith($uri, 'https://');
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Queue;

use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;

class FunctionalTestBase extends IntegrationTestBase
{
    private static $isOneTimeSetup = false;

    protected $accountName;

    public function setUp()
    {
        parent::setUp();
        $settings = StorageServiceSettings::createFromConnectionString($this->connectionString);
        $this->accountName = $settings->getName();
        if (!self::$isOneTimeSetup) {
            $this->doOneTimeSetup();
            self::$isOneTimeSetup = true;
        }
    }

    private function doOneTimeSetup()
    {
        QueueServiceFunctionalTestData::setupData();

        foreach(QueueServiceFunctionalTestData::$testQueueNames as $name)  {
            $this->safeDeleteQueue($name);
        }

        foreach(QueueServiceFunctionalTestData::$testQueueNames as $name)  {
            // self::println('Creating queue: ' . $name);
            $this->restProxy->createQueue($name);
        }
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            $testBase = new FunctionalTestBase();
            $testBase->setUp();
            foreach(QueueServiceFunctionalTestData::$testQueueNames as $name)  {
                $testBase->safeDeleteQueue($name);
            }
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }

    public static function println($msg)
    {
        // error_log($msg);
    }

    public static function tmptostring($obj)
    {
        return var_export($obj, true);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Queue;

use MicrosoftAzure\Storage\Tests\Framework\QueueServiceRestProxyTestBase;

class IntegrationTestBase extends QueueServiceRestProxyTestBase
{
    private static $isOneTimeSetup = false;

    public function setUp()
    {
        parent::setUp();
        if (!self::$isOneTimeSetup) {
            self::$isOneTimeSetup = true;
        }
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            $integrationTestBase = new IntegrationTestBase();
            $integrationTestBase->setUp();
            if (!$integrationTestBase->isEmulated()) {
                $serviceProperties = QueueServiceFunctionalTestData::getDefaultServiceProperties();
                $integrationTestBase->restProxy->setServiceProperties($serviceProperties);
            }
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Queue;

use MicrosoftAzure\Storage\Common\Models\Logging;
use MicrosoftAzure\Storage\Common\Models\Metrics;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions;

class QueueServiceFunctionalOptionsTest extends \PHPUnit_Framework_TestCase
{
    const INT_MAX_VALUE = 2147483647;
    const INT_MIN_VALUE = -2147483648;

    public function testCheckQueueServiceOptions()
    {
        $options = new QueueServiceOptions();
        $this->assertNull($options->getTimeout(), 'Default QueueServiceOptions->getTimeout should be null');
        $options->setTimeout(self::INT_MAX_VALUE);
        $this->assertEquals(self::INT_MAX_VALUE, $options->getTimeout(), 'Set QueueServiceOptions->getTimeout');
    }

    public function testCheckRetentionPolicy()
    {
        // Check that the default values of options are reasonable

        $rp = new RetentionPolicy();
        $this->assertNull($rp->getDays(), 'Default RetentionPolicy->getDays should be null');
        $this->assertNull($rp->getEnabled(), 'Default RetentionPolicy->getEnabled should be null');
        $rp->setDays(10);
        $rp->setEnabled(true);
        $this->assertEquals(10, $rp->getDays(), 'Set RetentionPolicy->getDays should be 10');
        $this->assertTrue($rp->getEnabled(), 'Set RetentionPolicy->getEnabled should be true');
    }

    public function testCheckLogging()
    {
        // Check that the default values of options are reasonable
        $rp = new RetentionPolicy();

        $l = new Logging();
        $this->assertNull($l->getRetentionPolicy(), 'Default Logging->getRetentionPolicy should be null');
        $this->assertNull($l->getVersion(), 'Default Logging->getVersion should be null');
        $this->assertNull($l->getDelete(), 'Default Logging->getDelete should be null');
        $this->assertNull($l->getRead(), 'Default Logging->getRead should be false');
        $this->assertNull($l->getWrite(), 'Default Logging->getWrite should be false');
        $l->setRetentionPolicy($rp);
        $l->setVersion('2.0');
        $l->setDelete(true);
        $l->setRead(true);
        $l->setWrite(true);

        $this->assertEquals($rp, $l->getRetentionPolicy(), 'Set Logging->getRetentionPolicy');
        $this->assertEquals('2.0', $l->getVersion(), 'Set Logging->getVersion');
        $this->assertTrue($l->getDelete(), 'Set Logging->getDelete should be true');
        $this->assertTrue($l->getRead(), 'Set Logging->getRead should be true');
        $this->assertTrue($l->getWrite(), 'Set Logging->getWrite should be true');
    }

    public function testCheckMetrics()
    {
        // Check that the default values of options are reasonable
        $rp = new RetentionPolicy();

        $m = new Metrics();
        $this->assertNull($m->getRetentionPolicy(), 'Default Metrics->getRetentionPolicy should be null');
        $this->assertNull($m->getVersion(), 'Default Metrics->getVersion should be null');
        $this->assertNull($m->getEnabled(), 'Default Metrics->getEnabled should be false');
        $this->assertNull($m->getIncludeAPIs(), 'Default Metrics->getIncludeAPIs should be null');
        $m->setRetentionPolicy($rp);
        $m->setVersion('2.0');
        $m->setEnabled(true);
        $m->setIncludeAPIs(true);
        $this->assertEquals($rp, $m->getRetentionPolicy(), 'Set Metrics->getRetentionPolicy');
        $this->assertEquals('2.0', $m->getVersion(), 'Set Metrics->getVersion');
        $this->assertTrue($m->getEnabled(), 'Set Metrics->getEnabled should be true');
        $this->assertTrue($m->getIncludeAPIs(), 'Set Metrics->getIncludeAPIs should be true');
    }

    public function testCheckServiceProperties()
    {
        // Check that the default values of options are reasonable
        $l = new Logging();
        $m = new Metrics();

        $sp = new ServiceProperties();
        $this->assertNull($sp->getLogging(), 'Default ServiceProperties->getLogging should not be null');
        $this->assertNull($sp->getMetrics(), 'Default ServiceProperties->getMetrics should not be null');

        $sp->setLogging($l);
        $sp->setMetrics($m);
        $this->assertEquals($sp->getLogging(), $l, 'Set ServiceProperties->getLogging');
        $this->assertEquals($sp->getMetrics(), $m, 'Set ServiceProperties->getMetrics');
    }

    public function testCheckListQueuesOptions()
    {
        $options = new ListQueuesOptions();
        $this->assertNull($options->getIncludeMetadata(), 'Default ListQueuesOptions->getIncludeMetadata');
        $this->assertNull($options->getMarker(), 'Default ListQueuesOptions->getMarker');
        $this->assertEquals(0, $options->getMaxResults(), 'Default ListQueuesOptions->getMaxResults');
        $this->assertNull($options->getPrefix(), 'Default ListQueuesOptions->getPrefix');
        $this->assertNull($options->getTimeout(), 'Default ListQueuesOptions->getTimeout');
        $options->setIncludeMetadata(true);
        $options->setMarker('foo');
        $options->setMaxResults(-10);
        $options->setPrefix('bar');
        $options->setTimeout(self::INT_MAX_VALUE);
        $this->assertTrue($options->getIncludeMetadata(), 'Set ListQueuesOptions->getIncludeMetadata');
        $this->assertEquals('foo', $options->getMarker(), 'Set ListQueuesOptions->getMarker');
        $this->assertEquals(-10, $options->getMaxResults(), 'Set ListQueuesOptions->getMaxResults');
        $this->assertEquals('bar', $options->getPrefix(), 'Set ListQueuesOptions->getPrefix');
        $this->assertEquals(self::INT_MAX_VALUE, $options->getTimeout(), 'Set ListQueuesOptions->getTimeout');
    }

    public function testCheckCreateQueueOptions()
    {
        $options = new CreateQueueOptions();
        $this->assertNull($options->getMetadata(), 'Default CreateQueueOptions->getMetadata');
        $this->assertEquals(0, count($options->getMetadata()), 'Default CreateQueueOptions->getMetadata->size');
        $this->assertNull($options->getTimeout(), 'Default CreateQueueOptions->getTimeout');
        $metadata = array(
            'foo' => 'bar',
            'baz' => 'bat',
        );
        $options->setMetadata($metadata);
        $options->setTimeout(-10);
        $this->assertEquals($options->getMetadata(), $metadata, 'Set CreateQueueOptions->getMetadata');
        $this->assertEquals(2, count($options->getMetadata()), 'Set CreateQueueOptions->getMetadata->size');
        $this->assertEquals(-10, $options->getTimeout(), 'Set CreateQueueOptions->getTimeout');
        $options->addMetadata('aaa', 'bbb');
        $this->assertEquals(3, count($options->getMetadata()), 'Set CreateQueueOptions->getMetadata->size');
    }

    public function testCheckCreateMessageOptions()
    {
        $options = new CreateMessageOptions();
        $this->assertNull($options->getTimeout(), 'Default CreateMessageOptions->getTimeout');
        $this->assertNull($options->getTimeToLiveInSeconds(), 'Default CreateMessageOptions->getTimeToLiveInSeconds');
        $this->assertNull($options->getVisibilityTimeoutInSeconds(), 'Default CreateMessageOptions->getVisibilityTimeoutInSeconds');
        $options->setTimeout(self::INT_MAX_VALUE);
        $options->setTimeToLiveInSeconds(0);
        $options->setVisibilityTimeoutInSeconds(self::INT_MIN_VALUE);
        $this->assertEquals(self::INT_MAX_VALUE, $options->getTimeout(), 'Set CreateMessageOptions->getTimeout');
        $this->assertEquals(0, $options->getTimeToLiveInSeconds(), 'Set CreateMessageOptions->getTimeToLiveInSeconds');
        $this->assertEquals(self::INT_MIN_VALUE, $options->getVisibilityTimeoutInSeconds(), 'Set CreateMessageOptions->getVisibilityTimeoutInSeconds');
    }

    public function testCheckListMessagesOptions()
    {
        $options = new ListMessagesOptions();
        $this->assertNull($options->getTimeout(), 'Default ListMessagesOptions->getTimeout');
        $this->assertNull($options->getNumberOfMessages(), 'Default ListMessagesOptions->getNumberOfMessages');
        $this->assertNull($options->getVisibilityTimeoutInSeconds(), 'Default ListMessagesOptions->getVisibilityTimeoutInSeconds');
        $options->setTimeout(self::INT_MAX_VALUE);
        $options->setNumberOfMessages(0);
        $options->setVisibilityTimeoutInSeconds(self::INT_MIN_VALUE);
        $this->assertEquals(self::INT_MAX_VALUE, $options->getTimeout(), 'Set ListMessagesOptions->getTimeout');
        $this->assertEquals(0, $options->getNumberOfMessages(), 'Set ListMessagesOptions->getNumberOfMessages');
        $this->assertEquals(self::INT_MIN_VALUE, $options->getVisibilityTimeoutInSeconds(), 'Set ListMessagesOptions->getVisibilityTimeoutInSeconds');
    }

    public function testCheckPeekMessagesOptions()
    {
        $options = new PeekMessagesOptions();
        $this->assertNull($options->getTimeout(), 'Default PeekMessagesOptions->getTimeout');
        $this->assertNull($options->getNumberOfMessages(), 'Default PeekMessagesOptions->getNumberOfMessages');
        $options->setTimeout(self::INT_MAX_VALUE);
        $options->setNumberOfMessages(0);
        $this->assertEquals(self::INT_MAX_VALUE, $options->getTimeout(), 'Set PeekMessagesOptions->getTimeout');
        $this->assertEquals(0, $options->getNumberOfMessages(), 'Set PeekMessagesOptions->getNumberOfMessages');
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Queue;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions;

class QueueServiceFunctionalParameterTest extends FunctionalTestBase
{
    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getServiceProperties
    */
    public function testGetServicePropertiesNullOptions()
    {
        try {
            $this->restProxy->getServiceProperties(null);
            $this->assertFalse($this->isEmulated(), 'Should fail if and only if in emulator');
        } catch (ServiceException $e) {
            // Expect failure when run this test with emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                // Properties are not supported in emulator
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNullOptions1()
    {

        $serviceProperties = QueueServiceFunctionalTestData::getDefaultServiceProperties();
        try {
            $this->restProxy->setServiceProperties($serviceProperties);
            $this->assertFalse($this->isEmulated(), 'service properties should throw in emulator');
        } catch (ServiceException $e) {
            if ($this->isEmulated()) {
                // Properties are not supported in emulator
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNullOptions2()
    {
        try {
            $this->restProxy->setServiceProperties(null);
            $this->fail('Expect null service properties to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::INVALID_SVC_PROP_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNullOptions3()
    {
        try {
            $this->restProxy->setServiceProperties(null, null);
            $this->fail('Expect service properties to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::INVALID_SVC_PROP_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNullOptions4()
    {
        $serviceProperties = QueueServiceFunctionalTestData::getDefaultServiceProperties();

        try {
            $this->restProxy->setServiceProperties($serviceProperties, null);
            $this->assertFalse($this->isEmulated(), 'service properties should throw in emulator');
        } catch (ServiceException $e) {
            if ($this->isEmulated()) {
                // Setting is not supported in emulator
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testListQueuesNullOptions()
    {
        $this->restProxy->listQueues(null);
        $this->assertTrue(true, 'Should just work');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    */
    public function testCreateQueueNullName()
    {
        try {
            $this->restProxy->createQueue(null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    */
    public function testDeleteQueueNullName()
    {
        try {
            $this->restProxy->deleteQueue(null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    */
    public function testGetQueueMetadataNullName()
    {
        try {
            $this->restProxy->getQueueMetadata(null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testSetQueueMetadataNullNameAndOptions()
    {
        try {
            $this->restProxy->setQueueMetadata(null, null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testSetQueueMetadataNullName()
    {
        try {
            $this->restProxy->setQueueMetadata(null, array());
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testSetQueueMetadataNullMetadata()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $this->restProxy->setQueueMetadata($queue, null);
        $this->assertTrue(true, 'Should just work');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testSetQueueMetadataEmptyMetadata()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $this->restProxy->setQueueMetadata($queue, array());
        $this->assertTrue(true, 'Should just work');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testSetQueueMetadataNullOptions()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $this->restProxy->setQueueMetadata($queue, array(), null);
        $this->assertTrue(true, 'Should just work');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    */
    public function testCreateMessageQueueNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        try {
            $this->restProxy->createMessage(null, null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
        $this->restProxy->clearMessages($queue);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    */
    public function testCreateMessageNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $this->restProxy->createMessage($queue, null);
        $this->restProxy->clearMessages($queue);
        $this->assertTrue(true, 'Should just work');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    */
    public function testCreateMessageBothMessageAndOptionsNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $this->restProxy->createMessage($queue, null, null);
        $this->restProxy->clearMessages($queue);
        $this->assertTrue(true, 'Should just work');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    */
    public function testCreateMessageMessageNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $this->restProxy->createMessage($queue, null, QueueServiceFunctionalTestData::getSimpleCreateMessageOptions());
        $this->restProxy->clearMessages($queue);
        $this->assertTrue(true, 'Should just work');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    */
    public function testCreateMessageOptionsNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText(), null);
        $this->restProxy->clearMessages($queue);
        $this->assertTrue(true, 'Should just work');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageQueueNull()
    {
        $queue = null;
        $messageId = 'abc';
        $popReceipt = 'abc';
        $messageText = 'abc';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect null name to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageQueueEmpty()
    {
        $queue = '';
        $messageId = 'abc';
        $popReceipt = 'abc';
        $messageText = 'abc';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect null name to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageMessageIdNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = null;
        $popReceipt = 'abc';
        $messageText = 'abc';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect null messageId to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'messageId'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageMessageIdEmpty()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = '';
        $popReceipt = 'abc';
        $messageText = 'abc';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect null messageId to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'messageId'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessagePopReceiptNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = 'abc';
        $popReceipt = null;
        $messageText = 'abc';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect null popReceipt to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'popReceipt'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessagePopReceiptEmpty()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = 'abc';
        $popReceipt = '';
        $messageText = 'abc';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect null popReceipt to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'popReceipt'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageMessageTextNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = 'abc';
        $popReceipt = 'abc';
        $messageText = null;
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect bogus message id to throw');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageMessageTextEmpty()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = 'abc';
        $popReceipt = 'abc';
        $messageText = '';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect bogus message id to throw');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageOptionsNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = 'abc';
        $popReceipt = 'abc';
        $messageText = 'abc';
        $options = null;
        $visibilityTimeoutInSeconds = 1;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect bogus message id to throw');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageVisibilityTimeout0()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = 'abc';
        $popReceipt = 'abc';
        $messageText = 'abc';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = 0;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect bogus message id to throw');
        } catch (\InvalidArgumentException $e) {
            $this->fail('Should not get an InvalidArgumentException exception');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageVisibilityTimeoutNull()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames[0];
        $messageId = 'abc';
        $popReceipt = 'abc';
        $messageText = 'abc';
        $options = new QueueServiceOptions();
        $visibilityTimeoutInSeconds = null;

        try {
            $this->restProxy->updateMessage($queue, $messageId, $popReceipt, $messageText, $visibilityTimeoutInSeconds, $options);
            $this->fail('Expect null visibilityTimeoutInSeconds to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_MSG, 'visibilityTimeoutInSeconds'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    */
    public function testDeleteMessageQueueNullNoOptions()
    {
        $queue = null;
        $messageId = 'abc';
        $popReceipt = 'abc';

        try {
            $this->restProxy->deleteMessage($queue, $messageId, $popReceipt);
            $this->fail('Expect null queue to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    */
    public function testDeleteMessageQueueEmptyNoOptions()
    {
        $queue = '';
        $messageId = 'abc';
        $popReceipt = 'abc';

        try {
            $this->restProxy->deleteMessage($queue, $messageId, $popReceipt);
            $this->fail('Expect empty queue to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    */
    public function testDeleteMessageQueueNullWithOptions()
    {
        $queue = null;
        $messageId = 'abc';
        $popReceipt = 'abc';
        $options = new QueueServiceOptions();

        try {
            $this->restProxy->deleteMessage($queue, $messageId, $popReceipt, $options);
            $this->fail('Expect null queue to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    */
    public function testDeleteMessageMessageIdNull()
    {
        $queue = 'abc';
        $messageId = null;
        $popReceipt = 'abc';
        $options = new QueueServiceOptions();

        try {
            $this->restProxy->deleteMessage($queue, $messageId, $popReceipt, $options);
            $this->fail('Expect null messageId to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'messageId'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    */
    public function testDeleteMessageMessageIdEmpty()
    {
        $queue = 'abc';
        $messageId = '';
        $popReceipt = 'abc';
        $options = new QueueServiceOptions();

        try {
            $this->restProxy->deleteMessage($queue, $messageId, $popReceipt, $options);
            $this->fail('Expect empty messageId to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'messageId'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    */
    public function testDeleteMessagePopReceiptNull()
    {
        $queue = 'abc';
        $messageId = 'abc';
        $popReceipt = null;
        $options = new QueueServiceOptions();

        try {
            $this->restProxy->deleteMessage($queue, $messageId, $popReceipt, $options);
            $this->fail('Expect null popReceipt to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'popReceipt'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    */
    public function testDeleteMessagePopReceiptEmpty()
    {
        $queue = 'abc';
        $messageId = 'abc';
        $popReceipt = '';
        $options = new QueueServiceOptions();

        try {
            $this->restProxy->deleteMessage($queue, $messageId, $popReceipt, $options);
            $this->fail('Expect empty popReceipt to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'popReceipt'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    */
    public function testDeleteMessageOptionsNull()
    {
        $queue = 'abc';
        $messageId = 'abc';
        $popReceipt = 'abc';
        $options = null;

        try {
            $this->restProxy->deleteMessage($queue, $messageId, $popReceipt, $options);
            $this->fail('Expect bogus message id to throw');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testListMessagesQueueNullNoOptions()
    {
        try {
            $this->restProxy->listMessages(null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testListMessagesQueueNullWithOptions()
    {
        try {
            $this->restProxy->listMessages(null, new ListMessagesOptions());
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testListMessagesOptionsNull()
    {
        try {
            $this->restProxy->listMessages('abc', null);
            $this->fail('Expect bogus queue name to throw');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'getCode');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testListMessagesAllNull()
    {
        try {
            $this->restProxy->listMessages(null, null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessagesQueueNullNoOptions()
    {
        try {
            $this->restProxy->peekMessages(null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessagesQueueEmptyNoOptions()
    {
        try {
            $this->restProxy->peekMessages('');
            $this->fail('Expect empty name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessagesQueueNullWithOptions()
    {
        try {
            $this->restProxy->peekMessages(null, new PeekMessagesOptions());
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessagesOptionsNull()
    {
        try {
            $this->restProxy->peekMessages('abc', null);
            $this->fail('Expect bogus queue name to throw');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'getCode');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessagesAllNull()
    {
        try {
            $this->restProxy->peekMessages(null, null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    */
    public function testClearMessagesQueueNullNoOptions()
    {
        try {
            $this->restProxy->clearMessages(null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    */
    public function testClearMessagesQueueNullWithOptions()
    {
        try {
            $this->restProxy->clearMessages(null, new QueueServiceOptions());
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    */
    public function testClearMessagesOptionsNull()
    {
        try {
            $this->restProxy->clearMessages('abc', null);
            $this->fail('Expect bogus queue name to throw');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'getCode');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    */
    public function testClearMessagesAllNull()
    {
        try {
            $this->restProxy->clearMessages(null, null);
            $this->fail('Expect null name to throw');
        } catch (ServiceException $e) {
            $this->fail('Should not get a service exception');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'queueName'), $e->getMessage(), 'Expect error message');
        }
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Queue;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions;

class QueueServiceFunctionalTest extends FunctionalTestBase
{
    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
    */
    public function testGetServicePropertiesNoOptions()
    {
        $serviceProperties = QueueServiceFunctionalTestData::getDefaultServiceProperties();

        $shouldReturn = false;
        try {
            $this->restProxy->setServiceProperties($serviceProperties);
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        $this->getServicePropertiesWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
    */
    public function testGetServiceProperties()
    {
        $serviceProperties = QueueServiceFunctionalTestData::getDefaultServiceProperties();

        $shouldReturn = false;
        try {
            $this->restProxy->setServiceProperties($serviceProperties);
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        // Now look at the combos.
        $interestingTimeouts = QueueServiceFunctionalTestData::getInterestingTimeoutValues();
        foreach($interestingTimeouts as $timeout)  {
            $options = new QueueServiceOptions();
            $options->setTimeout($timeout);
            $this->getServicePropertiesWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::getServiceProperties
     */
    private function getServicePropertiesWorker($options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options));
        $effOptions = (is_null($options) ? new QueueServiceOptions() : $options);
        try {
            $ret = (is_null($options) ? $this->restProxy->getServiceProperties() : $this->restProxy->getServiceProperties($effOptions));

            if (!is_null($effOptions->getTimeout()) && $effOptions->getTimeout() < 1) {
                $this->True('Expect negative timeouts in $options to throw', false);
            } else {
                $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
            }
            $this->verifyServicePropertiesWorker($ret, null);
        } catch (ServiceException $e) {
            if ($this->isEmulated()) {
                if (!is_null($options->getTimeout()) && $options->getTimeout() < 0) {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                } else {
                    // Expect failure in emulator, as v1.6 doesn't support this method
                    $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                }
            } else if (!is_null($effOptions->getTimeout()) && $effOptions->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
    }

    private function verifyServicePropertiesWorker($ret, $serviceProperties)
    {
        if (is_null($serviceProperties)) {
            $serviceProperties = QueueServiceFunctionalTestData::getDefaultServiceProperties();
        }

        $sp = $ret->getValue();
        $this->assertNotNull($sp, 'getValue should be non-null');

        $l = $sp->getLogging();
        $this->assertNotNull($l, 'getValue()->getLogging() should be non-null');
        $this->assertEquals($serviceProperties->getLogging()->getVersion(), $l->getVersion(), 'getValue()->getLogging()->getVersion');
        $this->assertEquals($serviceProperties->getLogging()->getDelete(), $l->getDelete(), 'getValue()->getLogging()->getDelete');
        $this->assertEquals($serviceProperties->getLogging()->getRead(), $l->getRead(), 'getValue()->getLogging()->getRead');
        $this->assertEquals($serviceProperties->getLogging()->getWrite(), $l->getWrite(), 'getValue()->getLogging()->getWrite');

        $r = $l->getRetentionPolicy();
        $this->assertNotNull($r, 'getValue()->getLogging()->getRetentionPolicy should be non-null');
        $this->assertEquals($serviceProperties->getLogging()->getRetentionPolicy()->getDays(), $r->getDays(), 'getValue()->getLogging()->getRetentionPolicy()->getDays');

        $m = $sp->getMetrics();
        $this->assertNotNull($m, 'getValue()->getMetrics() should be non-null');
        $this->assertEquals($serviceProperties->getMetrics()->getVersion(), $m->getVersion(), 'getValue()->getMetrics()->getVersion');
        $this->assertEquals($serviceProperties->getMetrics()->getEnabled(), $m->getEnabled(), 'getValue()->getMetrics()->getEnabled');
        $this->assertEquals($serviceProperties->getMetrics()->getIncludeAPIs(), $m->getIncludeAPIs(), 'getValue()->getMetrics()->getIncludeAPIs');

        $r = $m->getRetentionPolicy();
        $this->assertNotNull($r, 'getValue()->getMetrics()->getRetentionPolicy should be non-null');
        $this->assertEquals($serviceProperties->getMetrics()->getRetentionPolicy()->getDays(), $r->getDays(), 'getValue()->getMetrics()->getRetentionPolicy()->getDays');
    }

//     /**
//     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getServiceProperties
//     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
//     */
//     public function testSetServicePropertiesNoOptions()
//     {
//         $serviceProperties = QueueServiceFunctionalTestData::getDefaultServiceProperties();
//         $this->setServicePropertiesWorker($serviceProperties, null);
//     }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
    */
    public function testSetServiceProperties()
    {
        $interestingServiceProperties = QueueServiceFunctionalTestData::getInterestingServiceProperties();
        foreach($interestingServiceProperties as $serviceProperties)  {
            $interestingTimeouts = QueueServiceFunctionalTestData::getInterestingTimeoutValues();
            foreach($interestingTimeouts as $timeout)  {
                $options = new QueueServiceOptions();
                $options->setTimeout($timeout);
                $this->setServicePropertiesWorker($serviceProperties, $options);
            }
        }

        if (!$this->isEmulated()) {
            $this->restProxy->setServiceProperties($interestingServiceProperties[0]);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::getServiceProperties
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::setServiceProperties
     */
    private function setServicePropertiesWorker($serviceProperties, $options)
    {
        try {
            if (is_null($options)) {
                $this->restProxy->setServiceProperties($serviceProperties);
            } else {
                $this->restProxy->setServiceProperties($serviceProperties, $options);
            }

            if (is_null($options)) {
                $options = new QueueServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            } else {
                $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
            }

            $ret = (is_null($options) ? $this->restProxy->getServiceProperties() : $this->restProxy->getServiceProperties($options));
            $this->verifyServicePropertiesWorker($ret, $serviceProperties);
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new QueueServiceOptions();
            }

            if ($this->isEmulated()) {
                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                } else {
                    $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                }
            } else {
                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                } else {
                    throw $e;
                }
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testListQueuesNoOptions()
    {
        $this->listQueuesWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testListQueues()
    {
        $interestingListQueuesOptions = QueueServiceFunctionalTestData::getInterestingListQueuesOptions();
        foreach($interestingListQueuesOptions as $options)  {
            $this->listQueuesWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listQueues
     */
    private function listQueuesWorker($options)
    {
        $finished = false;
        while (!$finished) {
            try {
                $ret = (is_null($options) ? $this->restProxy->listQueues() : $this->restProxy->listQueues($options));

                if (is_null($options)) {
                    $options = new ListQueuesOptions();
                }

                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertTrue(false, 'Expect negative timeouts ' . $options->getTimeout() . ' in $options to throw');
                }
                $this->verifyListQueuesWorker($ret, $options);

                if (strlen($ret->getNextMarker()) == 0) {
                    self::println('Done with this loop');
                    $finished = true;
                } else {
                    self::println('Cycling to get the next marker: ' . $ret->getNextMarker());
                    $options->setMarker($ret->getNextMarker());
                }
            } catch (ServiceException $e) {
                $finished = true;
                if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                    $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
                } else {
                    throw $e;
                }
            }
        }
    }

    private function verifyListQueuesWorker($ret, $options)
    {
        // Uncomment when fixed
        // https://github.com/azure/azure-storage-php/issues/98
        //$this->assertEquals($accountName, $ret->getAccountName(), 'getAccountName');

        $this->assertEquals($options->getMarker(), $ret->getMarker(), 'getMarker');
        $this->assertEquals($options->getMaxResults(), $ret->getMaxResults(), 'getMaxResults');
        $this->assertEquals($options->getPrefix(), $ret->getPrefix(), 'getPrefix');

        $this->assertNotNull($ret->getQueues(), 'getQueues');

        if ($options->getMaxResults() == 0) {
            $this->assertNull($ret->getNextMarker(), 'When MaxResults is 0, expect getNextMarker (' . $ret->getNextMarker() . ')to be null');

            if (!is_null($options->getPrefix()) && $options->getPrefix() == QueueServiceFunctionalTestData::$nonExistQueuePrefix) {
                $this->assertEquals(0, count($ret->getQueues()), 'when MaxResults=0 and Prefix=(\'' . $options->getPrefix() . '\'), then Queues->length');
            } else if (!is_null($options->getPrefix()) && $options->getPrefix() == QueueServiceFunctionalTestData::$testUniqueId) {
                $this->assertEquals(count(QueueServiceFunctionalTestData::$testQueueNames), count($ret->getQueues()), 'when MaxResults=0 and Prefix=(\'' . $options->getPrefix() . '\'), then count Queues');
            } else {
                // Don't know how many there should be
            }
        } else if (strlen($ret->getNextMarker()) == 0) {
            $this->assertTrue(count($ret ->getQueues()) <= $options->getMaxResults(), 'when NextMarker (\'' . $ret->getNextMarker() . '\')==\'\', Queues->length (' . count($ret->getQueues()) . ') should be <= MaxResults (' . $options->getMaxResults() . ')');

            if (!is_null($options->getPrefix()) && $options->getPrefix() == QueueServiceFunctionalTestData::$nonExistQueuePrefix) {
                $this->assertEquals(0, count($ret->getQueues()), 'when no next marker and Prefix=(\'' . $options->getPrefix() . '\'), then Queues->length');
            } else if (!is_null($options->getPrefix()) && $options->getPrefix() == QueueServiceFunctionalTestData::$testUniqueId) {
                // Need to futz with the mod because you are allowed to get MaxResults items returned.
                $this->assertEquals(count(QueueServiceFunctionalTestData::$testQueueNames) % $options->getMaxResults(), count($ret ->getQueues()) % $options->getMaxResults(), 'when no next marker and Prefix=(\'' . $options->getPrefix() . '\'), then Queues->length');
            } else {
                // Don't know how many there should be
            }
        } else {
            $this->assertEquals(
                    count($ret ->getQueues()),
                    $options->getMaxResults(),
                    'when NextMarker (' . $ret->getNextMarker() .
                    ')!=\'\', Queues->length (' . count($ret->getQueues()) .
                    ') should be == MaxResults (' . $options->getMaxResults() . ')');

            if (!is_null($options->getPrefix()) && $options->getPrefix() == (QueueServiceFunctionalTestData::$nonExistQueuePrefix)) {
                $this->assertTrue(false, 'when a next marker and Prefix=(\'' . $options->getPrefix() . '\'), impossible');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testCreateQueueNoOptions()
    {
        $this->createQueueWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testCreateQueue()
    {
        $interestingCreateQueueOptions = QueueServiceFunctionalTestData::getInterestingCreateQueueOptions();
        foreach($interestingCreateQueueOptions as $options)  {
            $this->createQueueWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createQueue
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::deleteQueue
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::getQueueMetadata
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listQueues
     */
    private function createQueueWorker($options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options));
        $queue = QueueServiceFunctionalTestData::getInterestingQueueName();
        $created = false;

        try {
            if (is_null($options)) {
                $this->restProxy->createQueue($queue);
            } else {
                // TODO: https://github.com/azure/azure-storage-php/issues/105
                $this->restProxy->createQueue($queue, $options);
            }
            $created = true;

            if (is_null($options)) {
                $options = new CreateQueueOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            // Now check that the queue was created correctly.

            // Make sure that the list of all applicable queues is correctly updated.
            $opts = new ListQueuesOptions();
            $opts->setPrefix(QueueServiceFunctionalTestData::$testUniqueId);
            $qs = $this->restProxy->listQueues($opts);
            $this->assertEquals(count($qs->getQueues()), (count(QueueServiceFunctionalTestData::$testQueueNames) + 1), 'After adding one, with Prefix=(\'' . QueueServiceFunctionalTestData::$testUniqueId . '\'), then Queues->length');

            // Check the metadata on the queue
            $ret = $this->restProxy->getQueueMetadata($queue);
            $this->verifyCreateQueueWorker($ret, $options);
            $this->restProxy->deleteQueue($queue);
            $created = false;
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new CreateQueueOptions();
            }
            if (!is_null($options->getTimeout()) && $options->getTimeout() <= 0) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        if ($created) {
            $this->restProxy->deleteQueue($queue);
        }
    }

    private function verifyCreateQueueWorker($ret, $options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options) .
                ' and ret ' . self::tmptostring($ret));
        if (is_null($options)) {
            $options = QueueServiceFunctionalTestData::getInterestingCreateQueueOptions();
            $options = $options[0];
        }

        if (is_null($options->getMetadata())) {
            $this->assertNotNull($ret->getMetadata(), 'queue Metadata');
            $this->assertEquals(0, count($ret->getMetadata()), 'queue Metadata count');
        } else {
            $this->assertNotNull($ret->getMetadata(), 'queue Metadata');
            $this->assertEquals(count($options->getMetadata()), count($ret->getMetadata()), 'Metadata');
            $om = $options->getMetadata();
            $rm = $ret->getMetadata();
            foreach(array_keys($options->getMetadata()) as $key)  {
                $this->assertEquals($om[$key], $rm[$key], 'Metadata(' . $key . ')');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testDeleteQueueNoOptions()
    {
        $this->deleteQueueWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testDeleteQueue()
    {
        $interestingTimeouts = QueueServiceFunctionalTestData::getInterestingTimeoutValues();
        foreach($interestingTimeouts as $timeout)  {
            $options = new QueueServiceOptions();
            $options->setTimeout($timeout);
            $this->deleteQueueWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createQueue
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::deleteQueue
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listQueues
     */
    private function deleteQueueWorker($options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options));
        $queue = QueueServiceFunctionalTestData::getInterestingQueueName();

        // Make sure there is something to delete.
        $this->restProxy->createQueue($queue);

        // Make sure that the list of all applicable queues is correctly updated.
        $opts = new ListQueuesOptions();
        $opts->setPrefix(QueueServiceFunctionalTestData::$testUniqueId);
        $qs = $this->restProxy->listQueues($opts);
        $this->assertEquals(count($qs->getQueues()), (count(QueueServiceFunctionalTestData::$testQueueNames) + 1), 'After adding one, with Prefix=(\'' . QueueServiceFunctionalTestData::$testUniqueId . '\'), then Queues->length');

        $deleted = false;
        try {
            if (is_null($options)) {
                $this->restProxy->deleteQueue($queue);
            } else {
                $this->restProxy->deleteQueue($queue, $options);
            }

            $deleted = true;

            if (is_null($options)) {
                $options = new QueueServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            // Make sure that the list of all applicable queues is correctly updated.
            $opts = new ListQueuesOptions();
            $opts->setPrefix(QueueServiceFunctionalTestData::$testUniqueId);
            $qs = $this->restProxy->listQueues($opts);
            $this->assertEquals(count($qs->getQueues()), count(QueueServiceFunctionalTestData::$testQueueNames), 'After adding then deleting one, with Prefix=(\'' . QueueServiceFunctionalTestData::$testUniqueId . '\'), then Queues->length');

            // Nothing else interesting to check for the options.
        } catch (ServiceException $e) {
            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        if (!$deleted) {
            // Try again. If it doesn't work, not much else to try.
            $this->restProxy->deleteQueue($queue);
        }
    }

    // TODO: Negative tests, like accessing a non-existant queue, or recreating an existing queue?

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testGetQueueMetadataNoOptions()
    {
        $interestingMetadata = QueueServiceFunctionalTestData::getNiceMetadata();
        foreach ($interestingMetadata as $metadata) {
            $this->getQueueMetadataWorker(null, $metadata);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testGetQueueMetadata()
    {
        $interestingTimeouts = QueueServiceFunctionalTestData::getInterestingTimeoutValues();
        $interestingMetadata = QueueServiceFunctionalTestData::getNiceMetadata();

        foreach($interestingTimeouts as $timeout)  {
            foreach ($interestingMetadata as $metadata) {
                $options = new QueueServiceOptions();
                $options->setTimeout($timeout);
                $this->getQueueMetadataWorker($options, $metadata);
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createMessage
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createQueue
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::deleteQueue
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::getQueueMetadata
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::setQueueMetadata
     */
    private function getQueueMetadataWorker($options, $metadata)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options) .
                ' and $metadata: ' . self::tmptostring($metadata));
        $queue = QueueServiceFunctionalTestData::getInterestingQueueName();

        // Make sure there is something to test
        $this->restProxy->createQueue($queue);

        // Put some messages to verify getApproximateMessageCount
        if (!is_null($metadata)) {
            for ($i = 0; $i < count($metadata); $i++) {
                $this->restProxy->createMessage($queue, 'message ' . $i);
            }

            // And put in some metadata
            $this->restProxy->setQueueMetadata($queue, $metadata);
        }

        try {
            $res = (is_null($options) ? $this->restProxy->getQueueMetadata($queue) : $this->restProxy->getQueueMetadata( $queue, $options));

            if (is_null($options)) {
                $options = new QueueServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            $this->verifyGetSetQueueMetadataWorker($res, $metadata);
        } catch (ServiceException $e) {
            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        // Clean up->
        $this->restProxy->deleteQueue($queue);
    }

    private function verifyGetSetQueueMetadataWorker($ret, $metadata)
    {
        $this->assertNotNull($ret->getMetadata(), 'queue Metadata');
        if (is_null($metadata)) {
            $this->assertEquals(0, count($ret->getMetadata()), 'Metadata');
            $this->assertEquals(0, $ret->getApproximateMessageCount(), 'getApproximateMessageCount');
        } else {
            $this->assertEquals(count($metadata), count($ret->getMetadata()), 'Metadata');
            $rm =$ret->getMetadata();
            foreach(array_keys($metadata) as $key)  {
                $this->assertEquals($metadata[$key], $rm[$key], 'Metadata(' . $key . ')');
            }

            // Hard to test "approximate", so just verify that it is in the expected range
            $this->assertTrue(
                    (0 <= $ret->getApproximateMessageCount()) && ($ret->getApproximateMessageCount() <= count($metadata)),
                    '0 <= getApproximateMessageCount (' . $ret->getApproximateMessageCount() . ') <= $metadata count (' . count($metadata) . ')');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testSetQueueMetadataNoOptions()
    {
        $interestingMetadata = QueueServiceFunctionalTestData::getInterestingMetadata();
        foreach ($interestingMetadata as $metadata) {
            if (is_null($metadata)) {
                // This is tested above.
                continue;
            }
            $this->setQueueMetadataWorker(null, $metadata);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testSetQueueMetadata()
    {
        $interestingTimeouts = QueueServiceFunctionalTestData::getInterestingTimeoutValues();
        $interestingMetadata = QueueServiceFunctionalTestData::getInterestingMetadata();

        foreach($interestingTimeouts as $timeout)  {
            foreach ($interestingMetadata as $metadata) {
                if (is_null($metadata)) {
                    // This is tested above.
                    continue;
                }
                $options = new QueueServiceOptions();
                $options->setTimeout($timeout);
                $this->setQueueMetadataWorker($options, $metadata);
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createQueue
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::deleteQueue
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::getQueueMetadata
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::setQueueMetadata
     */
    private function setQueueMetadataWorker($options, $metadata)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options) .
                ' and $metadata: ' . self::tmptostring($metadata));
        $queue = QueueServiceFunctionalTestData::getInterestingQueueName();

        // Make sure there is something to test
        $this->restProxy->createQueue($queue);

        try {
            // And put in some metadata
            if (is_null($options)) {
                $this->restProxy->setQueueMetadata($queue, $metadata);
            } else {
                $this->restProxy->setQueueMetadata($queue, $metadata, $options);
            }

            if (is_null($options)) {
                $options = new QueueServiceOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            $res = $this->restProxy->getQueueMetadata($queue);
            $this->verifyGetSetQueueMetadataWorker($res, $metadata);
        } catch (ServiceException $e) {
            if (!is_null($metadata) && count($metadata) > 0) {
                $keypart = array_keys($metadata);
                $keypart = $keypart[0];
                if (substr($keypart, 0, 1) == '<')
                {
                    $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                }
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        // Clean up.
        $this->restProxy->deleteQueue($queue);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testCreateMessageEmpty()
    {
        $this->createMessageWorker('', QueueServiceFunctionalTestData::getSimpleCreateMessageOptions());
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testCreateMessageUnicodeMessage()
    {
        $this->createMessageWorker('Some unicode: ' .
                chr(0xEB) . chr(0x8B) . chr(0xA4) . // \uB2E4 in UTF-8
                chr(0xEB) . chr(0xA5) . chr(0xB4) . // \uB974 in UTF-8
                chr(0xEB) . chr(0x8B) . chr(0xA4) . // \uB2E4 in UTF-8
                chr(0xEB) . chr(0x8A) . chr(0x94) . // \uB294 in UTF-8
                chr(0xD8) . chr(0xA5) .             // \u0625 in UTF-8
                ' ' .
                chr(0xD9) . chr(0x8A) .             // \u064A in UTF-8
                chr(0xD8) . chr(0xAF) .             // \u062F in UTF-8
                chr(0xD9) . chr(0x8A) .             // \u064A in UTF-8
                chr(0xD9) . chr(0x88),              // \u0648 in UTF-8
                QueueServiceFunctionalTestData::getSimpleCreateMessageOptions());
        }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testCreateMessageXmlMessage()
    {
        $this->createMessageWorker('Some HTML: <this><is></a>', QueueServiceFunctionalTestData::getSimpleCreateMessageOptions());
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testCreateMessageWithSmallTTL()
    {
        $queue = QueueServiceFunctionalTestData::$testQueueNames;
        $queue = $queue[0];
        $this->restProxy->clearMessages($queue);
        $messageText = QueueServiceFunctionalTestData::getSimpleMessageText();

        $options = new CreateMessageOptions();
        $options->setVisibilityTimeoutInSeconds(2);
        $options->setTimeToLiveInSeconds('4');

        $this->restProxy->createMessage($queue, $messageText, $options);

        $lmr = $this->restProxy->listMessages($queue);

        // No messages, because it is not visible for 2 seconds.
        $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');
        sleep(6);
        // Try again, passed the VisibilityTimeout has passed, but also the 4 second TTL has passed.
        $lmr = $this->restProxy->listMessages($queue);

        $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');

        $this->restProxy->clearMessages($queue);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testCreateMessage()
    {
        $interestingTimes = array( null, -1, 0, QueueServiceFunctionalTestData::INTERESTING_TTL, 1000 );
        foreach($interestingTimes as $timeToLiveInSeconds)  {
            foreach($interestingTimes as $visibilityTimeoutInSeconds)  {
                $timeout = null;
                $options = new CreateMessageOptions();
                $options->setTimeout($timeout);

                $options->setTimeToLiveInSeconds($timeToLiveInSeconds);
                $options->setVisibilityTimeoutInSeconds($visibilityTimeoutInSeconds . '');
                $this->createMessageWorker(QueueServiceFunctionalTestData::getSimpleMessageText(), $options);
            }
        }

        foreach($interestingTimes as $timeout)  {
            $timeToLiveInSeconds = 1000;
            $visibilityTimeoutInSeconds = QueueServiceFunctionalTestData::INTERESTING_TTL;
            $options = new CreateMessageOptions();
            $options->setTimeout($timeout);

            $options->setTimeToLiveInSeconds($timeToLiveInSeconds . '');
            $options->setVisibilityTimeoutInSeconds($visibilityTimeoutInSeconds);
            $this->createMessageWorker(QueueServiceFunctionalTestData::getSimpleMessageText(), $options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::clearMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createMessage
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listMessages
     */
    private function createMessageWorker($messageText, $options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options));
        $queue = QueueServiceFunctionalTestData::$testQueueNames;
        $queue = $queue[0];
        $this->restProxy->clearMessages($queue);

        try {
            if (is_null($options)) {
                $this->restProxy->createMessage($queue, $messageText);
            } else {
                $this->restProxy->createMessage($queue, $messageText, $options);
            }

            if (is_null($options)) {
                $options = new CreateMessageOptions();
            }

            if (!is_null($options->getVisibilityTimeoutInSeconds()) && $options->getVisibilityTimeoutInSeconds() < 0) {
                $this->assertTrue(false, 'Expect negative getVisibilityTimeoutInSeconds in $options to throw');
            } else if (!is_null($options->getTimeToLiveInSeconds()) && $options->getTimeToLiveInSeconds() <= 0) {
                $this->assertTrue(false, 'Expect negative getVisibilityTimeoutInSeconds in $options to throw');
            } else if (!is_null($options->getVisibilityTimeoutInSeconds()) &&
                    !is_null($options->getTimeToLiveInSeconds()) &&
                    $options->getVisibilityTimeoutInSeconds() > 0 &&
                    $options->getTimeToLiveInSeconds() <= $options->getVisibilityTimeoutInSeconds()) {
                $this->assertTrue(false, 'Expect getTimeToLiveInSeconds() <= getVisibilityTimeoutInSeconds in $options to throw');
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            // Check that the message matches
            $lmr = $this->restProxy->listMessages($queue);
            if (!is_null($options->getVisibilityTimeoutInSeconds()) && $options->getVisibilityTimeoutInSeconds() > 0) {
                $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');
                sleep(QueueServiceFunctionalTestData::INTERESTING_TTL);
                // Try again, not that the 4 second visibility has passed
                $lmr = $this->restProxy->listMessages($queue);
                if ($options->getVisibilityTimeoutInSeconds() > QueueServiceFunctionalTestData::INTERESTING_TTL) {
                    $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');
                } else {
                    $this->assertEquals(1, count($lmr->getQueueMessages()), 'getQueueMessages() count');
                    $qm = $lmr->getQueueMessages();
                    $qm = $qm[0];
                    $this->assertEquals($messageText, $qm->getMessageText(), '$qm->getMessageText');
                }
            } else {
                $this->assertEquals(1, count($lmr->getQueueMessages()), 'getQueueMessages() count');
                $qm = $lmr->getQueueMessages();
                $qm = $qm[0];
                $this->assertEquals($messageText, $qm->getMessageText(), '$qm->getMessageText');
            }

        } catch (ServiceException $e) {
            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else if (!is_null($options->getVisibilityTimeoutInSeconds()) && $options->getVisibilityTimeoutInSeconds() < 0) {
                // Trying to pass bad metadata
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else if (!is_null($options->getTimeToLiveInSeconds()) && $options->getTimeToLiveInSeconds() <= 0) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else if (!is_null($options->getVisibilityTimeoutInSeconds()) && !is_null($options->getTimeToLiveInSeconds()) && $options->getVisibilityTimeoutInSeconds() > 0 && $options->getTimeToLiveInSeconds() <= $options->getVisibilityTimeoutInSeconds()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->restProxy->clearMessages($queue);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageNoOptions()
    {
        $interestingVisibilityTimes = array(-1, 0, QueueServiceFunctionalTestData::INTERESTING_TTL, QueueServiceFunctionalTestData::INTERESTING_TTL * 2);

        $startingMessage = new CreateMessageOptions();
        $startingMessage->setTimeout(QueueServiceFunctionalTestData::INTERESTING_TTL);
        $startingMessage->setTimeToLiveInSeconds(QueueServiceFunctionalTestData::INTERESTING_TTL * 1.5);

        foreach($interestingVisibilityTimes as $visibilityTimeoutInSeconds)  {
            $this->updateMessageWorker(QueueServiceFunctionalTestData::getSimpleMessageText(), $startingMessage, $visibilityTimeoutInSeconds, null);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessage()
    {
        $interestingTimes = array(null, -1, 0, QueueServiceFunctionalTestData::INTERESTING_TTL, 1000);

        $interestingVisibilityTimes = array(-1, 0, QueueServiceFunctionalTestData::INTERESTING_TTL, QueueServiceFunctionalTestData::INTERESTING_TTL * 2);

        $startingMessage = new CreateMessageOptions();
        $startingMessage->setTimeout( QueueServiceFunctionalTestData::INTERESTING_TTL);
        $startingMessage->setTimeToLiveInSeconds(QueueServiceFunctionalTestData::INTERESTING_TTL * 1.5);

        foreach($interestingTimes as $timeout)  {
            foreach($interestingVisibilityTimes as $visibilityTimeoutInSeconds)  {
                $options = new QueueServiceOptions();
                $options->setTimeout($timeout);
                $this->updateMessageWorker(QueueServiceFunctionalTestData::getSimpleMessageText(), $startingMessage, $visibilityTimeoutInSeconds, $options);
            }
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::clearMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createMessage
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::updateMessage
     */
    private function updateMessageWorker($messageText, $startingMessage, $visibilityTimeoutInSeconds, $options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options) .
                ' and $visibilityTimeoutInSeconds: ' . $visibilityTimeoutInSeconds);
        $queue = QueueServiceFunctionalTestData::$testQueueNames;
        $queue = $queue[0];
        $this->restProxy->clearMessages($queue);

        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText(), $startingMessage);
        $lmr = $this->restProxy->listMessages($queue);
        $m = $lmr->getQueueMessages();
        $m = $m[0];

        try {
            if (is_null($options)) {
                $this->restProxy->updateMessage($queue, $m->getMessageId(), $m->getPopReceipt(), $messageText, $visibilityTimeoutInSeconds);
            } else {
                $this->restProxy->updateMessage($queue, $m->getMessageId(), $m->getPopReceipt(), $messageText, $visibilityTimeoutInSeconds, $options);
            }

            if (is_null($options)) {
                $options = new CreateMessageOptions();
            }

            if ($visibilityTimeoutInSeconds < 0) {
                $this->assertTrue(false, 'Expect negative getVisibilityTimeoutInSeconds in $options to throw');
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            // Check that the message matches
            $lmr = $this->restProxy->listMessages($queue);
            if ($visibilityTimeoutInSeconds > 0) {
                $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');
                sleep(QueueServiceFunctionalTestData::INTERESTING_TTL);
                // Try again, not that the 4 second visibility has passed
                $lmr = $this->restProxy->listMessages($queue);
                if ($visibilityTimeoutInSeconds >= QueueServiceFunctionalTestData::INTERESTING_TTL) {
                    $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');
                } else {
                    $this->assertEquals(1, count($lmr->getQueueMessages()), 'getQueueMessages() count');
                    $qm = $lmr->getQueueMessages();
                    $qm = $qm[0];
                    $this->assertEquals($messageText, $qm->getMessageText(), '$qm->getMessageText');
                }
            } else {
                $this->assertEquals(1, count($lmr->getQueueMessages()), 'getQueueMessages() count');
                $qm = $lmr->getQueueMessages();
                $qm = $qm[0];
                $this->assertEquals($messageText, $qm->getMessageText(), '$qm->getMessageText');
            }

        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new CreateMessageOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else if ($visibilityTimeoutInSeconds < 0) {
                // Trying to pass bad metadata
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->restProxy->clearMessages($queue);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testDeleteMessageNoOptions()
    {
        $this->deleteMessageWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testDeleteMessage()
    {
        $interestingTimes = array(null, -1, 0, QueueServiceFunctionalTestData::INTERESTING_TTL, 1000);
        foreach($interestingTimes as $timeout)  {
            $options = new QueueServiceOptions();
            $options->setTimeout($timeout);
            $this->deleteMessageWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::clearMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createMessage
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::deleteMessage
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listMessages
     */
    private function deleteMessageWorker($options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options));
        $queue = QueueServiceFunctionalTestData::$testQueueNames;
        $queue = $queue[0];
        $this->restProxy->clearMessages($queue);

        $this->restProxy->createMessage($queue, 'test');
        $opts = new ListMessagesOptions();
        $opts->setVisibilityTimeoutInSeconds(QueueServiceFunctionalTestData::INTERESTING_TTL);
        $lmr = $this->restProxy->listMessages($queue, $opts);
        $m = $lmr->getQueueMessages();
        $m = $m[0];

        try {
            if (is_null($options)) {
                $this->restProxy->deleteMessage($queue, $m->getMessageId(), $m->getPopReceipt());
            } else {
                $this->restProxy->deleteMessage($queue, $m->getMessageId(), $m->getPopReceipt(), $options);
            }

            if (is_null($options)) {
                $options = new CreateMessageOptions();
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            // Check that the message matches
            $lmr = $this->restProxy->listMessages($queue);
            $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');

            // Wait until the popped message should be visible again.
            sleep(QueueServiceFunctionalTestData::INTERESTING_TTL + 1);
            // Try again, to make sure the message really is gone.
            $lmr = $this->restProxy->listMessages($queue);
            $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');
        } catch (ServiceException $e) {
            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else  {
                throw $e;
            }
        }
        $this->restProxy->clearMessages($queue);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testListMessagesNoOptions()
    {
        $this->listMessagesWorker(new ListMessagesOptions());
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testListMessages()
    {
        $interestingTimes = array(null, -1, 0, QueueServiceFunctionalTestData::INTERESTING_TTL, 1000);
        $interestingNums = array(null, -1, 0, 2, 10, 1000);
        foreach($interestingNums as $numberOfMessages)  {
            foreach($interestingTimes as $visibilityTimeoutInSeconds)  {
                $options = new ListMessagesOptions();
                $options->setNumberOfMessages($numberOfMessages);
                $options->setVisibilityTimeoutInSeconds($visibilityTimeoutInSeconds);
                $this->listMessagesWorker($options);
            }
        }

        foreach($interestingTimes as $timeout)  {
            $options = new ListMessagesOptions();
            $options->setTimeout($timeout);
            $options->setNumberOfMessages(2);
            $options->setVisibilityTimeoutInSeconds(2);
            $this->listMessagesWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::clearMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createMessage
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::peekMessages
     */
    private function listMessagesWorker($options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options));
        $queue = QueueServiceFunctionalTestData::$testQueueNames;
        $queue = $queue[0];
        $this->restProxy->clearMessages($queue);

        // Put three messages into the queue.
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());

        // Default is 1 message
        $effectiveNumOfMessages = (is_null($options) || is_null($options->getNumberOfMessages()) ? 1 : $options ->getNumberOfMessages());
        $effectiveNumOfMessages = ($effectiveNumOfMessages < 0 ? 0 : $effectiveNumOfMessages);

        // Default is 30 seconds
        $effectiveVisTimeout = (is_null($options) || is_null($options->getVisibilityTimeoutInSeconds()) ? 30 : $options ->getVisibilityTimeoutInSeconds());
        $effectiveVisTimeout = ($effectiveVisTimeout < 0 ? 0 : $effectiveVisTimeout);

        $expectedNumMessagesFirst = ($effectiveNumOfMessages > 3 ? 3 : $effectiveNumOfMessages);
        $expectedNumMessagesSecond = ($effectiveVisTimeout <= 2 ? 3 : 3 - $effectiveNumOfMessages);
        $expectedNumMessagesSecond = ($expectedNumMessagesSecond < 0 ? 0 : $expectedNumMessagesSecond);

        try {
            $res = (is_null($options) ? $this->restProxy->listMessages($queue) : $this->restProxy->listMessages($queue, $options));

            if (is_null($options)) {
                $options = new ListMessagesOptions();
            }

            if (!is_null($options->getVisibilityTimeoutInSeconds()) && $options->getVisibilityTimeoutInSeconds() < 1) {
                $this->assertTrue(false, 'Expect non-positive getVisibilityTimeoutInSeconds in $options to throw');
            } else if (!is_null($options->getNumberOfMessages()) && ($options->getNumberOfMessages() < 1 || $options->getNumberOfMessages() > 32)) {
                $this->assertTrue(false, 'Expect  getNumberOfMessages < 1 or 32 < numMessages in $options to throw');
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            $this->assertEquals($expectedNumMessagesFirst, count($res->getQueueMessages()), 'list getQueueMessages() count');
            $opts = new PeekMessagesOptions();
            $opts->setNumberOfMessages(32);
            $pres = $this->restProxy->peekMessages($queue, $opts);
            $this->assertEquals(3 - $expectedNumMessagesFirst, count($pres->getQueueMessages()), 'peek getQueueMessages() count');

            // The visibilityTimeoutInSeconds controls when the requested messages will be visible again.
            // Wait 2.5 seconds to see when the messages are visible again.
            sleep(2.5);
            $opts = new ListMessagesOptions();
            $opts->setNumberOfMessages(32);
            $res2 = $this->restProxy->listMessages($queue, $opts);
            $this->assertEquals($expectedNumMessagesSecond, count($res2->getQueueMessages()), 'list getQueueMessages() count');
            $opts = new PeekMessagesOptions();
            $opts->setNumberOfMessages(32);
            $pres2 = $this->restProxy->peekMessages($queue, $opts);
            $this->assertEquals(0, count($pres2->getQueueMessages()), 'peek getQueueMessages() count');

            // TODO: These might get screwy if the timing gets off. Might need to use times spaces farther apart.
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new ListMessagesOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else if (!is_null($options->getVisibilityTimeoutInSeconds()) && $options->getVisibilityTimeoutInSeconds() < 1) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else if (!is_null($options->getNumberOfMessages()) && ($options->getNumberOfMessages() < 1 || $options->getNumberOfMessages() > 32)) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->restProxy->clearMessages($queue);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessagesNoOptions()
    {
        $this->peekMessagesWorker(new PeekMessagesOptions());
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessages()
    {
        $interestingTimes = array(null, -1, 0, QueueServiceFunctionalTestData::INTERESTING_TTL, 1000);
        $interestingNums = array(null, -1, 0, 2, 10, 1000);
        foreach($interestingNums as $numberOfMessages)  {
            $options = new PeekMessagesOptions();
            $options->setNumberOfMessages($numberOfMessages);
            $this->peekMessagesWorker($options);
        }

        foreach($interestingTimes as $timeout)  {
            $options = new PeekMessagesOptions();
            $options->setTimeout($timeout);
            $options->setNumberOfMessages(2);
            $this->peekMessagesWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::clearMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createMessage
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::peekMessages
     */
    private function peekMessagesWorker($options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options));
        $queue = QueueServiceFunctionalTestData::$testQueueNames;
        $queue = $queue[0];
        $this->restProxy->clearMessages($queue);

        // Put three messages into the queue.
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());

        // Default is 1 message
        $effectiveNumOfMessages = (is_null($options) || is_null($options->getNumberOfMessages()) ? 1 : $options ->getNumberOfMessages());
        $effectiveNumOfMessages = ($effectiveNumOfMessages < 0 ? 0 : $effectiveNumOfMessages);

        $expectedNumMessagesFirst = ($effectiveNumOfMessages > 3 ? 3 : $effectiveNumOfMessages);

        try {
            $res = (is_null($options) ? $this->restProxy->peekMessages($queue) : $this->restProxy->peekMessages($queue, $options));

            if (is_null($options)) {
                $options = new PeekMessagesOptions();
            }

            if (!is_null($options->getNumberOfMessages()) && ($options->getNumberOfMessages() < 1 || $options->getNumberOfMessages() > 32)) {
                $this->assertTrue(false, 'Expect  getNumberOfMessages < 1 or 32 < numMessages in $options to throw');
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            $this->assertEquals($expectedNumMessagesFirst, count($res->getQueueMessages()), 'getQueueMessages() count');
            $opts = new PeekMessagesOptions();
            $opts->setNumberOfMessages(32);
            $res2 = $this->restProxy->peekMessages($queue, $opts);
            $this->assertEquals(3, count($res2->getQueueMessages()), 'getQueueMessages() count');
            $this->restProxy->listMessages($queue);
            $opts = new PeekMessagesOptions();
            $opts->setNumberOfMessages(32);
            $res3 = $this->restProxy->peekMessages($queue, $opts);
            $this->assertEquals(2, count($res3->getQueueMessages()), 'getQueueMessages() count');
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new PeekMessagesOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else if (!is_null($options->getNumberOfMessages()) && ($options->getNumberOfMessages() < 1 || $options->getNumberOfMessages() > 32)) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->restProxy->clearMessages($queue);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testClearMessagesNoOptions()
    {
        $this->clearMessagesWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testClearMessages()
    {
        $interestingTimes = array(null, -1, 0, QueueServiceFunctionalTestData::INTERESTING_TTL, 1000);
        foreach($interestingTimes as $timeout)  {
            $options = new QueueServiceOptions();
            $options->setTimeout($timeout);
            $this->clearMessagesWorker($options);
        }
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::clearMessages
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createMessage
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::listMessages
     */
    private function clearMessagesWorker($options)
    {
        self::println( 'Trying $options: ' .
                self::tmptostring($options));
        $queue = QueueServiceFunctionalTestData::$testQueueNames;
        $queue = $queue[0];
        $this->restProxy->clearMessages($queue);

        // Put three messages into the queue.
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());
        $this->restProxy->createMessage($queue, QueueServiceFunctionalTestData::getSimpleMessageText());
        // Wait a bit to make sure the messages are there.
        sleep(1);
        // Make sure the messages are there, and use a short visibility timeout to make sure the are visible again later.
        $opts = new ListMessagesOptions();
        $opts->setVisibilityTimeoutInSeconds(1);
        $opts->setNumberOfMessages(32);
        $lmr = $this->restProxy->listMessages($queue, $opts);
        $this->assertEquals(3, count($lmr->getQueueMessages()), 'getQueueMessages() count');
        sleep(2);
        try {
            if (is_null($options)) {
                $this->restProxy->clearMessages($queue);
            } else {
                $this->restProxy->clearMessages($queue, $options);
            }

            if (is_null($options)) {
                $options = new CreateMessageOptions();
            } else if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertTrue(false, 'Expect negative timeouts in $options to throw');
            }

            // Wait 2 seconds to make sure the messages would be visible again.
            $opts = new ListMessagesOptions();
            $opts->setVisibilityTimeoutInSeconds(1);
            $opts->setNumberOfMessages(32);
            $lmr = $this->restProxy->listMessages($queue, $opts);
            $this->assertEquals(0, count($lmr->getQueueMessages()), 'getQueueMessages() count');
        } catch (ServiceException $e) {
            if (is_null($options)) {
                $options = new CreateMessageOptions();
            }

            if (!is_null($options->getTimeout()) && $options->getTimeout() < 1) {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->restProxy->clearMessages($queue);
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Queue;

use MicrosoftAzure\Storage\Common\Models\Logging;
use MicrosoftAzure\Storage\Common\Models\Metrics;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions;

class QueueServiceFunctionalTestData
{
    const INTERESTING_TTL = 4;
    public static $testUniqueId;
    public static $tempQueueCounter;
    public static $nonExistQueuePrefix;
    public static $testQueueNames;

    public static function setupData()
    {
        $rint = mt_rand(0, 1000000);
        self::$testUniqueId = 'qa-' . $rint . '-';
        self::$nonExistQueuePrefix = 'qa-' . ($rint + 1) . '-';
        self::$testQueueNames = array(
            self::$testUniqueId . 'a1',
            self::$testUniqueId . 'a2',
            self::$testUniqueId . 'b1',
        );
        self::$tempQueueCounter = 0;
    }

    public static function getInterestingQueueName()
    {
        return self::$testUniqueId . 'int-' . (self::$tempQueueCounter++);
    }

    public static function getSimpleMessageText()
    {
        return 'simple message text #' . (self::$tempQueueCounter++);
    }

    public static function getInterestingTimeoutValues()
    {
        $ret = array();
        array_push($ret, null);
        array_push($ret, -1);
        array_push($ret,  0);
        array_push($ret,  1);
        array_push($ret,-2147483648);
        array_push($ret, 2147483647);
        return $ret;
    }

    public static function getDefaultServiceProperties()
    {
        // This is the default that comes from the server.
        $rp = new RetentionPolicy();
        $l = new Logging();
        $l->setRetentionPolicy($rp);
        $l->setVersion('1.0');
        $l->setDelete(false);
        $l->setRead(false);
        $l->setWrite(false);

        $m = new Metrics();
        $m->setRetentionPolicy($rp);
        $m->setVersion('1.0');
        $m->setEnabled(false);
        $m->setIncludeAPIs(null);

        $sp = new ServiceProperties();
        $sp->setLogging($l);
        $sp->setMetrics($m);

        return $sp;
    }

    public static function getInterestingServiceProperties()
    {
        $ret = array();

        {
            // This is the default that comes from the server.
            array_push($ret, self::getDefaultServiceProperties());
        }

        {
            $rp = new RetentionPolicy();
            $rp->setEnabled(true);
            $rp->setDays(10);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(true);
            $l->setRead(true);
            $l->setWrite(true);

            $m = new Metrics();
            $m->setRetentionPolicy($rp);
            $m->setVersion('1.0');
            $m->setEnabled(true);
            $m->setIncludeAPIs(true);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret,$sp);
        }

        {
            $rp = new RetentionPolicy();
            // The service does not accept setting days when enabled is false.
            $rp->setEnabled(false);
            $rp->setDays(null);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(false);
            $l->setRead(false);
            $l->setWrite(false);

            $m = new Metrics();
            $m->setRetentionPolicy($rp);
            $m->setVersion('1.0');
            $m->setEnabled(true);
            $m->setIncludeAPIs(true);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret,$sp);
        }

        {
            $rp = new RetentionPolicy();
            $rp->setEnabled(true);
            // Days has to be 0 < days <= 365
            $rp->setDays(364);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(false);
            $l->setRead(false);
            $l->setWrite(false);

            $m = new Metrics();
            $m->setVersion('1.0');
            $m->setEnabled(false);
            $m->setIncludeAPIs(null);
            $m->setRetentionPolicy($rp);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret,$sp);
        }

        return $ret;
    }

    public static function getInterestingMetadata()
    {
        $ret = self::getNiceMetadata();

        // Some metadata that HTTP will not like.
        $metadata = array('<>000' => '::::value');
        array_push($ret,$metadata);

        return $ret;
    }

    public static function getNiceMetadata()
    {
        $ret = array();

        array_push($ret, null);

        $metadata = array();
        array_push($ret, $metadata);

        $metadata = array(
            'key' => 'value',
            'foo' => 'bar',
            'baz' => 'boo');
        array_push($ret, $metadata);

        return $ret;
    }

    public static function getInterestingCreateQueueOptions()
    {
        $ret = array();

        $options = new CreateQueueOptions();
        array_push($ret, $options);

        $options = new CreateQueueOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new CreateQueueOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        $options = new CreateQueueOptions();
        $metadata = array();
        $metadata['foo'] =  'bar';
        $metadata['foo2'] = 'bar2';
        $metadata['foo3'] = 'bar3';
        $options->setMetadata($metadata);
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new CreateQueueOptions();
        $metadata = array('foo' => 'bar');
        $options->setMetadata($metadata);
        $options->setTimeout(-10);
        array_push($ret, $options);

        return $ret;
    }

    public static function getSimpleCreateMessageOptions()
    {
        $ret = new CreateMessageOptions();
        $ret->setTimeout(4);
        $ret->setTimeToLiveInSeconds(1000);
        $ret->setVisibilityTimeoutInSeconds(self::INTERESTING_TTL);
        return $ret;
    }

    public static function getInterestingListQueuesOptions()
    {
        $ret = array();

        $options = new ListQueuesOptions();
        array_push($ret, $options);

        $options = new ListQueuesOptions();
        $options->setMaxResults(2);
        $options->setMaxResults('2');
        array_push($ret, $options);

        $options = new ListQueuesOptions();
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new ListQueuesOptions();
        $options->setTimeout(-10);
        array_push($ret, $options);

        $options = new ListQueuesOptions();
        $options->setPrefix(self::$nonExistQueuePrefix);
        array_push($ret, $options);

        $options = new ListQueuesOptions();
        $options->setPrefix(self::$testUniqueId);
        array_push($ret, $options);

        $options = new ListQueuesOptions();
        // Cannot set Marker to arbitrary values. Must only use if the previous request returns a NextMarker.
        //            $options->setMarker('abc');
        // So, add logic in listQueuesWorker to loop and setMarker if there is a NextMarker.
        $options->setMaxResults(2);
        $options->setPrefix(self::$testUniqueId);
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new ListQueuesOptions();
        $options->setMaxResults(3);
        $options->setPrefix(self::$testUniqueId);
        $options->setTimeout(10);
        array_push($ret, $options);

        $options = new ListQueuesOptions();
        $options->setMaxResults(4);
        $options->setPrefix(self::$testUniqueId);
        $options->setTimeout(10);
        array_push($ret, $options);

        return $ret;
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Queue;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;

class QueueServiceIntegrationTest extends IntegrationTestBase
{
    private static $testQueuesPrefix = 'sdktest-';
    private static $createableQueuesPrefix = 'csdktest-';
    private static $testQueueForMessages;
    private static $testQueueForMessages2;
    private static $testQueueForMessages3;
    private static $testQueueForMessages4;
    private static $testQueueForMessages5;
    private static $testQueueForMessages6;
    private static $testQueueForMessages7;
    private static $testQueueForMessages8;
    private static $creatableQueue1;
    private static $creatableQueue2;
    private static $creatableQueue3;
    private static $creatableQueues;
    private static $testQueues;

    public function setUp()
    {
        parent::setUp();
        // Setup container names array (list of container names used by
        // integration tests)
        self::$testQueues = array();
        $rint = mt_rand(0, 1000000);
        for ($i = 0; $i < 10; $i++) {
            self::$testQueues[$i] = self::$testQueuesPrefix . $rint . ($i + 1);
        }

        self::$creatableQueues = array();
        for ($i = 0; $i < 3; $i++) {
            self::$creatableQueues[$i] = self::$createableQueuesPrefix . $rint . ($i + 1);
        }

        self::$testQueueForMessages = self::$testQueues[0];
        self::$testQueueForMessages2 = self::$testQueues[1];
        self::$testQueueForMessages3 = self::$testQueues[2];
        self::$testQueueForMessages4 = self::$testQueues[3];
        self::$testQueueForMessages5 = self::$testQueues[4];
        self::$testQueueForMessages6 = self::$testQueues[5];
        self::$testQueueForMessages7 = self::$testQueues[6];
        self::$testQueueForMessages8 = self::$testQueues[7];

        self::$creatableQueue1 = self::$creatableQueues[0];
        self::$creatableQueue2 = self::$creatableQueues[1];
        self::$creatableQueue3 = self::$creatableQueues[2];

        // Create all test containers and their content

        self::createQueues(self::$testQueuesPrefix, self::$testQueues);
    }

    public function tearDown()
    {
        parent::tearDown();
        self::deleteQueues(self::$testQueuesPrefix, self::$testQueues);
        self::deleteQueues(self::$createableQueuesPrefix, self::$creatableQueues);
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    */
    private function createQueues($prefix, $list)
    {
        $containers = self::listQueues($prefix);
        foreach($list as $item)  {
            if (!in_array($item, $containers)) {
                $this->restProxy->createQueue($item);
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    */
    private function deleteQueues($prefix, $list)
    {
        $containers = self::listQueues($prefix);
        foreach($list as $item)  {
            if (in_array($item, $containers)) {
                $this->restProxy->deleteQueue($item);
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    private function listQueues($prefix)
    {
        $result = array();
        $opts = new ListQueuesOptions();
        $opts->setPrefix($prefix);
        $list = $this->restProxy->listQueues($opts);
        foreach($list->getQueues() as $item)  {
            array_push($result, $item->getName());
        }
        return $result;
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getServiceProperties
    */
    public function testGetServicePropertiesWorks()
    {
        // Arrange

        // Act
        $shouldReturn = false;
        try {
            $props = $this->restProxy->getServiceProperties()->getValue();
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            }
        }
        if($shouldReturn) {
            return;
        }

        // Assert
        $this->assertNotNull($props, '$props');
        $this->assertNotNull($props->getLogging(), '$props->getLogging');
        $this->assertNotNull($props->getLogging()->getRetentionPolicy(), '$props->getLogging()->getRetentionPolicy');
        $this->assertNotNull($props->getLogging()->getVersion(), '$props->getLogging()->getVersion');
        $this->assertNotNull($props->getMetrics()->getRetentionPolicy(), '$props->getMetrics()->getRetentionPolicy');
        $this->assertNotNull($props->getMetrics()->getVersion(), '$props->getMetrics()->getVersion');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesWorks()
    {
        // Arrange

        // Act
        $shouldReturn = false;
        try {
            $props = $this->restProxy->getServiceProperties()->getValue();
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            }
        }
        if($shouldReturn) {
            return;
        }

        $props->getLogging()->setRead(true);
        $this->restProxy->setServiceProperties($props);

        $props = $this->restProxy->getServiceProperties()->getValue();

        // Assert
        $this->assertNotNull($props, '$props');
        $this->assertNotNull($props->getLogging(), '$props->getLogging');
        $this->assertNotNull($props->getLogging()->getRetentionPolicy(), '$props->getLogging()->getRetentionPolicy');
        $this->assertNotNull($props->getLogging()->getVersion(), '$props->getLogging()->getVersion');
        $this->assertTrue($props->getLogging()->getRead(), '$props->getLogging()->getRead');
        $this->assertNotNull($props->getMetrics()->getRetentionPolicy(), '$props->getMetrics()->getRetentionPolicy');
        $this->assertNotNull($props->getMetrics()->getVersion(), '$props->getMetrics()->getVersion');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    */
    public function testCreateQueueWorks()
    {
        // Arrange

        // Act
        $this->restProxy->createQueue(self::$creatableQueue1);
        $result = $this->restProxy->getQueueMetadata(self::$creatableQueue1);
        $this->restProxy->deleteQueue(self::$creatableQueue1);

        // Assert
        $this->assertNotNull($result, 'result');
        $this->assertEquals(0, $result->getApproximateMessageCount(), '$result->getApproximateMessageCount');
        $this->assertNotNull($result->getMetadata(), '$result->getMetadata');
        $this->assertEquals(0, count($result->getMetadata()), 'count($result->getMetadata');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    */
    public function testCreateQueueWithOptionsWorks()
    {
        // Arrange

        // Act
        $opts = new CreateQueueOptions();
        $opts->addMetadata('foo', 'bar');
        $opts->addMetadata('test', 'blah');
        $this->restProxy->createQueue(self::$creatableQueue2, $opts);
        $result = $this->restProxy->getQueueMetadata(self::$creatableQueue2);
        $this->restProxy->deleteQueue(self::$creatableQueue2);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(0, $result->getApproximateMessageCount(), '$result->getApproximateMessageCount');
        $this->assertNotNull($result->getMetadata(), '$result->getMetadata');
        $this->assertEquals(2, count($result->getMetadata()), 'count($result->getMetadata');
        $metadata = $result->getMetadata();
        $this->assertEquals('bar', $metadata['foo'], '$metadata[foo]');
        $this->assertEquals('blah', $metadata['test'], '$metadata[test]');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testListQueuesWorks()
    {
        // Arrange

        // Act
        $result = $this->restProxy->listQueues();

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getQueues(), '$result->getQueues');

        // TODO: Uncomment when the following issue is fixed:
        // https://github.com/azure/azure-storage-php/issues/98
        // $this->assertNotNull($result->getAccountName(), '$result->getAccountName()');
        $this->assertEquals('', $result->getMarker(), '$result->getMarker');
        $this->assertNull($result->getMaxResults(), '$result->getMaxResults');
        $this->assertTrue(count(self::$testQueues) <= count($result->getQueues()), 'counts');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
    */
    public function testListQueuesWithOptionsWorks()
    {
        // Arrange

        // Act
        $opts = new ListQueuesOptions();
        $opts->setMaxResults(3);
        $opts->setIncludeMetadata(true);
        $opts->setPrefix(self::$testQueuesPrefix);
        $result = $this->restProxy->listQueues($opts);

        $opts = new ListQueuesOptions();
        $opts->setMarker($result->getNextMarker());
        $opts->setIncludeMetadata(false);
        $opts->setPrefix(self::$testQueuesPrefix);
        $result2 = $this->restProxy->listQueues($opts);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getQueues(), '$result->getQueues');
        $this->assertEquals(3, count($result->getQueues()), 'count($result->getQueues');
        $this->assertEquals(3, $result->getMaxResults(), '$result->getMaxResults');
        // TODO: Uncomment when the following issue is fixed:
        // https://github.com/azure/azure-storage-php/issues/98
        // $this->assertNotNull($result->getAccountName(), '$result->getAccountName()');
        $this->assertNull($result->getMarker(), '$result->getMarker');
        $queue0 = $result->getQueues();
        $queue0 = $queue0[0];
        $this->assertNotNull($queue0, '$queue0');
        $this->assertNotNull(
                $queue0->getMetadata(),
                '$queue0->getMetadata' .
                ' (https://github.com/azure/azure-storage-php/issues/252)');
        $this->assertNotNull($queue0->getName(), '$queue0->getName');
        $this->assertNotNull($queue0->getUrl(), '$queue0->getUrl');

        $this->assertNotNull($result2, '$result2');
        $this->assertNotNull($result2->getQueues(), '$result2->getQueues');
        $this->assertTrue(count(self::$testQueues) - 3 <= count($result2->getQueues()), 'count');
        $this->assertEquals(0, $result2->getMaxResults(), '$result2->getMaxResults');
        // TODO: Uncomment when the following issue is fixed:
        // https://github.com/azure/azure-storage-php/issues/98
        // $this->assertNotNull($result2->getAccountName(), '$result2->getAccountName()');
        $this->assertEquals($result->getNextMarker(), $result2->getMarker(), '$result2->getMarker');
        $queue20 = $result2->getQueues();
        $queue20 = $queue20[0];
        $this->assertNotNull($queue20, '$queue20');
        $this->assertNotNull($queue20->getMetadata(), '$queue20->getMetadata');
        $this->assertEquals(0, count($queue20->getMetadata()), 'count($queue20->getMetadata)');
        $this->assertNotNull($queue20->getName(), '$queue20->getName');
        $this->assertNotNull($queue20->getUrl(), '$queue20->getUrl');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
    */
    public function testSetQueueMetadataWorks()
    {
        // Arrange

        // Act
        $this->restProxy->createQueue(self::$creatableQueue3);

        $metadata = array(
            'foo' => 'bar',
            'test' => 'blah',
            );
        $this->restProxy->setQueueMetadata(self::$creatableQueue3, $metadata);

        $result = $this->restProxy->getQueueMetadata(self::$creatableQueue3);

        $this->restProxy->deleteQueue(self::$creatableQueue3);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(0, $result->getApproximateMessageCount(), '$result->getApproximateMessageCount');
        $this->assertNotNull($result->getMetadata(), '$result->getMetadata');
        $this->assertEquals(2, count($result->getMetadata()), 'count($result->getMetadata');
        $metadata = $result->getMetadata();
        $this->assertEquals('bar', $metadata['foo'], '$metadata[\'foo\']');
        $this->assertEquals('blah', $metadata['test'], '$metadata[\'test\']');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    */
    public function testCreateMessageWorks()
    {
        // Arrange

        // Act
        $this->restProxy->createMessage(self::$testQueueForMessages, 'message1');
        $this->restProxy->createMessage(self::$testQueueForMessages, 'message2');
        $this->restProxy->createMessage(self::$testQueueForMessages, 'message3');
        $this->restProxy->createMessage(self::$testQueueForMessages, 'message4');

        // Assert
        $this->assertTrue(true, 'if get there, it is working');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testListMessagesWorks()
    {
        // Arrange
        $year2010 = new \DateTime;
        $year2010->setDate(2010, 1, 1);

        // Act
        $this->restProxy->createMessage(self::$testQueueForMessages2, 'message1');
        $this->restProxy->createMessage(self::$testQueueForMessages2, 'message2');
        $this->restProxy->createMessage(self::$testQueueForMessages2, 'message3');
        $this->restProxy->createMessage(self::$testQueueForMessages2, 'message4');
        $result = $this->restProxy->listMessages(self::$testQueueForMessages2);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(1, count($result->getQueueMessages()), 'count($result->getQueueMessages');

        $entry = $result->getQueueMessages();
        $entry = $entry[0];

        $this->assertNotNull($entry->getMessageId(), '$entry->getMessageId');
        $this->assertNotNull($entry->getMessageText(), '$entry->getMessageText');
        $this->assertNotNull($entry->getPopReceipt(), '$entry->getPopReceipt');
        $this->assertEquals(1, $entry->getDequeueCount(), '$entry->getDequeueCount');

        $this->assertNotNull($entry->getExpirationDate(), '$entry->getExpirationDate');
        $this->assertTrue($year2010 < $entry->getExpirationDate(), 'diff');

        $this->assertNotNull($entry->getInsertionDate(), '$entry->getInsertionDate');
        $this->assertTrue($year2010 < $entry->getInsertionDate(), 'diff');

        $this->assertNotNull($entry->getTimeNextVisible(), '$entry->getTimeNextVisible');
        $this->assertTrue($year2010 < $entry->getTimeNextVisible(), 'diff');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testListMessagesWithOptionsWorks()
    {
        // Arrange
        $year2010 = new \DateTime;
        $year2010->setDate(2010, 1, 1);

        // Act
        $this->restProxy->createMessage(self::$testQueueForMessages3, 'message1');
        $this->restProxy->createMessage(self::$testQueueForMessages3, 'message2');
        $this->restProxy->createMessage(self::$testQueueForMessages3, 'message3');
        $this->restProxy->createMessage(self::$testQueueForMessages3, 'message4');
        $opts = new ListMessagesOptions();
        $opts->setNumberOfMessages(4);
        $opts->setVisibilityTimeoutInSeconds(20);
        $result = $this->restProxy->listMessages(self::$testQueueForMessages3, $opts);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(4, count($result->getQueueMessages()), 'count($result->getQueueMessages())');
        for ($i = 0; $i < 4; $i++) {
            $entry = $result->getQueueMessages();
            $entry = $entry[$i];

            $this->assertNotNull($entry->getMessageId(), '$entry->getMessageId()');
            $this->assertNotNull($entry->getMessageText(), '$entry->getMessageText()');
            $this->assertNotNull($entry->getPopReceipt(), '$entry->getPopReceipt()');
            $this->assertEquals(1, $entry->getDequeueCount(), '$entry->getDequeueCount()');

            $this->assertNotNull($entry->getExpirationDate(), '$entry->getExpirationDate()');
            $this->assertTrue($year2010 < $entry->getExpirationDate(), '$year2010 < $entry->getExpirationDate()');

            $this->assertNotNull($entry->getInsertionDate(), '$entry->getInsertionDate()');
            $this->assertTrue($year2010 < $entry->getInsertionDate(), '$year2010 < $entry->getInsertionDate()');

            $this->assertNotNull($entry->getTimeNextVisible(), '$entry->getTimeNextVisible()');
            $this->assertTrue($year2010 < $entry->getTimeNextVisible(), '$year2010 < $entry->getTimeNextVisible()');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessagesWorks()
    {
        // Arrange

        $year2010 = new \DateTime;
        $year2010->setDate(2010, 1, 1);

        // Act
        $this->restProxy->createMessage(self::$testQueueForMessages4, 'message1');
        $this->restProxy->createMessage(self::$testQueueForMessages4, 'message2');
        $this->restProxy->createMessage(self::$testQueueForMessages4, 'message3');
        $this->restProxy->createMessage(self::$testQueueForMessages4, 'message4');
        $result = $this->restProxy->peekMessages(self::$testQueueForMessages4);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(1, count($result->getQueueMessages()), 'count($result->getQueueMessages())');

        $entry = $result ->getQueueMessages();
        $entry = $entry[0];

        $this->assertNotNull($entry->getMessageId(), '$entry->getMessageId()');
        $this->assertNotNull($entry->getMessageText(), '$entry->getMessageText()');
        $this->assertEquals(0, $entry->getDequeueCount(), '$entry->getDequeueCount()');

        $this->assertNotNull($entry->getExpirationDate(), '$entry->getExpirationDate()');
        $this->assertTrue($year2010 < $entry->getExpirationDate(), '$year2010 < $entry->getExpirationDate()');

        $this->assertNotNull($entry->getInsertionDate(), '$entry->getInsertionDate()');
        $this->assertTrue($year2010 < $entry->getInsertionDate(), '$year2010 < $entry->getInsertionDate()');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testPeekMessagesWithOptionsWorks()
    {
        // Arrange
        $year2010 = new \DateTime;
        $year2010->setDate(2010, 1, 1);

        // Act
        $this->restProxy->createMessage(self::$testQueueForMessages5, 'message1');
        $this->restProxy->createMessage(self::$testQueueForMessages5, 'message2');
        $this->restProxy->createMessage(self::$testQueueForMessages5, 'message3');
        $this->restProxy->createMessage(self::$testQueueForMessages5, 'message4');
        $opts = new PeekMessagesOptions();
        $opts->setNumberOfMessages(4);
        $result = $this->restProxy->peekMessages(self::$testQueueForMessages5, $opts);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(4, count($result->getQueueMessages()), 'count($result->getQueueMessages())');
        for ($i = 0; $i < 4; $i++) {
            $entry = $result ->getQueueMessages();
            $entry = $entry[$i];

            $this->assertNotNull($entry->getMessageId(), '$entry->getMessageId()');
            $this->assertNotNull($entry->getMessageText(), '$entry->getMessageText()');
            $this->assertEquals(0, $entry->getDequeueCount(), '$entry->getDequeueCount()');

            $this->assertNotNull($entry->getExpirationDate(), '$entry->getExpirationDate()');
            $this->assertTrue($year2010 < $entry->getExpirationDate(), '$year2010 < $entry->getExpirationDate()');

            $this->assertNotNull($entry->getInsertionDate(), '$entry->getInsertionDate()');
            $this->assertTrue($year2010 < $entry->getInsertionDate(), '$year2010 < $entry->getInsertionDate()');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
    */
    public function testClearMessagesWorks()
    {
        // Arrange

        // Act
        $this->restProxy->createMessage(self::$testQueueForMessages6, 'message1');
        $this->restProxy->createMessage(self::$testQueueForMessages6, 'message2');
        $this->restProxy->createMessage(self::$testQueueForMessages6, 'message3');
        $this->restProxy->createMessage(self::$testQueueForMessages6, 'message4');
        $this->restProxy->clearMessages(self::$testQueueForMessages6);

        $result = $this->restProxy->peekMessages(self::$testQueueForMessages6);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(0, count($result->getQueueMessages()), 'count($result->getQueueMessages())');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    */
    public function testDeleteMessageWorks()
    {
        // Arrange

        // Act
        $this->restProxy->createMessage(self::$testQueueForMessages7, 'message1');
        $this->restProxy->createMessage(self::$testQueueForMessages7, 'message2');
        $this->restProxy->createMessage(self::$testQueueForMessages7, 'message3');
        $this->restProxy->createMessage(self::$testQueueForMessages7, 'message4');

        $result = $this->restProxy->listMessages(self::$testQueueForMessages7);
        $message0 = $result->getQueueMessages();
        $message0 = $message0[0];

        $this->restProxy->deleteMessage(self::$testQueueForMessages7, $message0->getMessageId(), $message0->getPopReceipt());
        $opts = new ListMessagesOptions();
        $opts->setNumberOfMessages(32);
        $result2 = $this->restProxy->listMessages(self::$testQueueForMessages7, $opts);

        // Assert
        $this->assertNotNull($result2, '$result2');
        $this->assertEquals(3, count($result2->getQueueMessages()), 'count($result2->getQueueMessages())');
    }

    /**
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
    * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
    */
    public function testUpdateMessageWorks()
    {
        // Arrange
        $year2010 = new \DateTime;
        $year2010->setDate(2010, 1, 1);

        // Act
        $this->restProxy->createMessage(self::$testQueueForMessages8, 'message1');

        $listResult1 = $this->restProxy->listMessages(self::$testQueueForMessages8);
        $message0 = $listResult1->getQueueMessages();
        $message0 = $message0[0];

        $updateResult = $this->restProxy->updateMessage(
                self::$testQueueForMessages8,
                $message0->getMessageId(),
                $message0->getPopReceipt(),
                'new text',
                0);
        $listResult2 = $this->restProxy->listMessages(self::$testQueueForMessages8);

        // Assert
        $this->assertNotNull($updateResult, '$updateResult');
        $this->assertNotNull($updateResult->getPopReceipt(), '$updateResult->getPopReceipt()');
        $this->assertNotNull($updateResult->getTimeNextVisible(), '$updateResult->getTimeNextVisible()');
        $this->assertTrue($year2010 < $updateResult->getTimeNextVisible(), '$year2010 < $updateResult->getTimeNextVisible()');

        $this->assertNotNull($listResult2, '$listResult2');
        $entry = $listResult2->getQueueMessages();
        $entry = $entry[0];

        $blarg = $listResult1->getQueueMessages();
        $blarg = $blarg[0];

        $this->assertEquals($blarg->getMessageId(), $entry->getMessageId(), '$entry->getMessageId()');
        $this->assertEquals('new text', $entry->getMessageText(), '$entry->getMessageText()');
        $this->assertNotNull($entry->getPopReceipt(), '$entry->getPopReceipt()');
        $this->assertEquals(2, $entry->getDequeueCount(), '$entry->getDequeueCount()');

        $this->assertNotNull($entry->getExpirationDate(), '$entry->getExpirationDate()');
        $this->assertTrue($year2010 < $entry->getExpirationDate(), '$year2010 < $entry->getExpirationDate()');

        $this->assertNotNull($entry->getInsertionDate(), '$entry->getInsertionDate()');
        $this->assertTrue($year2010 < $entry->getInsertionDate(), '$year2010 < $entry->getInsertionDate()');

        $this->assertNotNull($entry->getTimeNextVisible(), '$entry->getTimeNextVisible()');
        $this->assertTrue($year2010 < $entry->getTimeNextVisible(), '$year2010 < $entry->getTimeNextVisible()');

    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table\Enums
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table\Enums;

class ConcurType
{
    const NO_KEY_MATCH            = 'NoKeyMatch';
    const KEY_MATCH_NO_ETAG       = 'KeyMatchNoETag';
    const KEY_MATCH_ETAG_MISMATCH = 'KeyMatchETagMismatch';
    const KEY_MATCH_ETAG_MATCH    = 'KeyMatchETagMatch';
    public static function values()
    {
        return array('NoKeyMatch', 'KeyMatchNoETag', 'KeyMatchETagMismatch', 'KeyMatchETagMatch');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table\Enums
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table\Enums;

class MutatePivot
{
    const CHANGE_VALUES   = 'ChangeValues';
    const ADD_PROPERTY    = 'AddProperty';
    const REMOVE_PROPERTY = 'RemoveProperty';
    const NULL_PROPERTY   = 'NullProperty';
    public static function values()
    {
        return array('ChangeValues', 'AddProperty', 'RemoveProperty', 'NullProperty');
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table\Enums
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table\Enums;

class OpType
{
    const DELETE_ENTITY            = 'deleteEntity';
    const INSERT_ENTITY            = 'insertEntity';
    const INSERT_OR_MERGE_ENTITY   = 'insertOrMergeEntity';
    const INSERT_OR_REPLACE_ENTITY = 'insertOrReplaceEntity';
    const MERGE_ENTITY             = 'mergeEntity';
    const UPDATE_ENTITY            = 'updateEntity';
    public static function values()
    {
        return array('deleteEntity', 'insertEntity', 'insertOrMergeEntity', 'insertOrReplaceEntity', 'mergeEntity', 'updateEntity');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;

class FunctionalTestBase extends IntegrationTestBase
{
    private static $isOneTimeSetup = false;

    public function setUp()
    {
        parent::setUp();
        if (!self::$isOneTimeSetup) {
            $this->doOneTimeSetup();
            self::$isOneTimeSetup = true;
        }
    }

    private function doOneTimeSetup()
    {
        TableServiceFunctionalTestData::setupData();

        foreach(TableServiceFunctionalTestData::$testTableNames as $name)  {
            // self::println('Creating Table: ' . $name);
            $this->restProxy->createTable($name);
        }
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            $testBase = new FunctionalTestBase();
            $testBase->setUp();
            foreach(TableServiceFunctionalTestData::$testTableNames as $name)  {
                $testBase->safeDeleteTable($name);
            }
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }

    /**
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::createTable
     * @covers MicrosoftAzure\Storage\ServiceBus\ServiceBusRestProxy::deleteTable
     */
    protected function clearTable($table)
    {
        $index = array_search($table, TableServiceFunctionalTestData::$testTableNames);
        if ($index !== false) {
            // This is a well-known table, so need to create a new one to replace it.
            TableServiceFunctionalTestData::$testTableNames[$index] = TableServiceFunctionalTestData::getInterestingTableName();
            $this->restProxy->createTable(TableServiceFunctionalTestData::$testTableNames[$index]);
        }

        $this->restProxy->deleteTable($table);
    }

    protected function getCleanTable()
    {
        $this->clearTable(TableServiceFunctionalTestData::$testTableNames[0]);
        return TableServiceFunctionalTestData::$testTableNames[0];
     }

    public static function println($msg)
    {
        error_log($msg);
    }

    public static function tmptostring($value)
    {
        if (is_null($value)) {
            return 'null';
        } else if (is_bool($value)) {
            return ($value == true ? 'true' : 'false');
        } else if ($value instanceof \DateTime) {
            return Utilities::convertToEdmDateTime($value);
        } else if ($value instanceof Entity) {
            return self::entityToString($value);
        } else if (is_array($value)) {
            return self::entityPropsToString($value);
        } else if ($value instanceof Filter) {
            return TableServiceFunctionalTestUtils::filtertoString($value);
        } else {
            return $value;
        }
    }

    public static function entityPropsToString($props)
    {
        $ret = '';
        foreach($props as $k => $value) {
            $ret .= $k . ':';
            if (is_null($value)) {
                $ret .= 'NULL PROP!';
             } else {
                $ret .= $value->getEdmType() . ':' . self::tmptostring($value->getValue());
            }
            $ret .= "\n";
        }
        return $ret;
    }

    public static function entityToString($ent)
    {
        $ret = 'ETag=' . self::tmptostring($ent->getETag()) . "\n";
        $ret .= 'Props=' . self::entityPropsToString($ent->getProperties());
        return $ret;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Tests\Framework\TableServiceRestProxyTestBase;

class IntegrationTestBase extends TableServiceRestProxyTestBase
{
    private static $isOneTimeSetup = false;

    public function setUp()
    {
        parent::setUp();
        if (!self::$isOneTimeSetup) {
            self::$isOneTimeSetup = true;
        }
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            $integrationTestBase = new IntegrationTestBase();
            $integrationTestBase->setUp();
            if (!$integrationTestBase->isEmulated()) {
                $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties();
                $integrationTestBase->restProxy->setServiceProperties($serviceProperties);
            }
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table\Models;

class BatchWorkerConfig
{
    public $opType;
    public $concurType;
    public $mutatePivot;
    public $ent;
    public $options;
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table\Models;

class FakeTableInfoEntry
{
    public $TableName;
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Common\Models\Logging;
use MicrosoftAzure\Storage\Common\Models\Metrics;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\Property;
use MicrosoftAzure\Storage\Table\Models\Query;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions;
use MicrosoftAzure\Storage\Table\Models\TableServiceOptions;
use MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\Filters\PropertyNameFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\QueryStringFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter;

class TableServiceFunctionalOptionsTest extends \PHPUnit_Framework_TestCase
{
    public function testCheckTableServiceOptions()
    {
        $options = new TableServiceOptions();
        $this->assertNotNull($options, 'Default TableServiceOptions');
    }

    public function testCheckRetentionPolicy()
    {
        $rp = new RetentionPolicy();
        $this->assertNull($rp->getDays(), 'Default RetentionPolicy->getDays should be null');
        $this->assertNull($rp->getEnabled(), 'Default RetentionPolicy->getEnabled should be null');
        $rp->setDays(10);
        $rp->setEnabled(true);
        $this->assertEquals(10, $rp->getDays(), 'Set RetentionPolicy->getDays should be 10');
        $this->assertTrue($rp->getEnabled(), 'Set RetentionPolicy->getEnabled should be true');
    }

    public function testCheckLogging()
    {
        $rp = new RetentionPolicy();

        $l = new Logging();
        $this->assertNull($l->getRetentionPolicy(), 'Default Logging->getRetentionPolicy should be null');
        $this->assertNull($l->getVersion(), 'Default Logging->getVersion should be null');
        $this->assertNull($l->getDelete(), 'Default Logging->getDelete should be null');
        $this->assertNull($l->getRead(), 'Default Logging->getRead should be false');
        $this->assertNull($l->getWrite(), 'Default Logging->getWrite should be false');
        $l->setRetentionPolicy($rp);
        $l->setVersion('2.0');
        $l->setDelete(true);
        $l->setRead(true);
        $l->setWrite(true);

        $this->assertEquals($rp, $l->getRetentionPolicy(), 'Set Logging->getRetentionPolicy');
        $this->assertEquals('2.0', $l->getVersion(), 'Set Logging->getVersion');
        $this->assertTrue($l->getDelete(), 'Set Logging->getDelete should be true');
        $this->assertTrue($l->getRead(), 'Set Logging->getRead should be true');
        $this->assertTrue($l->getWrite(), 'Set Logging->getWrite should be true');
    }

    public function testCheckMetrics()
    {
        $rp = new RetentionPolicy();

        $m = new Metrics();
        $this->assertNull($m->getRetentionPolicy(), 'Default Metrics->getRetentionPolicy should be null');
        $this->assertNull($m->getVersion(), 'Default Metrics->getVersion should be null');
        $this->assertNull($m->getEnabled(), 'Default Metrics->getEnabled should be false');
        $this->assertNull($m->getIncludeAPIs(), 'Default Metrics->getIncludeAPIs should be null');
        $m->setRetentionPolicy($rp);
        $m->setVersion('2.0');
        $m->setEnabled(true);
        $m->setIncludeAPIs(true);
        $this->assertEquals($rp, $m->getRetentionPolicy(), 'Set Metrics->getRetentionPolicy');
        $this->assertEquals('2.0', $m->getVersion(), 'Set Metrics->getVersion');
        $this->assertTrue($m->getEnabled(), 'Set Metrics->getEnabled should be true');
        $this->assertTrue($m->getIncludeAPIs(), 'Set Metrics->getIncludeAPIs should be true');
    }

    public function testCheckServiceProperties()
    {
        $l = new Logging();
        $m = new Metrics();

        $sp = new ServiceProperties();
        $this->assertNull($sp->getLogging(), 'Default ServiceProperties->getLogging should not be null');
        $this->assertNull($sp->getMetrics(), 'Default ServiceProperties->getMetrics should not be null');

        $sp->setLogging($l);
        $sp->setMetrics($m);
        $this->assertEquals($sp->getLogging(), $l, 'Set ServiceProperties->getLogging');
        $this->assertEquals($sp->getMetrics(), $m, 'Set ServiceProperties->getMetrics');
    }

    public function testCheckQueryTablesOptions()
    {
        $options = new QueryTablesOptions();
        $nextTableName = 'foo';
        $filter = new Filter();

        $this->assertNull($options->getNextTableName(), 'Default QueryTablesOptions->getNextTableName');
        $this->assertNotNull($options->getQuery(), 'Default QueryTablesOptions->getQuery');
        $options->setNextTableName($nextTableName);
        $options->setFilter($filter);
        $options->setTop(10);
        $this->assertEquals($nextTableName, $options->getNextTableName(), 'Set QueryTablesOptions->getNextTableName');
        $this->assertEquals($filter, $options->getFilter(), 'Set QueryTablesOptions->getFilter');
        $this->assertEquals($filter, $options->getQuery()->getFilter(), 'Set QueryTablesOptions->getQuery->getFilter');
        $this->assertEquals(10, $options->getTop(), 'Set QueryTablesOptions->getTop');
        $this->assertEquals(10, $options->getQuery()->getTop(), 'Set QueryTablesOptions->getQuery->getTop');
    }

    public function testCheckDeleteEntityOptions()
    {
        $options = new DeleteEntityOptions();
        $etag = 'foo';

        $this->assertNull($options->getETag(), 'Default DeleteEntityOptions->getETag');
        $options->setETag($etag);
        $this->assertEquals($etag, $options->getETag(), 'Set DeleteEntityOptions->getETag');
    }

    public function testCheckQueryEntitiesOptions()
    {
        $options = new QueryEntitiesOptions();
        $query = new Query();
        $nextPartitionKey = 'aaa';
        $nextRowKey = 'bbb';

        $this->assertNull($options->getNextPartitionKey(), 'Default QueryEntitiesOptions->getNextPartitionKey');
        $this->assertNull($options->getNextRowKey(), 'Default QueryEntitiesOptions->getNextRowKey');
        $this->assertNotNull($options->getQuery(), 'Default QueryEntitiesOptions->getQuery');
        $options->setNextPartitionKey($nextPartitionKey);
        $options->setNextRowKey($nextRowKey);
        $options->setQuery($query);
        $this->assertEquals($nextPartitionKey, $options->getNextPartitionKey(), 'Set QueryEntitiesOptions->getNextPartitionKey');
        $this->assertEquals($nextRowKey, $options->getNextRowKey(), 'Set QueryEntitiesOptions->getNextRowKey');
        $this->assertEquals($query, $options->getQuery(), 'Set QueryEntitiesOptions->getQuery');

        $options->addSelectField('bar');
        $options->addSelectField('baz');
        $this->assertNotNull($options->getSelectFields(), 'Add $options->getSelectFields');
        $this->assertNotNull($options->getQuery()->getSelectFields(), 'Add $options->getQuery->getSelectFields');
        $this->assertEquals(2, count($options->getSelectFields()), 'Add $options->getSelectFields->size');
        $this->assertEquals(2, count($options->getQuery()->getSelectFields()), 'Add $options->getQuery->getSelectFields->size');

        $filter = Filter::applyConstant('foo', EdmType::STRING);
        $options->setFilter($filter);
        $options->setSelectFields(null);
        $options->setTop(TableServiceFunctionalTestData::INT_MAX_VALUE);

        $this->assertEquals($filter, $options->getFilter(), 'Set $options->getFilter');
        $this->assertEquals($filter, $options->getQuery()->getFilter(), 'Set $options->getQuery->getFilter');
        $this->assertNull($options->getSelectFields(), 'Set $options->getSelectFields');
        $this->assertNull($options->getQuery()->getSelectFields(), 'Set $options->getQuery->getSelectFields');
        $this->assertEquals(TableServiceFunctionalTestData::INT_MAX_VALUE, $options->getTop(), 'Set $options->getTop');
        $this->assertEquals(TableServiceFunctionalTestData::INT_MAX_VALUE, $options->getQuery()->getTop(), 'Set $options->getQuery->getTop');
    }

    public function testCheckQuery()
    {
        $query = new Query();
        $this->assertNull($query->getFilter(), 'Default Query->getFilter');
        $this->assertNull($query->getSelectFields(), 'Default Query->getSelectFields');
        $this->assertNull($query->getTop(), 'Default Query->getTop');

        $query->addSelectField('bar');
        $query->addSelectField('baz');
        $this->assertNotNull($query->getSelectFields(), 'Add Query->getSelectFields');
        $this->assertEquals(2, count($query->getSelectFields()), 'Add Query->getSelectFields->size');

        $filter = Filter::applyConstant('foo', EdmType::STRING);
        $query->setFilter($filter);
        $query->setSelectFields(null);
        $query->setTop(TableServiceFunctionalTestData::INT_MAX_VALUE);

        $this->assertEquals($filter, $query->getFilter(), 'Set Query->getFilter');
        $this->assertNull($query->getSelectFields(), 'Set Query->getSelectFields');
        $this->assertEquals(TableServiceFunctionalTestData::INT_MAX_VALUE, $query->getTop(), 'Set Query->getTop');
    }

    public function testCheckFilter()
    {
        $filter = new Filter();
        $this->assertNotNull($filter, 'Default $filter');
    }

    public function testCheckBinaryFilter()
    {
        $filter = new BinaryFilter(null, null, null);
        $this->assertNotNull($filter, 'Default $filter');

        $this->assertNull($filter->getLeft(), 'Default BinaryFilter->getFilter');
        $this->assertNull($filter->getOperator(), 'Default BinaryFilter->getOperator');
        $this->assertNull($filter->getRight(), 'Default BinaryFilter->getRight');

        $left = new UnaryFilter(null, null);
        $operator = 'foo';
        $right = new ConstantFilter(null, EdmType::STRING);

        $filter = new BinaryFilter($left, $operator, $right);

        $this->assertEquals($left, $filter->getLeft(), 'Set BinaryFilter->getLeft');
        $this->assertEquals($operator, $filter->getOperator(), 'Set BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'Set BinaryFilter->getRight');

        // Now check the factory.
        $filter = Filter::applyAnd($left, $right);
        $this->assertEquals($left, $filter->getLeft(), 'and factory BinaryFilter->getLeft');
        $this->assertEquals('and', $filter->getOperator(), 'and factory BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'and factory BinaryFilter->getRight');

        $filter = Filter::applyEq($left, $right);
        $this->assertEquals($left, $filter->getLeft(), 'eq factory BinaryFilter->getLeft');
        $this->assertEquals('eq', $filter->getOperator(), 'eq factory BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'eq factory BinaryFilter->getRight');

        $filter = Filter::applyGe($left, $right);
        $this->assertEquals($left, $filter->getLeft(), 'ge factory BinaryFilter->getLeft');
        $this->assertEquals('ge', $filter->getOperator(), 'ge factory BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'ge factory BinaryFilter->getRight');

        $filter = Filter::applyGt($left, $right);
        $this->assertEquals($left, $filter->getLeft(), 'gt factory BinaryFilter->getLeft');
        $this->assertEquals('gt', $filter->getOperator(), 'gt factory BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'gt factory BinaryFilter->getRight');

        $filter = Filter::applyLe($left, $right);
        $this->assertEquals($left, $filter->getLeft(), 'le factory BinaryFilter->getLeft');
        $this->assertEquals('le', $filter->getOperator(), 'le factory BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'le factory BinaryFilter->getRight');

        $filter = Filter::applyLt($left, $right);
        $this->assertEquals($left, $filter->getLeft(), 'lt factory BinaryFilter->getLeft');
        $this->assertEquals('lt', $filter->getOperator(), 'lt factory BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'lt factory BinaryFilter->getRight');

        $filter = Filter::applyNe($left, $right);
        $this->assertEquals($left, $filter->getLeft(), 'ne factory BinaryFilter->getLeft');
        $this->assertEquals('ne', $filter->getOperator(), 'ne factory BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'ne factory BinaryFilter->getRight');

        $filter = Filter::applyOr($left, $right);
        $this->assertEquals($left, $filter->getLeft(), 'or factory BinaryFilter->getLeft');
        $this->assertEquals('or', $filter->getOperator(), 'or factory BinaryFilter->getOperator');
        $this->assertEquals($right, $filter->getRight(), 'or factory BinaryFilter->getRight');
    }

    public function testCheckConstantFilter()
    {
        $filter = new ConstantFilter(EdmType::STRING, null);
        $this->assertNotNull($filter, 'Default $filter');

        $this->assertNull($filter->getValue(), 'Default ConstantFilter->getValue');

        $value = 'foo';
        $filter = new ConstantFilter(EdmType::STRING, $value);

        $this->assertEquals($value, $filter->getValue(), 'Set ConstantFilter->getValue');

        // Now check the factory.
        $value = 'bar';
        $filter = Filter::applyConstant($value, EdmType::STRING);
        $this->assertEquals($value, $filter->getValue(), 'constant factory ConstantFilter->getValue');
    }

    public function testCheckPropertyNameFilter()
    {
        $filter = new PropertyNameFilter(null);
        $this->assertNotNull($filter, 'Default $filter');

        $this->assertNull($filter->getPropertyName(), 'Default PropertyNameFilter->getPropertyName');

        $propertyName = 'foo';
        $filter = new PropertyNameFilter($propertyName);
        $this->assertEquals($propertyName, $filter->getPropertyName(), 'Set PropertyNameFilter->getPropertyName');

        // Now check the factory.
        $PropertyName = 'bar';
        $filter = Filter::applyPropertyName($propertyName);
        $this->assertEquals($propertyName, $filter->getPropertyName(), 'PropertyName factory PropertyNameFilter->getPropertyName');
    }

    public function testCheckQueryStringFilter()
    {
        $filter = new QueryStringFilter(null);
        $this->assertNotNull($filter, 'Default $filter');

        $this->assertNull($filter->getQueryString(), 'Default QueryStringFilter->getQueryString');

        $queryString = 'foo';
        $filter = new QueryStringFilter($queryString);
        $this->assertEquals($queryString, $filter->getQueryString(), 'Set QueryStringFilter->getQueryString');

        // Now check the factory.
        $queryString = 'bar';
        $filter = Filter::applyQueryString($queryString);
        $this->assertEquals($queryString, $filter->getQueryString(), 'QueryString factory QueryStringFilter->getQueryString');
    }

    public function testCheckUnaryFilter()
    {
        $filter = new UnaryFilter(null, null);
        $this->assertNotNull($filter, 'Default $filter');

        $this->assertNull($filter->getOperand(), 'Default UnaryFilter->getOperand');
        $this->assertNull($filter->getOperator(), 'Default UnaryFilter->getOperator');

        $operand = new BinaryFilter(null, null, null);
        $operator = 'foo';
        $filter = new UnaryFilter($operator, $operand);
        $this->assertEquals($operand, $filter->getOperand(), 'Set UnaryFilter->getOperand');
        $this->assertEquals($operator, $filter->getOperator(), 'Set UnaryFilter->getOperator');

        // Now check the factory.
        $operand = new ConstantFilter(EdmType::STRING, null);
        $filter = Filter::applyNot($operand);
        $this->assertEquals($operand, $filter->getOperand(), 'Unary factory UnaryFilter->getOperand');
        $this->assertEquals('not', $filter->getOperator(), 'Unary factory UnaryFilter->getOperator');
    }

    public function testCheckProperty()
    {
        $property = new Property();
        $maxv = TableServiceFunctionalTestData::INT_MAX_VALUE;
        $edmType = EdmType::STRING;
        $this->assertNotNull($property, 'Default Property');
        $this->assertNull($property->getValue(), 'Default Property->getValue');
        $this->assertNull($property->getEdmType(), 'Default Property->getEdmType');
        $property->setValue($maxv);
        $property->setEdmType($edmType);
        $this->assertEquals($maxv, $property->getValue(), 'Set Property->getValue');
        $this->assertEquals($edmType, $property->getEdmType(), 'Set Property->getEdmType');
    }

    public function testCheckEntity()
    {
        $entity = new Entity();
        $etag = 'custom $etag';
        $partitionKey = 'custom partiton key';
        $rowKey = 'custom rowkey';
        $dates = TableServiceFunctionalTestData::getInterestingGoodDates();
        $timestamp = $dates[1];

        $property = new Property();
        $property->setEdmType(EdmType::INT32);
        $property->setValue(1234);
        $name = 'my name';
        $edmType = EdmType::STRING;
        $value = 'my value';

        $properties = array();
        $properties['goo'] = new Property();
        $properties['moo'] = new Property();

        $this->assertNotNull($entity, 'Default Entity');
        $this->assertNull($entity->getProperties(), 'Default Entity->getProperties');
        $this->assertNull($entity->getETag(), 'Default Entity->getETag');
        $this->assertNull($entity->getPartitionKey(), 'Default Entity->getPartitionKey');
        $this->assertNull($entity->getRowKey(), 'Default Entity->getRowKey');
        $this->assertNull($entity->getTimestamp(), 'Default Entity->getTimestamp');
        $this->assertNull($entity->getProperty('foo'), 'Default Entity->getProperty(\'foo\')');
        $this->assertNull($entity->getPropertyValue('foo'), 'Default Entity->tryGtPropertyValue(\'foo\')');

        // Now set some things.
        $entity->setETag($etag);
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey($rowKey);
        $entity->setTimestamp($timestamp);

        $this->assertEquals($etag, $entity->getETag(), 'Default Entity->getETag');
        $this->assertEquals($partitionKey, $entity->getPartitionKey(), 'Default Entity->getPartitionKey');
        $this->assertEquals($rowKey, $entity->getRowKey(), 'Default Entity->getRowKey');
        $this->assertEquals($timestamp, $entity->getTimestamp(), 'Default Entity->getTimestamp');

        $entity->setProperty($name, $property);
        $this->assertEquals($property, $entity->getProperty($name), 'Default Entity->getProperty(\'' . $name . '\')');

        $entity->addProperty($name, $edmType, $value);
        $this->assertEquals($value, $entity->getPropertyValue($name), 'Default Entity->getPropertyValue(\'' . $name . '\')');
        $this->assertEquals($edmType, $entity->getProperty($name)->getEdmType(), 'Default Entity->getProperty(\'' . $name . '\')->getEdmType');
        $this->assertEquals($value, $entity->getProperty($name)->getValue(), 'Default Entity->getProperty(\'' . $name . '\')->getValue');
        $this->assertTrue($property != $entity->getProperty($name), 'Default Entity->getProperty(\'' . $name . '\') changed');

        $entity->setProperties($properties);
        $this->assertNotNull($entity->getProperties(), 'Default Entity->getProperties');
        $this->assertEquals($properties, $entity->getProperties(), 'Default Entity->getProperties');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\TableServiceOptions;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;

class TableServiceFunctionalParametersTest extends FunctionalTestBase
{
    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    */
    public function testGetServicePropertiesNullOptions()
    {
        try {
            $this->restProxy->getServiceProperties(null);
            $this->assertFalse($this->isEmulated(), 'Should fail if and only if in emulator');
        } catch (ServiceException $e) {
            // Expect failure when run this test with emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                // Properties are not supported in emulator
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNullOptions1()
    {
        try {
            $this->restProxy->setServiceProperties(new ServiceProperties());
            $this->fail('Expect default service properties to cause service to error');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'Expect 400:BadRequest when sending default service properties to server');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNullOptions2()
    {
        try {
            $this->restProxy->setServiceProperties(null);
            $this->fail('Expect null service properties to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::INVALID_SVC_PROP_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNullOptions3()
    {
        try {
            $this->restProxy->setServiceProperties(null, null);
            $this->fail('Expect service properties to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::INVALID_SVC_PROP_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNullOptions4()
    {
        try {
            $this->restProxy->setServiceProperties(new ServiceProperties(), null);
            $this->fail('Expect default service properties to cause service to error');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'Expect 400:BadRequest when sending default service properties to server');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testQueryTablesNullOptions()
    {
        $this->restProxy->queryTables(null);
        $this->assertTrue(true, 'Null options should be fine.');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    */
    public function testCreateTableNullOptions()
    {
        try {
            $this->restProxy->createTable(null);
            $this->fail('Expect null table to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    */
    public function testDeleteTableNullOptions()
    {
        try {
            $this->restProxy->deleteTable(null);
            $this->fail('Expect null table to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable
    */
    public function testGetTableNullOptions()
    {
        try {
            $this->restProxy->getTable(null);
            $this->fail('Expect null table to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
    }


    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertEntity($table, null);
            $this->fail('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityTableAndEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertEntity(null, null);
            $this->fail('Expect null table and entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityTableNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertEntity(null, new Entity());
            $this->fail('Expect null table to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityEntityAndOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertEntity($table, null, null);
            $this->fail('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityEntityNullWithOptions()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertEntity($table, null, TableServiceFunctionalTestData::getSimpleinsertEntityOptions());
            $this->fail('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        $this->restProxy->insertEntity($table, TableServiceFunctionalTestData::getSimpleEntity(), null);
        $this->clearTable($table);
        $this->assertTrue(true, 'Null options should be fine.');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityEmptyPartitionKey()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        $e = new Entity();
        $e->setPartitionKey('normalRowKey');
        $e->setRowKey('');
        $this->restProxy->insertEntity($table, $e);
        $this->clearTable($table);
        $this->assertTrue(true, 'Should be fine.');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityEmptyRowKey()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        $e = new Entity();
        $e->setPartitionKey('normalPartitionKey');
        $e->setRowKey('');
        $this->restProxy->insertEntity($table, $e);
        $this->clearTable($table);
        $this->assertTrue(true, 'Should be fine.');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertStringWithAllAsciiCharacters()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        $e = new Entity();
        $e->setPartitionKey('foo');
        $e->setRowKey(TableServiceFunctionalTestData::getNewKey());

        // ASCII code points in the following ranges are valid in XML 1.0 documents
        // - 0x09, 0x0A, 0x0D, 0x20-0x7F
        // Note: 0x0D gets mapped to 0x0A by the server.

        $k = '';
        for ($b = 0x20; $b < 0x30; $b++) {
            $k .= chr($b);
        }
        $k .= chr(0x09);
        for ($b = 0x30; $b < 0x40; $b++) {
            $k .= chr($b);
        }
        $k .= chr(0x0A);
        for ($b= 0x40; $b < 0x50; $b++) {
            $k .= chr($b);
        }
        $k .= chr(0x0A);
        for ($b = 0x50; $b < 0x80; $b++) {
            $k .= chr($b);
        }

        $e->addProperty('foo', EdmType::STRING, $k);

        $ret = $this->restProxy->insertEntity($table, $e);
        $this->assertNotNull($ret, '$ret');
        $this->assertNotNull($ret->getEntity(), '$ret->getEntity');

        $l = $ret->getEntity()->getPropertyValue('foo');
        $this->assertEquals($k, $l, '$ret->getEntity()->getPropertyValue(\'foo\')');
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    */
    public function testGetEntityPartKeyNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->getEntity($table, null, TableServiceFunctionalTestData::getNewKey());
            $this->fail('Expect null options to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    */
    public function testGetEntityRowKeyNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->getEntity($table, TableServiceFunctionalTestData::getNewKey(), null);
            $this->assertTrue(true, 'Expect null row key to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    */
    public function testGetEntityKeysNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->getEntity($table, null, null);
            $this->fail('Expect null partition and row keys to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    */
    public function testGetEntityTableAndKeysNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->getEntity(null, null, null);
            $this->fail('Expect null table name to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    */
    public function testGetEntityTableNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->getEntity(null, TableServiceFunctionalTestData::getNewKey(), TableServiceFunctionalTestData::getNewKey());
            $this->fail('Expect null table name to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    */
    public function testGetEntityKeysAndOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->getEntity($table, null, null, null);
            $this->fail('Expect keys to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testGetEntityKeysNullWithOptions()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];
        $ent = TableServiceFunctionalTestData::getSimpleEntity();

        try {
            $this->restProxy->insertEntity($table, $ent);
            $this->restProxy->getEntity($table, null, null, new TableServiceOptions());
            $this->fail('Expect null keys to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testGetEntityOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];
        $ent = TableServiceFunctionalTestData::getSimpleEntity();

        $this->restProxy->insertEntity($table, $ent);
        $this->restProxy->getEntity($table, $ent->getPartitionKey(), $ent->getRowKey(), null);
        $this->clearTable($table);
        $this->assertTrue(true, 'Null options should be fine.');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    */
    public function testDeleteEntityPartKeyNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->deleteEntity($table, null, TableServiceFunctionalTestData::getNewKey());
            $this->fail('Expect null partition key to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    */
    public function testDeleteEntityRowKeyNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->deleteEntity($table, TableServiceFunctionalTestData::getNewKey(), null);
            $this->fail('Expect null row key to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    */
    public function testDeleteEntityKeysNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->deleteEntity($table, null, null);
            $this->fail('Expect null keys to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    */
    public function testDeleteEntityTableAndKeysNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->deleteEntity(null, null, null);
            $this->fail('Expect null table name to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    */
    public function testDeleteEntityTableNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->deleteEntity(null, TableServiceFunctionalTestData::getNewKey(), TableServiceFunctionalTestData::getNewKey());
            $this->fail('Expect null table name to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    */
    public function testDeleteEntityKeysAndOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->deleteEntity($table, null, null, null);
            $this->fail('Expect null keys to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testDeleteEntityKeysNullWithOptions()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];
        $ent = TableServiceFunctionalTestData::getSimpleEntity();

        try {
            $this->restProxy->insertEntity($table, $ent);
            $this->restProxy->deleteEntity($table, null, null, new DeleteEntityOptions());
            $this->fail('Expect null keys to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(Resources::NULL_TABLE_KEY_MSG, $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testDeleteEntityOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];
        $ent = TableServiceFunctionalTestData::getSimpleEntity();

        $this->restProxy->insertEntity($table, $ent);
        $this->restProxy->deleteEntity($table, $ent->getPartitionKey(), $ent->getRowKey(), null);
        $this->assertTrue(true, 'Expect null options to be fine');
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testDeleteEntityTroublesomePartitionKey()
    {
        // The service does not allow the following common characters in keys:
        // 35 '#'
        // 47 '/'
        // 63 '?'
        // 92 '\'
        // In addition, the following values are not allowed, as they make the URL bad:
        // 0-31, 127-159
        // That still leaves several options for making troublesome keys
        // * spaces
        // * single quotes
        // * Unicode
        // These need to be properly encoded when passed on the URL, else there will be trouble

        $table = TableServiceFunctionalTestData::$testTableNames[0];

        $e = new Entity();
        $e->setPartitionKey('partition\'Key\'');
        $e->setRowKey('niceKey');
        $this->restProxy->insertEntity($table, $e);
        $this->restProxy->deleteEntity($table, $e->getPartitionKey(), $e->getRowKey());
        $qopts = new QueryEntitiesOptions();
        $qopts->setFilter(Filter::applyEq(Filter::applyPropertyName('PartitionKey'), Filter::applyConstant($e->getRowKey(), EdmType::STRING)));
        $queryres = $this->restProxy->queryEntities($table, $qopts);
        $this->assertEquals(0, count($queryres->getEntities()), 'entities returned');

        $e = new Entity();
        $e->setPartitionKey('partition Key');
        $e->setRowKey('niceKey');
        $this->restProxy->insertEntity($table, $e);
        $this->restProxy->deleteEntity($table, $e->getPartitionKey(), $e->getRowKey());
        $qopts = new QueryEntitiesOptions();
        $qopts->setFilter(Filter::applyEq(Filter::applyPropertyName('PartitionKey'), Filter::applyConstant($e->getRowKey(), EdmType::STRING)));
        $queryres = $this->restProxy->queryEntities($table, $qopts);
        $this->assertEquals(0, count($queryres->getEntities()), 'entities returned');

        $e = new Entity();
        $e->setPartitionKey('partition '. TableServiceFunctionalTestData::getUnicodeString());
        $e->setRowKey('niceKey');
        $this->restProxy->insertEntity($table, $e);
        $this->restProxy->deleteEntity($table, $e->getPartitionKey(), $e->getRowKey());
        $qopts = new QueryEntitiesOptions();
        $qopts->setFilter(Filter::applyEq(Filter::applyPropertyName('PartitionKey'), Filter::applyConstant($e->getRowKey(), EdmType::STRING)));
        $queryres = $this->restProxy->queryEntities($table, $qopts);
        $this->assertEquals(0, count($queryres->getEntities()), 'entities returned');

        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testDeleteEntityTroublesomeRowKey()
    {
        // The service does not allow the following common characters in keys:
        // 35 '#'
        // 47 '/'
        // 63 '?'
        // 92 '\'
        // In addition, the following values are not allowed, as they make the URL bad:
        // 0-31, 127-159
        // That still leaves several options for making troublesome keys
        // spaces
        // single quotes
        // Unicode
        // These need to be properly encoded when passed on the URL, else there will be trouble

        $table = TableServiceFunctionalTestData::$testTableNames[0];

        $e = new Entity();
        $e->setRowKey('row\'Key\'');
        $e->setPartitionKey('niceKey');
        $this->restProxy->insertEntity($table, $e);
        $this->restProxy->deleteEntity($table, $e->getPartitionKey(), $e->getRowKey());
        $qopts = new QueryEntitiesOptions();
        $qopts->setFilter(Filter::applyEq(Filter::applyPropertyName('RowKey'), Filter::applyConstant($e->getRowKey(), EdmType::STRING)));
        $queryres = $this->restProxy->queryEntities($table, $qopts);
        $this->assertEquals(0, count($queryres->getEntities()), 'entities returned');

        $e = new Entity();
        $e->setRowKey('row Key');
        $e->setPartitionKey('niceKey');
        $this->restProxy->insertEntity($table, $e);
        $this->restProxy->deleteEntity($table, $e->getPartitionKey(), $e->getRowKey());
        $qopts = new QueryEntitiesOptions();
        $qopts->setFilter(Filter::applyEq(Filter::applyPropertyName('RowKey'), Filter::applyConstant($e->getRowKey(), EdmType::STRING)));
        $queryres = $this->restProxy->queryEntities($table, $qopts);
        $this->assertEquals(0, count($queryres->getEntities()), 'entities returned');

        $e = new Entity();
        $e->setRowKey('row ' . TableServiceFunctionalTestData::getUnicodeString());
        $e->setPartitionKey('niceKey');
        $this->restProxy->insertEntity($table, $e);
        $this->restProxy->deleteEntity($table, $e->getPartitionKey(), $e->getRowKey());
        $qopts = new QueryEntitiesOptions();
        $qopts->setFilter(Filter::applyEq(Filter::applyPropertyName('RowKey'), Filter::applyConstant($e->getRowKey(), EdmType::STRING)));
        $queryres = $this->restProxy->queryEntities($table, $qopts);
        $this->assertEquals(0, count($queryres->getEntities()), 'entities returned');

        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    */
    public function testMergeEntityEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->mergeEntity($table, null);
            $this->assertTrue('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    */
    public function testMergeEntityTableAndEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->mergeEntity(null, null);
            $this->fail('Expect null table name and entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    */
    public function testMergeEntityTableNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->mergeEntity(null, TableServiceFunctionalTestData::getSimpleEntity());
            $this->fail('Expect null table name to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    */
    public function testMergeEntityEntityAndOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->mergeEntity($table, null, null);
            $this->fail('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    */
    public function testMergeEntityEntityNullWithOptions()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->mergeEntity($table, null, TableServiceFunctionalTestData::getSimpleinsertEntityOptions());
            $this->fail('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    */
    public function testMergeEntityOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->mergeEntity($table, TableServiceFunctionalTestData::getSimpleEntity(), null);
            $this->fail('Expect 404:NotFound when merging with non-existant entity');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'Expect 404:NotFound when merging with non-existant entity');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testUpdateEntityEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->updateEntity($table, null);
            $this->fail('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testUpdateEntityTableAndEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->updateEntity(null, null);
            $this->fail('Expect null table name and entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testUpdateEntityTableNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->updateEntity(null, TableServiceFunctionalTestData::getSimpleEntity());
            $this->fail('Expect null options to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testUpdateEntityEntityAndOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->updateEntity($table, null, null);
            $this->fail('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testUpdateEntityEntityNullWithOptions()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->updateEntity($table, null, TableServiceFunctionalTestData::getSimpleinsertEntityOptions());
            $this->fail('Expect null entity to throw');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testUpdateEntityOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->updateEntity($table, TableServiceFunctionalTestData::getSimpleEntity(), null);
            $this->fail('Expect 404:NotFound when updating non-existant entity');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'Should be 404:NotFound for update nonexistant entity');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    */
    public function testInsertOrMergeEntityEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrMergeEntity($table, null);
            $this->fail('Expect to throw for null entity');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    */
    public function testInsertOrMergeEntityTableAndEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrMergeEntity(null, null);
            $this->fail('Expect to throw for null table name and entity');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    */
    public function testInsertOrMergeEntityTableNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrMergeEntity(null, new Entity());
            $this->fail('Expect to throw for null table name');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    */
    public function testInsertOrMergeEntityEntityAndOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrMergeEntity($table, null, null);
            $this->fail('Expect to throw for null entity');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    */
    public function testInsertOrMergeEntityEntityNullWithOptions()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrMergeEntity($table, null, TableServiceFunctionalTestData::getSimpleinsertEntityOptions());
            $this->fail('Expect to throw for null entity');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    */
    public function testInsertOrMergeEntityOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrMergeEntity($table, TableServiceFunctionalTestData::getSimpleEntity(), null);
            $this->assertFalse($this->isEmulated(), 'Should fail if and only if in emulator');
        } catch (ServiceException $e) {
            // Expect failure when run this test with emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'getCode');
            }
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    */
    public function testInsertOrReplaceEntityEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrReplaceEntity($table, null);
            $this->fail('Expect to throw for null entity');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    */
    public function testInsertOrReplaceEntityTableAndEntityNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrReplaceEntity(null, null);
            $this->fail('Expect to throw for null table name and entity');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    */
    public function testInsertOrReplaceEntityTableNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrReplaceEntity(null, new Entity());
            $this->fail('Expect to throw for null table name');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    */
    public function testInsertOrReplaceEntityEntityAndOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrReplaceEntity($table, null, null);
            $this->fail('Expect to throw for null entity');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    */
    public function testInsertOrReplaceEntityEntityNullWithOptions()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrReplaceEntity($table, null, TableServiceFunctionalTestData::getSimpleinsertEntityOptions());
            $this->fail('Expect to throw for null entity');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'entity'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    */
    public function testInsertOrReplaceEntityOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->insertOrReplaceEntity($table, TableServiceFunctionalTestData::getSimpleEntity(), null);
            $this->assertFalse($this->isEmulated(), 'Should fail if and only if in emulator');
        } catch (ServiceException $e) {
            // Expect failure when run this test with emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'getCode');
            }
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesTableNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->queryEntities(null);
            $this->fail('Expect to throw for null table name');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesTableNullOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->queryEntities(null, null);
            $this->fail('Expect to throw for null table name');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesTableNullWithOptions()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $this->restProxy->queryEntities(null, new QueryEntitiesOptions());
            $this->fail('Expect to throw for null table name');
        } catch (\InvalidArgumentException $e) {
            $this->assertEquals(sprintf(Resources::NULL_OR_EMPTY_MSG, 'table'), $e->getMessage(), 'Expect error message');
            $this->assertEquals(0, $e->getCode(), 'Expected error code');
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesOptionsNull()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        $this->restProxy->queryEntities($table, null);
        $this->clearTable($table);
        $this->assertTrue(true, 'Null options should be fine.');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Table\Models\BatchOperations;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\Query;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;

class TableServiceFunctionalQueryTest extends FunctionalTestBase
{
    private static $entitiesInTable;
    private static $Partitions = array('Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo');
    private static $curPartition;
    private static $curRowKey;

    private static $isOneTimeSetup = false;

    public function setUp()
    {
        parent::setUp();
        if (!self::$isOneTimeSetup) {
            $this->doOneTimeSetup();
            self::$isOneTimeSetup = true;
        }
    }

    private function doOneTimeSetup()
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];
        self::$entitiesInTable = self::getEntitiesToQueryOver();
        $parts = array();
        foreach(self::$entitiesInTable as $entity)  {
            if (array_key_exists($entity->getPartitionKey(), $parts) === false) {
                $parts[$entity->getPartitionKey()] = array();
            }
            array_push($parts[$entity->getPartitionKey()], $entity);
        }
        foreach($parts as $part) {
            $batch = new BatchOperations();
            foreach($part as $entity)  {
                $batch->addInsertEntity($table, $entity);
            }
            $this->restProxy->batch($batch);
        }
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }

    private static function getNewEntity()
    {
        if (is_null(self::$curPartition) || self::$curPartition == count(self::$Partitions) - 1) {
            self::$curPartition = 0;
            self::$curRowKey = TableServiceFunctionalTestData::getNewKey();
        } else {
            self::$curPartition++;
        }

        $entity = new Entity();
        $entity->setPartitionKey(self::$Partitions[self::$curPartition]);
        $entity->setRowKey(self::$curRowKey);
        return $entity;
    }

    private static function getEntitiesToQueryOver()
    {
        $ret = array();

        array_push($ret, self::getNewEntity());

        $entity = self::getNewEntity();
        $entity->addProperty('BOOLEAN', EdmType::BOOLEAN, true);
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('BOOLEAN', EdmType::BOOLEAN, false);
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('DATETIME', EdmType::DATETIME, new \DateTime());
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('DATETIME', EdmType::DATETIME, new \DateTime('2012-01-02'));
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('DOUBLE', EdmType::DOUBLE, 2.71828183);
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('DOUBLE', EdmType::DOUBLE, 3.14159265);
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('GUID', EdmType::GUID, '90ab64d6-d3f8-49ec-b837-b8b5b6367b74');
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('GUID', EdmType::GUID, '00000000-1111-2222-3333-444444444444');
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('INT32', EdmType::INT32, 23);
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('INT32', EdmType::INT32, 42);
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('INT64', EdmType::INT64, '-1');
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('INT64', EdmType::INT64, strval(TableServiceFunctionalTestData::LONG_BIG_VALUE));
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('STRING', EdmType::STRING, 'foo');
        array_push($ret, $entity);

        $entity = self::getNewEntity();
        $entity->addProperty('STRING', EdmType::STRING, 'o hai');
        array_push($ret, $entity);

        $entity = self::getNewEntity();

        $e = self::getNewEntity();
        $e->addProperty('test', EdmType::BOOLEAN, true);
        $e->addProperty('test2', EdmType::STRING, 'value');
        $e->addProperty('test3', EdmType::INT32, 3);
        $e->addProperty('test4', EdmType::INT64, '12345678901');
        $e->addProperty('test5', EdmType::DATETIME, new \DateTime());
        array_push($ret, $e);

        $booleans = TableServiceFunctionalTestData::getInterestingGoodBooleans();
        $dates = TableServiceFunctionalTestData::getInterestingGoodDates();
        $doubles = TableServiceFunctionalTestData::getInterestingGoodDoubles();
        $guids = TableServiceFunctionalTestData::getInterestingGoodGuids();
        $ints = TableServiceFunctionalTestData::getInterestingGoodInts();
        $longs = TableServiceFunctionalTestData::getInterestingGoodLongs();
        $binaries = TableServiceFunctionalTestData::getInterestingGoodBinaries();
        $strings = TableServiceFunctionalTestData::getInterestingGoodStrings();

        // The random here is not to generate random values, but to
        // get a good mix of values in the table entities.
        mt_srand(123);

        for ($i = 0; $i < 20; $i++) {
            $e = self::getNewEntity();
            TableServiceFunctionalTestData::addProperty($e, 'BINARY', EdmType::BINARY, $binaries);
            TableServiceFunctionalTestData::addProperty($e, 'BOOLEAN', EdmType::BOOLEAN, $booleans);
            TableServiceFunctionalTestData::addProperty($e, 'DATETIME', EdmType::DATETIME, $dates);
            TableServiceFunctionalTestData::addProperty($e, 'DOUBLE', EdmType::DOUBLE, $doubles);
            TableServiceFunctionalTestData::addProperty($e, 'GUID', EdmType::GUID, $guids);
            TableServiceFunctionalTestData::addProperty($e, 'INT32', EdmType::INT32, $ints);
            TableServiceFunctionalTestData::addProperty($e, 'INT64', EdmType::INT64, $longs);
            TableServiceFunctionalTestData::addProperty($e, 'STRING', EdmType::STRING, $strings);
            array_push($ret, $e);
        }

        return $ret;
    }

    public static function getInterestingQueryEntitiesOptions()
    {
        $ret = array();
        $e = self::$entitiesInTable[count(self::$entitiesInTable) - 3];

        $options = new QueryEntitiesOptions();
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $query->setTop(2);
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $query->setTop(-2);
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $query->addSelectField('TableName');
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $filter = Filter::applyPropertyName('BOOLEAN');
        $query->setFilter($filter);
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $filter = Filter::applyConstant(false, EdmType::BOOLEAN);
        $query->setFilter($filter);
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $filter = Filter::applyEq(Filter::applyConstant(23, EdmType::INT32), Filter::applyPropertyName('INT32'));
        $query->setFilter($filter);
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $filter = Filter::applyNe(Filter::applyConstant(23, EdmType::INT32), Filter::applyPropertyName('INT32'));
        $query->setFilter($filter);
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $query = new Query();
        $filter = Filter::applyNot(Filter::applyEq(Filter::applyConstant(23, EdmType::INT32), Filter::applyPropertyName('INT32')));
        $query->setFilter($filter);
        $options->setQuery($query);
        array_push($ret, $options);

        $options = new QueryEntitiesOptions();
        $options->setNextPartitionKey($e->getPartitionKey());
        $options->setNextRowKey($e->getRowKey());
        array_push($ret, $options);

        // Ask for an entity that does not exist.
        $options = new QueryEntitiesOptions();
        $options->setNextPartitionKey(self::$Partitions[2] . 'X');
        $options->setNextRowKey($e->getRowKey() . 'X');
        array_push($ret, $options);

        return $ret;
    }

    public static function getInterestingQueryEntitiesOptionsOfDepth($depth)
    {
        $ret = array();

        // The random here is not to generate random values, but to
        // get a good mix of values in the table entities.
        mt_srand(456 + $depth);
        for ($i = 1; $i < 20; $i++) {
            $filter = self::generateFilterWithBooleanParameters($depth, 0);
            $options = new QueryEntitiesOptions();
            $query = new Query();
            $query->setFilter($filter);
            $options->setQuery($query);
            array_push($ret, $options);
        }

        return $ret;
    }

    private static function generateFilterWithBooleanParameters($targetDepth, $depth)
    {
        // Use the filter grammar to construct a tree.
        // The random here is not to generate random values, but to
        // get a good mix of values in the table entities.

        // TODO: Treat raw string special
        if ($depth == $targetDepth) {
            switch (mt_rand(0,2)) {
                case 0:
                    return self::generateBinaryFilterWithAnyParameters();
                case 1:
                    return Filter::applyConstant(mt_rand(0,1) == 1, EdmType::BOOLEAN);
                case 2:
                    $e = self::getEntityFromTable();
                    $boolPropNames = array();
                    foreach($e->getProperties() as $key => $p)  {
                        if ($p->getEdmType() == EdmType::BOOLEAN) {
                            array_push($boolPropNames, $key);
                        }
                    }
                    if (count($boolPropNames) == 0) {
                        return Filter::applyConstant(mt_rand(0,1) == 1, EdmType::BOOLEAN);
                    } else {
                        $key = $boolPropNames[mt_rand(0, count($boolPropNames) - 1)];
                        return Filter::applyPropertyName($key);
                    }
                default:
                    return null;
            }
        } else {
            switch (mt_rand(0,8)) {
                case 0:
                case 1:
                case 2:
                case 3:
                    return Filter::applyAnd(
                            self::generateFilterWithBooleanParameters($targetDepth, $depth + 1),
                            self::generateFilterWithBooleanParameters($targetDepth, $depth + 1));
                case 4:
                case 5:
                case 6:
                case 7:
                    return Filter::applyOr(
                            self::generateFilterWithBooleanParameters($targetDepth, $depth + 1),
                            self::generateFilterWithBooleanParameters($targetDepth, $depth + 1));
                case 8:
                    return Filter::applyNot(self::generateFilterWithBooleanParameters($targetDepth, $depth + 1));
                default:
                    return null;
            }
        }
    }

    private static function generateBinaryFilterWithAnyParameters()
    {
        // get a good mix of values in the table entities.

        // Pull out one of the constants.
        $e = self::getEntityFromTable();
        $keys = array_keys($e->getProperties());
        $propId = mt_rand(0, count($keys) - 1);
        $key = $keys[$propId];

        $prop = $e->getProperty($key);
        $f1 = Filter::applyConstant($prop->getValue(), $prop->getEdmType());
        $f2 = Filter::applyPropertyName($key);

        if (mt_rand(0,1) == 1) {
            // Try swapping.
            $t = $f1;
            $f1 = $f2;
            $f2 = $t;
        }

        return self::getBinaryFilterFromIndex(mt_rand(0, 5), $f1, $f2);
    }

    private static function getEntityFromTable()
    {
        $entId = mt_rand(0, count(self::$entitiesInTable) - 1);
        $e = self::$entitiesInTable[$entId];
        return $e;
    }

    private static function getBinaryFilterFromIndex($index, $f1, $f2)
    {
        switch ($index) {
            case 0: return Filter::applyEq($f1, $f2);
            case 1: return Filter::applyGe($f1, $f2);
            case 2: return Filter::applyGt($f1, $f2);
            case 3: return Filter::applyLe($f1, $f2);
            case 4: return Filter::applyLt($f1, $f2);
            case 5: return Filter::applyNe($f1, $f2);
            default: return null;
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesaab()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::getInterestingQueryEntitiesOptions();
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesBooleanLevel1()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::addBinaryFilter('BOOLEAN', EdmType::BOOLEAN, TableServiceFunctionalTestData::getInterestingGoodBooleans());
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesDateTimeLevel1()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::addBinaryFilter('DATETIME', EdmType::DATETIME, TableServiceFunctionalTestData::getInterestingGoodDates());
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesDoubleLevel1()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::addBinaryFilter('DOUBLE', EdmType::DOUBLE, TableServiceFunctionalTestData::getInterestingGoodDoubles());
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesGuidLevel1()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::addBinaryFilter('GUID', EdmType::GUID, TableServiceFunctionalTestData::getInterestingGoodGuids());
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesIntLevel1()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

         $interestingqueryEntitiesOptions = self::addBinaryFilter('INT32', EdmType::INT32, TableServiceFunctionalTestData::getInterestingGoodInts());
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesLongLevel1()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::addBinaryFilter('INT64', EdmType::INT64, TableServiceFunctionalTestData::getInterestingGoodLongs());
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesStringLevel1()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::addBinaryFilter('STRING', EdmType::STRING, TableServiceFunctionalTestData::getInterestingGoodStrings());
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesBinaryLevel1()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::addBinaryFilter('BINARY', EdmType::BINARY, TableServiceFunctionalTestData::getInterestingGoodBinaries());
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesLevel2()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::getInterestingQueryEntitiesOptionsOfDepth(2);
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesLevel3()
    {
        // The emulator has problems with non-standard queries tested here.
        $this->skipIfEmulated();

        $interestingqueryEntitiesOptions = self::getInterestingQueryEntitiesOptionsOfDepth(3);
        foreach($interestingqueryEntitiesOptions as $options)  {
            $this->queryEntitiesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    private function queryEntitiesWorker($options)
    {
        $table = TableServiceFunctionalTestData::$testTableNames[0];

        try {
            $ret = (is_null($options) ? $this->restProxy->queryEntities($table) : $this->restProxy->queryEntities($table, $options));

            if (is_null($options)) {
                $options = new QueryEntitiesOptions();
            }

            if (!is_null($options->getQuery()) && !is_null($options->getQuery()->getTop()) && $options->getQuery()->getTop() <= 0) {
                $this->assertTrue(false, 'Expect non-positive Top in $options->query to throw');
            }

            $this->verifyqueryEntitiesWorker($ret, $options);

            // In principle, should check if there is a continuation, then use it.
            // However, the user cannot easily control when this happens, so I'm
            // not sure how useful it is.
            // To test that scenario, set NextTable in the $options.
        } catch (ServiceException $e) {
            if (!is_null($options->getQuery()) && !is_null($options->getQuery()->getTop()) && $options->getQuery()->getTop() <= 0) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            }
        }
    }

    private function verifyqueryEntitiesWorker($ret, $options)
    {
        $this->assertNotNull($ret->getEntities(), 'getTables');

        $expectedData = array();
        foreach(self::$entitiesInTable as $e)  {
            array_push($expectedData, $e);
        }

        sort($expectedData);

        $projected = false;

        if (!is_null($options->getNextPartitionKey()) && !is_null($options->getNextRowKey())) {
            $expectedDataTmp = array();
            foreach($expectedData as $e)  {
                if ( ($e->getPartitionKey() >  $options->getNextPartitionKey()) ||
                    (($e->getPartitionKey() == $options->getNextPartitionKey()) &&
                     ($e->getRowKey()       >= $options->getNextRowKey()))) {
                    array_push($expectedDataTmp, $e);
                }
            }
            $expectedData = $expectedDataTmp;
        }

        $q = $options->getQuery();
        $expectedFilter = $q->getFilter();
        $projected = (count($q->getSelectFields()) != 0);

        $expectedData = TableServiceFunctionalTestUtils::filterEntityList($expectedFilter, $expectedData);

        if (!is_null($q->getTop()) && $q->getTop() < count($expectedData)) {
            $expectedDataTmp = array();
            for ($i = 0; $i < $q->getTop(); $i++) {
                array_push($expectedDataTmp, $expectedData[$i]);
            }
            $expectedData = $expectedDataTmp;
        }
        
        $this->compareEntityLists($ret->getEntities(), $expectedData, $projected);
    }

    private function compareEntityLists($actualData, $expectedData, $projected)
    {
        // Need to sort the lists.
        $actualData = self::sortEntitiesByCompositeKey($actualData);
        $expectedData = self::sortEntitiesByCompositeKey($expectedData);
        $this->assertEquals(count($expectedData) , count($actualData), 'count(getEntities)');
        for ($i = 0; $i < count($expectedData); $i++) {
            $e1 = $expectedData[$i];
            $e2 = $actualData[$i];
            if (!$projected) {
                $this->assertTrue(
                    ($e1->getPartitionKey() == $e2->getPartitionKey()) && ($e1->getRowKey() == $e2->getRowKey()),
                    '(' . $e1->getPartitionKey() . ',' . $e1->getRowKey() . ') == (' . $e2->getPartitionKey() . ',' . $e2->getRowKey() . ')');
            }
            // Don't need to verify the whole entities, done elsewhere
        }
    }

    private static function addBinaryFilter($name, $edmType, $values)
    {
        $counter = 0;
        $ret = array();
        foreach($values as $o)  {
            $f = self::getBinaryFilterFromIndex($counter, Filter::applyPropertyName($name), Filter::applyConstant($o, $edmType));
            $q = new Query();
            $q->setFilter($f);
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            array_push($ret, $qeo);
            $counter = ($counter + 1) % 6;
        }
        return $ret;
    }

    public static function sortEntitiesByCompositeKey($originalArray)
    {
        $tmpArray = array();
        $isordered = true;
        $prevIndex = '/';
        foreach($originalArray as $e) {
            $index = $e->getPartitionKey() . '/' . $e->getRowKey();
            $tmpArray[$index] = $e;
            if ($isordered) {
                $isordered = $prevIndex <= $index;
            }
            $prevIndex = $index;
        }

        if ($isordered) {
            return $originalArray;
        }

        ksort($tmpArray);
        $ret = array();
        foreach($tmpArray as $e) {
            array_push($ret, $e);
        }
        return $ret;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Tests\Functional\Table\Enums\ConcurType;
use MicrosoftAzure\Storage\Tests\Functional\Table\Enums\MutatePivot;
use MicrosoftAzure\Storage\Tests\Functional\Table\Enums\OpType;
use MicrosoftAzure\Storage\Tests\Functional\Table\Models\BatchWorkerConfig;
use MicrosoftAzure\Storage\Tests\Functional\Table\Models\FakeTableInfoEntry;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Table\Models\BatchError;
use MicrosoftAzure\Storage\Table\Models\BatchOperations;
use MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\InsertEntityResult;
use MicrosoftAzure\Storage\Table\Models\Property;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions;
use MicrosoftAzure\Storage\Table\Models\TableServiceOptions;
use MicrosoftAzure\Storage\Table\Models\UpdateEntityResult;

class TableServiceFunctionalTest extends FunctionalTestBase
{
    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testGetServicePropertiesNoOptions()
    {
        $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties();

        $shouldReturn = false;
        try {
            $this->restProxy->setServiceProperties($serviceProperties);
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        $this->getServicePropertiesWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testGetServiceProperties()
    {
        $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties();

        try {
            $this->restProxy->setServiceProperties($serviceProperties);
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    */
    private function getServicePropertiesWorker($options)
    {
        self::println( 'Trying $options: ' . self::tmptostring($options));
        $effOptions = (is_null($options) ? new TableServiceOptions() : $options);
        try {
            $ret = (is_null($options) ? $this->restProxy->getServiceProperties() : $this->restProxy->getServiceProperties($effOptions));
            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
            $this->verifyServicePropertiesWorker($ret, null);
        } catch (ServiceException $e) {
            if ($this->isEmulated()) {
                // Expect failure in emulator, as v1.6 doesn't support this method
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                $this->assertEquals(TestResources::STATUS_INTERNAL_SERVER_ERROR, $e->getCode(), 'getCode');
            }
        }
    }

    private function verifyServicePropertiesWorker($ret, $serviceProperties)
    {
        if (is_null($serviceProperties)) {
            $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties();
        }

        $sp = $ret->getValue();
        $this->assertNotNull($sp, 'getValue should be non-null');

        $l = $sp->getLogging();
        $this->assertNotNull($l, 'getValue()->getLogging() should be non-null');
        $this->assertEquals($serviceProperties->getLogging()->getVersion(), $l->getVersion(), 'getValue()->getLogging()->getVersion');
        $this->assertEquals($serviceProperties->getLogging()->getDelete(), $l->getDelete(), 'getValue()->getLogging()->getDelete');
        $this->assertEquals($serviceProperties->getLogging()->getRead(), $l->getRead(), 'getValue()->getLogging()->getRead');
        $this->assertEquals($serviceProperties->getLogging()->getWrite(), $l->getWrite(), 'getValue()->getLogging()->getWrite');

        $r = $l->getRetentionPolicy();
        $this->assertNotNull($r, 'getValue()->getLogging()->getRetentionPolicy should be non-null');
        $this->assertEquals($serviceProperties->getLogging()->getRetentionPolicy()->getDays(), $r->getDays(), 'getValue()->getLogging()->getRetentionPolicy()->getDays');

        $m = $sp->getMetrics();
        $this->assertNotNull($m, 'getValue()->getMetrics() should be non-null');
        $this->assertEquals($serviceProperties->getMetrics()->getVersion(), $m->getVersion(), 'getValue()->getMetrics()->getVersion');
        $this->assertEquals($serviceProperties->getMetrics()->getEnabled(), $m->getEnabled(), 'getValue()->getMetrics()->getEnabled');
        $this->assertEquals($serviceProperties->getMetrics()->getIncludeAPIs(), $m->getIncludeAPIs(), 'getValue()->getMetrics()->getIncludeAPIs');

        $r = $m->getRetentionPolicy();
        $this->assertNotNull($r, 'getValue()->getMetrics()->getRetentionPolicy should be non-null');
        $this->assertEquals($serviceProperties->getMetrics()->getRetentionPolicy()->getDays(), $r->getDays(), 'getValue()->getMetrics()->getRetentionPolicy()->getDays');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesNoOptions()
    {
        $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties();
        $this->setServicePropertiesWorker($serviceProperties, null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testSetServiceProperties()
    {
        $interestingServiceProperties = TableServiceFunctionalTestData::getInterestingServiceProperties();
        foreach($interestingServiceProperties as $serviceProperties)  {
            $options = new TableServiceOptions();
            $this->setServicePropertiesWorker($serviceProperties, $options);
        }

        if (!$this->isEmulated()) {
            $serviceProperties = TableServiceFunctionalTestData::getDefaultServiceProperties();
            $this->restProxy->setServiceProperties($serviceProperties);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    private function setServicePropertiesWorker($serviceProperties, $options)
    {
        try {
            if (is_null($options)) {
                $this->restProxy->setServiceProperties($serviceProperties);
            } else {
                $this->restProxy->setServiceProperties($serviceProperties, $options);
            }

            $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
            $ret = (is_null($options) ? $this->restProxy->getServiceProperties() : $this->restProxy->getServiceProperties($options));
            $this->verifyServicePropertiesWorker($ret, $serviceProperties);
        } catch (ServiceException $e) {
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testQueryTablesNoOptions()
    {
        $this->queryTablesWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testQueryTables()
    {
        $interestingqueryTablesOptions = TableServiceFunctionalTestData::getInterestingQueryTablesOptions($this->isEmulated());
        foreach($interestingqueryTablesOptions as $options)  {
            $this->queryTablesWorker($options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    private function queryTablesWorker($options)
    {
        try {
            $ret = (is_null($options) ? $this->restProxy->queryTables() : $this->restProxy->queryTables($options));

            if (is_null($options)) {
                $options = new QueryTablesOptions();
            }

            if ((!is_null($options->getTop()) && $options->getTop() <= 0)) {
                if ($this->isEmulated()) {
                    $this->assertEquals(0, count($ret->getTables()), "should be no tables");
                } else {
                    $this->fail('Expect non-positive Top in $options to throw');
                }
            }

            $this->verifyqueryTablesWorker($ret, $options);
        } catch (ServiceException $e) {
            if ((!is_null($options->getTop()) && $options->getTop() <= 0) && !$this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
    }

    private function verifyqueryTablesWorker($ret, $options)
    {
        $this->assertNotNull($ret->getTables(), 'getTables');

        $effectivePrefix = $options->getPrefix();
        if (is_null($effectivePrefix)) {
            $effectivePrefix = '';
        }

        $expectedFilter = $options->getFilter();
        if (TableServiceFunctionalTestUtils::isEqNotInTopLevel($expectedFilter)) {
            // This seems wrong, but appears to be a bug in the $service itself.
            // So working around the limitation.
            $expectedFilter = TableServiceFunctionalTestUtils::cloneRemoveEqNotInTopLevel($expectedFilter);
        }

        $expectedData = array();
        foreach(TableServiceFunctionalTestData::$testTableNames as $s)  {
            if (substr($s, 0, strlen($effectivePrefix)) == $effectivePrefix) {
                $fte = new FakeTableInfoEntry();
                $fte->TableName = $s;
                array_push($expectedData, $fte);
            }
        }

        if (!is_null($options->getNextTableName())) {
            $tmpExpectedData = array();
            $foundNext = false;
            foreach($expectedData as $s)  {
                if ($s == $options->getNextTableName()) {
                    $foundNext = true;
                }

                if (!$foundNext) {
                    continue;
                }

                if (substr($s, 0, strlen($effectivePrefix)) == $effectivePrefix) {
                    $fte = new FakeTableInfoEntry();
                    $fte->TableName = $s;
                    array_push($expectedData, $fte);
                }
            }

            $expectedData = $tmpExpectedData;
        }


        $expectedData = TableServiceFunctionalTestUtils::filterList($expectedFilter, $expectedData);
        $effectiveTop = (is_null($options->getTop()) ? 100000 : $options->getTop());
        $expectedCount = min($effectiveTop, count($expectedData));

        $tables = $ret->getTables();
        for ($i = 0; $i < $expectedCount; $i++) {
            $expected = $expectedData[$i]->TableName;
            // Assume there are other tables. Make sure the expected ones are there.
            $foundNext = false;
            foreach($tables as $actual) {
                if ($expected == $actual) {
                    $foundNext = true;
                    break;
                }
            }
            $this->assertTrue($foundNext, $expected . ' should be in getTables');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testCreateTableNoOptions()
    {
        $this->createTableWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testCreateTable()
    {
        $options = new TableServiceOptions();
        $this->createTableWorker($options);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    private function createTableWorker($options)
    {
        $table = TableServiceFunctionalTestData::getInterestingTableName();
        $created = false;

        // Make sure that the list of all applicable Tables is correctly updated.
        $qto = new QueryTablesOptions();
        if (!$this->isEmulated()) {
            // The emulator has problems with some queries,
            // but full Azure allow this to be more efficient:
            $qto->setPrefix(TableServiceFunctionalTestData::$testUniqueId);
        }
        $qsStart = $this->restProxy->queryTables($qto);

        if (is_null($options)) {
            $this->restProxy->createTable($table);
        } else {
            $this->restProxy->createTable($table, $options);
        }
        $created = true;

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        // Make sure that the list of all applicable Tables is correctly updated.
        $qs = $this->restProxy->queryTables($qto);
        if ($created) {
            $this->restProxy->deleteTable($table);
        }

        $this->assertEquals(count($qsStart->getTables()) + 1, count($qs->getTables()), 'After adding one, with Prefix=(\'' . TableServiceFunctionalTestData::$testUniqueId . '\'), then count(Tables)');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testDeleteTableNoOptions()
    {
        $this->deleteTableWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testDeleteTable()
    {
        $options = new TableServiceOptions();
        $this->deleteTableWorker($options);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    private function deleteTableWorker($options)
    {
        $Table = TableServiceFunctionalTestData::getInterestingTableName();

        // Make sure that the list of all applicable Tables is correctly updated.
        $qto = new QueryTablesOptions();
        if (!$this->isEmulated()) {
            // The emulator has problems with some queries,
            // but full Azure allow this to be more efficient:
            $qto->setPrefix(TableServiceFunctionalTestData::$testUniqueId);
        }
        $qsStart = $this->restProxy->queryTables($qto);

        // Make sure there is something to delete.
        $this->restProxy->createTable($Table);

        // Make sure that the list of all applicable Tables is correctly updated.
        $qs = $this->restProxy->queryTables($qto);
        $this->assertEquals(count($qsStart->getTables()) + 1, count($qs->getTables()), 'After adding one, with Prefix=(\'' . TableServiceFunctionalTestData::$testUniqueId . '\'), then count Tables');

        $deleted = false;
        if (is_null($options)) {
            $this->restProxy->deleteTable($Table);
        } else {
            $this->restProxy->deleteTable($Table, $options);
        }

        $deleted = true;

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        // Make sure that the list of all applicable Tables is correctly updated.
        $qs = $this->restProxy->queryTables($qto);

        if (!$deleted) {
            $this->println('Test didn\'t delete the $Table, so try again more simply');
            // Try again. If it doesn't work, not much else to try.
            $this->restProxy->deleteTable($Table);
        }

        $this->assertEquals(count($qsStart->getTables()), count($qs->getTables()),'After adding then deleting one, with Prefix=(\'' . TableServiceFunctionalTestData::$testUniqueId . '\'), then count(Tables)');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable
    */
    public function testGetTableNoOptions()
    {
        $this->getTableWorker(null);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable
    */
    public function testGetTable()
    {
        $options = new TableServiceOptions();
        $this->getTableWorker($options);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable
    */
    private function getTableWorker($options)
    {
        $table = TableServiceFunctionalTestData::getInterestingTableName();
        $created = false;

        $this->restProxy->createTable($table);
        $created = true;

        $ret = (is_null($options) ? $this->restProxy->getTable($table) : $this->restProxy->getTable($table, $options));

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        $this->verifygetTableWorker($ret, $table);

        if ($created) {
            $this->restProxy->deleteTable($table);
        }
    }

    private function verifygetTableWorker($ret, $tableName)
    {
        $this->assertNotNull($ret, 'getTableEntry');
        $this->assertEquals($tableName, $ret->getName(), 'getTableEntry->Name');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testGetEntity()
    {
        $ents = TableServiceFunctionalTestData::getInterestingEntities();
        foreach($ents as $ent)  {
            $options = new TableServiceOptions();
            $this->getEntityWorker($ent, true, $options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    private function getEntityWorker($ent, $isGood, $options)
    {
        $table = $this->getCleanTable();
        try {
            // Upload the entity.
            $this->restProxy->insertEntity($table, $ent);
            $qer = (is_null($options) ? $this->restProxy->getEntity($table, $ent->getPartitionKey(), $ent->getRowKey()) : $this->restProxy->getEntity($table, $ent->getPartitionKey(), $ent->getRowKey(), $options));

            if (is_null($options)) {
                $options = new TableServiceOptions();
            }

            $this->assertNotNull($qer->getEntity(), 'getEntity()');
            $this->verifygetEntityWorker($ent, $qer->getEntity());
        } catch (ServiceException $e) {
            if (!$isGood) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else if (is_null($ent->getPartitionKey()) || is_null($ent->getRowKey())) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->clearTable($table);
    }

    private function verifygetEntityWorker($ent, $entReturned)
    {
        $expectedProps = array();
        foreach($ent->getProperties() as $pname => $actualProp)  {
            if (is_null($actualProp) || !is_null($actualProp->getValue())) {
                $cloneProp = null;
                if (!is_null($actualProp)) {
                    $cloneProp = new Property();
                    $cloneProp->setEdmType($actualProp->getEdmType());
                    $cloneProp->setValue($actualProp->getValue());
                }
                $expectedProps[$pname] = $cloneProp;
            }
        }

        // Compare the entities to make sure they match.
        $this->assertEquals($ent->getPartitionKey(), $entReturned->getPartitionKey(), 'getPartitionKey');
        $this->assertEquals($ent->getRowKey(), $entReturned->getRowKey(), 'getRowKey');
        $this->assertNotNull($entReturned->getETag(), 'getETag');
        if (!is_null($ent->getETag())) {
            $this->assertEquals($ent->getETag(), $entReturned->getETag(), 'getETag');
        }
        $this->assertNotNull($entReturned->getTimestamp(), 'getTimestamp');
        if (is_null($ent->getTimestamp())) {
            // This property will come back, so need to account for it.
            $expectedProps['Timestamp'] = null;
        } else {
            $this->assertEquals($ent->getTimestamp(), $entReturned->getTimestamp(), 'getTimestamp');
        }
        $this->assertNotNull($ent->getProperties(), 'getProperties');

        $nullCount = 0;
        foreach($entReturned->getProperties() as $pname => $actualProp) {
            if (is_null($actualProp->getValue())) {
                $nullCount++;
            }
        }

        // Need to skip null values from the count.
        $this->assertEquals(count($expectedProps) + $nullCount, count($entReturned->getProperties()), 'getProperties()');

        foreach($entReturned->getProperties() as $pname => $actualProp)  {
            $this->println($actualProp->getEdmType() . ':' . (is_null($actualProp->getValue()) ? 'NULL' :
                ($actualProp->getValue() instanceof \DateTime ? "date" : $actualProp->getValue())));
        }

        foreach($entReturned->getProperties() as $pname => $actualProp)  {
            $expectedProp = Utilities::tryGetValue($expectedProps, $pname, null);
            $this->assertNotNull($actualProp, 'getProperties[\'' . $pname . '\']');
            if (!is_null($expectedProp)) {
                $this->compareProperties($pname, $actualProp, $expectedProp);
            }

            $this->assertEquals($entReturned->getProperty($pname), $actualProp, 'getProperty(\'' . $pname . '\')');
            $this->assertEquals($entReturned->getPropertyValue($pname), $actualProp->getValue(), 'getPropertyValue(\'' . $pname . '\')');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testDeleteEntity()
    {
        $ents = TableServiceFunctionalTestData::getSimpleEntities(3);
        for ($useETag = 0; $useETag <= 2; $useETag++) {
            foreach($ents as $ent)  {
                $options = new DeleteEntityOptions();
                $this->deleteEntityWorker($ent, $useETag, $options);
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    private function deleteEntityWorker($ent, $useETag, $options)
    {
        $table = $this->getCleanTable();
        try {
            // Upload the entity.
            $ier = $this->restProxy->insertEntity($table, $ent);
            if ($useETag == 1) {
                $options->setETag($ier->getEntity()->getETag());
            } else if ($useETag == 2) {
                $options->setETag('W/"datetime\'2012-03-05T21%3A46%3A25->5385467Z\'"');
            }

            $this->restProxy->deleteEntity($table, $ent->getPartitionKey(), $ent->getRowKey(), $options);

            if ($useETag == 2) {
                $this->fail('Expect bad etag throws');
            }

            // Check that the entity really is gone

            $gotError = false;
            try {
                $this->restProxy->getEntity($table, $ent->getPartitionKey(), $ent->getRowKey());
            } catch (ServiceException $e2) {
                $gotError = ($e2->getCode() == TestResources::STATUS_NOT_FOUND);
            }
            $this->assertTrue($gotError, 'Expect error when entity is deleted');
        } catch (ServiceException $e) {
            if ($useETag == 2) {
                $this->assertEquals(TestResources::STATUS_PRECONDITION_FAILED, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntity()
    {
        $ents = TableServiceFunctionalTestData::getInterestingEntities();
        foreach($ents as $ent)  {
            $options = new TableServiceOptions();
            $this->insertEntityWorker($ent, true, $options);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertBadEntity()
    {
        $ents = TableServiceFunctionalTestData::getInterestingBadEntities();
        foreach($ents as $ent)  {
            $options = new TableServiceOptions();
            try {
                $this->insertEntityWorker($ent, true, $options);
                $this->fail('this call should fail');
            } catch (\InvalidArgumentException $e) {
                $this->assertEquals(0, $e->getCode(), 'getCode');
                $this->assertTrue(true, 'got expected exception');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityBoolean()
    {
        foreach(TableServiceFunctionalTestData::getInterestingGoodBooleans() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            $ent->addProperty('BOOLEAN', EdmType::BOOLEAN, $o);
            $this->insertEntityWorker($ent, true, null, $o);
        }
    }

//     /**
//     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
//     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
//     */
//     public function testInsertEntityBooleanNegative()
//     {
//         foreach(TableServiceFunctionalTestData::getInterestingBadBooleans() as $o)  {
//             $ent = new Entity();
//             $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
//             $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
//             try {
//                 $ent->addProperty('BOOLEAN', EdmType::BOOLEAN, $o);
//                 $this->fail('Should get an exception when trying to parse this value');
//                 $this->insertEntityWorker($ent, false, null, $o);
//             } catch (\Exception $e) {
//                 $this->assertEquals(0, $e->getCode(), 'getCode');
//                 $this->assertTrue(true, 'got expected exception');
//             }
//         }
//     }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityDate()
    {
        foreach(TableServiceFunctionalTestData::getInterestingGoodDates() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            $ent->addProperty('DATETIME', EdmType::DATETIME, $o);
            $this->insertEntityWorker($ent, true, null, $o);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityDateNegative()
    {
        foreach(TableServiceFunctionalTestData::getInterestingBadDates() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            try {
                $ent->addProperty('DATETIME', EdmType::DATETIME, $o);
                $this->fail('Should get an exception when trying to parse this value');
                $this->insertEntityWorker($ent, false, null, $o);
            } catch (\Exception $e) {
                $this->assertEquals(0, $e->getCode(), 'getCode');
                $this->assertTrue(true, 'got expected exception');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityDouble()
    {
        foreach(TableServiceFunctionalTestData::getInterestingGoodDoubles() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            $ent->addProperty('DOUBLE', EdmType::DOUBLE, $o);
            $this->insertEntityWorker($ent, true, null, $o);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityDoubleNegative()
    {
        foreach(TableServiceFunctionalTestData::getInterestingBadDoubles() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            try {
                $ent->addProperty('DOUBLE', EdmType::DOUBLE, $o);
                $this->fail('Should get an exception when trying to parse this value');
                $this->insertEntityWorker($ent, false, null, $o);
            } catch (\Exception $e) {
                $this->assertEquals(0, $e->getCode(), 'getCode');
                $this->assertTrue(true, 'got expected exception');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityGuid()
    {
        foreach(TableServiceFunctionalTestData::getInterestingGoodGuids() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            $ent->addProperty('GUID', EdmType::GUID, $o);
            $this->insertEntityWorker($ent, true, null, $o);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityGuidNegative()
    {
        foreach(TableServiceFunctionalTestData::getInterestingBadGuids() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            try {
                $ent->addProperty('GUID', EdmType::GUID, $o);
                $this->fail('Should get an exception when trying to parse this value');
                $this->insertEntityWorker($ent, false, null, $o);
            } catch (\Exception $e) {
                $this->assertEquals(0, $e->getCode(), 'getCode');
                $this->assertTrue(true, 'got expected exception');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityInt()
    {
        foreach(TableServiceFunctionalTestData::getInterestingGoodInts() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            $ent->addProperty('INT32', EdmType::INT32, $o);
            $this->insertEntityWorker($ent, true, null, $o);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityIntNegative()
    {
        foreach(TableServiceFunctionalTestData::getInterestingBadInts() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            try {
                $ent->addProperty('INT32', EdmType::INT32, $o);
                $this->fail('Should get an exception when trying to parse this value');
                $this->insertEntityWorker($ent, false, null, $o);
            } catch (\Exception $e) {
                $this->assertEquals(0, $e->getCode(), 'getCode');
                $this->assertTrue(true, 'got expected exception');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityLong()
    {
        foreach(TableServiceFunctionalTestData::getInterestingGoodLongs() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            $ent->addProperty('INT64', EdmType::INT64, $o);
            $this->insertEntityWorker($ent, true, null, $o);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityLongNegative()
    {
        foreach(TableServiceFunctionalTestData::getInterestingBadLongs() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            try {
                $ent->addProperty('INT64', EdmType::INT64, $o);
                $this->fail('Should get an exception when trying to parse this value');
                $this->insertEntityWorker($ent, false, null, $o);
            } catch (\Exception $e) {
                $this->assertEquals(0, $e->getCode(), 'getCode');
                $this->assertTrue(true, 'got expected exception');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityBinary()
    {
        foreach(TableServiceFunctionalTestData::getInterestingGoodBinaries() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            $ent->addProperty('BINARY', EdmType::BINARY, $o);
            $this->insertEntityWorker($ent, true, null, $o);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityBinaryNegative()
    {
        foreach(TableServiceFunctionalTestData::getInterestingBadBinaries() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            try {
                $ent->addProperty('BINARY', EdmType::BINARY, $o);
                $this->fail('Should get an exception when trying to parse this value');
                $this->insertEntityWorker($ent, false, null, $o);
            } catch (\Exception $e) {
                $this->assertEquals(0, $e->getCode(), 'getCode');
                $this->assertTrue(true, 'got expected exception');
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertEntityString()
    {
        foreach(TableServiceFunctionalTestData::getInterestingGoodStrings() as $o)  {
            $ent = new Entity();
            $ent->setPartitionKey(TableServiceFunctionalTestData::getNewKey());
            $ent->setRowKey(TableServiceFunctionalTestData::getNewKey());
            $ent->addProperty('STRING', EdmType::STRING, $o);
            $this->insertEntityWorker($ent, true, null, $o);
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    private function insertEntityWorker($ent, $isGood, $options, $specialValue = null)
    {
        $table = $this->getCleanTable();
        try {
            $ret = (is_null($options) ? $this->restProxy->insertEntity($table, $ent) : $this->restProxy->insertEntity($table, $ent, $options));

            if (is_null($options)) {
                $options = new TableServiceOptions();
            }

            // Check that the message matches
            $this->assertNotNull($ret->getEntity(), 'getEntity()');
            $this->verifyinsertEntityWorker($ent, $ret->getEntity());

            if (is_null($ent->getPartitionKey()) || is_null($ent->getRowKey())) {
                $this->fail('Expect missing keys throw');
            }

            if (!$isGood) {
                $this->fail('Expect bad values to throw: ' . self::tmptostring($specialValue));
            }

            // Check that the message matches
            $qer = $this->restProxy->queryEntities($table);
            $this->assertNotNull($qer->getEntities(), 'getEntities()');
            $this->assertEquals(1, count($qer->getEntities()), 'getEntities() count');
            $entReturned = $qer->getEntities();
            $entReturned = $entReturned[0];
            $this->assertNotNull($entReturned, 'getEntities()[0]');

            $this->verifyinsertEntityWorker($ent, $entReturned);
        } catch (ServiceException $e) {
            if (!$isGood) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else if (is_null($ent->getPartitionKey()) || is_null($ent->getRowKey())) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testUpdateEntity()
    {
        $ents = TableServiceFunctionalTestData::getSimpleEntities(2);
        foreach(MutatePivot::values() as $mutatePivot) {
            foreach($ents as $initialEnt)  {
                $options = new TableServiceOptions();
                $ent = TableServiceFunctionalTestUtils::cloneEntity($initialEnt);
                TableServiceFunctionalTestUtils::mutateEntity($ent, $mutatePivot);
                $this->updateEntityWorker($initialEnt, $ent, $options);
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    private function updateEntityWorker($initialEnt, $ent, $options)
    {
        $table = $this->getCleanTable();

        // Upload the entity.
        $this->restProxy->insertEntity($table, $initialEnt);

        if (is_null($options)) {
            $this->restProxy->updateEntity($table, $ent);
        } else {
            $this->restProxy->updateEntity($table, $ent, $options);
        }

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        // Check that the message matches
        $qer = $this->restProxy->queryEntities($table);
        $this->assertNotNull($qer->getEntities(), 'getEntities()');
        $this->assertEquals(1, count($qer->getEntities()), 'getEntities()');
        $entReturned = $qer->getEntities();
        $entReturned = $entReturned[0];
        $this->assertNotNull($entReturned, 'getEntities()[0]');
        $this->verifyinsertEntityWorker($ent, $entReturned);
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testMergeEntity()
    {
        $ents = TableServiceFunctionalTestData::getSimpleEntities(2);
        foreach(MutatePivot::values() as $mutatePivot) {
            foreach($ents as $initialEnt)  {
                $options = new TableServiceOptions();
                $ent = TableServiceFunctionalTestUtils::cloneEntity($initialEnt);
                TableServiceFunctionalTestUtils::mutateEntity($ent, $mutatePivot);
                $this->mergeEntityWorker($initialEnt, $ent, $options);
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    private function mergeEntityWorker($initialEnt, $ent, $options)
    {
        $table = $this->getCleanTable();

        // Upload the entity.
        $this->restProxy->insertEntity($table, $initialEnt);

        if (is_null($options)) {
            $this->restProxy->mergeEntity($table, $ent);
        } else {
            $this->restProxy->mergeEntity($table, $ent, $options);
        }

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        // Check that the message matches
        $qer = $this->restProxy->queryEntities($table);
        $this->assertNotNull($qer->getEntities(), 'getEntities()');
        $this->assertEquals(1, count($qer->getEntities()), 'getEntities() count');
        $entReturned = $qer->getEntities();
        $entReturned = $entReturned[0];
        $this->assertNotNull($entReturned, 'getEntities()[0]');

        $this->verifymergeEntityWorker($initialEnt, $ent, $entReturned);
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertOrReplaceEntity()
    {
        $ents = TableServiceFunctionalTestData::getSimpleEntities(2);
        foreach(MutatePivot::values() as $mutatePivot) {
            foreach($ents as $initialEnt)  {
                $options = new TableServiceOptions();
                $ent = TableServiceFunctionalTestUtils::cloneEntity($initialEnt);
                TableServiceFunctionalTestUtils::mutateEntity($ent, $mutatePivot);
                try {
                    $this->insertOrReplaceEntityWorker($initialEnt, $ent, $options);
                    $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
                } catch (ServiceException $e) {
                    // Expect failure in emulator, as v1.6 doesn't support this method
                    if ($this->isEmulated()) {
                        $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                    } else {
                        throw $e;
                    }
                }
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    private function insertOrReplaceEntityWorker($initialEnt, $ent, $options)
    {
        $table = $this->getCleanTable();

        // Upload the entity.
        $this->restProxy->insertEntity($table, $initialEnt);
        if (is_null($options)) {
            $this->restProxy->insertOrReplaceEntity($table, $ent);
        } else {
            $this->restProxy->insertOrReplaceEntity($table, $ent, $options);
        }

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        // Check that the message matches
        $qer = $this->restProxy->queryEntities($table);
        $this->assertNotNull($qer->getEntities(), 'getEntities()');
        $this->assertEquals(1, count($qer->getEntities()), 'getEntities() count');
        $entReturned = $qer->getEntities();
        $entReturned = $entReturned[0];
        $this->assertNotNull($entReturned, 'getEntities()[0]');

        $this->verifyinsertEntityWorker($ent, $entReturned);
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testInsertOrMergeEntity()
    {
        $ents = TableServiceFunctionalTestData::getSimpleEntities(2);
        foreach(MutatePivot::values() as $mutatePivot) {
            foreach($ents as $initialEnt)  {
                $options = new TableServiceOptions();
                $ent = TableServiceFunctionalTestUtils::cloneEntity($initialEnt);
                TableServiceFunctionalTestUtils::mutateEntity($ent, $mutatePivot);
                try {
                    $this->insertOrMergeEntityWorker($initialEnt, $ent, $options);
                    $this->assertFalse($this->isEmulated(), 'Should succeed when not running in emulator');
                } catch (ServiceException $e) {
                    // Expect failure in emulator, as v1.6 doesn't support this method
                    if ($this->isEmulated()) {
                        $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                    } else {
                        throw $e;
                    }
                }
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    private function insertOrMergeEntityWorker($initialEnt, $ent, $options)
    {
        $table = $this->getCleanTable();

        // Upload the entity.
        $this->restProxy->insertEntity($table, $initialEnt);

        if (is_null($options)) {
            $this->restProxy->insertOrMergeEntity($table, $ent);
        } else {
            $this->restProxy->insertOrMergeEntity($table, $ent, $options);
        }

        if (is_null($options)) {
            $options = new TableServiceOptions();
        }

        // Check that the message matches
        $qer = $this->restProxy->queryEntities($table);
        $this->assertNotNull($qer->getEntities(), 'getEntities()');
        $this->assertEquals(1, count($qer->getEntities()), 'getEntities() count');
        $entReturned = $qer->getEntities();
        $entReturned = $entReturned[0];
        $this->assertNotNull($entReturned, 'getEntities()[0]');

        $this->verifymergeEntityWorker($initialEnt, $ent, $entReturned);
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testCRUDdeleteEntity()
    {
        foreach(ConcurType::values() as $concurType)  {
            foreach(MutatePivot::values() as $mutatePivot) {
                for ($i = 0; $i <= 1; $i++) {
                    foreach(TableServiceFunctionalTestData::getSimpleEntities(2) as $ent)  {
                        $options = ($i == 0 ? null : new TableServiceOptions());
                        $this->crudWorker(OpType::DELETE_ENTITY, $concurType, $mutatePivot, $ent, $options);
                    }
                }
            }
        }
    }
/*
    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testCRUDinsertEntity()
    {
        foreach(ConcurType::values() as $concurType)  {
            foreach(MutatePivot::values() as $mutatePivot) {
                for ($i = 0; $i <= 1; $i++) {
                    foreach(TableServiceFunctionalTestData::getSimpleEntities(2) as $ent)  {
                        $options = ($i == 0 ? null : new TableServiceOptions());
                        $this->crudWorker(OpType::INSERT_ENTITY, $concurType, $mutatePivot, $ent, $options);
                    }
                }
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testCRUDinsertOrMergeEntity()
    {
        $this->skipIfEmulated();

        foreach(ConcurType::values() as $concurType)  {
            foreach(MutatePivot::values() as $mutatePivot) {
                for ($i = 0; $i <= 1; $i++) {
                    foreach(TableServiceFunctionalTestData::getSimpleEntities(2) as $ent)  {
                        $options = ($i == 0 ? null : new TableServiceOptions());
                        $this->crudWorker(OpType::INSERT_OR_MERGE_ENTITY, $concurType, $mutatePivot, $ent, $options);
                    }
                }
            }
        }
    }
/*
    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testCRUDinsertOrReplaceEntity()
    {
        $this->skipIfEmulated();

        foreach(ConcurType::values() as $concurType)  {
            foreach(MutatePivot::values() as $mutatePivot) {
                for ($i = 0; $i <= 1; $i++) {
                    foreach(TableServiceFunctionalTestData::getSimpleEntities(2) as $ent)  {
                        $options = ($i == 0 ? null : new TableServiceOptions());
                        $this->crudWorker(OpType::INSERT_OR_REPLACE_ENTITY, $concurType, $mutatePivot, $ent, $options);
                    }
                }
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testCRUDmergeEntity()
    {
        foreach(ConcurType::values() as $concurType)  {
            foreach(MutatePivot::values() as $mutatePivot) {
                for ($i = 0; $i <= 1; $i++) {
                    foreach(TableServiceFunctionalTestData::getSimpleEntities(2) as $ent)  {
                        $options = ($i == 0 ? null : new TableServiceOptions());
                        $this->crudWorker(OpType::MERGE_ENTITY, $concurType, $mutatePivot, $ent, $options);
                    }
                }
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testCRUDupdateEntity()
    {
        foreach(ConcurType::values() as $concurType)  {
            foreach(MutatePivot::values() as $mutatePivot) {
                for ($i = 0; $i <= 1; $i++) {
                    foreach(TableServiceFunctionalTestData::getSimpleEntities(2) as $ent)  {
                        $options = ($i == 0 ? null : new TableServiceOptions());
                        $this->crudWorker(OpType::UPDATE_ENTITY, $concurType, $mutatePivot, $ent, $options);
                    }
                }
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    private function crudWorker($opType, $concurType, $mutatePivot, $ent, $options)
    {
        $exptErr = $this->expectConcurrencyFailure($opType, $concurType);
        $table = $this->getCleanTable();

        try {
            // Upload the entity.
            $initial = $this->restProxy->insertEntity($table, $ent);
            $targetEnt = $this->createTargetEntity($table, $initial->getEntity(), $concurType, $mutatePivot);

            $this->executeCrudMethod($table, $targetEnt, $opType, $concurType, $options);

            if (!is_null($exptErr)) {
                $this->fail('Expected a failure when opType=' . $opType . ' and concurType=' . $concurType . ' :' . $this->expectConcurrencyFailure($opType, $concurType));
            }

            $this->verifyCrudWorker($opType, $table, $ent, $targetEnt, true);
        } catch (ServiceException $e) {
            if (!is_null($exptErr)) {
                $this->assertEquals($exptErr, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }
        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchPositiveFirstNoKeyMatch()
    {
        $this->batchPositiveOuter(ConcurType::NO_KEY_MATCH, 123);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchPositiveFirstKeyMatchNoETag()
    {
        $this->batchPositiveOuter(ConcurType::KEY_MATCH_NO_ETAG, 234);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchPositiveFirstKeyMatchETagMismatch()
    {
        $this->skipIfEmulated();
        $this->batchPositiveOuter(ConcurType::KEY_MATCH_ETAG_MISMATCH, 345);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchPositiveFirstKeyMatchETagMatch()
    {
        $this->batchPositiveOuter(ConcurType::KEY_MATCH_ETAG_MATCH, 456);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchNegative()
    {
        $this->skipIfEmulated();

        // The random here is not to generate random values, but to
        // get a good mix of values in the table entities.

        mt_srand(456);
        $concurTypes = ConcurType::values();
        $mutatePivots = MutatePivot::values();
        $opTypes = OpType::values();

        for ($j = 0; $j < 10; $j++) {
            $configs = array();
            foreach(TableServiceFunctionalTestData::getSimpleEntities(6) as $ent)  {
                $config = new BatchWorkerConfig();
                $config->concurType = $concurTypes[mt_rand(0, count($concurTypes))];
                $config->opType = $opTypes[mt_rand(0, count($opTypes))];
                $config->mutatePivot = $mutatePivots[mt_rand(0, count($mutatePivots))];
                $config->ent = $ent;
                array_push($configs, $config);
            }

            for ($i = 0; $i <= 1; $i++) {
                $options = ($i == 0 ? null : new TableServiceOptions());
                $this->batchWorker($configs, $options);
            }
        }
    }

    private function verifyinsertEntityWorker($ent, $entReturned)
    {
        $this->verifyinsertOrMergeEntityWorker(null, $ent, $entReturned);
    }

    private function verifymergeEntityWorker($intitalEnt, $ent, $entReturned)
    {
        $this->verifyinsertOrMergeEntityWorker($intitalEnt, $ent, $entReturned);
    }

    private function verifyinsertOrMergeEntityWorker($initialEnt, $ent, $entReturned)
    {
        $expectedProps = array();
        if (!is_null($initialEnt) && $initialEnt->getPartitionKey() == $ent->getPartitionKey() && $initialEnt->getRowKey() == $ent->getRowKey()) {
            foreach($initialEnt->getProperties() as $pname => $actualProp)  {
                if (!is_null($actualProp) && !is_null($actualProp->getValue())) {
                    $cloneProp = null;
                    if (!is_null($actualProp)) {
                        $cloneProp = new Property();
                        $cloneProp->setEdmType($actualProp->getEdmType());
                        $cloneProp->setValue($actualProp->getValue());
                    }
                    $expectedProps[$pname] = $cloneProp;
                }
            }
        }
        foreach($ent->getProperties() as $pname => $actualProp)  {
            // Any properties with null values are ignored by the Merge Entity operation.
            // All other properties will be updated.
            if (!is_null($actualProp) && !is_null($actualProp->getValue())) {
                $cloneProp = new Property();
                $cloneProp->setEdmType($actualProp->getEdmType());
                $cloneProp->setValue($actualProp->getValue());
                $expectedProps[$pname] = $cloneProp;
            }
        }

        $effectiveProps = array();
        foreach($entReturned->getProperties() as $pname => $actualProp)  {
            // This is to work with Dev Storage, which returns items for all
            // columns, null valued or not.
            if (!is_null($actualProp) && !is_null($actualProp->getValue())) {
                $cloneProp = new Property();
                $cloneProp->setEdmType($actualProp->getEdmType());
                $cloneProp->setValue($actualProp->getValue());
                $effectiveProps[$pname] = $cloneProp;
            }
        }

        // Compare the entities to make sure they match.
        $this->assertEquals($ent->getPartitionKey(), $entReturned->getPartitionKey(), 'getPartitionKey');
        $this->assertEquals($ent->getRowKey(), $entReturned->getRowKey(), 'getRowKey');
        $this->assertNotNull($entReturned->getETag(), 'getETag');
        if (!is_null($ent->getETag())) {
            $this->assertTrue($ent->getETag() != $entReturned->getETag(), 'getETag should change after submit: initial \'' . $ent->getETag() . '\', returned \'' . $entReturned->getETag() . '\'');
        }
        $this->assertNotNull($entReturned->getTimestamp(), 'getTimestamp');
        if (is_null($ent->getTimestamp())) {
            // This property will come back, so need to account for it.
            $expectedProps['Timestamp'] = null;
        } else {
            $this->assertEquals($ent->getTimestamp(), $entReturned->getTimestamp(), 'getTimestamp');
        }
        $this->assertNotNull($ent->getProperties(), 'getProperties');

        // Need to skip null values from the count.
        $this->assertEquals(count($expectedProps), count($effectiveProps), 'getProperties()');

        foreach($expectedProps as $pname => $expectedProp)  {
            $actualProp = $effectiveProps;
            $actualProp = $actualProp[$pname];

            $this->assertNotNull($actualProp, 'getProperties()[\'' . $pname . '\')');
            if (!is_null($expectedProp) ) {
                $this->compareProperties($pname, $actualProp, $expectedProp);
            }

            $this->assertEquals($entReturned->getProperty($pname), $actualProp, 'getProperty(\'' . $pname . '\')');
            $this->assertEquals($entReturned->getPropertyValue($pname), $actualProp->getValue(), 'getPropertyValue(\'' . $pname . '\')');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    private function batchPositiveOuter($firstConcurType, $seed)
    {
        // The random here is not to generate random values, but to
        // get a good mix of values in the table entities.
        mt_srand($seed);
        $concurTypes = ConcurType::values();
        $mutatePivots = MutatePivot::values();
        $opTypes = OpType::values();

        // Main loop.
        foreach($opTypes as $firstOpType)  {
            if (!is_null($this->expectConcurrencyFailure($firstOpType, $firstConcurType))) {
                // Want to know there is at least one part that does not fail.
                continue;
            }
            if ($this->isEmulated() && (
                    ($firstOpType == OpType::INSERT_OR_MERGE_ENTITY) ||
                    ($firstOpType == OpType::INSERT_OR_REPLACE_ENTITY))) {
                // Emulator does not support these operations.
                continue;
            }

            $simpleEntities = TableServiceFunctionalTestData::getSimpleEntities(6);
            $configs = array();
            $firstConfig = new BatchWorkerConfig();
            $firstConfig->concurType = $firstConcurType;
            $firstConfig->opType = $firstOpType;
            $firstConfig->ent = $simpleEntities[0];
            $firstConfig->mutatePivot = $mutatePivots[mt_rand(0, count($mutatePivots))];
            array_push($configs, $firstConfig);

            for ($i = 1; $i < count($simpleEntities); $i++) {
                $config = new BatchWorkerConfig();
                while (!is_null($this->expectConcurrencyFailure($config->opType, $config->concurType))) {
                    $config->concurType = $concurTypes[mt_rand(0, count($concurTypes))];
                    $config->opType = $opTypes[mt_rand(0, count($opTypes))];
                    if ($this->isEmulated()) {
                        if ($config->opType == OpType::INSERT_OR_MERGE_ENTITY) {
                            $config->opType = OpType::MERGE_ENTITY;
                        }
                        if ($config->opType == OpType::INSERT_OR_REPLACE_ENTITY) {
                            $config->opType = OpType::UPDATE_ENTITY;
                        }
                    }
                }
                $config->mutatePivot = $mutatePivots[mt_rand(0, count($mutatePivots) -1)];
                $config->ent = $simpleEntities[$i];
                array_push($configs, $config);
            }

            for ($i = 0; $i <= 1; $i++) {
                $options = ($i == 0 ? null : new TableServiceOptions());
                if ($this->isEmulated()) {
                    // The emulator has trouble with some batches.
                    for ($j = 0; $j < count($configs); $j++) {
                        $tmpconfigs = array();
                        $tmpconfigs[] = $configs[$j];
                        $this->batchWorker($tmpconfigs, $options);
                    }
                } else {
                    $this->batchWorker($configs, $options);
                }
            }
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    private function batchWorker($configs, $options)
    {
        $exptErrs = array();
        $expectedReturned = count($configs);
        $expectedError = false;
        $expectedErrorCount = 0;
        for ($i = 0; $i < count($configs); $i++) {
            $err = $this->expectConcurrencyFailure($configs[$i]->opType, $configs[$i]->concurType);
            if (!is_null($err)) {
                $expectedErrorCount++;
                $expectedError = true;
            }
            array_push($exptErrs, $err);
        }

        $table = $this->getCleanTable();

        try {
            // Upload the initial entities and get the target entities.
            $targetEnts = array();
            for ($i = 0; $i < count($configs); $i++) {
                $initial = $this->restProxy->insertEntity($table, $configs[$i]->ent);
                array_push($targetEnts, $this->createTargetEntity($table, $initial->getEntity(),
                        $configs[$i]->concurType,
                        $configs[$i]->mutatePivot));
            }

            // Build up the batch.
            $operations = new BatchOperations();
            for ($i = 0; $i < count($configs); $i++) {
                $this->buildBatchOperations($table, $operations, $targetEnts[$i],
                        $configs[$i]->opType,
                        $configs[$i]->concurType,
                        $configs[$i]->options);
            }

            // Execute the batch.
            $ret = (is_null($options) ? $this->restProxy->batch($operations) : $this->restProxy->batch($operations, $options));

            if (is_null($options)) {
                $options = new QueryEntitiesOptions();
            }

            // Verify results.
            if ($expectedError) {
                $this->assertEquals($expectedErrorCount, count($ret->getEntries()), 'count $ret->getEntries()');

                // No changes should have gone through.
                for ($i = 0; $i < count($configs); $i++) {
                    $this->verifyCrudWorker($configs[$i]->opType, $table, $configs[$i]->ent, $configs[$i]->ent, false);
                }
            } else {
                $this->assertEquals($expectedReturned, count($ret->getEntries()), 'count $ret->getEntries()');
                for ($i = 0; $i < count($ret->getEntries()); $i++) {
                    $opResult = $ret->getEntries();
                    $opResult = $opResult[$i];
                    $this->verifyBatchEntryType($configs[$i]->opType, $exptErrs[$i], $opResult);
                    $this->verifyEntryData($table, $exptErrs[$i], $targetEnts[$i], $opResult);
                    // Check out the entities.
                    $this->verifyCrudWorker($configs[$i]->opType, $table, $configs[$i]->ent, $targetEnts[$i], true);
                }
            }
        } catch (ServiceException $e) {
            if ($expectedError) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
            } else {
                throw $e;
            }
        }

        $this->clearTable($table);
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    */
    private function verifyEntryData($table, $exptErr, $targetEnt, $opResult)
    {
        if ($opResult instanceof InsertEntityResult) {
            $this->verifyinsertEntityWorker($targetEnt, $opResult->getEntity());
        } else if ($opResult instanceof UpdateEntityResult) {
            $ger = $this->restProxy->getEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey());
            $this->assertEquals($opResult->getETag(), $ger->getEntity()->getETag(), 'op->getETag');
        } else if (is_string($opResult)) {
            // Nothing special to do.
        } else if ($opResult instanceof BatchError) {
            $this->assertEquals($exptErr, $opResult->getError()->getCode(), 'getError()->getCode');
        } else {
            $this->fail('opResult is of an unknown type');
        }
    }

    private function verifyBatchEntryType($opType, $exptErr, $opResult)
    {
        if (is_null($exptErr)) {
            switch ($opType) {
                case OpType::INSERT_ENTITY:
                    $this->assertTrue($opResult instanceof InsertEntityResult,
                            'When opType=' . $opType . ' expect opResult instanceof InsertEntityResult');
                    break;
                case OpType::DELETE_ENTITY:
                    $this->assertTrue(
                            is_string($opResult),
                            'When opType=' . $opType . ' expect opResult is a string');
                    break;
                case OpType::UPDATE_ENTITY:
                case OpType::INSERT_OR_REPLACE_ENTITY:
                case OpType::MERGE_ENTITY:
                case OpType::INSERT_OR_MERGE_ENTITY:
                    $this->assertTrue($opResult instanceof UpdateEntityResult,
                            'When opType=' . $opType . ' expect opResult instanceof UpdateEntityResult');
                    break;
            }
        } else {
            $this->assertTrue($opResult instanceof BatchError, 'When expect an error, expect opResult instanceof BatchError');
        }
    }

    private function buildBatchOperations($table, $operations, $targetEnt, $opType, $concurType, $options)
    {
        switch ($opType) {
            case OpType::DELETE_ENTITY:
                if (is_null($options) && $concurType != ConcurType::KEY_MATCH_ETAG_MISMATCH) {
                    $operations->addDeleteEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey(), null);
                } else {
                    $operations->addDeleteEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey(), $targetEnt->getETag());
                }
                break;
            case OpType::INSERT_ENTITY:
                $operations->addInsertEntity($table, $targetEnt);
                break;
            case OpType::INSERT_OR_MERGE_ENTITY:
                $operations->addInsertOrMergeEntity($table, $targetEnt);
                break;
            case OpType::INSERT_OR_REPLACE_ENTITY:
                $operations->addInsertOrReplaceEntity($table, $targetEnt);
                break;
            case OpType::MERGE_ENTITY:
                $operations->addMergeEntity($table, $targetEnt);
                break;
            case OpType::UPDATE_ENTITY:
                $operations->addUpdateEntity($table, $targetEnt);
                break;
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    private function executeCrudMethod($table, $targetEnt, $opType, $concurType, $options)
    {
        switch ($opType) {
            case OpType::DELETE_ENTITY:
                if (is_null($options) && $concurType != ConcurType::KEY_MATCH_ETAG_MISMATCH) {
                    $this->restProxy->deleteEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey());
                } else {
                    $delOptions = new DeleteEntityOptions();
                    $delOptions->setETag($targetEnt->getETag());
                    $this->restProxy->deleteEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey(), $delOptions);
                }
                break;
            case OpType::INSERT_ENTITY:
                if (is_null($options)) {
                    $this->restProxy->insertEntity($table, $targetEnt);
                } else {
                    $this->restProxy->insertEntity($table, $targetEnt, $options);
                }
                break;
            case OpType::INSERT_OR_MERGE_ENTITY:
                if (is_null($options)) {
                    $this->restProxy->insertOrMergeEntity($table, $targetEnt);
                } else {
                    $this->restProxy->insertOrMergeEntity($table, $targetEnt, $options);
                }
                break;
            case OpType::INSERT_OR_REPLACE_ENTITY:
                if (is_null($options)) {
                    $this->restProxy->insertOrReplaceEntity($table, $targetEnt);
                } else {
                    $this->restProxy->insertOrReplaceEntity($table, $targetEnt, $options);
                }
                break;
            case OpType::MERGE_ENTITY:
                if (is_null($options)) {
                    $this->restProxy->mergeEntity($table, $targetEnt);
                } else {
                    $this->restProxy->mergeEntity($table, $targetEnt, $options);
                }
                break;
            case OpType::UPDATE_ENTITY:
                if (is_null($options)) {
                    $this->restProxy->updateEntity($table, $targetEnt);
                } else {
                    $this->restProxy->updateEntity($table, $targetEnt, $options);
                }
                break;
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    */
    private function verifyCrudWorker($opType, $table, $initialEnt, $targetEnt, $expectedSuccess)
    {
        $entInTable = null;
        try {
            $ger = $this->restProxy->getEntity($table, $targetEnt->getPartitionKey(), $targetEnt->getRowKey());
            $entInTable = $ger->getEntity();
        } catch (ServiceException $e) {
            $this->assertTrue(($opType == OpType::DELETE_ENTITY) && (TestResources::STATUS_NOT_FOUND == $e->getCode()), '404:NotFound is expected for deletes');
        }

        switch ($opType) {
            case OpType::DELETE_ENTITY:
                // Check that the entity really is gone
                if ($expectedSuccess) {
                    $this->assertNull($entInTable, 'Entity from table');
                } else {
                    // Check that the message matches
                    $this->assertNotNull($entInTable, 'Entity from table');
                    $this->verifyinsertEntityWorker($targetEnt, $entInTable);
                }
                break;
            case OpType::INSERT_ENTITY:
                // Check that the message matches
                $this->assertNotNull($entInTable, 'Entity from table');
                $this->verifyinsertEntityWorker($targetEnt, $entInTable);
                break;
            case OpType::INSERT_OR_MERGE_ENTITY:
                $this->assertNotNull($entInTable, 'Entity from table');
                $this->verifymergeEntityWorker($initialEnt, $targetEnt, $entInTable);
                break;
            case OpType::INSERT_OR_REPLACE_ENTITY:
                // Check that the message matches
                $this->assertNotNull($entInTable, 'Entity from table');
                $this->verifyinsertEntityWorker($targetEnt, $entInTable);
                break;
            case OpType::MERGE_ENTITY:
                $this->assertNotNull($entInTable, 'Entity from table');
                $this->verifymergeEntityWorker($initialEnt, $targetEnt, $entInTable);
                break;
            case OpType::UPDATE_ENTITY:
                // Check that the message matches
                $this->assertNotNull($entInTable, 'Entity from table');
                $this->verifyinsertEntityWorker($targetEnt, $entInTable);
                break;
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    private function createTargetEntity($table, $initialEnt, $concurType, $mutatePivot)
    {
        $targetEnt = TableServiceFunctionalTestUtils::cloneEntity($initialEnt);

        // Update the entity/table state to get the requested concurrency type error.
        switch ($concurType) {
            case ConcurType::NO_KEY_MATCH:
                // Mutate the keys to not match.
                $targetEnt->setRowKey(TableServiceFunctionalTestData::getNewKey());
                break;
            case ConcurType::KEY_MATCH_NO_ETAG:
                $targetEnt->setETag(null);
                break;
            case ConcurType::KEY_MATCH_ETAG_MISMATCH:
                $newETag =  $this->restProxy->updateEntity($table, $initialEnt)->getETag();
                $initialEnt->setETag($newETag);
                // Now the $targetEnt ETag will not match.
                $this->assertTrue($targetEnt->getETag() != $initialEnt->getETag(), 'targetEnt->ETag(\'' . $targetEnt->getETag() . '\') !=  updated->ETag(\'' . $initialEnt->getETag() . '\')');

                break;
            case ConcurType::KEY_MATCH_ETAG_MATCH:
                // Don't worry here.
                break;
        }

        // Mutate the properties.
        TableServiceFunctionalTestUtils::mutateEntity($targetEnt, $mutatePivot);
        return $targetEnt;
    }

    private static function expectConcurrencyFailure($opType, $concurType)
    {
        if (is_null($concurType) || is_null($opType)) {
            return -1;
        }

        switch ($concurType) {
            case ConcurType::NO_KEY_MATCH:
                if (($opType == OpType::DELETE_ENTITY) || ($opType == OpType::MERGE_ENTITY) || ($opType == OpType::UPDATE_ENTITY)) {
                    return TestResources::STATUS_NOT_FOUND;
                }
                break;
            case ConcurType::KEY_MATCH_NO_ETAG:
                if ($opType == OpType::INSERT_ENTITY) {
                    return TestResources::STATUS_CONFLICT;
                }
                break;
            case ConcurType::KEY_MATCH_ETAG_MATCH:
                if ($opType == OpType::INSERT_ENTITY) {
                    return TestResources::STATUS_CONFLICT;
                }
                break;
            case ConcurType::KEY_MATCH_ETAG_MISMATCH:
                if ($opType == OpType::INSERT_ENTITY) {
                    return TestResources::STATUS_CONFLICT;
                } else if ($opType == OpType::INSERT_OR_REPLACE_ENTITY || $opType == OpType::INSERT_OR_MERGE_ENTITY) {
                    // If exists, just clobber.
                    return null;
                }
                return TestResources::STATUS_PRECONDITION_FAILED;
        }
        return null;
    }

    function compareProperties($pname, $actualProp, $expectedProp)
    {
        $effectiveExpectedProp = (is_null($expectedProp->getEdmType()) ? EdmType::STRING : $expectedProp->getEdmType());
        $effectiveActualProp = (is_null($expectedProp->getEdmType()) ? EdmType::STRING : $expectedProp->getEdmType());

        $this->assertEquals($effectiveExpectedProp, $effectiveActualProp,
                'getProperties()->get(\'' . $pname . '\')->getEdmType');

        $effExp = $expectedProp->getValue();
        $effAct = $actualProp->getValue();

        if ($effExp instanceof \DateTime) {
            $effExp = $effExp->setTimezone(new \DateTimeZone('UTC'));
        }
        if ($effAct instanceof \DateTime) {
            $effAct = $effAct->setTimezone(new \DateTimeZone('UTC'));
        }

        $this->assertEquals($expectedProp->getValue(), $actualProp->getValue(), 'getProperties()->get(\'' . $pname . '\')->getValue [' . $effectiveExpectedProp . ']');
    }

}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Models\Logging;
use MicrosoftAzure\Storage\Common\Models\Metrics;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions;
use MicrosoftAzure\Storage\Table\Models\TableServiceOptions;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;

class TableServiceFunctionalTestData
{
    private static $tempTableCounter;
    private static $nonExistTablePrefix;
    public static $testUniqueId;
    public static $testTableNames;

    const INT_MAX_VALUE = 2147483647;
    // Intent is to be a constant, but cannot represent in code.
    public static $INT_MIN_VALUE;
    const LONG_BIG_VALUE = 1234567890;
    const LONG_BIG_VALUE_NEGATIVE = -123456789032;

    public function __construct()
    {
        self:: $setupData;
    }

    public static function setupData()
    {
        self::$INT_MIN_VALUE = -1 - self::INT_MAX_VALUE;
        $rint = rand(0,1000000);
        self::$testUniqueId = 'qaX' . $rint . 'X';
        self::$nonExistTablePrefix = 'qaX' . ($rint + 1) . 'X';
        self::$testTableNames = array( self::$testUniqueId . 'a1', self::$testUniqueId . 'a2', self::$testUniqueId . 'b1' );
    }

    static function getInterestingTableName()
    {
        return self::$testUniqueId . 'int' . (self::$tempTableCounter++);
    }

    static function getNewKey()
    {
        return self::$testUniqueId . 'key' . (self::$tempTableCounter++);
    }

    static function getUnicodeString()
    {
        return  chr(0xEB) . chr(0x8B) . chr(0xA4) . // \uB2E4 in UTF-8
                chr(0xEB) . chr(0xA5) . chr(0xB4) . // \uB974 in UTF-8
                chr(0xEB) . chr(0x8B) . chr(0xA4) . // \uB2E4 in UTF-8
                chr(0xEB) . chr(0x8A) . chr(0x94) . // \uB294 in UTF-8
                chr(0xD8) . chr(0xA5) .             // \u0625 in UTF-8
                ' ' .
                chr(0xD9) . chr(0x8A) .             // \u064A in UTF-8
                chr(0xD8) . chr(0xAF) .             // \u062F in UTF-8
                chr(0xD9) . chr(0x8A) .             // \u064A in UTF-8
                chr(0xD9) . chr(0x88);              // \u0648 in UTF-8
    }

    public static function getDefaultServiceProperties()
    {
        // This is the default that comes from the server.
        $rp = new RetentionPolicy();
        $l = new Logging();
        $l->setRetentionPolicy($rp);
        $l->setVersion('1.0');
        $l->setDelete(false);
        $l->setRead(false);
        $l->setWrite(false);

        $m = new Metrics();
        $m->setRetentionPolicy($rp);
        $m->setVersion('1.0');
        $m->setEnabled(false);
        $m->setIncludeAPIs(null);

        $sp = new ServiceProperties();
        $sp->setLogging($l);
        $sp->setMetrics($m);

        return $sp;
    }

    public static function getInterestingServiceProperties()
    {
        $ret = array();

        // This is the default that comes from the server.
        array_push($ret, self::getDefaultServiceProperties());

        {
            $rp = new RetentionPolicy();
            $rp->setEnabled(true);
            $rp->setDays(10);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(true);
            $l->setRead(true);
            $l->setWrite(true);

            $m = new Metrics();
            $m->setRetentionPolicy($rp);
            $m->setVersion('1.0');
            $m->setEnabled(true);
            $m->setIncludeAPIs(true);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret, $sp);
        }

        {
            $rp = new RetentionPolicy();
            $rp->setEnabled(false);
            $rp->setDays(null);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(false);
            $l->setRead(false);
            $l->setWrite(false);

            $m = new Metrics();
            $m->setRetentionPolicy($rp);
            $m->setVersion('1.0');
            $m->setEnabled(true);
            $m->setIncludeAPIs(true);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret, $sp);
        }

        {
            $rp = new RetentionPolicy();
            $rp->setEnabled(true);
            // Days has to be 0 < days <= 365
            $rp->setDays(364);

            $l = new Logging();
            $l->setRetentionPolicy($rp);
            // Note: looks like only v1.0 is available now.
            // http://msdn.microsoft.com/en-us/library/windowsazure/hh360996.aspx
            $l->setVersion('1.0');
            $l->setDelete(false);
            $l->setRead(false);
            $l->setWrite(false);

            $m = new Metrics();
            $m->setVersion('1.0');
            $m->setEnabled(false);
            $m->setIncludeAPIs(null);
            $m->setRetentionPolicy($rp);

            $sp = new ServiceProperties();
            $sp->setLogging($l);
            $sp->setMetrics($m);

            array_push($ret, $sp);
        }

        return $ret;
    }

    static function getInterestingQueryTablesOptions($isEmulated)
    {
        $ret = array();


        $options = new QueryTablesOptions();
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $options->setTop(2);
        $options->setPrefix(self::$nonExistTablePrefix);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $options->setTop(-2);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyEq(Filter::applyConstant(self::$testTableNames[1]), Filter::applyPropertyName('TableName'));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyEq(Filter::applyConstant(self::$testTableNames[2]), Filter::applyPropertyName('TableName'));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyAnd(
                Filter::applyEq(Filter::applyConstant(self::$testTableNames[1]), Filter::applyPropertyName('TableName')),
                Filter::applyEq(Filter::applyConstant(self::$testTableNames[2]), Filter::applyPropertyName('TableName')));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyAnd(
                Filter::applyGe(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[1])),
                Filter::applyLe(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[2])));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyOr(
                Filter::applyGe(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[1])),
                Filter::applyGe(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[2])));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyAnd(
                Filter::applyEq(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[1])),
                Filter::applyGe(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[0])));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyOr(
                Filter::applyEq(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[1])),
                Filter::applyGe(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[2])));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyOr(
                Filter::applyEq(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[1])),
                Filter::applyEq(Filter::applyPropertyName('TableName'), Filter::applyConstant(self::$testTableNames[2])));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $filter = Filter::applyOr(
                Filter::applyEq(Filter::applyConstant(self::$testTableNames[1]), Filter::applyPropertyName('TableName')),
                Filter::applyEq(Filter::applyConstant(self::$testTableNames[2]), Filter::applyPropertyName('TableName')));
        $options->setFilter($filter);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $options->setPrefix(self::$nonExistTablePrefix);
        array_push($ret, $options);

        if (!$isEmulated) {
            $options = new QueryTablesOptions();
            $options->setPrefix(self::$testUniqueId);
            array_push($ret, $options);
        }

        $options = new QueryTablesOptions();
        $nextTableName = self::$testTableNames[1];
        $options->setNextTableName($nextTableName);
        array_push($ret, $options);

        $options = new QueryTablesOptions();
        $nextTableName = self::$nonExistTablePrefix;
        $options->setNextTableName($nextTableName);
        array_push($ret, $options);


        return $ret;
    }

    static function getSimpleinsertEntityOptions()
    {
        return new TableServiceOptions();
    }

    static function getSimpleEntity()
    {
        $entity = new Entity();
        $entity->setPartitionKey(self::getNewKey());
        $entity->setRowKey(self::getNewKey());
        return $entity;
    }

    static function getInterestingEntities()
    {
        $ret = array();

        array_push($ret, self::getSimpleEntity());

        $e = new Entity();
        $e->addProperty('RowKey', EdmType::STRING, self::getNewKey());
        $e->addProperty('PartitionKey', null, self::getNewKey());
        array_push($ret, $e);

        $e = new Entity();
        $e->setPartitionKey(self::getNewKey());
        $e->setRowKey(self::getNewKey());
        $e->addProperty('BINARY', EdmType::BINARY, chr(0) . chr(1) . chr(2) . chr(3) . chr(4));
        $e->addProperty('BOOLEAN', EdmType::BOOLEAN, true);
        $e->addProperty('DATETIME', EdmType::DATETIME, Utilities::convertToDateTime('2012-01-26T18:26:19.0000473Z'));
        $e->addProperty('DOUBLE', EdmType::DOUBLE, 12345678901);
        $e->addProperty('GUID', EdmType::GUID, '90ab64d6-d3f8-49ec-b837-b8b5b6367b74');
        $e->addProperty('INT32', EdmType::INT32, 23);
        $e->addProperty('INT64', EdmType::INT64, '-1');
        $now = new \DateTime();
        $e->addProperty('STRING', EdmType::STRING, $now->format(\DateTime::COOKIE));
        array_push($ret, $e);

        $e = new Entity();
        $e->setPartitionKey(self::getNewKey());
        $e->setRowKey(self::getNewKey());
        $e->addProperty('test', EdmType::BOOLEAN, true);
        $e->addProperty('test2', EdmType::STRING, 'value');
        $e->addProperty('test3', EdmType::INT32, 3);
        $e->addProperty('test4', EdmType::INT64, '12345678901');
        $e->addProperty('test5', EdmType::DATETIME, new \DateTime());
        array_push($ret, $e);

        $e = new Entity();
        $e->setPartitionKey(self::getNewKey());+


        $e->setRowKey(self::getNewKey());
        $e->addProperty('BINARY', EdmType::BINARY, null);
        $e->addProperty('BOOLEAN', EdmType::BOOLEAN, null);
        $e->addProperty('DATETIME', EdmType::DATETIME, null);
        $e->addProperty('DOUBLE', EdmType::DOUBLE, null);
        $e->addProperty('GUID', EdmType::GUID, null);
        $e->addProperty('INT32', EdmType::INT32, null);
        $e->addProperty('INT64', EdmType::INT64, null);
        $e->addProperty('STRING', EdmType::STRING, null);
        array_push($ret, $e);

        return $ret;
    }

    static function getInterestingBadEntities()
    {
        $ret = array();

        $e = new Entity();
        array_push($ret, $e);

        $e = new Entity();
        $e->setRowKey(self::getNewKey());
        array_push($ret, $e);

        $e = new Entity();
        $e->setPartitionKey(self::getNewKey());
        array_push($ret, $e);

        return $ret;
    }

    static function getSimpleEntities($count)
    {
        $ret = array();

        $e = new Entity();
        $e->setPartitionKey('singlePartition');
        $e->setRowKey(self::getNewKey());
        $e->addProperty('INT32', EdmType::INT32, 23);
        array_push($ret, $e);

        $booleans = self::getInterestingGoodBooleans();
        $dates = self::getInterestingGoodDates();
        $doubles = self::getInterestingGoodDoubles();
        $guids = self::getInterestingGoodGuids();
        $ints = self::getInterestingGoodInts();
        $longs = self::getInterestingGoodLongs();
        $binaries = self::getInterestingGoodBinaries();
        $strings = self::getInterestingGoodStrings();

        // The random here is not to generate random values, but to
        // get a good mix of values in the table entities.
        mt_srand(123);
        for ($i = 0; $i < $count - 1; $i++) {
            $e = new Entity();
            $e->setPartitionKey('singlePartition');
            $e->setRowKey(self::getNewKey());
            self::addProperty($e, 'BINARY', EdmType::BINARY, $binaries);
            self::addProperty($e, 'BOOLEAN', EdmType::BOOLEAN, $booleans);
            self::addProperty($e, 'DATETIME', EdmType::DATETIME, $dates);
            self::addProperty($e, 'DOUBLE', EdmType::DOUBLE, $doubles);
            self::addProperty($e, 'GUID', EdmType::GUID, $guids);
            self::addProperty($e, 'INT32', EdmType::INT32, $ints);
            self::addProperty($e, 'INT64', EdmType::INT64, $longs);
            self::addProperty($e, 'STRING', EdmType::STRING, $strings);
            array_push($ret, $e);
        }

        return $ret;
    }

    static function addProperty($e, $name, $edmType, $binaries)
    {
        $index = mt_rand(0, count($binaries));
        if ($index < count($binaries)) {
            $e->addProperty($name, $edmType, $binaries[$index]);
        }
    }

    static function getInterestingGoodBooleans()
    {
        $ret = array();
        array_push($ret, true);
        array_push($ret, false);
//        array_push($ret, 'TRUE');
//        array_push($ret, 1);
        return $ret;
    }

    static function getInterestingBadBooleans()
    {
        $ret = array();
        array_push($ret, 'BOO!');
        return $ret;
    }

    static function getInterestingGoodDates()
    {
        $ret = array();

        array_push($ret, new \DateTime());

        $c = new \DateTime;
        $c->setDate(2010, 2, 3);
        $c->setTime(20, 3, 4);
        array_push($ret, $c);

        $c = new \DateTime;
        $c->setDate(2012, 1, 27);
        $c->setTime(21, 46, 59);
        array_push($ret, $c);

        $c = new \DateTime('27 Jan 2012 22:00:00.800 GMT');
        array_push($ret, $c);

        return $ret;
    }

    static function getInterestingBadDates()
    {
        $ret = array();
        array_push($ret, true);
        array_push($ret, 0);
        return $ret;
    }

    static function getInterestingGoodDoubles()
    {
        $ret = array();
        array_push($ret, pi());
        array_push($ret, 0.0);
        array_push($ret, floatval(self::INT_MAX_VALUE));
        array_push($ret, floatval(self::LONG_BIG_VALUE));
        array_push($ret, 2.3456);
        array_push($ret, 1.0e-10);
        return $ret;
    }

    static function getInterestingBadDoubles()
    {
        $ret = array();
        array_push($ret, 'ABCDEFGH-D3F8-49EC-B837-B8B5B6367B74');
        return $ret;
    }

    static function getInterestingGoodGuids()
    {
        $ret = array();
        array_push($ret, '90ab64d6-d3f8-49ec-b837-b8b5b6367b74');
        array_push($ret, '00000000-0000-0000-0000-000000000000');
        return $ret;
    }

    static function getInterestingBadGuids()
    {
        $ret = array();
        array_push($ret, 'ABCDEFGH-D3F8-49EC-B837-B8B5B6367B74');
        array_push($ret, '');
        return $ret;
    }

    static function getInterestingGoodInts()
    {
        $ret = array();
        array_push($ret, 0);
        array_push($ret, self::INT_MAX_VALUE);
        array_push($ret, self::$INT_MIN_VALUE);
        array_push($ret, 35536);
        return $ret;
    }

    static function getInterestingBadInts()
    {
        $ret = array();
        array_push($ret, false);
        array_push($ret, self::INT_MAX_VALUE + 1);
        return $ret;
    }

    static function getInterestingGoodLongs()
    {
        $ret = array();
        array_push($ret, '0');
        array_push($ret, strval(self::LONG_BIG_VALUE));
        array_push($ret, strval(self::LONG_BIG_VALUE_NEGATIVE));
        array_push($ret, '35536');
        return $ret;
    }

    static function getInterestingBadLongs()
    {
        $ret = array();
        array_push($ret, false);
        array_push($ret, '9223372036854775808');
        return $ret;
    }

    static function getInterestingGoodBinaries()
    {
        $ret = array();
        array_push($ret, '');
        array_push($ret, chr(1) . chr(2) . chr(3) . chr(4) . chr(5));
        array_push($ret, chr(255) . chr(254) . chr(253));
        return $ret;
    }

    static function getInterestingBadBinaries()
    {
        $ret = array();
        array_push($ret, 12345);
        array_push($ret, new \DateTime());
        return $ret;
    }

    static function getInterestingGoodStrings()
    {
        $ret = array();
        array_push($ret, 'AQIDBAU='); // Base-64 encoded byte array { 0x01, 0x02, 0x03, 0x04, 0x05 };
        array_push($ret, 'false');
        array_push($ret, '12345');
        array_push($ret, '\\' . '\\' . '\'' . '(?++\\.&==/&?\'\'$@://   .ne');
        array_push($ret, '12345');
        array_push($ret, 'Some unicode: ' . self::getUnicodeString());
        array_push($ret, strval(self::INT_MAX_VALUE));
        array_push($ret, '<some><XML></stuff>');
        array_push($ret, "\t\tSomething you entered\n\n\ttranscended parameters\r\n\r\n\t\tSo much is unknown\r\r");
        return $ret;
    }

    static function getInterestingBadStrings()
    {
        $ret = array();
        // Are there any?
        return $ret;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Tests\Functional\Table\Enums\MutatePivot;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\Property;
use MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\Filters\PropertyNameFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\QueryStringFilter;
use MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter;

class TableServiceFunctionalTestUtils
{
    static function isEqNotInTopLevel($filter)
    {
        return self::isEqNotInTopLevelWorker($filter, 0);
    }

    private static function isEqNotInTopLevelWorker($filter, $depth)
    {
        if (is_null($filter)) {
            return false;
        } else if ($filter instanceof UnaryFilter) {
            return self::isEqNotInTopLevelWorker($filter->getOperand(), $depth + 1);
        } else if ($filter instanceof BinaryFilter) {
            $binaryFilter = $filter;
            if ($binaryFilter->getOperator() == ('eq') && $depth != 0) {
                return true;
            }

            $left = self::isEqNotInTopLevelWorker($binaryFilter->getLeft(), $depth + 1);
            $right = self::isEqNotInTopLevelWorker($binaryFilter->getRight(), $depth + 1);
            return $left || $right;
        } else {
            return false;
        }
    }

    static function cloneRemoveEqNotInTopLevel($filter)
    {
        return self::cloneRemoveEqNotInTopLevelWorker($filter, 0);
    }

    private static function cloneRemoveEqNotInTopLevelWorker($filter, $depth)
    {
        if ($filter instanceof PropertyNameFilter) {
            $ret = new PropertyNameFilter($filter->getPropertyName());
            return $ret;
        } else if ($filter instanceof ConstantFilter) {
            $ret = new ConstantFilter($filter->getEdmType(), $filter->getValue());
            return $ret;
        } else if ($filter instanceof UnaryFilter) {
            $operand = self::cloneRemoveEqNotInTopLevelWorker($filter->getOperand(), $depth + 1);
            $ret = new UnaryFilter($filter->getOperator(), $operand);
            return $ret;
        } else if ($filter instanceof BinaryFilter) {
            if ($filter->getOperator() == ('eq') && $depth != 0) {
                return Filter::applyConstant(false);
            }
            $left = self::cloneRemoveEqNotInTopLevelWorker($filter->getLeft(), $depth + 1);
            $right = self::cloneRemoveEqNotInTopLevelWorker($filter->getRight(), $depth + 1);
            $ret = new BinaryFilter($left, $filter->getOperator(), $right);
            return $ret;
        } else if ($filter instanceof QueryStringFilter) {
            $ret = new QueryStringFilter($filter->getQueryString());
            return $ret;
        } else {
            throw new \Exception();
        }
    }

    public static function filterList($filter, $input)
    {
        $output = array();
        foreach($input as $i)  {
            if (self::filterInterperter($filter, $i)) {
                array_push($output, $i);
            }
        }
        return $output;
    }

    public static function filterEntityList($filter, $input)
    {
        $output = array();
        foreach($input as $i)  {
            $result = self::filterInterperter($filter, $i);
            if (!is_null($result) && $result) {
                array_push($output, $i);
            }
        }
        return $output;
    }

    static function cloneEntity($initialEnt)
    {
        $ret = new Entity();
        $initialProps = $initialEnt->getProperties();
        $retProps = array();
        foreach($initialProps as $propName => $initialProp)  {
            // Don't mess with the timestamp.
            if ($propName == ('Timestamp')) {
                continue;
            }

            $retProp = new Property();
            $retProp->setEdmType($initialProp->getEdmType());
            $retProp->setValue($initialProp->getValue());
            $retProps[$propName] = $retProp;
        }
        $ret->setProperties($retProps);
        $ret->setETag($initialEnt->getETag());
        return $ret;
    }

    static function mutateEntity($ent, $pivot)
    {
        if ($pivot == MutatePivot::CHANGE_VALUES) {
            self::mutateEntityChangeValues($ent);
        } else if ($pivot == MutatePivot::ADD_PROPERTY) {
            $ent->addProperty('BOOLEAN' . TableServiceFunctionalTestData::getNewKey(), EdmType::BOOLEAN, true);
            $ent->addProperty('DATETIME' . TableServiceFunctionalTestData::getNewKey(), EdmType::DATETIME, Utilities::convertToDateTime('2012-01-26T18:26:19.0000473Z'));
            $ent->addProperty('DOUBLE' . TableServiceFunctionalTestData::getNewKey(), EdmType::DOUBLE, 12345678901);
            $ent->addProperty('GUID' . TableServiceFunctionalTestData::getNewKey(), EdmType::GUID, '90ab64d6-d3f8-49ec-b837-b8b5b6367b74');
            $ent->addProperty('INT32' . TableServiceFunctionalTestData::getNewKey(), EdmType::INT32, 23);
            $ent->addProperty('INT64' . TableServiceFunctionalTestData::getNewKey(), EdmType::INT64, '-1');
            $ent->addProperty('STRING' . TableServiceFunctionalTestData::getNewKey(), EdmType::STRING, 'this is a test!');
        } else if ($pivot == MutatePivot::REMOVE_PROPERTY) {
            $propToRemove = null;
            foreach($ent->getProperties() as $propName => $propValue)  {
                // Don't mess with the keys.
                if ($propName == ('PartitionKey') || $propName == ('RowKey') || $propName == ('Timestamp')) {
                    continue;
                }
                $propToRemove = $propName;
                break;
            }

            $props = $ent->getProperties();
            unset($props[$propToRemove]);
        } else if ($pivot == MutatePivot::NULL_PROPERTY) {
            foreach($ent->getProperties() as $propName => $propValue)  {
                // Don't mess with the keys.
                if ($propName == ('PartitionKey') || $propName == ('RowKey') || $propName == ('Timestamp')) {
                    continue;
                }
                $propValue->setValue(null);
            }
        }
    }

    private static function mutateEntityChangeValues($ent)
    {
        foreach($ent->getProperties() as $propName => $initialProp)  {
            // Don't mess with the keys.
            if ($propName == ('PartitionKey') || $propName == ('RowKey') || $propName == ('Timestamp')) {
                continue;
            }

            $ptype = $initialProp->getEdmType();
            if (is_null($ptype)) {
                $eff = $initialProp->getValue();
                $initialProp->setValue($eff . 'AndMore');
            } else if ($ptype == (EdmType::DATETIME)) {
                $value = $initialProp->getValue();
                if (is_null($value)) {
                    $value = new \DateTime("1/26/1692");
                }
                $value->modify('+1 day');
                $initialProp->setValue($value);
            } else if ($ptype == (EdmType::BINARY)) {
                $eff = $initialProp->getValue();
                $initialProp->setValue($eff . 'x');
            } else if ($ptype == (EdmType::BOOLEAN)) {
                $eff = $initialProp->getValue();
                $initialProp->setValue(!$eff);
            } else if ($ptype == (EdmType::DOUBLE)) {
                $eff = $initialProp->getValue();
                $initialProp->setValue($eff + 1);
            } else if ($ptype == (EdmType::GUID)) {
                $initialProp->setValue(Utilities::getGuid());
            } else if ($ptype == (EdmType::INT32)) {
                $eff = $initialProp->getValue();
                $eff = ($eff > 10 ? 0 : $eff + 1);
                $initialProp->setValue($eff);
            } else if ($ptype == (EdmType::INT64)) {
                $eff = $initialProp->getValue();
                $eff = ($eff > 10 ? 0 : $eff + 1);
                $initialProp->setValue(strval($eff));
            } else if ($ptype == (EdmType::STRING)) {
                $eff = $initialProp->getValue();
                $initialProp->setValue($eff . 'AndMore');
            }
        }
    }

    public static function filterToString($filter, $pad = '  ')
    {
        if (is_null($filter)) {
            return $pad . 'filter <null>' . "\n";
        } else if ($filter instanceof PropertyNameFilter) {
            return $pad . 'entity.' . $filter->getPropertyName() . "\n";
        } else if ($filter instanceof ConstantFilter) {
           $ret = $pad;
           if (is_null($filter->getValue())) {
               $ret .= 'constant <null>';
           } else if (is_bool ($filter->getValue())) {
               $ret .= ($filter->getValue() ? 'true' : 'false');
           }  else {
               $ret .=  '\'' . FunctionalTestBase::tmptostring($filter->getValue()) . '\'';
           }
           return $ret . "\n";
        } else if ($filter instanceof UnaryFilter) {
            $ret = $pad . $filter->getOperator() . "\n";
            $ret .= self::filterToString($filter->getOperand(), $pad . '  ');
            return $ret;
        } else if ($filter instanceof BinaryFilter) {
            $ret = self::filterToString($filter->getLeft(), $pad . '  ');
            $ret .= $pad . $filter->getOperator() . "\n";
            $ret .= self::filterToString($filter->getRight(), $pad . '  ');
            return $ret;
        }
    }

    private static function filterInterperter($filter, $obj)
    {
        if (is_null($filter)) {
            return true;
        } else if (is_null($obj)) {
            return false;
        } else if ($filter instanceof PropertyNameFilter) {
            $name = $filter->getPropertyName();
            $value = ($obj instanceof Entity ? $obj->getPropertyValue($name) : $obj->{$name});
            return $value;
        } else if ($filter instanceof ConstantFilter) {
            $value = $filter->getValue();
            return $value;
        } else if ($filter instanceof UnaryFilter) {
            $ret = null;
            if ($filter->getOperator() == ('not')) {
                $op = self::filterInterperter($filter->getOperand(), $obj);
                if (is_null($op)) {
                    // http://msdn.microsoft/com/en-us/library/ms191504.aspx
                    // Not (null) -> null
                    $ret = null;
                } else {
                    $ret = !$op;
                }

                return $ret;
            }
        } else if ($filter instanceof BinaryFilter) {
            $left = self::filterInterperter($filter->getLeft(), $obj);
            $right = self::filterInterperter($filter->getRight(), $obj);
            
            $ret = null;
            if ($filter->getOperator() == ('and')) {
                $ret = self::nullPropAnd($left, $right);
            } else if ($filter->getOperator() == ('or')) {
                $ret = self::nullPropOr($left, $right);
            } else if ($filter->getOperator() == ('eq')) {
                $ret = self::nullPropEq($left, $right);
            } else if ($filter->getOperator() == ('ne')) {
                $ret = self::nullPropNe($left, $right);
            } else if ($filter->getOperator() == ('ge')) {
                $ret = self::nullPropGe($left, $right);
            } else if ($filter->getOperator() == ('gt')) {
                $ret = self::nullPropGt($left, $right);
            } else if ($filter->getOperator() == ('lt')) {
                $ret = self::nullPropLt($left, $right);
            } else if ($filter->getOperator() == ('le')) {
                $ret = self::nullPropLe($left, $right);
            }

            return $ret;
        }

        throw new \Exception();
    }

    private static function nullPropAnd($left, $right)
    {
        // http://msdn.microsoft.com/en-us/library/ms191504.aspx
        if (is_null($left) && is_null($right)) {
            return null;
        } else if (is_null($left)) {
            return ($right ? null : false);
        } else if (is_null($right)) {
            return ($left ? null : false);
        } else {
            return $left && $right;
        }
    }

    private static function nullPropOr($left, $right)
    {
        // http://msdn.microsoft.com/en-us/library/ms191504.aspx
        if (is_null($left) && is_null($right)) {
            return null;
        } else if (is_null($left)) {
            return ($right ? true : null);
        } else if (is_null($right)) {
            return ($left ? true : null);
        } else {
            return $left || $right;
        }
    }

    private static function nullPropEq($left, $right)
    {
        if (is_null($left) || is_null($right)) {
            return null;
        } else if (is_string($left) || is_string($right)) {
            return ('' . $left) == ('' . $right);
        }
        return $left == $right;
    }

    private static function nullPropNe($left, $right)
    {
        if (is_null($left) || is_null($right)) {
            return null;
        } else if (is_string($left) || is_string($right)) {
            return ('' . $left) != ('' . $right);
        }
        return $left != $right;
    }

    private static function nullPropGt($left, $right)
    {
        if (is_null($left) || is_null($right)) {
            return null;
        } else if (is_string($left) || is_string($right)) {
            return ('' . $left) > ('' . $right);
        }
        return $left > $right;
    }

    private static function nullPropGe($left, $right)
    {
        if (is_null($left) || is_null($right)) {
            return null;
        } else if (is_string($left) || is_string($right)) {
            return ('' . $left) >= ('' . $right);
        }
        return $left >= $right;
    }

    private static function nullPropLt($left, $right)
    {
        if (is_null($left) || is_null($right)) {
            return null;
        } else if (is_string($left) || is_string($right)) {
            return ('' . $left) < ('' . $right);
        }
        return $left < $right;
    }

    private static function nullPropLe($left, $right)
    {
        if (is_null($left) || is_null($right)) {
            return null;
        } else if (is_string($left) || is_string($right)) {
            return ('' . $left) <= ('' . $right);
        }
        return $left <= $right;
    }

    public static function showEntityListDiff($actualData, $expectedData)
    {
        $ret = '';
        if (count($expectedData) != count($actualData)) {
            $ret .= 'VVV actual VVV' . "\n";
            for ($i = 0; $i < count($actualData); $i++) {
                $e = $actualData[$i];
                $ret .= $e->getPartitionKey() . '/' . $e->getRowKey() . "\n";
            }
            $ret .= '-----------------' . "\n";

            for ($i = 0; $i < count($expectedData); $i++) {
                $e = $expectedData[$i];
                $ret .= $e->getPartitionKey() . '/' . $e->getRowKey() . "\n";
            }
            $ret .= '^^^ expected ^^^' . "\n";

            for ($i = 0; $i < count($actualData); $i++) {
                $in = false;
                $ei = $actualData[$i];
                for ($j = 0; $j < count($expectedData); $j++) {
                    $ej = $expectedData[$j];
                    if ($ei->getPartitionKey() == $ej->getPartitionKey() && $ei->getRowKey() == $ej->getRowKey()) {
                        $in = true;
                    }
                }
                if (!$in) {
                    $ret .= 'returned ' . $this->tmptostring($ei). "\n";
                }
            }

            for ($j = 0; $j < count($expectedData); $j++) {
                $in = false;
                $ej = $expectedData[$j];
                for ($i = 0; $i < count($actualData); $i++) {
                    $ei = $actualData[$i];
                    if ($ei->getPartitionKey() == $ej->getPartitionKey() && $ei->getRowKey() == $ej->getRowKey()) {
                        $in = true;
                    }
                }
                if (!$in) {
                    $ret .= 'expected ' . $this->tmptostring($ej). "\n";
                }
            }
        }
    return $ret;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Functional\Table
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Functional\Table;

use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Table\Models\BatchError;
use MicrosoftAzure\Storage\Table\Models\BatchOperations;
use MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\InsertEntityResult;
use MicrosoftAzure\Storage\Table\Models\Query;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions;
use MicrosoftAzure\Storage\Table\Models\UpdateEntityResult;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;

class TableServiceIntegrationTest extends IntegrationTestBase
{
    private static $testTablesPrefix = 'sdktest';
    private static $createableTablesPrefix = 'csdktest';
    private static $testTable1;
    private static $testTable2;
    private static $testTable3;
    private static $testTable4;
    private static $testTable5;
    private static $testTable6;
    private static $testTable7;
    private static $testTable8;
    private static $createTable1;
    private static $createTable2;
    private static $creatableTables;
    private static $testTables;

    private static $isOneTimeSetup = false;

    public function setUp()
    {
        parent::setUp();
        if (!self::$isOneTimeSetup) {
            $this->doOneTimeSetup();
            self::$isOneTimeSetup = true;
        }
    }

    private function doOneTimeSetup()
    {
        self::$testTablesPrefix .= rand(0,1000);
        // Setup container names array (list of container names used by
        // integration tests)
        self::$testTables = array();
        for ($i = 0; $i < 10; $i++) {
            self::$testTables[$i] = self::$testTablesPrefix . ($i + 1);
        }

        self::$creatableTables = array();
        for ($i = 0; $i < 10; $i++) {
            self::$creatableTables[$i] = self::$createableTablesPrefix . ($i + 1);
        }

        self::$testTable1 = self::$testTables[0];
        self::$testTable2 = self::$testTables[1];
        self::$testTable3 = self::$testTables[2];
        self::$testTable4 = self::$testTables[3];
        self::$testTable5 = self::$testTables[4];
        self::$testTable6 = self::$testTables[5];
        self::$testTable7 = self::$testTables[6];
        self::$testTable8 = self::$testTables[7];

        self::$createTable1 = self::$creatableTables[0];
        self::$createTable2 = self::$creatableTables[1];

        // Create all test containers and their content
        $this->deleteAllTables(self::$testTables);
        $this->deleteAllTables(self::$creatableTables);
        $this->createTables(self::$testTablesPrefix, self::$testTables);
    }

    public static function tearDownAfterClass()
    {
        if (self::$isOneTimeSetup) {
            $tmp = new TableServiceIntegrationTest();
            $tmp->setUp();
            $tmp->deleteAllTables(self::$testTables);
            $tmp->deleteTables(self::$createableTablesPrefix, self::$creatableTables);
            self::$isOneTimeSetup = false;
        }
        parent::tearDownAfterClass();
    }
    
    protected function tearDown()
    {
        // tearDown of parent will delete the container created in setUp
        // Do nothing here
    }

    private function createTables($prefix, $list)
    {
        $containers = $this->listTables($prefix);
        foreach($list as $item)  {
            if (!in_array($item, $containers)) {
                $this->createTable($item);
            }
        }
    }

    private function deleteTables($prefix, $list)
    {
        $containers = $this->listTables($prefix);
        foreach($list as $item)  {
            if (in_array($item, $containers)) {
                $this->safeDeleteTable($item);
            }
        }
    }

    private function deleteAllTables($list)
    {
        foreach($list as $item)  {
            $this->safeDeleteTable($item);
        }
    }

    private function listTables($prefix)
    {
        $result = array();
        $qto = new QueryTablesOptions();
        $qto->setPrefix($prefix);
        $list = $this->restProxy->queryTables($qto);
        foreach($list->getTables() as $item)  {
            array_push($result, $item);
        }
        return $result;
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    */
    public function testGetServicePropertiesWorks()
    {
        // Arrange

        // Act
        $shouldReturn = false;
        try {
            $props = $this->restProxy->getServiceProperties()->getValue();
            $this->assertTrue(!$this->isEmulated(), 'Should succeed if and only if not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        // Assert
        $this->assertNotNull($props, '$props');
        $this->assertNotNull($props->getLogging(), '$props->getLogging');
        $this->assertNotNull($props->getLogging()->getRetentionPolicy(), '$props->getLogging()->getRetentionPolicy');
        $this->assertNotNull($props->getLogging()->getVersion(), '$props->getLogging()->getVersion');
        $this->assertNotNull($props->getMetrics()->getRetentionPolicy(), '$props->getMetrics()->getRetentionPolicy');
        $this->assertNotNull($props->getMetrics()->getVersion(), '$props->getMetrics()->getVersion');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
    */
    public function testSetServicePropertiesWorks()
    {
        // Arrange

        // Act
        $shouldReturn = false;
        try {
            $props = $this->restProxy->getServiceProperties()->getValue();
            $this->assertTrue(!$this->isEmulated(), 'Should succeed if and only if not running in emulator');
        } catch (ServiceException $e) {
            // Expect failure in emulator, as v1.6 doesn't support this method
            if ($this->isEmulated()) {
                $this->assertEquals(TestResources::STATUS_BAD_REQUEST, $e->getCode(), 'getCode');
                $shouldReturn = true;
            } else {
                throw $e;
            }
        }
        if($shouldReturn) {
            return;
        }

        $props->getLogging()->setRead(true);
        $this->restProxy->setServiceProperties($props);

        $props = $this->restProxy->getServiceProperties()->getValue();

        // Assert
        $this->assertNotNull($props, '$props');
        $this->assertNotNull($props->getLogging(), '$props->getLogging');
        $this->assertNotNull($props->getLogging()->getRetentionPolicy(), '$props->getLogging()->getRetentionPolicy');
        $this->assertNotNull($props->getLogging()->getVersion(), '$props->getLogging()->getVersion');
        $this->assertTrue($props->getLogging()->getRead(), '$props->getLogging()->getRead');
        $this->assertNotNull($props->getMetrics()->getRetentionPolicy(), '$props->getMetrics()->getRetentionPolicy');
        $this->assertNotNull($props->getMetrics()->getVersion(), '$props->getMetrics()->getVersion');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable
    */
    public function testCreateTablesWorks()
    {
        // Act
        try {
            $this->restProxy->getTable(self::$createTable1);
            $error = null;
        } catch (ServiceException $e) {
            $error = $e;
        }
        $this->restProxy->createTable(self::$createTable1);
        $result = $this->restProxy->getTable(self::$createTable1);

        // Assert
        $this->assertNotNull($error, '$error');
        $this->assertNotNull($result, '$result');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable
    */
    public function testDeleteTablesWorks()
    {
        // Act
        $this->restProxy->createTable(self::$createTable2);
        $result = $this->restProxy->getTable(self::$createTable2);

        $this->restProxy->deleteTable(self::$createTable2);
        try {
            $this->restProxy->getTable(self::$createTable2);
            $error = null;
        } catch (ServiceException $e) {
            $error = $e;
        }

        // Assert
        $this->assertNotNull($error, '$error');
        $this->assertNotNull($result, '$result');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testQueryTablesWorks()
    {
        // Act
        $result = $this->restProxy->queryTables();

        // Assert
        $this->assertNotNull($result, '$result');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
    */
    public function testQueryTablesWithPrefixWorks()
    {
        // Act
        $qto = new QueryTablesOptions();
        $qto->setPrefix(self::$testTablesPrefix);
        $result = $this->restProxy->queryTables($qto);

        // Assert
        $this->assertNotNull($result, '$result');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable
    */
    public function testGetTableWorks()
    {
        // Act
        $result = $this->restProxy->getTable(self::$testTable1);

        // Assert
        $this->assertNotNull($result, '$result');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testInsertEntityWorks()
    {
        // Arrange
        $binaryData = chr(1) . chr(2) . chr(3) . chr(4);
        $uuid = Utilities::getGuid();
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('insertEntityWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());
        $entity->addProperty('test6', EdmType::BINARY, $binaryData);
        $entity->addProperty('test7', EdmType::GUID, $uuid);

        // Act
        $result = $this->restProxy->insertEntity(self::$testTable2, $entity);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getEntity(), '$result->getEntity()');

        $this->assertEquals('001', $result->getEntity()->getPartitionKey(), '$result->getEntity()->getPartitionKey()');
        $this->assertEquals('insertEntityWorks', $result->getEntity()->getRowKey(), '$result->getEntity()->getRowKey()');
        $this->assertNotNull($result->getEntity()->getTimestamp(), '$result->getEntity()->getTimestamp()');
        $this->assertNotNull($result->getEntity()->getETag(), '$result->getEntity()->getETag()');

        $this->assertNotNull($result->getEntity()->getProperty('test'), '$result->getEntity()->getProperty(\'test\')');
        $this->assertEquals(true, $result->getEntity()->getProperty('test')->getValue(), '$result->getEntity()->getProperty(\'test\')->getValue()');

        $this->assertNotNull($result->getEntity()->getProperty('test2'), '$result->getEntity()->getProperty(\'test2\')');
        $this->assertEquals('value', $result->getEntity()->getProperty('test2')->getValue(), '$result->getEntity()->getProperty(\'test2\')->getValue()');

        $this->assertNotNull($result->getEntity()->getProperty('test3'), '$result->getEntity()->getProperty(\'test3\')');
        $this->assertEquals(3, $result->getEntity()->getProperty('test3')->getValue(), '$result->getEntity()->getProperty(\'test3\')->getValue()');

        $this->assertNotNull($result->getEntity()->getProperty('test4'), '$result->getEntity()->getProperty(\'test4\')');
        $this->assertEquals('12345678901', $result->getEntity()->getProperty('test4')->getValue(), '$result->getEntity()->getProperty(\'test4\')->getValue()');

        $this->assertNotNull($result->getEntity()->getProperty('test5'), '$result->getEntity()->getProperty(\'test5\')');
        $this->assertTrue($result->getEntity()->getProperty('test5')->getValue() instanceof \DateTime, '$result->getEntity()->getProperty(\'test5\')->getValue() instanceof \DateTime');

        $this->assertNotNull($result->getEntity()->getProperty('test6'), '$result->getEntity()->getProperty(\'test6\')');
        $returnedBinaryData = $result->getEntity()->getProperty('test6')->getValue();
        $this->assertTrue(is_string($returnedBinaryData), 'binary data is string');
        $this->assertEquals(strlen($binaryData), strlen($returnedBinaryData), 'binary data lengths are the same');
        $this->assertEquals($binaryData, $returnedBinaryData, 'binary data are the same');

        $this->assertNotNull($result->getEntity()->getProperty('test7'), '$result->getEntity()->getProperty(\'test7\')');
        $this->assertTrue(is_string($result->getEntity()->getProperty('test7')->getValue()), 'is_string($result->getEntity()->getProperty(\'test7\')->getValue())');
        $this->assertEquals($uuid, $result->getEntity()->getPropertyValue('test7'), 'GUIDs are the same');
    }
    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testUpdateEntityWorks()
    {
        // Arrange
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('updateEntityWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        // Act
        $result = $this->restProxy->insertEntity(self::$testTable2, $entity);
        $result->getEntity()->addProperty('test4', EdmType::INT32, 5);
        $this->restProxy->updateEntity(self::$testTable2, $result->getEntity());

        // Assert
        $this->assertTrue(true, 'Expect success in testUpdateEntityWorks');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
    */
    public function testInsertOrReplaceEntityWorks()
    {
        // Arrange
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('insertOrReplaceEntityWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        // Act
        if($this->isEmulated()) {
            try {
                $this->restProxy->insertOrReplaceEntity(self::$testTable2, $entity);
                $this->assertFalse($this->isEmulated(), 'Expect failure when in emulator');
            } catch (ServiceException $e) {
                $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'e->getCode');
            }
        } else {
            $this->restProxy->insertOrReplaceEntity(self::$testTable2, $entity);
            $entity->addProperty('test4', EdmType::INT32, 5);
            $entity->addProperty('test6', EdmType::INT32, 6);
            $this->restProxy->insertOrReplaceEntity(self::$testTable2, $entity);

            // Assert
            $this->assertTrue(true, 'Expect success in testInsertOrReplaceEntityWorks');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrMergeEntity
    */
    public function testInsertOrMergeEntityWorks()
    {
        // Arrange
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('insertOrMergeEntityWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        // Act
        if($this->isEmulated()) {
            try {
                $this->restProxy->insertOrMergeEntity(self::$testTable2, $entity);
                $this->assertFalse($this->isEmulated(), 'Expect failure when in emulator');
            } catch (ServiceException $e) {
                $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'e->getCode');
            }
        } else {
            $this->restProxy->insertOrMergeEntity(self::$testTable2, $entity);
            $entity->addProperty('test4', EdmType::INT32, 5);
            $entity->addProperty('test6', EdmType::INT32, 6);
            $this->restProxy->insertOrMergeEntity(self::$testTable2, $entity);

            // Assert
            $this->assertTrue(true, 'Expect success in testInsertOrMergeEntityWorks');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
    */
    public function testMergeEntityWorks()
    {
        // Arrange
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('mergeEntityWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        // Act
        $result = $this->restProxy->insertEntity(self::$testTable2, $entity);

        $result->getEntity()->addProperty('test4', EdmType::INT32, 5);
        $result->getEntity()->addProperty('test6', EdmType::INT32, 6);
        $this->restProxy->mergeEntity(self::$testTable2, $result->getEntity());

        // Assert
        $this->assertTrue(true, 'expect no errors');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testDeleteEntityWorks()
    {
        // Arrange
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('deleteEntityWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        // Act
        $result = $this->restProxy->insertEntity(self::$testTable2, $entity);

        $this->restProxy->deleteEntity(self::$testTable2, $result->getEntity()->getPartitionKey(), $result->getEntity()->getRowKey());

        // Assert
        $this->assertTrue(true, 'expect no errors');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testDeleteEntityTroublesomeKeyWorks()
    {
        // The service does not allow the following common characters in keys:
        // * chr(35) = '#'
        // * chr(47) = '/'
        // * chr(63) = '?'
        // * chr(92) = '\'
        //
        // In addition, the following values are not allowed, as they make the URL bad:
        // * 0-31, 127-159
        // That still leaves several options for making troublesome keys
        // * spaces
        // * single quotes
        // * Unicode
        // These need to be properly encoded when passed on the URL, else there will be trouble

        // Arrange
        $entity1 = new Entity();
        $entity1->setPartitionKey('001');
        $entity1->setRowKey('key with spaces');
        $entity2 = new Entity();
        $entity2->setPartitionKey('001');
        $entity2->setRowKey('key\'with\'quotes');
        $entity3 = new Entity();
        $entity3->setPartitionKey('001');
        $entity3->setRowKey('keyWithUnicode' . chr(0xEB) . chr(0x8B) . chr(0xA4)); // \uB2E4 in UTF8
        $entity4 = new Entity();
        $entity4->setPartitionKey('001');
        $entity4->setRowKey('key \'with\'\'' . chr(0xEB) . chr(0x8B) . chr(0xA4)); // \uB2E4 in UTF8
        $entity5 = new Entity();
        $entity5->setPartitionKey('001');
        $entity5->setRowKey('Qbert_Says=.!@%^&');

        // Act
        $result1 = $this->restProxy->insertEntity(self::$testTable8, $entity1);
        $result2 = $this->restProxy->insertEntity(self::$testTable8, $entity2);
        $result3 = $this->restProxy->insertEntity(self::$testTable8, $entity3);
        $result4 = $this->restProxy->insertEntity(self::$testTable8, $entity4);
        $result5 = $this->restProxy->insertEntity(self::$testTable8, $entity5);

        $this->restProxy->deleteEntity(self::$testTable8, $result1->getEntity()->getPartitionKey(), $result1->getEntity()->getRowKey());
        $this->restProxy->deleteEntity(self::$testTable8, $result2->getEntity()->getPartitionKey(), $result2->getEntity()->getRowKey());
        $this->restProxy->deleteEntity(self::$testTable8, $result3->getEntity()->getPartitionKey(), $result3->getEntity()->getRowKey());
        $this->restProxy->deleteEntity(self::$testTable8, $result4->getEntity()->getPartitionKey(), $result4->getEntity()->getRowKey());
        $this->restProxy->deleteEntity(self::$testTable8, $result5->getEntity()->getPartitionKey(), $result5->getEntity()->getRowKey());

        // Assert
        try {
            $this->restProxy->getEntity(self::$testTable8, $result1->getEntity()->getPartitionKey(), $result1->getEntity()->getRowKey());
            $this->fail('Expect an exception when getting an entity that does not exist');
        } catch (ServiceException $e) {
            $this->assertEquals(TestResources::STATUS_NOT_FOUND, $e->getCode(), 'getCode');
        }

        $qopts = new QueryEntitiesOptions();
        $qopts->setFilter(Filter::applyEq(Filter::applyPropertyName('RowKey'), Filter::applyConstant('key\'with\'quotes', EdmType::STRING)));
        $assertResult2 = $this->restProxy->queryEntities(self::$testTable8, $qopts);

        $this->assertEquals(0, count($assertResult2->getEntities()), 'entities returned');

        $assertResult3 = $this->restProxy->queryEntities(self::$testTable8);
        foreach($assertResult3->getEntities() as $entity)  {
            $this->assertFalse($entity3->getRowKey() == $entity->getRowKey(), 'Entity3 should be removed from the table');
            $this->assertFalse($entity4->getRowKey() == $entity->getRowKey(), 'Entity4 should be removed from the table');
            $this->assertFalse($entity5->getRowKey() == $entity->getRowKey(), 'Entity5 should be removed from the table');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testDeleteEntityWithETagWorks()
    {
        // Arrange
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('deleteEntityWithETagWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        // Act
        $result = $this->restProxy->insertEntity(self::$testTable2, $entity);

        $deo = new DeleteEntityOptions();
        $deo->setETag($result->getEntity()->getETag());
        $this->restProxy->deleteEntity(self::$testTable2, $result->getEntity()->getPartitionKey(), $result->getEntity()->getRowKey(),
                $deo);

        // Assert
        $this->assertTrue(true, 'expect no errors');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testGetEntityWorks()
    {
        // Arrange
        $binaryData = chr(1) . chr(2) . chr(3) . chr(4);
        $uuid = Utilities::getGuid();
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('getEntityWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());
        $entity->addProperty('test6', EdmType::BINARY, $binaryData);
        $entity->addProperty('test7', EdmType::GUID, $uuid);

        // Act
        $insertResult = $this->restProxy->insertEntity(self::$testTable2, $entity);
        $result = $this->restProxy->getEntity(self::$testTable2, $insertResult->getEntity()->getPartitionKey(), $insertResult->getEntity()->getRowKey());

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getEntity(), '$result->getEntity()');

        $this->assertEquals('001', $result->getEntity()->getPartitionKey(), '$result->getEntity()->getPartitionKey()');
        $this->assertEquals('getEntityWorks', $result->getEntity()->getRowKey(), '$result->getEntity()->getRowKey()');
        $this->assertNotNull($result->getEntity()->getTimestamp(), '$result->getEntity()->getTimestamp()');
        $this->assertNotNull($result->getEntity()->getETag(), '$result->getEntity()->getETag()');

        $this->assertNotNull($result->getEntity()->getProperty('test'), '$result->getEntity()->getProperty(\'test\')');
        $this->assertEquals(true, $result->getEntity()->getProperty('test')->getValue(), '$result->getEntity()->getProperty(\'test\')->getValue()');

        $this->assertNotNull($result->getEntity()->getProperty('test2'), '$result->getEntity()->getProperty(\'test2\')');
        $this->assertEquals('value', $result->getEntity()->getProperty('test2')->getValue(), '$result->getEntity()->getProperty(\'test2\')->getValue()');

        $this->assertNotNull($result->getEntity()->getProperty('test3'), '$result->getEntity()->getProperty(\'test3\')');
        $this->assertEquals(3, $result->getEntity()->getProperty('test3')->getValue(), '$result->getEntity()->getProperty(\'test3\')->getValue()');

        $this->assertNotNull($result->getEntity()->getProperty('test4'), '$result->getEntity()->getProperty(\'test4\')');
        $this->assertEquals('12345678901', $result->getEntity()->getProperty('test4')->getValue(), '$result->getEntity()->getProperty(\'test4\')->getValue()');

        $this->assertNotNull($result->getEntity()->getProperty('test5'), '$result->getEntity()->getProperty(\'test5\')');
        $this->assertTrue($result->getEntity()->getProperty('test5')->getValue() instanceof \DateTime, '$result->getEntity()->getProperty(\'test5\')->getValue() instanceof \DateTime');

        $this->assertNotNull($result->getEntity()->getProperty('test6'), '$result->getEntity()->getProperty(\'test6\')');
        $returnedBinaryData = $result->getEntity()->getProperty('test6')->getValue();
        $this->assertTrue(is_string($returnedBinaryData), 'binary data is string');
        $this->assertEquals(strlen($binaryData), strlen($returnedBinaryData), 'binary data lengths are the same');
        $this->assertEquals($binaryData, $returnedBinaryData, 'binary data are the same');

        $this->assertNotNull($result->getEntity()->getProperty('test7'), '$result->getEntity()->getProperty(\'test7\')');
        $this->assertTrue(is_string($result->getEntity()->getProperty('test7')->getValue()), 'is_string($result->getEntity()->getProperty(\'test7\')->getValue())');
        $this->assertEquals($uuid, $result->getEntity()->getPropertyValue('test7'), 'GUIDs are the same');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesWorks()
    {
        // Arrange
        $entity = new Entity();
        $entity->setPartitionKey('001');
        $entity->setRowKey('queryEntitiesWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        // Act
        $this->restProxy->insertEntity(self::$testTable3, $entity);
        $result = $this->restProxy->queryEntities(self::$testTable3);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertNotNull($result->getEntities(), '$result->getEntities()');
        $this->assertEquals(1, count($result->getEntities()), 'count($result->getEntities())');

        $entities = $result->getEntities();
        $this->assertNotNull($entities[0], '$entities[0];');

        $this->assertEquals('001', $entities[0]->getPartitionKey(), '$entities[0]->getPartitionKey()');
        $this->assertEquals('queryEntitiesWorks', $entities[0]->getRowKey(), '$entities[0]->getRowKey()');
        $this->assertNotNull($entities[0]->getTimestamp(), '$entities[0]->getTimestamp()');
        $this->assertNotNull($entities[0]->getETag(), '$entities[0]->getETag()');

        $this->assertNotNull($entities[0]->getProperty('test'), '$entities[0]->getProperty(\'test\')');
        $this->assertEquals(true, $entities[0]->getProperty('test')->getValue(), '$entities[0]->getProperty(\'test\')->getValue()');

        $this->assertNotNull($entities[0]->getProperty('test2'), '$entities[0]->getProperty(\'test2\')');
        $this->assertEquals('value', $entities[0]->getProperty('test2')->getValue(), '$entities[0]->getProperty(\'test2\')->getValue()');

        $this->assertNotNull($entities[0]->getProperty('test3'), '$entities[0]->getProperty(\'test3\')');
        $this->assertEquals(3, $entities[0]->getProperty('test3')->getValue(), '$entities[0]->getProperty(\'test3\')->getValue()');

        $this->assertNotNull($entities[0]->getProperty('test4'), '$entities[0]->getProperty(\'test4\')');
        $this->assertEquals('12345678901', $entities[0]->getProperty('test4')->getValue(), '$entities[0]->getProperty(\'test4\')->getValue()');

        $this->assertNotNull($entities[0]->getProperty('test5'), '$entities[0]->getProperty(\'test5\')');
        $this->assertTrue($entities[0]->getProperty('test5')->getValue() instanceof \DateTime, '$entities[0]->getProperty(\'test5\')->getValue() instanceof \DateTime');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesWithPaginationWorks()
    {
        // Arrange
        $table = self::$testTable4;
        $numberOfEntries = 20;
        for ($i = 0; $i < $numberOfEntries; $i++) {
            $entity = new Entity();
            $entity->setPartitionKey('001');
            $entity->setRowKey('queryEntitiesWithPaginationWorks-' . $i);
            $entity->addProperty('test', EdmType::BOOLEAN, true);
            $entity->addProperty('test2', EdmType::STRING, 'value');
            $entity->addProperty('test3', EdmType::INT32, 3);
            $entity->addProperty('test4', EdmType::INT64, '12345678901');
            $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

            $this->restProxy->insertEntity($table, $entity);
        }

        // Act
        $entryCount = 0;
        $nextPartitionKey = null;
        $nextRowKey = null;
        while (true) {
            $qeo = new QueryEntitiesOptions();
            $qeo->setNextPartitionKey($nextPartitionKey);
            $qeo->setNextRowKey($nextRowKey);
            $result = $this->restProxy->queryEntities($table, $qeo);

            $entryCount += count($result->getEntities());

            if (is_null($nextPartitionKey)) break;

            $nextPartitionKey = $result->getNextPartitionKey();
            $nextRowKey = $result->getNextRowKey();
        }

        // Assert
        $this->assertEquals($numberOfEntries, $entryCount, '$entryCount');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
    */
    public function testQueryEntitiesWithFilterWorks()
    {
        // Arrange
        $table = self::$testTable5;
        $numberOfEntries = 5;
        $entities = array();
        for ($i = 0; $i < $numberOfEntries; $i++) {
            $entity = new Entity();
            $entity->setPartitionKey('001');
            $entity->setRowKey('queryEntitiesWithFilterWorks-' . $i);
            $entity->addProperty('test', EdmType::BOOLEAN, $i % 2 == 0);
            $entity->addProperty('test2', EdmType::STRING, '\'value" ' . $i);
            $entity->addProperty('test3', EdmType::INT32, $i);
            $entity->addProperty('test4', EdmType::INT64, strval('12345678901' + $i));
            $entity->addProperty('test5', EdmType::DATETIME, new \DateTime('2012-01-0' . $i));
            $entity->addProperty('test6', EdmType::BINARY, chr($i));
            $entity->addProperty('test7', EdmType::GUID, Utilities::getGuid());
            $entities[$i] = $entity;
            $this->restProxy->insertEntity($table, $entity);
        }

        {
            // Act
            $f = Filter::applyEq(Filter::applyPropertyName('RowKey'), Filter::applyConstant('queryEntitiesWithFilterWorks-3', EdmType::STRING));
            $q =new Query();
            $q->setFilter($f);
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            $result = $this->restProxy->queryEntities($table, $qeo);

            // Assert
            $this->assertNotNull($result, '$result');
            $this->assertEquals(1, count($result->getEntities()), 'count($result->getEntities())');
            $resEnts = $result->getEntities();
            $this->assertEquals('queryEntitiesWithFilterWorks-3', $resEnts[0]->getRowKey(), '$resEnts[0]->getRowKey()');
        }

        {
            // Act
            $q = new Query();
            $q->setFilter(Filter::applyQueryString('RowKey eq \'queryEntitiesWithFilterWorks-3\''));
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            $result = $this->restProxy->queryEntities($table, $qeo);

            // Assert
            $this->assertNotNull($result, '$result');
            $this->assertEquals(1, count($result->getEntities()), 'count($result->getEntities())');
            $resEnts = $result->getEntities();
            $this->assertEquals('queryEntitiesWithFilterWorks-3', $resEnts[0]->getRowKey(), '$resEnts[0]->getRowKey()');
        }

        {
            // Act
            $q = new Query();
            $q->setFilter(Filter::applyEq(Filter::applyPropertyName('test'), Filter::applyConstant(true, EdmType::BOOLEAN)));
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            $result = $this->restProxy->queryEntities($table, $qeo);

            // Assert
            $this->assertNotNull($result, '$result');
            $this->assertEquals(3, count($result->getEntities()), 'count($result->getEntities())');
        }

        {
            // Act
            $q = new Query();
            $q->setFilter(Filter::applyEq(Filter::applyPropertyName('test2'), Filter::applyConstant('\'value" 3', EdmType::STRING)));
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            $result = $this->restProxy->queryEntities($table, $qeo);

            // Assert
            $this->assertNotNull($result, '$result');
            $this->assertEquals(1, count($result->getEntities()), 'count($result->getEntities())');
            $resEnts = $result->getEntities();
            $this->assertEquals('queryEntitiesWithFilterWorks-3', $resEnts[0]->getRowKey(), '$resEnts[0]->getRowKey()');
        }

        {
            // Act
            $q = new Query();
            $q->setFilter(Filter::applyEq(Filter::applyPropertyName('test4'), Filter::applyConstant(12345678903, EdmType::INT64)));
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            $result = $this->restProxy->queryEntities($table, $qeo);

            // Assert
            $this->assertNotNull($result, '$result');
            $this->assertEquals(1, count($result->getEntities()), 'count($result->getEntities())');
            $resEnts = $result->getEntities();
            $this->assertEquals('queryEntitiesWithFilterWorks-2', $resEnts[0]->getRowKey(), '$resEnts[0]->getRowKey()');
        }

        {
            // Act
            $q = new Query();
            $q->setFilter(Filter::applyEq(Filter::applyPropertyName('test5'), Filter::applyConstant(new \DateTime('2012-01-03'), EdmType::DATETIME)));
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            $result = $this->restProxy->queryEntities($table, $qeo);

            // Assert
            $this->assertNotNull($result, '$result');
            $this->assertEquals(1, count($result->getEntities()), 'count($result->getEntities())');
            $resEnts = $result->getEntities();
            $this->assertEquals('queryEntitiesWithFilterWorks-3', $resEnts[0]->getRowKey(), '$resEnts[0]->getRowKey()');
        }

        {
            // Act
            $q = new Query();
            $ent3 = $entities[3];
            $q->setFilter(Filter::applyEq(Filter::applyPropertyName('test6'), Filter::applyConstant(chr(3), EdmType::BINARY)));
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            $result = $this->restProxy->queryEntities($table, $qeo);

            // Assert
            $this->assertNotNull($result, '$result');
            $this->assertEquals(1, count($result->getEntities()), 'count($result->getEntities())');
            $resEnts = $result->getEntities();
            $this->assertEquals('queryEntitiesWithFilterWorks-3', $resEnts[0]->getRowKey(), '$resEnts[0]->getRowKey()');
        }

        {
            // Act
            $q = new Query();
            $ent3 = $entities[3];
            $q->setFilter(Filter::applyEq(Filter::applyPropertyName('test7'), Filter::applyConstant($ent3->getPropertyValue('test7'), EdmType::GUID)));
            $qeo = new QueryEntitiesOptions();
            $qeo->setQuery($q);
            $result = $this->restProxy->queryEntities($table, $qeo);

            // Assert
            $this->assertNotNull($result, '$result');
            $this->assertEquals(1, count($result->getEntities()), 'count($result->getEntities())');
            $resEnts = $result->getEntities();
            $this->assertEquals('queryEntitiesWithFilterWorks-3', $resEnts[0]->getRowKey(), '$resEnts[0]->getRowKey()');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    */
    public function testBatchInsertWorks()
    {
        // Arrange
        $table = self::$testTable6;
        $partitionKey = '001';

        // Act
        $entity = new Entity();
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey('batchInsertWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        $bo = new BatchOperations();
        $bo->addInsertEntity($table, $entity);
        $result = $this->restProxy->batch($bo);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(1, count($result->getEntries()), 'count($result->getEntries())');
        $ents = $result->getEntries();
        $this->assertTrue($ents[0] instanceof InsertEntityResult, '$result->getEntries()->get(0)->getClass()');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchUpdateWorks()
    {
        // Arrange
        $table = self::$testTable6;
        $partitionKey = '001';
        $entity = new Entity();
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey('batchUpdateWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());
        $entity = $this->restProxy->insertEntity($table, $entity)->getEntity();

        // Act
        $entity->addProperty('test', EdmType::BOOLEAN, false);
        $bo = new BatchOperations();
        $bo->addUpdateEntity($table, $entity);
        $result = $this->restProxy->batch($bo);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(1, count($result->getEntries()), 'count($result->getEntries())');
        $ents = $result->getEntries();
        $this->assertTrue($ents[0] instanceof UpdateEntityResult, '$result->getEntries()->get(0)->getClass()');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchMergeWorks()
    {
        // Arrange
        $table = self::$testTable6;
        $partitionKey = '001';
        $entity = new Entity();
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey('batchMergeWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());
        $entity = $this->restProxy->insertEntity($table, $entity)->getEntity();

        // Act
        $bo = new BatchOperations();
        $bo->addMergeEntity($table, $entity);
        $result = $this->restProxy->batch($bo);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(1, count($result->getEntries()), 'count($result->getEntries())');
        $ents = $result->getEntries();
        $this->assertTrue($ents[0] instanceof UpdateEntityResult, '$result->getEntries()->get(0)->getClass()');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    */
    public function testBatchInsertOrReplaceWorks()
    {
        $this->skipIfEmulated();

        $table = self::$testTable6;
        $partitionKey = '001';

        // Act
        $entity = new Entity();
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey('batchInsertOrReplaceWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        $bo = new BatchOperations();
        $bo->addInsertOrReplaceEntity($table, $entity);
        $result = $this->restProxy->batch($bo);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(1, count($result->getEntries()), 'count($result->getEntries())');
        $ents = $result->getEntries();
        $this->assertTrue($ents[0] instanceof UpdateEntityResult, '$result->getEntries()->get(0)->getClass()');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    */
    public function testBatchInsertOrMergeWorks()
    {
        $this->skipIfEmulated();

        $table = self::$testTable6;
        $partitionKey = '001';

        // Act
        $entity = new Entity();
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey('batchInsertOrMergeWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

        $bo = new BatchOperations();
        $bo->addInsertOrMergeEntity($table, $entity);
        $result = $this->restProxy->batch($bo);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(1, count($result->getEntries()), 'count($result->getEntries())');
        $ents = $result->getEntries();
        $this->assertTrue($ents[0] instanceof UpdateEntityResult, '$result->getEntries()->get(0)->getClass()');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchDeleteWorks()
    {
        // Arrange
        $table = self::$testTable6;
        $partitionKey = '001';
        $entity = new Entity();
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey('batchDeleteWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());
        $entity = $this->restProxy->insertEntity($table, $entity)->getEntity();

        // Act
        $bo = new BatchOperations();
        $bo->addDeleteEntity($table, $entity->getPartitionKey(), $entity->getRowKey(), $entity->getETag());
        $result = $this->restProxy->batch($bo);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(1, count($result->getEntries()), 'count($result->getEntries())');
        $ents = $result->getEntries();
        $this->assertTrue(is_string($ents[0]), '$result->getEntries()[0] is string');
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    */
    public function testBatchLotsOfInsertsWorks()
    {
        // Arrange
        $table = self::$testTable7;
        $partitionKey = '001';
        $insertCount = 100;

        // Act
        $batchOperations = new BatchOperations();
        for ($i = 0; $i < $insertCount; $i++) {

            $entity = new Entity();
            $entity->setPartitionKey($partitionKey);
            $entity->setRowKey('batchWorks-' . $i);
            $entity->addProperty('test', EdmType::BOOLEAN, true);
            $entity->addProperty('test2', EdmType::STRING, 'value');
            $entity->addProperty('test3', EdmType::INT32, 3);
            $entity->addProperty('test4', EdmType::INT64, '12345678901');
            $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());

            $batchOperations->addInsertEntity($table, $entity);
        }
        $result = $this->restProxy->batch($batchOperations);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals($insertCount, count($result->getEntries()), 'count($result->getEntries())');
        for ($i = 0; $i < $insertCount; $i++) {
            $entity = $result->getEntries();
            $entity = $entity[$i]->getEntity();

            $this->assertEquals('001', $entity->getPartitionKey(), '$entity->getPartitionKey()');
            $this->assertEquals('batchWorks-' . $i, $entity->getRowKey(), '$entity->getRowKey()');
            $this->assertNotNull($entity->getTimestamp(), '$entity->getTimestamp()');                     
            $this->assertNotNull($entity->getETag(), '$entity->getETag()');

            $this->assertNotNull($entity->getProperty('test'), '$entity->getProperty(\'test\')');
            $this->assertEquals(true, $entity->getProperty('test')->getValue(), '$entity->getProperty(\'test\')->getValue()');

            $this->assertNotNull($entity->getProperty('test2'), '$entity->getProperty(\'test2\')');
            $this->assertEquals('value', $entity->getProperty('test2')->getValue(), '$entity->getProperty(\'test2\')->getValue()');

            $this->assertNotNull($entity->getProperty('test3'), '$entity->getProperty(\'test3\')');
            $this->assertEquals(3, $entity->getProperty('test3')->getValue(), '$entity->getProperty(\'test3\')->getValue()');

            $this->assertNotNull($entity->getProperty('test4'), '$entity->getProperty(\'test4\')');
            $this->assertEquals('12345678901', $entity->getProperty('test4')->getValue(), '$entity->getProperty(\'test4\')->getValue()');

            $this->assertNotNull($entity->getProperty('test5'), '$entity->getProperty(\'test5\')');
            $this->assertTrue($entity->getProperty('test5')->getValue() instanceof \DateTime, '$entity->getProperty(\'test5\')->getValue() instanceof \DateTime');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    */
    public function testBatchAllOperationsTogetherWorks()
    {
        // Arrange
        $table = self::$testTable8;
        $partitionKey = '001';

        // Insert a few entities to allow updating them in batch
        $entity1 = new Entity();
        $entity1->setPartitionKey($partitionKey);
        $entity1->setRowKey('batchAllOperationsWorks-' . 1);
        $entity1->addProperty('test', EdmType::BOOLEAN, true);
        $entity1->addProperty('test2', EdmType::STRING, 'value');
        $entity1->addProperty('test3', EdmType::INT32, 3);
        $entity1->addProperty('test4', EdmType::INT64, '12345678901');
        $entity1->addProperty('test5', EdmType::DATETIME, new \DateTime());

        $entity1 = $this->restProxy->insertEntity($table, $entity1)->getEntity();

        $entity2 = new Entity();
        $entity2->setPartitionKey($partitionKey);
        $entity2->setRowKey('batchAllOperationsWorks-' . 2);
        $entity2->addProperty('test', EdmType::BOOLEAN, true);
        $entity2->addProperty('test2', EdmType::STRING, 'value');
        $entity2->addProperty('test3', EdmType::INT32, 3);
        $entity2->addProperty('test4', EdmType::INT64, '12345678901');
        $entity2->addProperty('test5', EdmType::DATETIME, new \DateTime());

        $entity2 = $this->restProxy->insertEntity($table, $entity2)->getEntity();

        $entity3 = new Entity();
        $entity3->setPartitionKey($partitionKey);
        $entity3->setRowKey('batchAllOperationsWorks-' . 3);
        $entity3->addProperty('test', EdmType::BOOLEAN, true);
        $entity3->addProperty('test2', EdmType::STRING, 'value');
        $entity3->addProperty('test3', EdmType::INT32, 3);
        $entity3->addProperty('test4', EdmType::INT64, '12345678901');
        $entity3->addProperty('test5', EdmType::DATETIME, new \DateTime());

        $entity3 = $this->restProxy->insertEntity($table, $entity3)->getEntity();

        $entity4 = new Entity();
        $entity4->setPartitionKey($partitionKey);
        $entity4->setRowKey('batchAllOperationsWorks-' . 4);
        $entity4->addProperty('test', EdmType::BOOLEAN, true);
        $entity4->addProperty('test2', EdmType::STRING, 'value');
        $entity4->addProperty('test3', EdmType::INT32, 3);
        $entity4->addProperty('test4', EdmType::INT64, '12345678901');
        $entity4->addProperty('test5', EdmType::DATETIME, new \DateTime());

        $entity4 = $this->restProxy->insertEntity($table, $entity4)->getEntity();

        // Act
        $batchOperations = new BatchOperations();

        $entity = new Entity();
        $entity->setPartitionKey($partitionKey);
        $entity->setRowKey('batchAllOperationsWorks');
        $entity->addProperty('test', EdmType::BOOLEAN, true);
        $entity->addProperty('test2', EdmType::STRING, 'value');
        $entity->addProperty('test3', EdmType::INT32, 3);
        $entity->addProperty('test4', EdmType::INT64, '12345678901');
        $entity->addProperty('test5', EdmType::DATETIME, new \DateTime());
        $batchOperations->addInsertEntity($table, $entity);
        $batchOperations->addDeleteEntity($table, $entity1->getPartitionKey(), $entity1->getRowKey(), $entity1->getETag());
        $entity2->addProperty('test3', EdmType::INT32, 5);
        $batchOperations->addUpdateEntity($table, $entity2);
        $entity3->addProperty('test3', EdmType::INT32, 5);
        $batchOperations->addMergeEntity($table, $entity3);
        $entity4->addProperty('test3', EdmType::INT32, 5);
        // Use different behavior in the emulator, as v1.6 does not support this method
        if (!$this->isEmulated()) {
            $batchOperations->addInsertOrReplaceEntity($table, $entity4);
        } else {
            $batchOperations->addUpdateEntity($table, $entity4);
        }

        $entity5 = new Entity();
        $entity5->setPartitionKey($partitionKey);
        $entity5->setRowKey('batchAllOperationsWorks-' . 5);
        $entity5->addProperty('test', EdmType::BOOLEAN, true);
        $entity5->addProperty('test2', EdmType::STRING, 'value');
        $entity5->addProperty('test3', EdmType::INT32, 3);
        $entity5->addProperty('test4', EdmType::INT64, '12345678901');
        $entity5->addProperty('test5', EdmType::DATETIME, new \DateTime());
        // Use different behavior in the emulator, as v1.6 does not support this method
        if ($this->isEmulated()) {
            $batchOperations->addInsertEntity($table, $entity5);
        } else {
            $batchOperations->addInsertOrMergeEntity($table, $entity5);
        }

        $result = $this->restProxy->batch($batchOperations);

        // Assert
        $this->assertNotNull($result, '$result');
        $this->assertEquals(count($batchOperations->getOperations()), count($result->getEntries()), 'count($result->getEntries())');

        $ents = $result->getEntries();
        $this->assertTrue($ents[0] instanceof InsertEntityResult, '$result->getEntries()->get(0)->getClass()');
        $this->assertTrue(is_string($ents[1]), '$result->getEntries()->get(1)->getClass()');
        $this->assertTrue($ents[2] instanceof UpdateEntityResult, '$result->getEntries()->get(2)->getClass()');
        $this->assertTrue($ents[3] instanceof UpdateEntityResult, '$result->getEntries()->get(3)->getClass()');
        $this->assertTrue($ents[4] instanceof UpdateEntityResult, '$result->getEntries()->get(4)->getClass()');
        if ($this->isEmulated()) {
            $this->assertTrue($ents[5] instanceof InsertEntityResult, '$result->getEntries()->get(5)->getClass()');
        } else {
            $this->assertTrue($ents[5] instanceof UpdateEntityResult, '$result->getEntries()->get(5)->getClass()');
        }
    }

    /**
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
    * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
    */
    public function testBatchNegativeWorks()
    {
        // Arrange
        $table = self::$testTable8;
        $partitionKey = '001';

        // Insert an entity the modify it outside of the batch
        $entity1 = new Entity();
        $entity1->setPartitionKey($partitionKey);
        $entity1->setRowKey('batchNegativeWorks1');
        $entity1->addProperty('test', EdmType::INT32, 1);
        $entity2 = new Entity();
        $entity2->setPartitionKey($partitionKey);
        $entity2->setRowKey('batchNegativeWorks2');
        $entity2->addProperty('test', EdmType::INT32, 2);
        $entity3 = new Entity();
        $entity3->setPartitionKey($partitionKey);
        $entity3->setRowKey('batchNegativeWorks3');
        $entity3->addProperty('test', EdmType::INT32, 3);

        $entity1 = $this->restProxy->insertEntity($table, $entity1)->getEntity();
        $entity2 = $this->restProxy->insertEntity($table, $entity2)->getEntity();
        $entity2->addProperty('test', EdmType::INT32, -2);
        $this->restProxy->updateEntity($table, $entity2);

        // Act
        $batchOperations = new BatchOperations();

        // The $entity1 still has the original etag from the first submit,
        // so this update should fail, because another update was already made.
        $entity1->addProperty('test', EdmType::INT32, 3);
        $batchOperations->addDeleteEntity($table, $entity1->getPartitionKey(), $entity1->getRowKey(), $entity1->getETag());
        $batchOperations->addUpdateEntity($table, $entity2);
        $batchOperations->addInsertEntity($table, $entity3);

        $result = $this->restProxy->batch($batchOperations);

        // Assert
        $this->assertNotNull($result, '$result');
        $entries = $result->getEntries();
        $this->assertEquals(1, count($entries), 'count($result->getEntries())');
        $this->assertNotNull($entries[0], 'First $result should not be null');
        $this->assertTrue($entries[0] instanceof BatchError, 'First $result type');
        $error = $entries[0];
        $this->assertEquals(412, $error->getError()->getCode(), 'First $result status code');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Common\Internal\Authentication\OAuthScheme;

/**
 * Mock class to wrap OAuthScheme class.
 *
 * @package    WindowsAzure-sdk-for-php
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class OAuthSchemeMock extends OAuthScheme
{
    public function __construct($accountName, $accountKey, $grantType, $scope, $oauthService)
    {
        parent::__construct($accountName, $accountKey, $grantType, $scope, $oauthService);
    }

    public function getAccountName()
    {
        return $this->accountName;
    }

    public function getAccountKey()
    {
        return $this->accountKey;
    }

    public function getGrantType()
    {
        return $this->grantType;
    }

    public function getScope()
    {
        return $this->scope;
    }

    public function getOAuthService()
    {
        return $this->oauthService;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Common\Internal\Authentication\SharedKeyAuthScheme;

/**
 * Mock class to wrap SharedKeyAuthScheme class.
 *
 * @package    WindowsAzure-sdk-for-php
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class SharedKeyAuthSchemeMock extends SharedKeyAuthScheme
{
  public function getIncludedHeaders()
  {
    return $this->includedHeaders;
  }
  
  public function computeSignatureMock($headers, $url, $queryParams, $httpMethod)
  {
    return parent::computeSignature($headers, $url, $queryParams, $httpMethod);
  }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme;

/**
 * Mock class to wrap StorageAuthScheme class.
 *
 * @package    WindowsAzure-sdk-for-php
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class StorageAuthSchemeMock extends StorageAuthScheme
{
    public function __construct($accountName, $accountKey)
    {
        parent::__construct($accountName, $accountKey);
    }

    public function computeCanonicalizedHeadersMock($headers)
    {
        return parent::computeCanonicalizedHeaders($headers);
    }

    public function computeCanonicalizedResourceMock($url, $queryParams)
    {
        return parent::computeCanonicalizedResource($url, $queryParams);
    }
    
    public function computeCanonicalizedResourceForTableMock($url, $queryParams)
    {
        return parent::computeCanonicalizedResourceForTable($url, $queryParams);
    }

    public function getAccountName()
    {
        return $this->accountName;
    }

    public function getAccountKey()
    {
        return $this->accountKey;
    }

    protected function computeSignature($headers, $url, $queryParams, $httpMethod) 
    {
        // Do nothing
    }

    public function getAuthorizationHeader($headers, $url, $queryParams, $httpMethod)
    {
        // Do nothing
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Common\Internal\Authentication\TableSharedKeyLiteAuthScheme;

/**
 * Mock class to wrap SharedKeyAuthScheme class.
 *
 * @package    WindowsAzure-sdk-for-php
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class TableSharedKeyLiteAuthSchemeMock extends TableSharedKeyLiteAuthScheme
{
  public function getIncludedHeaders()
  {
    return $this->includedHeaders;
  }
  
  public function computeSignatureMock($headers, $url, $queryParams, $httpMethod)
  {
    return parent::computeSignature($headers, $url, $queryParams, $httpMethod);
  }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Filters;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Alters request headers and response to mock real filter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SimpleFilterMock implements \MicrosoftAzure\Storage\Common\Internal\IServiceFilter
{
    private $_headerName;
    private $_data;
    
    public function __construct($headerName, $data)
    {
        $this->_data       = $data;
        $this->_headerName = $headerName;
    }
    
    public function handleRequest($request)
    {
        return $request->withHeader($this->_headerName, $this->_data)
                       ->withHeader('Accept-Encoding', 'identity');
    }
    
    public function handleResponse($request, $response)
    {
        $body = $response->getBody();
        return $response->withBody($body.$this->_data);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob;
use MicrosoftAzure\Storage\Tests\Framework\VirtualFileSystem;
use MicrosoftAzure\Storage\Tests\Framework\BlobServiceRestProxyTestBase;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Blob\Models\ListContainersOptions;
use MicrosoftAzure\Storage\Blob\Models\ListContainersResult;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult;
use MicrosoftAzure\Storage\Blob\Models\ContainerAcl;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsResult;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult;
use MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataResult;
use MicrosoftAzure\Storage\Blob\Models\GetBlobResult;
use MicrosoftAzure\Storage\Blob\Models\BlobType;
use MicrosoftAzure\Storage\Blob\Models\PageRange;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult;
use MicrosoftAzure\Storage\Blob\Models\BlockList;
use MicrosoftAzure\Storage\Blob\Models\BlobBlockType;
use MicrosoftAzure\Storage\Blob\Models\GetBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\Block;
use MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult;
use MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions;

/**
 * Unit tests for class BlobRestProxy
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobRestProxyTest extends BlobServiceRestProxyTestBase
{
    private function createSuffix()
    {
        return sprintf('-%04x', mt_rand(0, 65535));
    }

    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getServiceProperties
    */
    public function testGetServiceProperties()
    {
        $this->skipIfEmulated();
        
        // Test
        $result = $this->restProxy->getServiceProperties();
        
        // Assert
        $this->assertEquals($this->defaultProperties->toArray(), $result->getValue()->toArray());
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setServiceProperties
    */
    public function testSetServiceProperties()
    {
        $this->skipIfEmulated();
        
        // Setup
        $expected = ServiceProperties::create(TestResources::setServicePropertiesSample());
        
        // Test
        $this->setServiceProperties($expected);
        $actual = $this->restProxy->getServiceProperties();
        
        // Assert
        $this->assertEquals($expected->toXml($this->xmlSerializer), $actual->getValue()->toXml($this->xmlSerializer));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::send
     */
    public function testListContainersSimple()
    {
        // Setup
        $container1 = 'listcontainersimple1' . $this->createSuffix();
        $container2 = 'listcontainersimple2' . $this->createSuffix();
        $container3 = 'listcontainersimple3' . $this->createSuffix();

        parent::createContainer($container1);
        parent::createContainer($container2);
        parent::createContainer($container3);
        
        // Test
        $result = $this->restProxy->listContainers();

        // Assert
        $containers = $result->getContainers();
        $this->assertNotNull($result->getAccountName());
        $this->assertEquals($container1, $containers[0]->getName());
        $this->assertEquals($container2, $containers[1]->getName());
        $this->assertEquals($container3, $containers[2]->getName());
    }

    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
    */
    public function testListContainersWithOptions()
    {
        // Setup
        $container1    = 'listcontainerwithoptions1' . $this->createSuffix();
        $container2    = 'listcontainerwithoptions2' . $this->createSuffix();
        $container3    = 'mlistcontainerwithoptions3' . $this->createSuffix();
        $metadataName  = 'Mymetadataname';
        $metadataValue = 'MetadataValue';
        $options = new CreateContainerOptions();
        $options->addMetadata($metadataName, $metadataValue);
        parent::createContainer($container1);
        parent::createContainer($container2, $options);
        parent::createContainer($container3);
        $options = new ListContainersOptions();
        $options->setPrefix('list');
        $options->setIncludeMetadata(true);
        
        // Test
        $result = $this->restProxy->listContainers($options);
        
        // Assert
        $containers   = $result->getContainers();
        $metadata = $containers[1]->getMetadata();
        $this->assertEquals(2, count($containers));
        $this->assertEquals($container1, $containers[0]->getName());
        $this->assertEquals($container2, $containers[1]->getName());
        $this->assertEquals($metadataValue, $metadata[$metadataName]);
    }

    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
    */
    public function testListContainersWithNextMarker()
    {
        // Setup
        $container1 = 'listcontainerswithnextmarker1' . $this->createSuffix();
        $container2 = 'listcontainerswithnextmarker2' . $this->createSuffix();
        $container3 = 'listcontainerswithnextmarker3' . $this->createSuffix();
        parent::createContainer($container1);
        parent::createContainer($container2);
        parent::createContainer($container3);
        $options = new ListContainersOptions();
        $options->setMaxResults('2');
        
        // Test
        $result = $this->restProxy->listContainers($options);
        
        // Assert
        $containers = $result->getContainers();
        $this->assertEquals(2, count($containers));
        $this->assertEquals($container1, $containers[0]->getName());
        $this->assertEquals($container2, $containers[1]->getName());
        
        // Test
        $options->setMarker($result->getNextMarker());
        $result = $this->restProxy->listContainers($options);
        $containers = $result->getContainers();

        // Assert
        $this->assertEquals(1, count($containers));
        $this->assertEquals($container3, $containers[0]->getName());
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
    */
    public function testListContainersWithInvalidNextMarkerFail()
    {
        $this->skipIfEmulated();
        
        // Setup
        $container1 = 'listcontainerswithinvalidnextmarker1' . $this->createSuffix();
        $container2 = 'listcontainerswithinvalidnextmarker2' . $this->createSuffix();
        $container3 = 'listcontainerswithinvalidnextmarker3' . $this->createSuffix();
        parent::createContainer($container1);
        parent::createContainer($container2);
        parent::createContainer($container3);
        $options = new ListContainersOptions();
        $options->setMaxResults('2');
        $this->setExpectedException(get_class(new ServiceException('409')));
        
        // Test
        $this->restProxy->listContainers($options);
        $options->setMarker('wrong marker');
        $this->restProxy->listContainers($options);
    }

    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
    */
    public function testListContainersWithNoContainers()
    {
        // Test
        $result = $this->restProxy->listContainers();
        
        // Assert
        $containers = $result->getContainers();
        $this->assertTrue(empty($containers));
    }

    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listContainers
    */
    public function testListContainersWithOneResult()
    {
        // Setup
        $containerName = 'listcontainerswithoneresult' . $this->createSuffix();
        parent::createContainer($containerName);
        
        // Test
        $result = $this->restProxy->listContainers();
        $containers = $result->getContainers();

        // Assert
        $this->assertEquals(1, count($containers));
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
    */
    public function testCreateContainerSimple()
    {
        // Setup
        $containerName = 'createcontainersimple' . $this->createSuffix();
        
        // Test
        $this->createContainer($containerName);
        
        // Assert
        $result = $this->restProxy->listContainers();
        $containers = $result->getContainers();
        $this->assertEquals(1, count($containers));
        $this->assertEquals($containers[0]->getName(), $containerName);
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
    */
    public function testCreateContainerWithoutOptions()
    {
        // Setup
        $containerName = 'createcontainerwithoutoptions' . $this->createSuffix();
        
        // Test
        $this->restProxy->createContainer($containerName);
        $this->_createdContainers[] = $containerName;
        
        // Assert
        $result = $this->restProxy->listContainers();
        $containers = $result->getContainers();
        $this->assertEquals(1, count($containers));
        $this->assertEquals($containers[0]->getName(), $containerName);
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
    */
    public function testCreateContainerWithMetadata()
    {
        $containerName = 'createcontainerwithmetadata' . $this->createSuffix();
        $metadataName  = 'Name';
        $metadataValue = 'MyName';
        $options = new CreateContainerOptions();
        $options->addMetadata($metadataName, $metadataValue);
        $options->setPublicAccess('blob');
        
        // Test
        $this->createContainer($containerName, $options);

        // Assert
        $options = new ListContainersOptions();
        $options->setIncludeMetadata(true);
        $result   = $this->restProxy->listContainers($options);
        $containers   = $result->getContainers();
        $metadata = $containers[0]->getMetadata();
        $this->assertEquals($metadataValue, $metadata[$metadataName]);
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
    */
    public function testCreateContainerInvalidNameFail()
    {
        // Setup
        $containerName = 'CreateContainerInvalidNameFail' . $this->createSuffix();
        $this->setExpectedException(get_class(new ServiceException('400')));
        
        // Test
        $this->createContainer($containerName);
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createContainer
    */
    public function testCreateContainerAlreadyExitsFail()
    {
        // Setup
        $containerName = 'createcontaineralreadyexitsfail' . $this->createSuffix();
        $this->setExpectedException(get_class(new ServiceException('204')));
        $this->createContainer($containerName);

        // Test
        $this->createContainer($containerName);
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
    */
    public function testDeleteContainer()
    {
        // Setup
        $containerName = 'deletecontainer' . $this->createSuffix();
        $this->restProxy->createContainer($containerName);
        
        // Test
        $this->restProxy->deleteContainer($containerName);
        
        // Assert
        $result = $this->restProxy->listContainers();
        $containers = $result->getContainers();
        $this->assertTrue(empty($containers));
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteContainer
    */
    public function testDeleteContainerFail()
    {
        // Setup
        $containerName = 'deletecontainerfail' . $this->createSuffix();
        $this->setExpectedException(get_class(new ServiceException('404')));
        
        // Test
        $this->restProxy->deleteContainer($containerName);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getContainerPropertiesImpl
     */
    public function testGetContainerProperties()
    {
        // Setup
        $name = 'getcontainerproperties' . $this->createSuffix();
        $this->createContainer($name);
        
        // Test
        $result = $this->restProxy->getContainerProperties($name);
        
        // Assert
        $this->assertNotNull($result->getETag());
        $this->assertNotNull($result->getLastModified());
        $this->assertCount(0, $result->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerMetadata
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getContainerPropertiesImpl
     */
    public function testGetContainerMetadata()
    {
        // Setup
        $name     = 'getcontainermetadata' . $this->createSuffix();
        $options  = new CreateContainerOptions();
        $expected = array ('name1' => 'MyName1', 'mymetaname' => '12345', 'values' => 'Microsoft_');
        $options->setMetadata($expected);
        $this->createContainer($name, $options);
        $result = $this->restProxy->getContainerProperties($name);
        $expectedETag = $result->getETag();
        $expectedLastModified = $result->getLastModified();
        
        // Test
        $result = $this->restProxy->getContainerMetadata($name);
        
        // Assert
        $this->assertEquals($expectedETag, $result->getETag());
        $this->assertEquals($expectedLastModified, $result->getLastModified());
        $this->assertEquals($expected, $result->getMetadata());
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getContainerAcl
     */
    public function testGetContainerAcl()
    {
        // Setup
        $name = 'getcontaineracl' . $this->createSuffix();
        $expectedAccess = 'container';
        $this->createContainer($name);
        
        // Test
        $result = $this->restProxy->getContainerAcl($name);
        
        // Assert
        $this->assertEquals($expectedAccess, $result->getContainerAcl()->getPublicAccess());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerAcl
     */
    public function testSetContainerAcl()
    {
        // Setup
        $name = 'setcontaineracl' . $this->createSuffix();
        $this->createContainer($name);
        $sample = TestResources::getContainerAclMultipleEntriesSample();
        $expectedETag = '0x8CAFB82EFF70C46';
        $expectedLastModified = new \DateTime('Sun, 25 Sep 2011 19:42:18 GMT');
        $expectedPublicAccess = 'container';
        $acl = ContainerAcl::create($expectedPublicAccess, $sample['SignedIdentifiers']);

        // Test
        $this->restProxy->setContainerAcl($name, $acl);
        
        // Assert
        $actual = $this->restProxy->getContainerAcl($name);
        $this->assertEquals($acl->getPublicAccess(), $actual->getContainerAcl()->getPublicAccess());
        $this->assertEquals($acl->getSignedIdentifiers(), $actual->getContainerAcl()->getSignedIdentifiers());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setContainerMetadata
     */
    public function testSetContainerMetadata()
    {
        // Setup
        $name     = 'setcontainermetadata' . $this->createSuffix();
        $expected = array ('name1' => 'MyName1', 'mymetaname' => '12345', 'values' => 'Microsoft_');
        $this->createContainer($name);
        
        // Test
        $this->restProxy->setContainerMetadata($name, $expected);
        
        // Assert
        $result = $this->restProxy->getContainerProperties($name);
        $expectedETag = $result->getETag();
        $expectedLastModified = $result->getLastModified();
        $this->assertEquals($expectedETag, $result->getETag());
        $this->assertEquals($expectedLastModified, $result->getLastModified());
        $this->assertEquals($expected, $result->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::send
     */
    public function testListBlobsSimple()
    {
        // Setup
        $name  = 'listblobssimple' . $this->createSuffix();
        $blob1 = 'blob1';
        $blob2 = 'blob2';
        $blob3 = 'blob3';
        $length = 512;

        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob1, $length);
        $this->restProxy->createPageBlob($name, $blob2, $length);
        $this->restProxy->createPageBlob($name, $blob3, $length);
        
        // Test
        $result = $this->restProxy->listBlobs($name);

        // Assert
        $blobs = $result->getBlobs();
        $this->assertNotNull($result->getContainerName());
        $this->assertEquals($blob1, $blobs[0]->getName());
        $this->assertEquals($blob2, $blobs[1]->getName());
        $this->assertEquals($blob3, $blobs[2]->getName());
        $this->assertNull($blobs[2]->getSnapshot());
        $this->assertNotNull($blobs[2]->getUrl());
        $this->assertCount(0, $blobs[2]->getMetadata());
        $this->assertInstanceOf('MicrosoftAzure\Storage\Blob\Models\BlobProperties', $blobs[2]->getProperties());
    }

    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
    */
    public function testListBlobsWithOptions()
    {
        // Setup
        $name  = 'listblobswithoptions' . $this->createSuffix();
        $blob1 = 'blob1';
        $blob2 = 'blob2';
        $blob3 = 'blob3';
        $blob4 = 'lblob1';
        $blob5 = 'lblob2';
        $blob6 = 'lblob3';
        $length = 512;
        $options = new ListBlobsOptions();
        $options->setIncludeMetadata(true);
        $options->setIncludeSnapshots(true);
        $options->setIncludeUncommittedBlobs(true);
        $options->setMaxResults(10);
        $options->setPrefix('lb');

        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob1, $length);
        $this->restProxy->createPageBlob($name, $blob2, $length);
        $this->restProxy->createPageBlob($name, $blob3, $length);
        $this->restProxy->createPageBlob($name, $blob4, $length);
        $this->restProxy->createPageBlob($name, $blob5, $length);
        $this->restProxy->createPageBlob($name, $blob6, $length);
        
        // Test
        $result = $this->restProxy->listBlobs($name, $options);

        // Assert
        $this->assertCount(3, $result->getBlobs());
        $this->assertCount(0, $result->getBlobPrefixes());
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
    */
    public function testListBlobsWithOptionsWithDelimiter()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name  = 'listblobswithoptionswithdelimiter' . $this->createSuffix();
        $blob1 = 'blob1';
        $blob2 = 'blob2';
        $blob3 = 'blob3';
        $blob4 = 'lblob1';
        $blob5 = 'lblob2';
        $blob6 = 'lblob3';
        $length = 512;
        $options = new ListBlobsOptions();
        $options->setDelimiter('b');
        $options->setIncludeMetadata(true);
        $options->setIncludeUncommittedBlobs(true);
        $options->setMaxResults(10);
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob1, $length);
        $this->restProxy->createPageBlob($name, $blob2, $length);
        $this->restProxy->createPageBlob($name, $blob3, $length);
        $this->restProxy->createPageBlob($name, $blob4, $length);
        $this->restProxy->createPageBlob($name, $blob5, $length);
        $this->restProxy->createPageBlob($name, $blob6, $length);
        
        // Test
        $result = $this->restProxy->listBlobs($name, $options);

        // Assert
        $this->assertCount(0, $result->getBlobs());
        $this->assertCount(2, $result->getBlobPrefixes());
    }

    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
    */
    public function testListBlobsWithNextMarker()
    {
        // Setup
        $name  = 'listblobswithnextmarker' . $this->createSuffix();
        $blob1 = 'blob1';
        $blob2 = 'blob2';
        $blob3 = 'blob3';
        $length = 512;
        $options = new ListBlobsOptions();
        $options->setMaxResults(2);

        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob1, $length);
        $this->restProxy->createPageBlob($name, $blob2, $length);
        $this->restProxy->createPageBlob($name, $blob3, $length);
        
        // Test
        $result = $this->restProxy->listBlobs($name, $options);
        
        // Assert
        $this->assertCount(2, $result->getBlobs());
        
        // Setup
        $options->setMarker($result->getNextMarker());
        
        $result = $this->restProxy->listBlobs($name, $options);

        // Assert
        $this->assertCount(1, $result->getBlobs());
    }

    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
    */
    public function testListBlobsWithNoBlobs()
    {
        // Test
        $name = 'listblobswithnoblobs' . $this->createSuffix();
        $this->createContainer($name);
        $result = $this->restProxy->listBlobs($name);
        
        // Assert
        $this->assertCount(0, $result->getBlobs());
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobs
     */
    public function testListBlobsWithOneResult()
    {
        // Test
        $name = 'listblobswithoneresult' . $this->createSuffix();
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, 'myblob', 512);
        $result = $this->restProxy->listBlobs($name);
        
        // Assert
        $this->assertCount(1, $result->getBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addCreateBlobOptionalHeaders
     */
    public function testCreatePageBlob()
    {
        // Setup
        $name = 'createpageblob' . $this->createSuffix();
        $this->createContainer($name);
        
        // Test
        $createResult = $this->restProxy->createPageBlob($name, 'myblob', 512);
        
        // Assert
        $result = $this->restProxy->listBlobs($name);
        $this->assertNotNull($createResult->getETag());
        $this->assertInstanceOf('\DateTime', $createResult->getLastModified());
        $this->assertCount(1, $result->getBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createPageBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addCreateBlobOptionalHeaders
     */
    public function testCreatePageBlobWithExtraOptions()
    {
        // Setup
        $name = 'createpageblobwithextraoptions' . $this->createSuffix();
        $this->createContainer($name);
        $metadata = array('Name1' => 'Value1', 'Name2' => 'Value2');
        $contentType = Resources::BINARY_FILE_TYPE;
        $options = new CreateBlobOptions();
        $options->setMetadata($metadata);
        $options->setContentType($contentType);
        
        // Test
        $this->restProxy->createPageBlob($name, 'myblob', 512, $options);
        
        // Assert
        $result = $this->restProxy->listBlobs($name);
        $this->assertCount(1, $result->getBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addCreateBlobOptionalHeaders
     */
    public function testCreateBlockBlobWithBinary()
    {
        // Setup
        $name = 'createblockblobwithbinary' . $this->createSuffix();
        $this->createContainer($name);
        
        // Test
        $createResult = $this->restProxy->createBlockBlob($name, 'myblob', '123455');
        
        // Assert
        $result = $this->restProxy->listBlobs($name);
        $blobs = $result->getBlobs();
        $blob = $blobs[0];
        $this->assertNotNull($createResult->getETag());
        $this->assertInstanceOf('\DateTime', $createResult->getLastModified());
        $this->assertCount(1, $result->getBlobs());
        $this->assertEquals(Resources::BINARY_FILE_TYPE, $blob->getProperties()->getContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addCreateBlobOptionalHeaders
     */
    public function testCreateBlockBlobWithPlainText()
    {
        // Setup
        $name = 'createblockblobwithplaintext' . $this->createSuffix();
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        
        // Test
        $this->restProxy->createBlockBlob($name, 'myblob', 'Hello world', $options);
        
        // Assert
        $result = $this->restProxy->listBlobs($name);
        $blobs = $result->getBlobs();
        $blob = $blobs[0];
        $this->assertCount(1, $result->getBlobs());
        $this->assertEquals($contentType, $blob->getProperties()->getContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addCreateBlobOptionalHeaders
     */
    public function testCreateBlockBlobWithStream()
    {
        // Setup
        $name = 'createblockblobwithstream' . $this->createSuffix();
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $fileContents = 'Hello world, I\'m a file';
        $stream = fopen(VirtualFileSystem::newFile($fileContents), 'r');
        
        // Test
        $this->restProxy->createBlockBlob($name, 'myblob', $stream, $options);
        
        // Assert
        $result = $this->restProxy->listBlobs($name);
        $blobs = $result->getBlobs();
        $blob = $blobs[0];
        $this->assertCount(1, $result->getBlobs());
        $this->assertEquals($contentType, $blob->getProperties()->getContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getBlobPropertiesResultFromResponse
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::create
     */
    public function testGetBlobProperties()
    {
        // Setup
        $name = 'getblobproperties' . $this->createSuffix();
        $contentLength = 512;
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, 'myblob', $contentLength);
        
        // Test
        $result = $this->restProxy->getBlobProperties($name, 'myblob');
        
        // Assert
        $this->assertEquals($contentLength, $result->getProperties()->getContentLength());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getBlobPropertiesResultFromResponse
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::create
     */
    public function testSetBlobProperties()
    {
        // Setup
        $name = 'setblobproperties' . $this->createSuffix();
        $contentLength = 1024;
        $blob = 'myblob';
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, 'myblob', 512);
        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentLength($contentLength);
        
        // Test
        $this->restProxy->setBlobProperties($name, $blob, $options);
        
        // Assert
        $result = $this->restProxy->getBlobProperties($name, $blob);
        $this->assertEquals($contentLength, $result->getProperties()->getContentLength());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobProperties
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getBlobPropertiesResultFromResponse
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::create
     */
    public function testSetBlobPropertiesWithNoOptions()
    {
        // Setup
        $name = 'setblobpropertieswithnooptions' . $this->createSuffix();
        $blob = 'myblob';
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob, 512);
        
        // Test
        $result = $this->restProxy->setBlobProperties($name, $blob);
        
        // Assert
        $this->assertInstanceOf('\DateTime', $result->getLastModified());
        $this->assertTrue(!is_null($result->getETag()));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult::create
     */
    public function testGetBlobMetadata()
    {
        // Setup
        $name = 'getblobmetadata' . $this->createSuffix();
        $metadata = array('m1' => 'v1', 'm2' => 'v2');
        $blob = 'myblob';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setMetadata($metadata);
        $this->restProxy->createPageBlob($name, $blob, 512, $options);
        
        // Test
        $result = $this->restProxy->getBlobMetadata($name, $blob);
        
        // Assert
        $this->assertEquals($metadata, $result->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setBlobMetadata
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataResult::create
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::addMetadataHeaders
     */
    public function testSetBlobMetadata()
    {
        // Setup
        $name = 'setblobmetadata' . $this->createSuffix();
        $metadata = array('m1' => 'v1', 'm2' => 'v2');
        $blob = 'myblob';
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob, 512);
        
        // Test
        $this->restProxy->setBlobMetadata($name, $blob, $metadata);
        
        // Assert
        $result = $this->restProxy->getBlobMetadata($name, $blob);
        $this->assertEquals($metadata, $result->getMetadata());
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addOptionalRangeHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::create
     */
    public function testGetBlob()
    {
        // Setup
        $name = 'getblob' . $this->createSuffix();
        $blob = 'myblob';
        $metadata = array('m1' => 'v1', 'm2' => 'v2');
        $contentType = 'text/plain; charset=UTF-8';
        $contentStream = 'Hello world';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $options->setMetadata($metadata);
        $this->restProxy->createBlockBlob($name, $blob, $contentStream, $options);
        
        // Test
        $result = $this->restProxy->getBlob($name, $blob);
        
        // Assert
        $this->assertEquals(BlobType::BLOCK_BLOB, $result->getProperties()->getBlobType());
        $this->assertEquals($metadata, $result->getMetadata());
        $this->assertEquals($contentStream, stream_get_contents($result->getContentStream()));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addOptionalRangeHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::create
     */
    public function testGetBlobWithRange()
    {
        // Setup
        $name = '$root';
        $blob = 'myblob';
        $this->restProxy->createContainer($name);
        $this->_createdContainers[] = '$root';
        $length = 512;
        $range = new PageRange(0, 511);
        $contentStream = Resources::EMPTY_STRING;
        $this->restProxy->createPageBlob('', $blob, $length);
        for ($i = 0; $i < 512; $i++) {
            $contentStream .= 'A';
        }
        $this->restProxy->createBlobPages('', $blob, $range, $contentStream);
        $options = new GetBlobOptions();
        $options->setRangeStart(0);
        $options->setRangeEnd(511);
        
        // Test
        $result = $this->restProxy->getBlob('', $blob, $options);
        
        // Assert
        $this->assertEquals(BlobType::PAGE_BLOB, $result->getProperties()->getBlobType());
        $this->assertEquals($contentStream, stream_get_contents($result->getContentStream()));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addOptionalRangeHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::create
     */
    public function testGetBlobWithEndRange()
    {
        // Setup
        $name = 'getblobwithendrange' . $this->createSuffix();
        $blob = 'myblob';
        $this->createContainer($name);
        $length = 512;
        $range = new PageRange(0, 511);
        $contentStream = Resources::EMPTY_STRING;
        $this->restProxy->createPageBlob($name, $blob, $length);
        for ($i = 0; $i < 512; $i++) {
            $contentStream .= 'A';
        }
        $this->restProxy->createBlobPages($name, $blob, $range, $contentStream);
        $options = new GetBlobOptions();
        $options->setRangeStart(null);
        $options->setRangeEnd(511);
        
        // Test
        $result = $this->restProxy->getBlob($name, $blob, $options);
        
        // Assert
        $this->assertEquals(BlobType::PAGE_BLOB, $result->getProperties()->getBlobType());
        $this->assertEquals($contentStream, stream_get_contents($result->getContentStream()));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addOptionalRangeHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::create
     */
    public function testGetBlobGarbage()
    {
        // Setup
        $name = 'getblobwithgarbage' . $this->createSuffix();
        $blob = 'myblob';
        $metadata = array('m1' => 'v1', 'm2' => 'v2');
        $contentType = 'text/plain; charset=UTF-8';
        $contentStream = chr(0);
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $options->setMetadata($metadata);
        $this->restProxy->createBlockBlob($name, $blob, $contentStream, $options);
        
        // Test
        $result = $this->restProxy->getBlob($name, $blob);
        
        // Assert
        $this->assertEquals(BlobType::BLOCK_BLOB, $result->getProperties()->getBlobType());
        $this->assertEquals($metadata, $result->getMetadata());
        $this->assertEquals($contentStream, stream_get_contents($result->getContentStream()));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     */
    public function testDeleteBlob()
    {
        // Setup
        $name = 'deleteblob' . $this->createSuffix();
        $blob = 'myblob';
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, $blob, 'Hello world', $options);
        
        // Test
        $this->restProxy->deleteBlob($name, $blob);
        
        // Assert
        $result = $this->restProxy->listBlobs($name);
        $this->assertCount(0, $result->getBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     */
    public function testDeleteBlobSnapshot()
    {
        // Setup
        $name = 'deleteblobsnapshot' . $this->createSuffix();
        $blob = 'myblob';
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, $blob, 'Hello world', $options);
        $snapshot = $this->restProxy->createBlobSnapshot($name, $blob);
        $options = new DeleteBlobOptions();
        $options->setSnapshot($snapshot->getSnapshot());
        
        // Test
        $this->restProxy->deleteBlob($name, $blob, $options);
        
        // Assert
        $listOptions = new ListBlobsOptions();
        $listOptions->setIncludeSnapshots(true);
        $blobsResult = $this->restProxy->listBlobs($name, $listOptions);
        $blobs = $blobsResult->getBlobs();
        $actualBlob = $blobs[0];
        $this->assertNull($actualBlob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::deleteBlob
     */
    public function testDeleteBlobSnapshotsOnly()
    {
        // Setup
        $name = 'deleteblobsnapshotsonly' . $this->createSuffix();
        $blob = 'myblob';
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, $blob, 'Hello world', $options);
        $this->restProxy->createBlobSnapshot($name, $blob);
        $options = new DeleteBlobOptions();
        $options->setDeleteSnaphotsOnly(true);
        
        // Test
        $this->restProxy->deleteBlob($name, $blob, $options);
        
        // Assert
        $listOptions = new ListBlobsOptions();
        $listOptions->setIncludeSnapshots(true);
        $blobsResult = $this->restProxy->listBlobs($name, $listOptions);
        $blobs = $blobsResult->getBlobs();
        $actualBlob = $blobs[0];
        $this->assertNull($actualBlob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::acquireLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_putLeaseImpl
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_createPath
     */
    public function testAcquireLease()
    {
        // Setup
        $name = 'acquirelease' . $this->createSuffix();
        $blob = 'myblob';
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, $blob, 'Hello world', $options);
        
        // Test
        $result = $this->restProxy->acquireLease($name, $blob);
        
        // Assert
        $this->assertNotNull($result->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::renewLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_putLeaseImpl
     */
    public function testRenewLease()
    {
        // Setup
        $name = 'renewlease' . $this->createSuffix();
        $blob = 'myblob';
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, $blob, 'Hello world', $options);
        $result = $this->restProxy->acquireLease($name, $blob);
        
        // Test
        $result = $this->restProxy->renewLease($name, $blob, $result->getLeaseId());
        
        // Assert
        $this->assertNotNull($result->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::releaseLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_putLeaseImpl
     */
    public function testReleaseLease()
    {
        // Setup
        $name = 'releaselease' . $this->createSuffix();
        $blob = 'myblob';
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, $blob, 'Hello world', $options);
        $result = $this->restProxy->acquireLease($name, $blob);
        
        // Test
        $this->restProxy->releaseLease($name, $blob, $result->getLeaseId());
        
        // Assert
        $result = $this->restProxy->acquireLease($name, $blob);
        $this->assertNotNull($result->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::breakLease
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_putLeaseImpl
     */
    public function testBreakLease()
    {
        // Setup
        $name = 'breaklease' . $this->createSuffix();
        $blob = 'myblob';
        $contentType = 'text/plain; charset=UTF-8';
        $this->createContainer($name);
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, $blob, 'Hello world', $options);
        $this->restProxy->acquireLease($name, $blob);
        
        // Test
        $result = $this->restProxy->breakLease($name, $blob);
        
        // Assert
        $this->assertInstanceOf('MicrosoftAzure\Storage\Blob\Models\BreakLeaseResult', $result);
        $this->assertNotNull($result->getLeaseTime());
        $result = $this->restProxy->acquireLease($name, $blob);
        $this->assertNotNull($result->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobPages
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_updatePageBlobPagesImpl
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addOptionalRangeHeader
     */
    public function testCreateBlobPages()
    {
        // Setup
        $name = 'createblobpages' . $this->createSuffix();
        $blob = 'myblob';
        $length = 512;
        $range = new PageRange(0, 511);
        $content = Resources::EMPTY_STRING;
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob, $length);
        for ($i = 0; $i < 512; $i++) {
            $content .= 'A';
        }
        
        // Test
        $actual = $this->restProxy->createBlobPages($name, $blob, $range, $content);
        
        // Assert
        $this->assertNotNull($actual->getETag());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::clearBlobPages
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_updatePageBlobPagesImpl
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_addOptionalRangeHeader
     */
    public function testClearBlobPages()
    {
        // Setup
        $name = 'clearblobpages' . $this->createSuffix();
        $blob = 'myblob';
        $length = 512;
        $range = new PageRange(0, 511);
        $content = Resources::EMPTY_STRING;
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob, $length);
        for ($i = 0; $i < 512; $i++) {
            $content .= 'A';
        }
        $this->restProxy->createBlobPages($name, $blob, $range, $content);
        
        // Test
        $actual = $this->restProxy->clearBlobPages($name, $blob, $range);
        
        // Assert
        $this->assertNotNull($actual->getETag());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listPageBlobRanges
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::create
     */
    public function testListPageBlobRanges()
    {
        // Setup
        $name = 'listpageblobranges' . $this->createSuffix();
        $blob = 'myblob';
        $length = 512;
        $range = new PageRange(0, 511);
        $content = Resources::EMPTY_STRING;
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob, $length);
        for ($i = 0; $i < 512; $i++) {
            $content .= 'A';
        }
        $this->restProxy->createBlobPages($name, $blob, $range, $content);
        
        // Test
        $result = $this->restProxy->listPageBlobRanges($name, $blob);
        
        // Assert
        $this->assertNotNull($result->getETag());
        $this->assertCount(1, $result->getPageRanges());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listPageBlobRanges
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::create
     */
    public function testListPageBlobRangesEmpty()
    {
        // Setup
        $name = 'listpageblobrangesempty' . $this->createSuffix();
        $blob = 'myblob';
        $length = 512;
        $this->createContainer($name);
        $this->restProxy->createPageBlob($name, $blob, $length);
        
        // Test
        $result = $this->restProxy->listPageBlobRanges($name, $blob);
        
        // Assert
        $this->assertNotNull($result->getETag());
        $this->assertCount(0, $result->getPageRanges());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobBlock
     */
    public function testCreateBlobBlock()
    {
        // Setup
        $name = 'createblobblock' . $this->createSuffix();
        $this->createContainer($name);
        $options = new ListBlobsOptions();
        $options->setIncludeUncommittedBlobs(true);

        // Test
        $this->restProxy->createBlobBlock($name, 'myblob', 'AAAAAA==', 'Hello world');

        // Assert
        $result = $this->restProxy->listBlobs($name, $options);
        $this->assertCount(1, $result->getBlobs());
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::commitBlobBlocks
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::toXml
     */
    public function testCommitBlobBlocks()
    {
        // Setup
        $name = 'commitblobblocks' . $this->createSuffix();
        $blob = 'myblob';
        $id1 = 'AAAAAA==';
        $id2 = 'ANAAAA==';
        $this->createContainer($name);
        $this->restProxy->createBlobBlock($name, $blob, $id1, 'Hello world');
        $this->restProxy->createBlobBlock($name, $blob, $id2, 'Hello world');
        $blockList = new BlockList();
        
        $blockList->addEntry($id1, BlobBlockType::LATEST_TYPE);
        $blockList->addEntry($id2, BlobBlockType::LATEST_TYPE);
        
        // Test
        $this->restProxy->commitBlobBlocks($name, $blob, $blockList);
        
        // Assert
        $result = $this->restProxy->listBlobs($name);
        $this->assertCount(1, $result->getBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::commitBlobBlocks
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::toXml
     */
    public function testCommitBlobBlocksWithArray()
    {
        // Setup
        $name = 'commitblobblockswitharray' . $this->createSuffix();
        $blob = 'myblob';
        $id1 = 'AAAAAA==';
        $id2 = 'ANAAAA==';
        $block1 = new Block();
        $block1->setBlockId($id1);
        $block1->setType(BlobBlockType::LATEST_TYPE);
        $block2 = new Block();
        $block2->setBlockId($id2);
        $block2->setType(BlobBlockType::LATEST_TYPE);
        $blockList = array($block1, $block2);
        $this->createContainer($name);
        $this->restProxy->createBlobBlock($name, $blob, $id1, 'Hello world');
        $this->restProxy->createBlobBlock($name, $blob, $id2, 'Hello world');
        
        // Test
        $this->restProxy->commitBlobBlocks($name, $blob, $blockList);

        // Assert
        $result = $this->restProxy->listBlobs($name);
        $this->assertCount(1, $result->getBlobs());
    }
     
   /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobBlocks
    * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::create
    * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getContentLength     
    * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getUncommittedBlocks
    * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getCommittedBlocks
    */
   public function testListBlobBlocks()
   {
       // Setup
       $name = 'listblobblocks' . $this->createSuffix();
       $blob = 'myblob';
       $id1 = 'AAAAAA==';
       $id2 = 'ANAAAA==';
       $this->createContainer($name);
       $this->restProxy->createBlobBlock($name, $blob, $id1, 'Hello world');
       $this->restProxy->createBlobBlock($name, $blob, $id2, 'Hello world');

       // Test
       $result = $this->restProxy->listBlobBlocks($name, $blob);

       // Assert
       $this->assertNull($result->getETag());
       $this->assertEquals(0, $result->getContentLength());
       $this->assertCount(2, $result->getUncommittedBlocks());
       $this->assertCount(0, $result->getCommittedBlocks());
    }
      
    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::listBlobBlocks
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::create
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::_getEntries
     */
    public function testListBlobBlocksEmpty()
    {
        // Setup
        $name = 'listblobblocksempty' . $this->createSuffix();
        $blob = 'myblob';
        $content = 'Hello world';
        $this->createContainer($name);
        $this->restProxy->createBlockBlob($name, $blob, $content);
        
        // Test
        $result = $this->restProxy->listBlobBlocks($name, $blob);
        
        // Assert
        $this->assertNotNull($result->getETag());
        $this->assertEquals(strlen($content), $result->getContentLength());
        $this->assertCount(0, $result->getUncommittedBlocks());
        $this->assertCount(0, $result->getCommittedBlocks());

    }

    /** 
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::copyBlob 
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getCopyBlobSourceName
     */ 
    public function testCopyBlobDifferentContainer()
    {
        // Setup
        $sourceContainerName = 'copyblobdiffcontainerssource' . $this->createSuffix();
        $sourceBlobName = 'sourceblob';
        $blobValue = 'testBlobValue'; 
        $destinationContainerName = 'copyblobdiffcontainersdestination' . $this->createSuffix();
        $destinationBlobName = 'destinationblob';
        $this->createContainer($sourceContainerName);
        $this->createContainer($destinationContainerName); 
        $this->restProxy->createBlockBlob($sourceContainerName, $sourceBlobName, $blobValue); 
        
        // Test
        $result = $this->restProxy->copyBlob( 
            $destinationContainerName, 
            $destinationBlobName, 
            $sourceContainerName, 
            $sourceBlobName
        ); 
        
        // Assert
        $sourceBlob = $this->restProxy->getBlob($sourceContainerName, $sourceBlobName);
        $destinationBlob = $this->restProxy->getBlob($destinationContainerName, $destinationBlobName);
        $sourceBlobContent = stream_get_contents($sourceBlob->getContentStream());
        $destinationBlobContent = stream_get_contents($destinationBlob->getContentStream());
        
        $this->assertEquals($sourceBlobContent, $destinationBlobContent);
        $this->assertNotNull($result->getETag());
        $this->assertInstanceOf('\DateTime', $result->getlastModified());
    }
    
    /** 
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::copyBlob 
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getCopyBlobSourceName
     */ 
    public function testCopyBlobSameContainer()
    {
        // Setup
        $containerName = 'copyblobsamecontainer' . $this->createSuffix();
        $sourceBlobName = 'sourceblob';
        $blobValue = 'testBlobValue'; 
        $destinationBlobName = 'destinationblob';
        $this->createContainer($containerName);
        $this->restProxy->createBlockBlob($containerName, $sourceBlobName, $blobValue); 
        
        // Test
        $this->restProxy->copyBlob( 
            $containerName, 
            $destinationBlobName, 
            $containerName, 
            $sourceBlobName
        ); 
        
        // Assert
        $sourceBlob = $this->restProxy->getBlob($containerName, $sourceBlobName);
        $destinationBlob = $this->restProxy->getBlob($containerName, $destinationBlobName);

        $sourceBlobContent = stream_get_contents($sourceBlob->getContentStream());
        $destinationBlobContent = stream_get_contents($destinationBlob->getContentStream());
        $this->assertEquals($sourceBlobContent, $destinationBlobContent);
    }
    
    /** 
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::copyBlob 
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getCopyBlobSourceName
     */ 
    public function testCopyBlobExistingBlob()
    {
        // Setup
        $containerName = 'copyblobexistingblob' . $this->createSuffix();
        $sourceBlobName = 'sourceblob';
        $blobValue = 'testBlobValue'; 
        $oldBlobValue = 'oldBlobValue';
        $destinationBlobName = 'destinationblob';
        $this->createContainer($containerName);
        $this->restProxy->createBlockBlob($containerName, $sourceBlobName, $blobValue); 
        $this->restProxy->createBlockBlob($containerName, $destinationBlobName, $oldBlobValue); 
        
        // Test
        $this->restProxy->copyBlob( 
            $containerName, 
            $destinationBlobName, 
            $containerName, 
            $sourceBlobName
        ); 
        
        // Assert
        $sourceBlob = $this->restProxy->getBlob($containerName, $sourceBlobName);
        $destinationBlob = $this->restProxy->getBlob($containerName, $destinationBlobName);
        $sourceBlobContent = stream_get_contents($sourceBlob->getContentStream());
        $destinationBlobContent = stream_get_contents($destinationBlob->getContentStream());
        
        $this->assertEquals($sourceBlobContent, $destinationBlobContent);
        $this->assertNotEquals($destinationBlobContent, $oldBlobValue);
    }
    
    /** 
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::copyBlob 
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::_getCopyBlobSourceName
     */ 
    public function testCopyBlobSnapshot()
    {
        // Setup
        $containerName = 'copyblobsnapshot' . $this->createSuffix();
        $sourceBlobName = 'sourceblob';
        $blobValue = 'testBlobValue'; 
        $destinationBlobName = 'destinationblob';
        $this->createContainer($containerName);
        $this->restProxy->createBlockBlob($containerName, $sourceBlobName, $blobValue);
        $snapshotResult = $this->restProxy->createBlobSnapshot($containerName, $sourceBlobName);
        $options = new CopyBlobOptions();
        $options->setSourceSnapshot($snapshotResult->getSnapshot());
        
        // Test
        $this->restProxy->copyBlob(
            $containerName, 
            $destinationBlobName, 
            $containerName, 
            $sourceBlobName,
            $options
        ); 
        
        // Assert
        $sourceBlob = $this->restProxy->getBlob($containerName, $sourceBlobName);
        $destinationBlob = $this->restProxy->getBlob($containerName, $destinationBlobName);
        $sourceBlobContent = stream_get_contents($sourceBlob->getContentStream());
        $destinationBlobContent = stream_get_contents($destinationBlob->getContentStream());
        
        $this->assertEquals($sourceBlobContent, $destinationBlobContent);
    }
    
   /**  
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlobSnapshot 
    * @covers MicrosoftAzure\Storage\Blob\Models\createBlobSnapshotResult::create 
    */ 
    public function testCreateBlobSnapshot()
    { 
        // Setup
        $containerName = 'createblobsnapshot' . $this->createSuffix();
        $blobName = 'testBlob'; 
        $blobValue = 'TestBlobValue'; 
        $this->createContainer($containerName);
        $this->restProxy->createBlockBlob($containerName, $blobName, $blobValue);
        
        // Test
        $snapshotResult = $this->restProxy->createBlobSnapshot($containerName, $blobName);
        
        // Assert
        $listOptions = new ListBlobsOptions();
        $listOptions->setIncludeSnapshots(true);
        $blobsResult = $this->restProxy->listBlobs($containerName, $listOptions);
        $blobs = $blobsResult->getBlobs();
        $actualBlob = $blobs[0];
        $this->assertNotNull($snapshotResult->getETag());
        $this->assertNotNull($snapshotResult->getLastModified());
        $this->assertNotNull($snapshotResult->getSnapshot());
        $this->assertEquals($snapshotResult->getSnapshot(), $actualBlob->getSnapshot());
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
     * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getBlob
     */
    public function testSingleBlobUploadZeroBytes()
    {
        // Bug reported for zero byte upload similar to unix touch command failing
        $name = 'createemptyblob' . $this->createSuffix();
        $blob = 'EmptyFile';
        $this->createContainer($name);
        $contentType = 'text/plain; charset=UTF-8';
        $content = "";
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, $blob, $content, $options);
    
        // Now see if we can pick thje file back up.
        $result = $this->restProxy->getBlob($name, $blob);
    
        // Assert
        $this->assertEquals($content, stream_get_contents($result->getContentStream()));
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::getSingleBlobUploadThresholdInBytes
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::setSingleBlobUploadThresholdInBytes
    */
    public function testSingleBlobUploadThresholdInBytes()
    {
        // Values based on http://msdn.microsoft.com/en-us/library/microsoft.windowsazure.storageclient.cloudblobclient.singleblobuploadthresholdinbytes.aspx
        // Read initial value
        $this->assertEquals($this->restProxy->getSingleBlobUploadThresholdInBytes(), 33554432);

        // Change value
        $this->restProxy->setSingleBlobUploadThresholdInBytes(50);
        $this->assertEquals($this->restProxy->getSingleBlobUploadThresholdInBytes(), 50);

        // Test over limit
        $this->restProxy->setSingleBlobUploadThresholdInBytes(65*1024*1024);
        // Should be truncated to 64M
        $this->assertEquals($this->restProxy->getSingleBlobUploadThresholdInBytes(), 67108864);

        // Under limit
        $this->restProxy->setSingleBlobUploadThresholdInBytes( -50);
        // value can not be less than 1, so reset to default value
        $this->assertEquals($this->restProxy->getSingleBlobUploadThresholdInBytes(), 33554432);

        $this->restProxy->setSingleBlobUploadThresholdInBytes( 0);
        // value can not be less than 1, so reset to default value
        $this->assertEquals($this->restProxy->getSingleBlobUploadThresholdInBytes(), 33554432);
    }

   /**
    * @covers MicrosoftAzure\Storage\Blob\BlobRestProxy::createBlockBlob
    **/
    public function testCreateBlobLargerThanSingleBlock()
    {
        // First step, lets set the value for automagic splitting to somethign very small
    $max_size = 50;
        $this->restProxy->setSingleBlobUploadThresholdInBytes( $max_size );
        $this->assertEquals($this->restProxy->getSingleBlobUploadThresholdInBytes(), $max_size);
        $name = 'createbloblargerthansingleblock' . $this->createSuffix();
        $this->createContainer($name);
        $contentType = 'text/plain; charset=UTF-8';
    $content = "This is a really long section of text needed for this test.";
    // Note this grows fast, each loop doubles the last run. Do not make too big
        // This results in a 1888 byte string, divided by 50 results in 38 blocks
    for($i = 0; $i < 5; $i++){
        $content .= $content;
    }
        $options = new CreateBlobOptions();
        $options->setContentType($contentType);
        $this->restProxy->createBlockBlob($name, 'little_split', $content, $options);

    // Block specific
    $boptions = new ListBlobBlocksOptions();
    $boptions->setIncludeUncommittedBlobs(true);
    $boptions->setIncludeCommittedBlobs(true);
        $result = $this->restProxy->listBlobBlocks($name, 'little_split', $boptions);
    $blocks = $result->getUnCommittedBlocks();
        $this->assertEquals(count($blocks), 0);
    $blocks = $result->getCommittedBlocks();
    $this->assertEquals(count($blocks), ceil(strlen($content) / $max_size));

        // Setting back to default value for one shot test
        $this->restProxy->setSingleBlobUploadThresholdInBytes( 0 );
        $this->restProxy->createBlockBlob($name, 'oneshot', $content, $options);
        $result = $this->restProxy->listBlobBlocks($name, 'oneshot', $boptions);
    $blocks = $result->getUnCommittedBlocks();
        $this->assertEquals(count($blocks), 0);
    $blocks = $result->getCommittedBlocks();
        // this one doesn't make sense. On emulator, there is no block created, 
        // so relying on content size to be final approval
    $this->assertEquals(count($blocks), 0);
        $this->assertEquals($result->getContentLength(), strlen($content));

        // make string even larger for automagic splitting
        // This should result in a string longer than 32M, and force the blob into 2 blocks
    for($i = 0; $i < 15; $i++){
        $content .= $content;
    }
        $this->restProxy->createBlockBlob($name, 'bigsplit', $content, $options);
        $result = $this->restProxy->listBlobBlocks($name, 'bigsplit', $boptions);
    $blocks = $result->getUnCommittedBlocks();
        $this->assertEquals(count($blocks), 0);
    $blocks = $result->getCommittedBlocks();
    $this->assertEquals(count($blocks), ceil(strlen($content)/(4*1024*1024)));
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class AccessCondition
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AccessConditionTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::__construct
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getValue
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::setHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::setValue
     */
    public function test__construct()
    {
        // Setup
        $expectedHeaderType = Resources::IF_MATCH;
        $expectedValue = '0x8CAFB82EFF70C46';
        
        // Test
        $actual = AccessCondition::ifMatch($expectedValue);
        
        // Assert
        $this->assertEquals($expectedHeaderType, $actual->getHeader());
        $this->assertEquals($expectedValue, $actual->getValue());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::none
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getValue
     */
    public function testNone()
    {
        // Setup
        $expectedHeader = Resources::EMPTY_STRING;
        $expectedValue = null;
        
        // Test
        $actual = AccessCondition::none();
        
        // Assert
        $this->assertEquals($expectedHeader, $actual->getHeader());
        $this->assertEquals($expectedValue, $actual->getValue());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::ifModifiedSince
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getValue
     */
    public function testIfModifiedSince()
    {
        // Setup
        $expectedHeader = Resources::IF_MODIFIED_SINCE;
        $expectedValue = new \DateTime('Sun, 25 Sep 2011 00:42:49 GMT');
        
        // Test
        $actual = AccessCondition::ifModifiedSince($expectedValue);
        
        // Assert
        $this->assertEquals($expectedHeader, $actual->getHeader());
        $this->assertEquals($expectedValue, $actual->getValue());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::ifMatch
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getValue
     */
    public function testIfMatch()
    {
        // Setup
        $expectedHeader = Resources::IF_MATCH;
        $expectedValue = '0x8CAFB82EFF70C46';
        
        // Test
        $actual = AccessCondition::ifMatch($expectedValue);
        
        // Assert
        $this->assertEquals($expectedHeader, $actual->getHeader());
        $this->assertEquals($expectedValue, $actual->getValue());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::ifNoneMatch
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getValue
     */
    public function testIfNoneMatch()
    {
        // Setup
        $expectedHeader = Resources::IF_NONE_MATCH;
        $expectedValue = '0x8CAFB82EFF70C46';
        
        // Test
        $actual = AccessCondition::ifNoneMatch($expectedValue);
        
        // Assert
        $this->assertEquals($expectedHeader, $actual->getHeader());
        $this->assertEquals($expectedValue, $actual->getValue());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::ifNotModifiedSince
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getHeader
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::getValue
     */
    public function testIfNotModifiedSince()
    {
        // Setup
        $expectedHeader = Resources::IF_UNMODIFIED_SINCE;
        $expectedValue = new \DateTime('Sun, 25 Sep 2011 00:42:49 GMT');
        
        // Test
        $actual = AccessCondition::ifNotModifiedSince($expectedValue);
        
        // Assert
        $this->assertEquals($expectedHeader, $actual->getHeader());
        $this->assertEquals($expectedValue, $actual->getValue());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::isValid
     */
    public function testIsValidWithValid()
    {
        // Test
        $actual = AccessCondition::isValid(Resources::IF_MATCH);
        
        // Assert
        $this->assertTrue($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessCondition::isValid
     */
    public function testIsValidWithInvalid()
    {
        // Test
        $actual = AccessCondition::isValid('1234');
        
        // Assert
        $this->assertFalse($actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\AccessPolicy;

/**
 * Unit tests for class AccessPolicy
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AccessPolicyTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessPolicy::getStart 
     */
    public function testGetStart()
    {
        // Setup
        $accessPolicy = new AccessPolicy();
        $expected = new \DateTime('2009-09-28T08:49:37');
        $accessPolicy->setStart($expected);
        
        // Test
        $actual = $accessPolicy->getStart();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessPolicy::setStart 
     */
    public function testSetStart()
    {
        // Setup
        $accessPolicy = new AccessPolicy();
        $expected = new \DateTime('2009-09-28T08:49:37');
        
        // Test
        $accessPolicy->setStart($expected);
        
        // Assert
        $this->assertEquals($expected, $accessPolicy->getStart());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessPolicy::getExpiry 
     */
    public function testGetExpiry()
    {
        // Setup
        $accessPolicy = new AccessPolicy();
        $expected = new \DateTime('2009-09-28T08:49:37');
        $accessPolicy->setExpiry($expected);
        
        // Test
        $actual = $accessPolicy->getExpiry();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessPolicy::setExpiry 
     */
    public function testSetExpiry()
    {
        // Setup
        $accessPolicy = new AccessPolicy();
        $expected = new \DateTime('2009-09-28T08:49:37');
        
        // Test
        $accessPolicy->setExpiry($expected);
        
        // Assert
        $this->assertEquals($expected, $accessPolicy->getExpiry());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessPolicy::getPermission 
     */
    public function testGetPermission()
    {
        // Setup
        $accessPolicy = new AccessPolicy();
        $expected = 'rw';
        $accessPolicy->setPermission($expected);
        
        // Test
        $actual = $accessPolicy->getPermission();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessPolicy::setPermission 
     */
    public function testSetPermission()
    {
        // Setup
        $accessPolicy = new AccessPolicy();
        $expected = 'rw';
        
        // Test
        $accessPolicy->setPermission($expected);
        
        // Assert
        $this->assertEquals($expected, $accessPolicy->getPermission());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AccessPolicy::toArray
     */
    public function testToArray()
    {
        // Setup
        $accessPolicy = new AccessPolicy();
        $permission = 'rw';
        $start = '2009-09-28T08:49:37.3942040Z';
        $expiry = '2009-10-28T08:49:37.3942040Z';
        $startDate = new \DateTime($start);
        $expiryDate = new \DateTime($expiry);
        $accessPolicy->setPermission($permission);
        $accessPolicy->setStart($startDate);
        $accessPolicy->setExpiry($expiryDate);
        
        // Test
        $actual = $accessPolicy->toArray();
        
        // Assert
        $this->assertEquals($permission, $actual['Permission']);
        $this->assertEquals($start, urldecode($actual['Start']));
        $this->assertEquals($expiry, urldecode($actual['Expiry']));
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\AcquireLeaseOptions;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;

/**
 * Unit tests for class AcquireLeaseOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AcquireLeaseOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AcquireLeaseOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $options = new AcquireLeaseOptions();
        $options->setAccessCondition($expected);
        
        // Test
        $actual = $options->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AcquireLeaseOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $options = new AcquireLeaseOptions();
        
        // Test
        $options->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getAccessCondition());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\AcquireLeaseResult;

/**
 * Unit tests for class AcquireLeaseResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AcquireLeaseResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AcquireLeaseResult::create
     */
    public function testCreate()
    {
        // Setup
        $expected = '10';
        $headers = array('x-ms-lease-id' => $expected);
        
        // Test
        $result = AcquireLeaseResult::create($headers);
        
        // Assert
        $this->assertEquals($expected, $result->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\AcquireLeaseResult::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\AcquireLeaseResult::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $result = new AcquireLeaseResult();
        $result->setLeaseId($expected);
        
        // Test
        $result->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getLeaseId());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\BlobBlockType;

/**
 * Unit tests for class BlobBlockType
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobBlockTypeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobBlockType
     */
    public function testBlobBlockType()
    {
        $this->assertEquals(BlobBlockType::COMMITTED_TYPE, 'Committed');
        $this->assertEquals(BlobBlockType::UNCOMMITTED_TYPE, 'Uncommitted');
        $this->assertEquals(BlobBlockType::LATEST_TYPE, 'Latest');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\BlobPrefix;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class BlobPrefix
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobPrefixTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobPrefix::setName
     */
    public function testSetName()
    {
        // Setup
        $blob = new BlobPrefix();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setName($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobPrefix::getName
     */
    public function testGetName()
    {
        // Setup
        $blob = new BlobPrefix();
        $expected = TestResources::QUEUE1_NAME;
        $blob->setName($expected);
        
        // Test
        $actual = $blob->getName();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class BlobProperties
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobPropertiesTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::create
     */
    public function testCreate()
    {
        // Setup
        $sample = TestResources::listBlobsOneEntry();
        $expected = $sample['Blobs']['Blob']['Properties'];
        $expectedDate = Utilities::rfc1123ToDateTime($expected['Last-Modified']);
        
        // Test
        $actual = BlobProperties::create($expected);
        
        // Assert
        $this->assertEquals($expectedDate, $actual->getLastModified());
        $this->assertEquals($expected['Etag'], $actual->getETag());
        $this->assertEquals(intval($expected['Content-Length']), $actual->getContentLength());
        $this->assertEquals($expected['Content-Type'], $actual->getContentType());
        $this->assertEquals($expected['Content-Encoding'], $actual->getContentEncoding());
        $this->assertEquals($expected['Content-Language'], $actual->getContentLanguage());
        $this->assertEquals($expected['Content-MD5'], $actual->getContentMD5());
        $this->assertEquals($expected['Cache-Control'], $actual->getCacheControl());
        $this->assertEquals(intval($expected['x-ms-blob-sequence-number']), $actual->getSequenceNumber());
        $this->assertEquals($expected['BlobType'], $actual->getBlobType());
        $this->assertEquals($expected['LeaseStatus'], $actual->getLeaseStatus());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setLastModified
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $expected = Utilities::rfc1123ToDateTime('Sun, 25 Sep 2011 19:42:18 GMT');
        $properties = new BlobProperties();
        $properties->setLastModified($expected);
        
        // Test
        $properties->setLastModified($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getLastModified());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setETag
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getETag
     */
    public function testSetETag()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setETag($expected);
        
        // Test
        $properties->setETag($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getETag());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setContentRange
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getContentRange
     */
    public function testSetContentRange()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setContentRange($expected);
        
        // Test
        $properties->setContentRange($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getContentRange());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setContentType
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getContentType
     */
    public function testSetContentType()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setContentType($expected);
        
        // Test
        $properties->setContentType($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setContentLength
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getContentLength
     */
    public function testSetContentLength()
    {
        // Setup
        $expected = 100;
        $properties = new BlobProperties();
        $properties->setContentLength($expected);
        
        // Test
        $properties->setContentLength($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getContentLength());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setContentEncoding
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getContentEncoding
     */
    public function testSetContentEncoding()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setContentEncoding($expected);
        
        // Test
        $properties->setContentEncoding($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getContentEncoding());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setContentLanguage
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getContentLanguage
     */
    public function testSetContentLanguage()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setContentLanguage($expected);
        
        // Test
        $properties->setContentLanguage($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getContentLanguage());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setContentMD5
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getContentMD5
     */
    public function testSetContentMD5()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setContentMD5($expected);
        
        // Test
        $properties->setContentMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getContentMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setCacheControl
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getCacheControl
     */
    public function testSetCacheControl()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setCacheControl($expected);
        
        // Test
        $properties->setCacheControl($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getCacheControl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setBlobType
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getBlobType
     */
    public function testSetBlobType()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setBlobType($expected);
        
        // Test
        $properties->setBlobType($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getblobType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setLeaseStatus
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getLeaseStatus
     */
    public function testSetLeaseStatus()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $properties = new BlobProperties();
        $properties->setLeaseStatus($expected);
        
        // Test
        $properties->setLeaseStatus($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getLeaseStatus());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::setSequenceNumber
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobProperties::getSequenceNumber
     */
    public function testSetSequenceNumber()
    {
        // Setup
        $expected = 123;
        $properties = new BlobProperties();
        $properties->setSequenceNumber($expected);
        
        // Test
        $properties->setSequenceNumber($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getSequenceNumber());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions;

/**
 * Unit tests for class BlobServiceOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobServiceOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions::setTimeout
     */
    public function testSetTimeout()
    {
        // Setup
        $options = new BlobServiceOptions();
        $value = 10;
        
        // Test
        $options->setTimeout($value);
        
        // Assert
        $this->assertEquals($value, $options->getTimeout());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions::getTimeout
     */
    public function testGetTimeout()
    {
        // Setup
        $options = new BlobServiceOptions();
        $value = 10;
        $options->setTimeout($value);
        
        // Test
        $actualValue = $options->getTimeout();
        
        // Assert
        $this->assertEquals($value, $actualValue);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\Blob;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;

/**
 * Unit tests for class Blob
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::setName
     */
    public function testSetName()
    {
        // Setup
        $blob = new Blob();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setName($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::getName
     */
    public function testGetName()
    {
        // Setup
        $blob = new Blob();
        $expected = TestResources::QUEUE1_NAME;
        $blob->setName($expected);
        
        // Test
        $actual = $blob->getName();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::setUrl
     */
    public function testSetUrl()
    {
        // Setup
        $blob = new Blob();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setUrl($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getUrl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::getUrl
     */
    public function testGetUrl()
    {
        // Setup
        $blob = new Blob();
        $expected = TestResources::QUEUE_URI;
        $blob->setUrl($expected);
        
        // Test
        $actual = $blob->getUrl();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::setSnapshot
     */
    public function testSetSnapshot()
    {
        // Setup
        $blob = new Blob();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setSnapshot($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::getSnapshot
     */
    public function testGetSnapshot()
    {
        // Setup
        $blob = new Blob();
        $expected = TestResources::QUEUE_URI;
        $blob->setSnapshot($expected);
        
        // Test
        $actual = $blob->getSnapshot();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $blob = new Blob();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $blob->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $blob = new Blob();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $blob->setMetadata($expected);
        
        // Test
        $actual = $blob->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::setProperties
     */
    public function testSetProperties()
    {
        // Setup
        $blob = new Blob();
        $expected = new BlobProperties();
        
        // Test
        $blob->setProperties($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Blob::getProperties
     */
    public function testGetProperties()
    {
        // Setup
        $blob = new Blob();
        $expected = new BlobProperties();
        $blob->setProperties($expected);
        
        // Test
        $actual = $blob->getProperties();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\BlobType;

/**
 * Unit tests for class BlobType
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlobTypeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlobType
     */
    public function testBlobType()
    {
        $this->assertEquals(BlobType::BLOCK_BLOB, 'BlockBlob');
        $this->assertEquals(BlobType::PAGE_BLOB, 'PageBlob');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;
use MicrosoftAzure\Storage\Blob\Models\BlockList;
use MicrosoftAzure\Storage\Blob\Models\BlobBlockType;
use MicrosoftAzure\Storage\Blob\Models\Block;

/**
 * Unit tests for class BlockList
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlockListTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::addEntry
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::getEntry
     */
    public function testAddEntry()
    {
        // Setup
        $expectedId = '1234';
        $expectedType = BlobBlockType::COMMITTED_TYPE;
        $blockList = new BlockList();
        
        // Test
        $blockList->addEntry($expectedId, $expectedType);
        
        // Assert
        $entry = $blockList->getEntry($expectedId);
        $this->assertEquals($expectedType, $entry->getType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::getEntries
     */
    public function testGetEntries()
    {
        // Setup
        $expectedId = '1234';
        $expectedType = BlobBlockType::COMMITTED_TYPE;
        $blockList = new BlockList();
        $blockList->addEntry($expectedId, $expectedType);
        
        // Test
        $entries = $blockList->getEntries();
        
        // Assert
        $this->assertCount(1, $entries);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::addCommittedEntry
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::getEntry
     */
    public function testAddCommittedEntry()
    {
        // Setup
        $expectedId = '1234';
        $expectedType = BlobBlockType::COMMITTED_TYPE;
        $blockList = new BlockList();
        
        // Test
        $blockList->addCommittedEntry($expectedId, $expectedType);
        
        // Assert
        $entry = $blockList->getEntry($expectedId);
        $this->assertEquals($expectedId, $entry->getBlockId());
        $this->assertEquals($expectedType, $entry->getType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::addUncommittedEntry
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::getEntry
     */
    public function testAddUncommittedEntry()
    {
        // Setup
        $expectedId = '1234';
        $expectedType = BlobBlockType::UNCOMMITTED_TYPE;
        $blockList = new BlockList();
        
        // Test
        $blockList->addUncommittedEntry($expectedId, $expectedType);
        
        // Assert
        $entry = $blockList->getEntry($expectedId);
        $this->assertEquals($expectedId, $entry->getBlockId());
        $this->assertEquals($expectedType, $entry->getType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::addLatestEntry
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::getEntry
     */
    public function testAddLatestEntry()
    {
        // Setup
        $expectedId = '1234';
        $expectedType = BlobBlockType::LATEST_TYPE;
        $blockList = new BlockList();
        
        // Test
        $blockList->addLatestEntry($expectedId, $expectedType);
        
        // Assert
        $entry = $blockList->getEntry($expectedId);
        $this->assertEquals($expectedId, $entry->getBlockId());
        $this->assertEquals($expectedType, $entry->getType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::create
     */
    public function testCreate()
    {
        // Setup
        $block1 = new Block();
        $block1->setBlockId('123');
        $block1->setType(BlobBlockType::COMMITTED_TYPE);
        $block2 = new Block();
        $block2->setBlockId('223');
        $block2->setType(BlobBlockType::UNCOMMITTED_TYPE);
        $block3 = new Block();
        $block3->setBlockId('333');
        $block3->setType(BlobBlockType::LATEST_TYPE);
        
        // Test
        $blockList = BlockList::create(array($block1, $block2, $block3));
        
        // Assert
        $this->assertCount(3, $blockList->getEntries());
        $b1 = $blockList->getEntry($block1->getBlockId());
        $b2 = $blockList->getEntry($block2->getBlockId());
        $b3 = $blockList->getEntry($block3->getBlockId());
        $this->assertEquals($block1, $b1);
        $this->assertEquals($block2, $b2);
        $this->assertEquals($block3, $b3);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BlockList::toXml
     */
    public function testToXml()
    {
        // Setup
        $blockList = new BlockList();
        $blockList->addLatestEntry(base64_encode('1234'));
        $blockList->addCommittedEntry(base64_encode('1239'));
        $blockList->addLatestEntry(base64_encode('1236'));
        $blockList->addCommittedEntry(base64_encode('1237'));
        $blockList->addUncommittedEntry(base64_encode('1238'));
        $blockList->addLatestEntry(base64_encode('1235'));
        $blockList->addUncommittedEntry(base64_encode('1240'));
        $expected = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
                    '<BlockList>' . "\n" .
                    ' <Latest>MTIzNA==</Latest>' . "\n" .
                    ' <Committed>MTIzOQ==</Committed>' . "\n" .
                    ' <Latest>MTIzNg==</Latest>' . "\n" .
                    ' <Committed>MTIzNw==</Committed>' . "\n" .
                    ' <Uncommitted>MTIzOA==</Uncommitted>' . "\n" .
                    ' <Latest>MTIzNQ==</Latest>' . "\n" .
                    ' <Uncommitted>MTI0MA==</Uncommitted>' . "\n" .
                    '</BlockList>' . "\n";
        
        // Test
        $actual = $blockList->toXml(new XmlSerializer());
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\Block;

/**
 * Unit tests for class Block.
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BlockTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Block::setBlockId
     * @covers MicrosoftAzure\Storage\Blob\Models\Block::getBlockId
     */
    public function testSetBlockId()
    {
        // Setup
        $block = new Block();
        $expected = '1234';
        
        // Test
        $block->setBlockId($expected);
        
        // Assert
        $this->assertEquals($expected, $block->getBlockId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Block::setType
     * @covers MicrosoftAzure\Storage\Blob\Models\Block::getType
     */
    public function testSetType()
    {
        // Setup
        $block = new Block();
        $expected = 'BlockType';
        
        // Test
        $block->setType($expected);
        
        // Assert
        $this->assertEquals($expected, $block->getType());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\BreakLeaseResult;

/**
 * Unit tests for class BreakLeaseResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BreakLeaseResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BreakLeaseResult::create
     */
    public function testCreate()
    {
        // Setup
        $expected = '10';
        $headers = array('x-ms-lease-time' => $expected);
        
        // Test
        $result = BreakLeaseResult::create($headers);
        
        // Assert
        $this->assertEquals($expected, $result->getLeaseTime());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\BreakLeaseResult::setLeaseTime
     * @covers MicrosoftAzure\Storage\Blob\Models\BreakLeaseResult::getLeaseTime
     */
    public function testSetLeaseTime()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $result = new BreakLeaseResult();
        $result->setLeaseTime($expected);
        
        // Test
        $result->setLeaseTime($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getLeaseTime());
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;

/**
 * Unit tests for class CommitBlobBlocksOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CommitBlobBlocksOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::setBlobContentType
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::getBlobContentType
     */
    public function testSetBlobContentType()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CommitBlobBlocksOptions();
        $options->setBlobContentType($expected);
        
        // Test
        $options->setBlobContentType($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::setBlobContentEncoding
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::getBlobContentEncoding
     */
    public function testSetBlobContentEncoding()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CommitBlobBlocksOptions();
        $options->setBlobContentEncoding($expected);
        
        // Test
        $options->setBlobContentEncoding($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentEncoding());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::setBlobContentLanguage
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::getBlobContentLanguage
     */
    public function testSetBlobContentLanguage()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CommitBlobBlocksOptions();
        $options->setBlobContentLanguage($expected);
        
        // Test
        $options->setBlobContentLanguage($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentLanguage());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::setBlobContentMD5
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::getBlobContentMD5
     */
    public function testSetBlobContentMD5()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CommitBlobBlocksOptions();
        $options->setBlobContentMD5($expected);
        
        // Test
        $options->setBlobContentMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::setBlobCacheControl
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::getBlobCacheControl
     */
    public function testSetBlobCacheControl()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CommitBlobBlocksOptions();
        $options->setBlobCacheControl($expected);
        
        // Test
        $options->setBlobCacheControl($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobCacheControl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CommitBlobBlocksOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $container = new CommitBlobBlocksOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $container->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $container = new CommitBlobBlocksOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $container->setMetadata($expected);
        
        // Test
        $actual = $container->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new CommitBlobBlocksOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CommitBlobBlocksOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new CommitBlobBlocksOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ContainerAcl;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * Unit tests for class ContainerAcl
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ContainerAclTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::create
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::getPublicAccess
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::getSignedIdentifiers
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::addSignedIdentifier
     */
    public function testCreateEmpty()
    {
        // Setup
        $sample = Resources::EMPTY_STRING;
        $expectedPublicAccess = 'container';
        
        // Test
        $acl = ContainerAcl::create($expectedPublicAccess, $sample);
        
        // Assert
        $this->assertEquals($expectedPublicAccess, $acl->getPublicAccess());
        $this->assertCount(0, $acl->getSignedIdentifiers());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::create
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::getPublicAccess
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::getSignedIdentifiers
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::addSignedIdentifier
     */
    public function testCreateOneEntry()
    {
        // Setup
        $sample = TestResources::getContainerAclOneEntrySample();
        $expectedPublicAccess = 'container';
        
        // Test
        $acl = ContainerAcl::create($expectedPublicAccess, $sample['SignedIdentifiers']);
        
        // Assert
        $this->assertEquals($expectedPublicAccess, $acl->getPublicAccess());
        $this->assertCount(1, $acl->getSignedIdentifiers());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::create
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::getPublicAccess
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::getSignedIdentifiers
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::addSignedIdentifier
     */
    public function testCreateMultipleEntries()
    {
        // Setup
        $sample = TestResources::getContainerAclMultipleEntriesSample();
        $expectedPublicAccess = 'container';
        
        // Test
        $acl = ContainerAcl::create($expectedPublicAccess, $sample['SignedIdentifiers']);
        
        // Assert
        $this->assertEquals($expectedPublicAccess, $acl->getPublicAccess());
        $this->assertCount(2, $acl->getSignedIdentifiers());
        
        return $acl;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::setSignedIdentifiers
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::getSignedIdentifiers
     */
    public function testSetSignedIdentifiers()
    {
        // Setup
        $sample = TestResources::getContainerAclOneEntrySample();
        $expectedPublicAccess = 'container';
        $acl = ContainerAcl::create($expectedPublicAccess, $sample['SignedIdentifiers']);
        $expected = $acl->getSignedIdentifiers();
        $expected[0]->setId('newXid');
        
        // Test
        $acl->setSignedIdentifiers($expected);
        
        // Assert
        $this->assertEquals($expectedPublicAccess, $acl->getPublicAccess());
        $this->assertEquals($expected, $acl->getSignedIdentifiers());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::setPublicAccess
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::getPublicAccess
     */
    public function testSetPublicAccess()
    {
        // Setup
        $expected = 'container';
        $acl = new ContainerAcl();
        $acl->setPublicAccess($expected);
        
        // Test
        $acl->setPublicAccess($expected);
        
        // Assert
        $this->assertEquals($expected, $acl->getPublicAccess());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::toXml
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerAcl::toArray
     * @depends testCreateMultipleEntries
     */
    public function testToXml($acl)
    {
        // Setup
        $sample = TestResources::getContainerAclMultipleEntriesSample();
        $expected = ContainerAcl::create('container', $sample['SignedIdentifiers']);
        $xmlSerializer = new XmlSerializer();
        
        // Test
        $xml = $acl->toXml($xmlSerializer);
        
        // Assert
        $array = Utilities::unserialize($xml);
        $acl = ContainerAcl::create('container', $array);
        $this->assertEquals($expected->getSignedIdentifiers(), $acl->getSignedIdentifiers());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ContainerProperties;

/**
 * Unit tests for class ContainerProperties
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ContainerPropertiesTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerProperties::getETag
     */
    public function testGetETag()
    {
        // Setup
        $properties = new ContainerProperties();
        $expected = '0x8CACB9BD7C6B1B2';
        $properties->setETag($expected);
        
        // Test
        $actual = $properties->getETag();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerProperties::setETag
     */
    public function testSetETag()
    {
        // Setup
        $properties = new ContainerProperties();
        $expected = '0x8CACB9BD7C6B1B2';
        
        // Test
        $properties->setETag($expected);
        
        // Assert
        $actual = $properties->getETag();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerProperties::getLastModified
     */
    public function testGetLastModified()
    {
        // Setup
        $properties = new ContainerProperties();
        $expected = 'Fri, 09 Oct 2009 21:04:30 GMT';
        $properties->setLastModified($expected);
        
        // Test
        $actual = $properties->getLastModified();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ContainerProperties::setLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $properties = new ContainerProperties();
        $expected = 'Fri, 09 Oct 2009 21:04:30 GMT';
        
        // Test
        $properties->setLastModified($expected);
        
        // Assert
        $actual = $properties->getLastModified();
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\Container;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\ContainerProperties;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class Container
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ContainerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Container::setName
     */
    public function testSetName()
    {
        // Setup
        $container = new Container();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $container->setName($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Container::getName
     */
    public function testGetName()
    {
        // Setup
        $container = new Container();
        $expected = TestResources::QUEUE1_NAME;
        $container->setName($expected);
        
        // Test
        $actual = $container->getName();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Container::setUrl
     */
    public function testSetUrl()
    {
        // Setup
        $container = new Container();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $container->setUrl($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getUrl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Container::getUrl
     */
    public function testGetUrl()
    {
        // Setup
        $container = new Container();
        $expected = TestResources::QUEUE_URI;
        $container->setUrl($expected);
        
        // Test
        $actual = $container->getUrl();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Container::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $container = new Container();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $container->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Container::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $container = new Container();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $container->setMetadata($expected);
        
        // Test
        $actual = $container->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Container::setProperties
     */
    public function testSetProperties()
    {
        // Setup
        $date = Utilities::rfc1123ToDateTime('Wed, 12 Aug 2009 20:39:39 GMT');
        $container = new Container();
        $expected = new ContainerProperties();
        $expected->setETag('0x8CACB9BD7C1EEEC');
        $expected->setLastModified($date);
        
        // Test
        $container->setProperties($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\Container::getProperties
     */
    public function testGetProperties()
    {
        // Setup
        $date = Utilities::rfc1123ToDateTime('Wed, 12 Aug 2009 20:39:39 GMT');
        $container = new Container();
        $expected = new ContainerProperties();
        $expected->setETag('0x8CACB9BD7C1EEEC');
        $expected->setLastModified($date);
        $container->setProperties($expected);
        
        // Test
        $actual = $container->getProperties();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions;

/**
 * Unit tests for class CopyBlobBlobOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CopyBlobOptionsTest extends \PHPUnit_Framework_TestCase
{  
    /** 
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::setMetadata
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::getMetadata
     */
    public function testSetMetadata()
    {
        $copyBlobOptions = new CopyBlobOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $copyBlobOptions->setMetadata($expected);
        
        $this->assertEquals(
            $expected,
            $copyBlobOptions->getMetadata()
            );
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::setAccessCondition
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::getAccessCondition
     */
    public function testSetAccessCondition()
    {
        $copyBlobOptions = new CopyBlobOptions();
        $expected = AccessCondition::ifMatch("12345");
        $copyBlobOptions->setAccessCondition($expected);
        
        $this->assertEquals(
            $expected,
            $copyBlobOptions->getAccessCondition()
        );
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::setSourceAccessCondition
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::getSourceAccessCondition
     */
    public function testSetSourceAccessCondition()
    {
        $copyBlobOptions = new CopyBlobOptions();
        $expected = AccessCondition::IfMatch("x");
        $copyBlobOptions->setSourceAccessCondition($expected);
        
        $this->assertEquals(
            $expected,
            $copyBlobOptions->getSourceAccessCondition()
         );
    }
    
    /** 
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        $expected = '0x8CAFB82EFF70C46';
        $options = new CopyBlobOptions();
        
        $options->setLeaseId($expected);
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /** 
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::setSourceLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobOptions::getSourceLeaseId
     */
    public function testSetSourceLeaseId()
    {
        $expected = '0x8CAFB82EFF70C46';
        $options = new CopyBlobOptions();
        
        $options->setSourceLeaseId($expected);
        $this->assertEquals($expected, $options->getSourceLeaseId());
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\CopyBlobResult;

/**
 * Unit tests for class SnapshotBlobResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CopyBlobResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobResult::getETag
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobResult::setETag
     */
    public function testSetETag()
    {
        $createBlobSnapshotResult = new CopyBlobResult();
        $expected = "12345678";
        $createBlobSnapshotResult->setETag($expected);
        
        $this->assertEquals(
            $expected,
            $createBlobSnapshotResult->getETag()
            );
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobResult::getLastModified
     * @covers MicrosoftAzure\Storage\Blob\Models\CopyBlobResult::setLastModified
     */
    public function testSetLastModified()
    {
        $createBlobSnapshotResult = new CopyBlobResult();
        $expected = new \DateTime("2008-8-8");
        $createBlobSnapshotResult->setLastModified($expected);
        
        $this->assertEquals(
            $expected,
            $createBlobSnapshotResult->getLastModified()
            );
        
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobBlockOptions;

/**
 * Unit tests for class CreateBlobBlockOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobBlockOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobBlockOptions::setContentMD5
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobBlockOptions::getContentMD5
     */
    public function testSetContentMD5()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobBlockOptions();
        $options->setContentMD5($expected);
        
        // Test
        $options->setContentMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getContentMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobBlockOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobBlockOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobBlockOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;

/**
 * Unit tests for class CreateBlobOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setContentType
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getContentType
     */
    public function testSetContentType()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setContentType($expected);
        
        // Test
        $options->setContentType($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setContentEncoding
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getContentEncoding
     */
    public function testSetContentEncoding()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setContentEncoding($expected);
        
        // Test
        $options->setContentEncoding($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getContentEncoding());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setContentLanguage
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getContentLanguage
     */
    public function testSetContentLanguage()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setContentLanguage($expected);
        
        // Test
        $options->setContentLanguage($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getContentLanguage());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setContentMD5
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getContentMD5
     */
    public function testSetContentMD5()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setContentMD5($expected);
        
        // Test
        $options->setContentMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getContentMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setCacheControl
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getCacheControl
     */
    public function testSetCacheControl()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setCacheControl($expected);
        
        // Test
        $options->setCacheControl($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getCacheControl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setBlobContentType
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getBlobContentType
     */
    public function testSetBlobContentType()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setBlobContentType($expected);
        
        // Test
        $options->setBlobContentType($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setBlobContentEncoding
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getBlobContentEncoding
     */
    public function testSetBlobContentEncoding()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setBlobContentEncoding($expected);
        
        // Test
        $options->setBlobContentEncoding($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentEncoding());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setBlobContentLanguage
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getBlobContentLanguage
     */
    public function testSetBlobContentLanguage()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setBlobContentLanguage($expected);
        
        // Test
        $options->setBlobContentLanguage($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentLanguage());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setBlobContentMD5
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getBlobContentMD5
     */
    public function testSetBlobContentMD5()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setBlobContentMD5($expected);
        
        // Test
        $options->setBlobContentMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setBlobCacheControl
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getBlobCacheControl
     */
    public function testSetBlobCacheControl()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setBlobCacheControl($expected);
        
        // Test
        $options->setBlobCacheControl($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobCacheControl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setSequenceNumber
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getSequenceNumber
     */
    public function testSetSequenceNumber()
    {
        // Setup
        $expected = 123;
        $options = new CreateBlobOptions();
        $options->setSequenceNumber($expected);
        
        // Test
        $options->setSequenceNumber($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getSequenceNumber());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $container = new CreateBlobOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $container->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $container = new CreateBlobOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $container->setMetadata($expected);
        
        // Test
        $actual = $container->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new CreateBlobOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new CreateBlobOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesOptions;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;

/**
 * Unit tests for class CreateBlobPagesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobPagesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $options = new CreateBlobPagesOptions();
        $options->setAccessCondition($expected);
        
        // Test
        $actual = $options->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $options = new CreateBlobPagesOptions();
        
        // Test
        $options->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getAccessCondition());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesOptions::setContentMD5
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesOptions::getContentMD5
     */
    public function testSetContentMD5()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobPagesOptions();
        $options->setContentMD5($expected);
        
        // Test
        $options->setContentMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getContentMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobPagesOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult;


/**
 * Unit tests for class CreateBlobPagesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobPagesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::create
     */
    public function testCreate()
    {
        // Setup
        $sample = TestResources::listBlobsOneEntry();
        $expected = $sample['Blobs']['Blob']['Properties'];
        $expectedDate = Utilities::rfc1123ToDateTime($expected['Last-Modified']);
        
        // Test
        $actual = CreateBlobPagesResult::create($expected);
        
        // Assert
        $this->assertEquals($expectedDate, $actual->getLastModified());
        $this->assertEquals($expected['Etag'], $actual->getETag());
        $this->assertEquals($expected['Content-MD5'], $actual->getContentMD5());
        $this->assertEquals(intval($expected['x-ms-blob-sequence-number']), $actual->getSequenceNumber());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::setLastModified
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::getLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $expected = Utilities::rfc1123ToDateTime('Sun, 25 Sep 2011 19:42:18 GMT');
        $options = new CreateBlobPagesResult();
        $options->setLastModified($expected);
        
        // Test
        $options->setLastModified($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLastModified());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::setETag
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::getETag
     */
    public function testSetETag()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobPagesResult();
        $options->setETag($expected);
        
        // Test
        $options->setETag($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getETag());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::setContentMD5
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::getContentMD5
     */
    public function testSetContentMD5()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new CreateBlobPagesResult();
        $options->setContentMD5($expected);
        
        // Test
        $options->setContentMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getContentMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::setSequenceNumber
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobPagesResult::getSequenceNumber
     */
    public function testSetSequenceNumber()
    {
        // Setup
        $expected = 123;
        $options = new CreateBlobPagesResult();
        $options->setSequenceNumber($expected);
        
        // Test
        $options->setSequenceNumber($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getSequenceNumber());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions;

/**
 * Unit tests for class CreateBlobSnapshotOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobSnapshotOptionsTest extends \PHPUnit_Framework_TestCase
{  
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions::setMetadata
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions::getMetadata
     */
    public function testSetMetadata()
    {
        $createBlobSnapshotOptions = new CreateBlobSnapshotOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $createBlobSnapshotOptions->setMetadata($expected);
        
        $this->assertEquals(
            $expected,
            $createBlobSnapshotOptions->getMetadata()
        );
    }
    
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        $createBlobSnapshotOptions = new CreateBlobSnapshotOptions();
        $expected = "123456789";
        $createBlobSnapshotOptions->setLeaseId($expected);
        
        $this->assertEquals(
            $expected,
            $createBlobSnapshotOptions->getLeaseId()
        );
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;

/**
 * Unit tests for class SnapshotBlobResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateBlobSnapshotResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult::getSnapshot
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult::setSnapshot
     */
    public function testSetSnapshot()
    {
        $createBlobSnapshotResult = new CreateBlobSnapshotResult();
        $expected = new \DateTime("2008-8-8");
        $createBlobSnapshotResult->setSnapshot($expected);
        
        $this->assertEquals(
            $expected,
            $createBlobSnapshotResult->getSnapshot()
            );
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult::getETag
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult::setETag
     */
    public function testSetETag()
    {
        $createBlobSnapshotResult = new CreateBlobSnapshotResult();
        $expected = "12345678";
        $createBlobSnapshotResult->setETag($expected);
        
        $this->assertEquals(
            $expected,
            $createBlobSnapshotResult->getETag()
            );
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult::getLastModified
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateBlobSnapshotResult::setLastModified
     */
    public function testSetLastModified()
    {
        $createBlobSnapshotResult = new CreateBlobSnapshotResult();
        $expected = new \DateTime("2008-8-8");
        $createBlobSnapshotResult->setLastModified($expected);
        
        $this->assertEquals(
            $expected,
            $createBlobSnapshotResult->getLastModified()
            );
        
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;

/**
 * Unit tests for class CreateContainerOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateContainerOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions::getPublicAccess
     */
    public function testGetPublicAccess()
    {
        // Setup
        $properties = new CreateContainerOptions();
        $expected = 'blob';
        $properties->setPublicAccess($expected);
        
        // Test
        $actual = $properties->getPublicAccess();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions::setPublicAccess
     */
    public function testSetPublicAccess()
    {
        // Setup
        $properties = new CreateContainerOptions();
        $expected = 'container';
        
        // Test
        $properties->setPublicAccess($expected);
        
        // Assert
        $actual = $properties->getPublicAccess();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions::setPublicAccess
     */
    public function testSetPublicAccessInvalidValueFail()
    {
        // Setup
        $properties = new CreateContainerOptions();
        $expected = new \DateTime();
        $this->setExpectedException(get_class(new InvalidArgumentTypeException('')));
        
        // Test
        $properties->setPublicAccess($expected);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $container = new CreateContainerOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $container->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $container = new CreateContainerOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $container->setMetadata($expected);
        
        // Test
        $actual = $container->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions::addMetadata
     */
    public function testAddMetadata()
    {
        // Setup
        $container = new CreateContainerOptions();
        $key = 'key1';
        $value = 'value1';
        $expected = array($key => $value);
        
        // Test
        $container->addMetadata($key, $value);
        
        // Assert
        $this->assertEquals($expected, $container->getMetadata());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions;

/**
 * Unit tests for class DeleteBlobOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DeleteBlobOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new DeleteBlobOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new DeleteBlobOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new DeleteBlobOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions::setSnapshot
     */
    public function testSetSnapshot()
    {
        // Setup
        $blob = new DeleteBlobOptions();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setSnapshot($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions::getSnapshot
     */
    public function testGetSnapshot()
    {
        // Setup
        $blob = new DeleteBlobOptions();
        $expected = TestResources::QUEUE_URI;
        $blob->setSnapshot($expected);
        
        // Test
        $actual = $blob->getSnapshot();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions::setDeleteSnaphotsOnly
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteBlobOptions::getDeleteSnaphotsOnly
     */
    public function testSetDeleteSnaphotsOnly()
    {
        // Setup
        $expected = true;
        $options = new DeleteBlobOptions();
        $options->setDeleteSnaphotsOnly($expected);
        
        // Test
        $options->setDeleteSnaphotsOnly($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getDeleteSnaphotsOnly());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\DeleteContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;

/**
 * Unit tests for class DeleteContainerOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DeleteContainerOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteContainerOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $options = new DeleteContainerOptions();
        $options->setAccessCondition($expected);
        
        // Test
        $actual = $options->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\DeleteContainerOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $options = new DeleteContainerOptions();
        
        // Test
        $options->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getAccessCondition());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions;

/**
 * Unit tests for class GetBlobMetadataOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobMetadataOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new GetBlobMetadataOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new GetBlobMetadataOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new GetBlobMetadataOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions::setSnapshot
     */
    public function testSetSnapshot()
    {
        // Setup
        $blob = new GetBlobMetadataOptions();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setSnapshot($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataOptions::getSnapshot
     */
    public function testGetSnapshot()
    {
        // Setup
        $blob = new GetBlobMetadataOptions();
        $expected = TestResources::QUEUE_URI;
        $blob->setSnapshot($expected);
        
        // Test
        $actual = $blob->getSnapshot();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult;

/**
 * Unit tests for class GetBlobMetadataResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobMetadataResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult::getETag
     */
    public function testGetETag()
    {
        // Setup
        $getBlobMetadataResult = new GetBlobMetadataResult();
        $expected = '0x8CACB9BD7C6B1B2';
        $getBlobMetadataResult->setETag($expected);
        
        // Test
        $actual = $getBlobMetadataResult->getETag();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult::setETag
     */
    public function testSetETag()
    {
        // Setup
        $getBlobMetadataResult = new GetBlobMetadataResult();
        $expected = '0x8CACB9BD7C6B1B2';
        
        // Test
        $getBlobMetadataResult->setETag($expected);
        
        // Assert
        $actual = $getBlobMetadataResult->getETag();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult::getLastModified
     */
    public function testGetLastModified()
    {
        // Setup
        $getBlobMetadataResult = new GetBlobMetadataResult();
        $expected = Utilities::rfc1123ToDateTime('Fri, 09 Oct 2009 21:04:30 GMT');
        $getBlobMetadataResult->setLastModified($expected);
        
        // Test
        $actual = $getBlobMetadataResult->getLastModified();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult::setLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $getBlobMetadataResult = new GetBlobMetadataResult();
        $expected = Utilities::rfc1123ToDateTime('Fri, 09 Oct 2009 21:04:30 GMT');
        
        // Test
        $getBlobMetadataResult->setLastModified($expected);
        
        // Assert
        $actual = $getBlobMetadataResult->getLastModified();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $container = new GetBlobMetadataResult();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $container->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobMetadataResult::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $container = new GetBlobMetadataResult();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $container->setMetadata($expected);
        
        // Test
        $actual = $container->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\GetBlobOptions;

/**
 * Unit tests for class GetBlobOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new GetBlobOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new GetBlobOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new GetBlobOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::setSnapshot
     */
    public function testSetSnapshot()
    {
        // Setup
        $blob = new GetBlobOptions();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setSnapshot($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::getSnapshot
     */
    public function testGetSnapshot()
    {
        // Setup
        $blob = new GetBlobOptions();
        $expected = TestResources::QUEUE_URI;
        $blob->setSnapshot($expected);
        
        // Test
        $actual = $blob->getSnapshot();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::setRangeStart
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::getRangeStart
     */
    public function testSetRangeStart()
    {
        // Setup
        $expected = 123;
        $prooperties = new GetBlobOptions();
        $prooperties->setRangeStart($expected);
        
        // Test
        $prooperties->setRangeStart($expected);
        
        // Assert
        $this->assertEquals($expected, $prooperties->getRangeStart());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::setRangeEnd
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::getRangeEnd
     */
    public function testSetRangeEnd()
    {
        // Setup
        $expected = 123;
        $prooperties = new GetBlobOptions();
        $prooperties->setRangeEnd($expected);
        
        // Test
        $prooperties->setRangeEnd($expected);
        
        // Assert
        $this->assertEquals($expected, $prooperties->getRangeEnd());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::setComputeRangeMD5
     */
    public function testSetComputeRangeMD5()
    {
        // Setup
        $options = new GetBlobOptions();
        $expected = true;
        
        // Test
        $options->setComputeRangeMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getComputeRangeMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobOptions::getComputeRangeMD5
     */
    public function testGetComputeRangeMD5()
    {
        // Setup
        $options = new GetBlobOptions();
        $expected = true;
        $options->setComputeRangeMD5($expected);
        
        // Test
        $actual = $options->getComputeRangeMD5();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions;

/**
 * Unit tests for class GetBlobPropertiesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobPropertiesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new GetBlobPropertiesOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new GetBlobPropertiesOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new GetBlobPropertiesOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions::setSnapshot
     */
    public function testSetSnapshot()
    {
        // Setup
        $blob = new GetBlobPropertiesOptions();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setSnapshot($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesOptions::getSnapshot
     */
    public function testGetSnapshot()
    {
        // Setup
        $blob = new GetBlobPropertiesOptions();
        $expected = TestResources::QUEUE_URI;
        $blob->setSnapshot($expected);
        
        // Test
        $actual = $blob->getSnapshot();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesResult;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;

/**
 * Unit tests for class GetBlobPropertiesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobPropertiesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesResult::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $properties = new GetBlobPropertiesResult();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $properties->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesResult::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $properties = new GetBlobPropertiesResult();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $properties->setMetadata($expected);
        
        // Test
        $actual = $properties->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesResult::setProperties
     */
    public function testSetProperties()
    {
        // Setup
        $properties = new GetBlobPropertiesResult();
        $expected = new BlobProperties();
        
        // Test
        $properties->setProperties($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesResult::getProperties
     */
    public function testGetProperties()
    {
        // Setup
        $properties = new GetBlobPropertiesResult();
        $expected = new BlobProperties();
        $properties->setProperties($expected);
        
        // Test
        $actual = $properties->getProperties();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\GetBlobResult;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;

/**
 * Unit tests for class GetBlobResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetBlobResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $properties = new GetBlobResult();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $properties->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $properties = new GetBlobResult();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $properties->setMetadata($expected);
        
        // Test
        $actual = $properties->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::setProperties
     */
    public function testSetProperties()
    {
        // Setup
        $properties = new GetBlobResult();
        $expected = new BlobProperties();
        
        // Test
        $properties->setProperties($expected);
        
        // Assert
        $this->assertEquals($expected, $properties->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::getProperties
     */
    public function testGetProperties()
    {
        // Setup
        $properties = new GetBlobResult();
        $expected = new BlobProperties();
        $properties->setProperties($expected);
        
        // Test
        $actual = $properties->getProperties();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::setContentStream
     * @covers MicrosoftAzure\Storage\Blob\Models\GetBlobResult::getContentStream
     */
    public function testSetContentStream()
    {
        // Setup
        $expected = Utilities::stringToStream('0x8CAFB82EFF70C46');
        $result = new GetBlobResult();
        
        // Test
        $result->setContentStream($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getContentStream());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\GetContainerAclResult;
use MicrosoftAzure\Storage\Blob\Models\ContainerAcl;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class GetContainerAclResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetContainerAclResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerAclResult::create
     */
    public function testCreate()
    {
        // Setup
        $sample = Resources::EMPTY_STRING;
        $expectedETag = '0x8CAFB82EFF70C46';
        $expectedDate = new \DateTime('Sun, 25 Sep 2011 19:42:18 GMT');
        $expectedPublicAccess = 'container';
        
        // Test
        $result = GetContainerAclResult::create($expectedPublicAccess, $expectedETag, 
            $expectedDate, $sample);
        
        // Assert
        $obj = $result->getContainerAcl();
        $this->assertEquals($expectedPublicAccess, $obj->getPublicAccess());
        $this->assertCount(0, $obj->getSignedIdentifiers());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerAclResult::getContainerAcl
     */
    public function testGetContainerAcl()
    {
        // Setup
        $expected = new ContainerAcl();
        $obj = new GetContainerAclResult();
        
        // Test
        $obj->setContainerAcl($expected);
        
        // Assert
        $this->assertCount(0, $obj->getContainerAcl()->getSignedIdentifiers());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerAclResult::setContainerAcl
     */
    public function testSetContainerAcl()
    {
        // Setup
        $expected = new ContainerAcl();
        $obj = new GetContainerAclResult();
        $obj->setContainerAcl($expected);
        
        // Test
        $actual = $obj->getContainerAcl();
        
        // Assert
        $this->assertCount(0, $actual->getSignedIdentifiers());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerAclResult::setLastModified
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerAclResult::getLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $expected = new \DateTime('Sun, 25 Sep 2011 19:42:18 GMT');
        $obj = new GetContainerAclResult();
        $obj->setLastModified($expected);
        
        // Test
        $obj->setLastModified($expected);
        
        // Assert
        $this->assertEquals($expected, $obj->getLastModified());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerAclResult::setETag
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerAclResult::getETag
     */
    public function testSetETag()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $obj = new GetContainerAclResult();
        $obj->setETag($expected);
        
        // Test
        $obj->setETag($expected);
        
        // Assert
        $this->assertEquals($expected, $obj->getETag());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult;

/**
 * Unit tests for class GetContainerPropertiesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetContainerPropertiesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult::getETag
     */
    public function testGetETag()
    {
        // Setup
        $properties = new GetContainerPropertiesResult();
        $expected = '0x8CACB9BD7C6B1B2';
        $properties->setETag($expected);
        
        // Test
        $actual = $properties->getETag();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult::setETag
     */
    public function testSetETag()
    {
        // Setup
        $properties = new GetContainerPropertiesResult();
        $expected = '0x8CACB9BD7C6B1B2';
        
        // Test
        $properties->setETag($expected);
        
        // Assert
        $actual = $properties->getETag();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult::getLastModified
     */
    public function testGetLastModified()
    {
        // Setup
        $properties = new GetContainerPropertiesResult();
        $expected = 'Fri, 09 Oct 2009 21:04:30 GMT';
        $properties->setLastModified($expected);
        
        // Test
        $actual = $properties->getLastModified();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult::setLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $properties = new GetContainerPropertiesResult();
        $expected = 'Fri, 09 Oct 2009 21:04:30 GMT';
        
        // Test
        $properties->setLastModified($expected);
        
        // Assert
        $actual = $properties->getLastModified();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $container = new GetContainerPropertiesResult();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $container->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $container->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\GetContainerPropertiesResult::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $container = new GetContainerPropertiesResult();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $container->setMetadata($expected);
        
        // Test
        $actual = $container->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\LeaseMode;

/**
 * Unit tests for class LeaseMode
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class LeaseModeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\LeaseMode
     */
    public function testLeaseMode()
    {
        $this->assertEquals(LeaseMode::ACQUIRE_ACTION, 'acquire');
        $this->assertEquals(LeaseMode::BREAK_ACTION, 'break');
        $this->assertEquals(LeaseMode::RELEASE_ACTION, 'release');
        $this->assertEquals(LeaseMode::RENEW_ACTION, 'renew');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions;

/**
 * Unit tests for class ListBlobBlocksOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListBlobBlocksOptionsTest extends \PHPUnit_Framework_TestCase
{   
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::setSnapshot
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::__construct
     */
    public function testSetSnapshot()
    {
        // Setup
        $blob = new ListBlobBlocksOptions();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setSnapshot($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::getSnapshot
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::__construct
     */
    public function testGetSnapshot()
    {
        // Setup
        $blob = new ListBlobBlocksOptions();
        $expected = TestResources::QUEUE_URI;
        $blob->setSnapshot($expected);
        
        // Test
        $actual = $blob->getSnapshot();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::getLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::__construct
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new ListBlobBlocksOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::setIncludeUncommittedBlobs
     */
    public function testSetIncludeUncommittedBlobs()
    {
        // Setup
        $options = new ListBlobBlocksOptions();
        $expected = true;
        
        // Test
        $options->setIncludeUncommittedBlobs($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getIncludeUncommittedBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::getIncludeUncommittedBlobs
     */
    public function testGetIncludeUncommittedBlobs()
    {
        // Setup
        $options = new ListBlobBlocksOptions();
        $expected = true;
        $options->setIncludeUncommittedBlobs($expected);
        
        // Test
        $actual = $options->getIncludeUncommittedBlobs();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::setIncludeCommittedBlobs
     */
    public function testSetIncludeCommittedBlobs()
    {
        // Setup
        $options = new ListBlobBlocksOptions();
        $expected = true;
        
        // Test
        $options->setIncludeCommittedBlobs($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getIncludeCommittedBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::getIncludeCommittedBlobs
     */
    public function testGetIncludeCommittedBlobs()
    {
        // Setup
        $options = new ListBlobBlocksOptions();
        $expected = true;
        $options->setIncludeCommittedBlobs($expected);
        
        // Test
        $actual = $options->getIncludeCommittedBlobs();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksOptions::getBlockListType
     */
    public function testGetBlockListType()
    {
        // Setup
        $options = new ListBlobBlocksOptions();
        $expected = 'all';
        
        // Test
        $actual = $options->getBlockListType();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class ListBlobBlocksResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListBlobBlocksResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::setLastModified
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $expected = Utilities::rfc1123ToDateTime('Sun, 25 Sep 2011 19:42:18 GMT');
        $result = new ListBlobBlocksResult();
        $result->setLastModified($expected);
        
        // Test
        $result->setLastModified($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getLastModified());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::setETag
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getETag
     */
    public function testSetETag()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $result = new ListBlobBlocksResult();
        $result->setETag($expected);
        
        // Test
        $result->setETag($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getETag());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::setContentType
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getContentType
     */
    public function testSetContentType()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $result = new ListBlobBlocksResult();
        $result->setContentType($expected);
        
        // Test
        $result->setContentType($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::setContentLength
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getContentLength
     */
    public function testSetContentLength()
    {
        // Setup
        $expected = 100;
        $result = new ListBlobBlocksResult();
        $result->setContentLength($expected);
        
        // Test
        $result->setContentLength($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getContentLength());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::setUncommittedBlocks
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getUncommittedBlocks
     */
    public function testSetUncommittedBlocks()
    {
        // Setup
        $result = new ListBlobBlocksResult();
        $expected = array('Block1' => 10, 'Block2' => 20, 'Block3' => 30);
        
        // Test
        $result->setUncommittedBlocks($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getUncommittedBlocks());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::setCommittedBlocks
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobBlocksResult::getCommittedBlocks
     */
    public function testSetCommittedBlocks()
    {
        // Setup
        $result = new ListBlobBlocksResult();
        $expected = array('Block1' => 10, 'Block2' => 20, 'Block3' => 30);
        
        // Test
        $result->setCommittedBlocks($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getCommittedBlocks());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class ListBlobsOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListBlobsOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::setPrefix
     */
    public function testSetPrefix()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = 'myprefix';
        
        // Test
        $options->setPrefix($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getPrefix());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::getPrefix
     */
    public function testGetPrefix()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = 'myprefix';
        $options->setPrefix($expected);
        
        // Test
        $actual = $options->getPrefix();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::setDelimiter
     */
    public function testSetDelimiter()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = 'mydelimiter';
        
        // Test
        $options->setDelimiter($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getDelimiter());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::getDelimiter
     */
    public function testGetDelimiter()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = 'mydelimiter';
        $options->setDelimiter($expected);
        
        // Test
        $actual = $options->getDelimiter();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::setMarker
     */
    public function testSetMarker()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = 'mymarker';
        
        // Test
        $options->setMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::getMarker
     */
    public function testGetMarker()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = 'mymarker';
        $options->setMarker($expected);
        
        // Test
        $actual = $options->getMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::setMaxResults
     */
    public function testSetMaxResults()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = 3;
        
        // Test
        $options->setMaxResults($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getMaxResults());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::getMaxResults
     */
    public function testGetMaxResults()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = 3;
        $options->setMaxResults($expected);
        
        // Test
        $actual = $options->getMaxResults();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::setIncludeMetadata
     */
    public function testSetIncludeMetadata()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = true;
        
        // Test
        $options->setIncludeMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getIncludeMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::getIncludeMetadata
     */
    public function testGetIncludeMetadata()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = true;
        $options->setIncludeMetadata($expected);
        
        // Test
        $actual = $options->getIncludeMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::setIncludeSnapshots
     */
    public function testSetIncludeSnapshots()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = true;
        
        // Test
        $options->setIncludeSnapshots($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getIncludeSnapshots());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::getIncludeSnapshots
     */
    public function testGetIncludeSnapshots()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = true;
        $options->setIncludeSnapshots($expected);
        
        // Test
        $actual = $options->getIncludeSnapshots();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::setIncludeUncommittedBlobs
     */
    public function testSetIncludeUncommittedBlobs()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = true;
        
        // Test
        $options->setIncludeUncommittedBlobs($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getIncludeUncommittedBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions::getIncludeUncommittedBlobs
     */
    public function testGetIncludeUncommittedBlobs()
    {
        // Setup
        $options = new ListBlobsOptions();
        $expected = true;
        $options->setIncludeUncommittedBlobs($expected);
        
        // Test
        $actual = $options->getIncludeUncommittedBlobs();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsResult;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class ListBlobsResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListBlobsResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::create 
     */
    public function testCreateWithEmpty()
    {
        // Setup
        $sample = TestResources::listBlobsEmpty();
        
        // Test
        $actual = ListBlobsResult::create($sample);
        
        // Assert
        $this->assertCount(0, $actual->getBlobs());
        $this->assertCount(0, $actual->getBlobPrefixes());
        $this->assertEquals(0,$actual->getMaxResults());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::create 
     */
    public function testCreateWithOneEntry()
    {
        // Setup
        $sample = TestResources::listBlobsOneEntry();
        
        // Test
        $actual = ListBlobsResult::create($sample);
        
        // Assert
        $this->assertCount(1, $actual->getBlobs());
        $this->assertCount(1, $actual->getBlobPrefixes());
        $this->assertEquals($sample['Marker'], $actual->getMarker());
        $this->assertEquals(intval($sample['MaxResults']), $actual->getMaxResults());
        $this->assertEquals($sample['NextMarker'], $actual->getNextMarker());
        $this->assertEquals($sample['Delimiter'], $actual->getDelimiter());
        $this->assertEquals($sample['Prefix'], $actual->getPrefix());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::create 
     */
    public function testCreateWithMultipleEntries()
    {
        // Setup
        $sample = TestResources::listBlobsMultipleEntries();
        
        // Test
        $actual = ListBlobsResult::create($sample);
        
        // Assert
        $this->assertCount(2, $actual->getBlobs());
        $this->assertCount(2, $actual->getBlobPrefixes());
        $this->assertEquals($sample['Marker'], $actual->getMarker());
        $this->assertEquals(intval($sample['MaxResults']), $actual->getMaxResults());
        $this->assertEquals($sample['NextMarker'], $actual->getNextMarker());
        
        return $actual;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::getBlobPrefixes
     * @depends testCreateWithMultipleEntries
     */
    public function testGetBlobPrefixs($result)
    {
        // Test
        $actual = $result->getBlobPrefixes();
        
        // Assert
        $this->assertCount(2, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::setBlobPrefixes
     * @depends testCreateWithMultipleEntries
     */
    public function testSetBlobPrefixs($result)
    {
        // Setup
        $sample = new ListBlobsResult();
        $expected = $result->getBlobPrefixes();
        
        // Test
        $sample->setBlobPrefixes($expected);
        
        // Assert
        $this->assertEquals($expected, $sample->getBlobPrefixes());
        $expected[0]->setName('test');
        $this->assertNotEquals($expected, $sample->getBlobPrefixes());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::getBlobs
     * @depends testCreateWithMultipleEntries
     */
    public function testGetBlobs($result)
    {
        // Test
        $actual = $result->getBlobs();
        
        // Assert
        $this->assertCount(2, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::setBlobs
     * @depends testCreateWithMultipleEntries
     */
    public function testSetBlobs($result)
    {
        // Setup
        $sample = new ListBlobsResult();
        $expected = $result->getBlobs();
        
        // Test
        $sample->setBlobs($expected);
        
        // Assert
        $this->assertEquals($expected, $sample->getBlobs());
        $expected[0]->setName('test');
        $this->assertNotEquals($expected, $sample->getBlobs());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::setPrefix
     */
    public function testSetPrefix()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'myprefix';
        
        // Test
        $result->setPrefix($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getPrefix());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::getPrefix
     */
    public function testGetPrefix()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'myprefix';
        $result->setPrefix($expected);
        
        // Test
        $actual = $result->getPrefix();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::setNextMarker
     */
    public function testSetNextMarker()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'mymarker';
        
        // Test
        $result->setNextMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getNextMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::getNextMarker
     */
    public function testGetNextMarker()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'mymarker';
        $result->setNextMarker($expected);
        
        // Test
        $actual = $result->getNextMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::setMarker
     */
    public function testSetMarker()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'mymarker';
        
        // Test
        $result->setMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::getMarker
     */
    public function testGetMarker()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'mymarker';
        $result->setMarker($expected);
        
        // Test
        $actual = $result->getMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::setMaxResults
     */
    public function testSetMaxResults()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 3;
        
        // Test
        $result->setMaxResults($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getMaxResults());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::getMaxResults
     */
    public function testGetMaxResults()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 3;
        $result->setMaxResults($expected);
        
        // Test
        $actual = $result->getMaxResults();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::setContainerName
     */
    public function testSetContainerName()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'name';
        
        // Test
        $result->setContainerName($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getContainerName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::getContainerName
     */
    public function testGetContainerName()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'name';
        $result->setContainerName($expected);
        
        // Test
        $actual = $result->getContainerName();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::setDelimiter
     */
    public function testSetDelimiter()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'mydelimiter';
        
        // Test
        $result->setDelimiter($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getDelimiter());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListBlobsResult::getDelimiter
     */
    public function testGetDelimiter()
    {
        // Setup
        $result = new ListBlobsResult();
        $expected = 'mydelimiter';
        $result->setDelimiter($expected);
        
        // Test
        $actual = $result->getDelimiter();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ListContainersOptions;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class ListContainersOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListContainersOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersOptions::setPrefix
     */
    public function testSetPrefix()
    {
        // Setup
        $options = new ListContainersOptions();
        $expected = 'myprefix';
        
        // Test
        $options->setPrefix($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getPrefix());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersOptions::getPrefix
     */
    public function testGetPrefix()
    {
        // Setup
        $options = new ListContainersOptions();
        $expected = 'myprefix';
        $options->setPrefix($expected);
        
        // Test
        $actual = $options->getPrefix();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersOptions::setMarker
     */
    public function testSetMarker()
    {
        // Setup
        $options = new ListContainersOptions();
        $expected = 'mymarker';
        
        // Test
        $options->setMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersOptions::getMarker
     */
    public function testGetMarker()
    {
        // Setup
        $options = new ListContainersOptions();
        $expected = 'mymarker';
        $options->setMarker($expected);
        
        // Test
        $actual = $options->getMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersOptions::setMaxResults
     */
    public function testSetMaxResults()
    {
        // Setup
        $options = new ListContainersOptions();
        $expected = '3';
        
        // Test
        $options->setMaxResults($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getMaxResults());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersOptions::getMaxResults
     */
    public function testGetMaxResults()
    {
        // Setup
        $options = new ListContainersOptions();
        $expected = '3';
        $options->setMaxResults($expected);
        
        // Test
        $actual = $options->getMaxResults();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersOptions::setIncludeMetadata
     */
    public function testSetIncludeMetadata()
    {
        // Setup
        $options = new ListContainersOptions();
        $expected = true;
        
        // Test
        $options->setIncludeMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getIncludeMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersOptions::getIncludeMetadata
     */
    public function testGetIncludeMetadata()
    {
        // Setup
        $options = new ListContainersOptions();
        $expected = true;
        $options->setIncludeMetadata($expected);
        
        // Test
        $actual = $options->getIncludeMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ListContainersResult;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class ListContainersResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListContainersResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::create 
     */
    public function testCreateWithEmpty()
    {
        // Setup
        $sample = TestResources::listContainersEmpty();
        
        // Test
        $actual = ListContainersResult::create($sample);
        
        // Assert
        $this->assertCount(0, $actual->getContainers());
        $this->assertTrue(empty($sample['NextMarker']));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::create 
     */
    public function testCreateWithOneEntry()
    {
        // Setup
        $sample = TestResources::listContainersOneEntry();
        
        // Test
        $actual = ListContainersResult::create($sample);
        
        // Assert
        $containers = $actual->getContainers();
        $this->assertCount(1, $containers);
        $this->assertEquals($sample['Containers']['Container']['Name'], $containers[0]->getName());
        $this->assertEquals($sample['@attributes']['ServiceEndpoint'] . $sample['Containers']['Container']['Name'], $containers[0]->getUrl());
        $this->assertEquals(
         Utilities::rfc1123ToDateTime($sample['Containers']['Container']['Properties']['Last-Modified']),
        $containers[0]->getProperties()->getLastModified());
        $this->assertEquals(
            $sample['Containers']['Container']['Properties']['Etag'],
            $containers[0]->getProperties()->getETag());
        $this->assertEquals($sample['Marker'], $actual->getMarker());
        $this->assertEquals($sample['MaxResults'], $actual->getMaxResults());
        $this->assertEquals($sample['NextMarker'], $actual->getNextMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::create 
     */
    public function testCreateWithMultipleEntries()
    {
        // Setup
        $sample = TestResources::listContainersMultipleEntries();
        
        // Test
        $actual = ListContainersResult::create($sample);
        
        // Assert
        $containers = $actual->getContainers();
        $this->assertCount(2, $containers);
        $this->assertEquals($sample['Containers']['Container'][0]['Name'], $containers[0]->getName());
        $this->assertEquals($sample['@attributes']['ServiceEndpoint'] . $sample['Containers']['Container'][0]['Name'], $containers[0]->getUrl());
        $this->assertEquals(
            Utilities::rfc1123ToDateTime($sample['Containers']['Container'][0]['Properties']['Last-Modified']), 
            $containers[0]->getProperties()->getLastModified());
        $this->assertEquals(
            $sample['Containers']['Container'][0]['Properties']['Etag'],
            $containers[0]->getProperties()->getETag());
        $this->assertEquals($sample['Containers']['Container'][1]['Name'], $containers[1]->getName());
        $this->assertEquals($sample['@attributes']['ServiceEndpoint'] . $sample['Containers']['Container'][1]['Name'], $containers[1]->getUrl());
        $this->assertEquals(
            Utilities::rfc1123ToDateTime($sample['Containers']['Container'][1]['Properties']['Last-Modified']), 
            $containers[1]->getProperties()->getLastModified());
        $this->assertEquals(
            $sample['Containers']['Container'][1]['Properties']['Etag'],
            $containers[1]->getProperties()->getETag());
        $this->assertEquals($sample['MaxResults'], $actual->getMaxResults());
        $this->assertEquals($sample['NextMarker'], $actual->getNextMarker());
        
        return $actual;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::getContainers
     * @depends testCreateWithMultipleEntries
     */
    public function testGetContainers($result)
    {
        // Test
        $actual = $result->getContainers();
        
        // Assert
        $this->assertCount(2, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::setContainers
     * @depends testCreateWithMultipleEntries
     */
    public function testSetContainers($result)
    {
        // Setup
        $sample = new ListContainersResult();
        $expected = $result->getContainers();
        
        // Test
        $sample->setContainers($expected);
        
        // Assert
        $this->assertEquals($expected, $sample->getContainers());
        $expected[0]->setName('test');
        $this->assertNotEquals($expected, $sample->getContainers());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::setPrefix
     */
    public function testSetPrefix()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = 'myprefix';
        
        // Test
        $result->setPrefix($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getPrefix());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::getPrefix
     */
    public function testGetPrefix()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = 'myprefix';
        $result->setPrefix($expected);
        
        // Test
        $actual = $result->getPrefix();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::setNextMarker
     */
    public function testSetNextMarker()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = 'mymarker';
        
        // Test
        $result->setNextMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getNextMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::getNextMarker
     */
    public function testGetNextMarker()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = 'mymarker';
        $result->setNextMarker($expected);
        
        // Test
        $actual = $result->getNextMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::setMarker
     */
    public function testSetMarker()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = 'mymarker';
        
        // Test
        $result->setMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::getMarker
     */
    public function testGetMarker()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = 'mymarker';
        $result->setMarker($expected);
        
        // Test
        $actual = $result->getMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::setMaxResults
     */
    public function testSetMaxResults()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = '3';
        
        // Test
        $result->setMaxResults($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getMaxResults());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::getMaxResults
     */
    public function testGetMaxResults()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = '3';
        $result->setMaxResults($expected);
        
        // Test
        $actual = $result->getMaxResults();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::setAccountName
     */
    public function testSetAccountName()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = 'name';
        
        // Test
        $result->setAccountName($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccountName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListContainersResult::getAccountName
     */
    public function testGetAccountName()
    {
        // Setup
        $result = new ListContainersResult();
        $expected = 'name';
        $result->setAccountName($expected);
        
        // Test
        $actual = $result->getAccountName();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class ListPageBlobRangesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListPageBlobRangesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new ListPageBlobRangesOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new ListPageBlobRangesOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new ListPageBlobRangesOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::setSnapshot
     */
    public function testSetSnapshot()
    {
        // Setup
        $blob = new ListPageBlobRangesOptions();
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $blob->setSnapshot($expected);
        
        // Assert
        $this->assertEquals($expected, $blob->getSnapshot());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::getSnapshot
     */
    public function testGetSnapshot()
    {
        // Setup
        $blob = new ListPageBlobRangesOptions();
        $expected = TestResources::QUEUE_URI;
        $blob->setSnapshot($expected);
        
        // Test
        $actual = $blob->getSnapshot();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::setRangeStart
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::getRangeStart
     */
    public function testSetRangeStart()
    {
        // Setup
        $expected = 123;
        $prooperties = new ListPageBlobRangesOptions();
        $prooperties->setRangeStart($expected);
        
        // Test
        $prooperties->setRangeStart($expected);
        
        // Assert
        $this->assertEquals($expected, $prooperties->getRangeStart());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::setRangeEnd
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesOptions::getRangeEnd
     */
    public function testSetRangeEnd()
    {
        // Setup
        $expected = 123;
        $prooperties = new ListPageBlobRangesOptions();
        $prooperties->setRangeEnd($expected);
        
        // Test
        $prooperties->setRangeEnd($expected);
        
        // Assert
        $this->assertEquals($expected, $prooperties->getRangeEnd());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult;
use MicrosoftAzure\Storage\Blob\Models\PageRange;

/**
 * Unit tests for class ListPageBlobRangesResultTest
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListPageBlobRangesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::setLastModified
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::getLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $expected = Utilities::rfc1123ToDateTime('Sun, 25 Sep 2011 19:42:18 GMT');
        $result = new ListPageBlobRangesResult();
        $result->setLastModified($expected);
        
        // Test
        $result->setLastModified($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getLastModified());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::setETag
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::getETag
     */
    public function testSetETag()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $result = new ListPageBlobRangesResult();
        $result->setETag($expected);
        
        // Test
        $result->setETag($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getETag());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::setContentLength
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::getContentLength
     */
    public function testSetContentLength()
    {
        // Setup
        $expected = 100;
        $result = new ListPageBlobRangesResult();
        $result->setContentLength($expected);
        
        // Test
        $result->setContentLength($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getContentLength());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::setPageRanges
     * @covers MicrosoftAzure\Storage\Blob\Models\ListPageBlobRangesResult::getPageRanges
     */
    public function testSetPageRanges()
    {
        // Setup
        $expected = array(0 => new PageRange(0, 10), 1 => new PageRange(20, 40));
        $result = new ListPageBlobRangesResult();
        
        // Test
        $result->setPageRanges($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getPageRanges());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\PageRange;

/**
 * Unit tests for class PageRange
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PageRangeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\PageRange::__construct
     * @covers MicrosoftAzure\Storage\Blob\Models\PageRange::getStart
     * @covers MicrosoftAzure\Storage\Blob\Models\PageRange::getEnd
     */
    public function test__construct()
    {
        // Setup
        $expectedStart = 0;
        $expectedEnd = 512;
        
        // Test
        $actual = new PageRange($expectedStart, $expectedEnd);
        
        // Assert
        $this->assertEquals($expectedStart, $actual->getStart());
        $this->assertEquals($expectedEnd, $actual->getEnd());
     
        return $actual;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\PageRange::setStart
     * @depends test__construct
     */
    public function testSetStart($obj)
    {
        // Setup
        $expected = 10;
        
        // Test
        $obj->setStart($expected);
        
        // Assert
        $this->assertEquals($expected, $obj->getStart());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\PageRange::setEnd
     * @depends test__construct
     */
    public function testSetEnd($obj)
    {
        // Setup
        $expected = 10;
        
        // Test
        $obj->setEnd($expected);
        
        // Assert
        $this->assertEquals($expected, $obj->getEnd());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\PageRange::setLength
     * @depends test__construct
     */
    public function testSetLength($obj)
    {
        // Setup
        $expected = 10;
        $start = $obj->getStart();
        
        // Test
        $obj->setLength($expected);
        
        // Assert
        $this->assertEquals($start + $expected - 1, $obj->getEnd());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\PageRange::getLength
     * @depends test__construct
     */
    public function testGetLength($obj)
    {
        // Setup
        $expected = 10;
        $obj->setLength($expected);
        
        // Test
        $actual = $obj->getLength();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\PublicAccessType;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Unit tests for class PublicAccessType
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PublicAccessTypeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\PublicAccessType
     */
    public function testPublicAccessType()
    {
        $this->assertEquals(PublicAccessType::BLOBS_ONLY, 'blob');
        $this->assertEquals(PublicAccessType::CONTAINER_AND_BLOBS, 'container');
        $this->assertEquals(PublicAccessType::NONE, Resources::EMPTY_STRING);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataOptions;

/**
 * Unit tests for class SetBlobMetadataOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetBlobMetadataOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new SetBlobMetadataOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new SetBlobMetadataOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new SetBlobMetadataOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataResult;

/**
 * Unit tests for class SetBlobMetadataResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetBlobMetadataResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataResult::getETag
     */
    public function testGetETag()
    {
        // Setup
        $getBlobMetadataResult = new SetBlobMetadataResult();
        $expected = '0x8CACB9BD7C6B1B2';
        $getBlobMetadataResult->setETag($expected);
        
        // Test
        $actual = $getBlobMetadataResult->getETag();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataResult::setETag
     */
    public function testSetETag()
    {
        // Setup
        $getBlobMetadataResult = new SetBlobMetadataResult();
        $expected = '0x8CACB9BD7C6B1B2';
        
        // Test
        $getBlobMetadataResult->setETag($expected);
        
        // Assert
        $actual = $getBlobMetadataResult->getETag();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataResult::getLastModified
     */
    public function testGetLastModified()
    {
        // Setup
        $getBlobMetadataResult = new SetBlobMetadataResult();
        $expected = Utilities::rfc1123ToDateTime('Fri, 09 Oct 2009 21:04:30 GMT');
        $getBlobMetadataResult->setLastModified($expected);
        
        // Test
        $actual = $getBlobMetadataResult->getLastModified();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobMetadataResult::setLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $getBlobMetadataResult = new SetBlobMetadataResult();
        $expected = Utilities::rfc1123ToDateTime('Fri, 09 Oct 2009 21:04:30 GMT');
        
        // Test
        $getBlobMetadataResult->setLastModified($expected);
        
        // Assert
        $actual = $getBlobMetadataResult->getLastModified();
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
 
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;

/**
 * Unit tests for class SetBlobPropertiesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetBlobPropertiesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::__construct
     */
    public function test__construct()
    {
        // Setup
        $expectedLength = 10;
        $blobProperties = new BlobProperties();
        $blobProperties->setContentLength($expectedLength);
        
        // Test
        $options = new SetBlobPropertiesOptions($blobProperties);
        
        // Assert
        $this->assertNotNull($options);
        $this->assertEquals($expectedLength, $options->getBlobContentLength());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setBlobContentType
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getBlobContentType
     */
    public function testSetBlobContentType()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentType($expected);
        
        // Test
        $options->setBlobContentType($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setBlobContentEncoding
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getBlobContentEncoding
     */
    public function testSetBlobContentEncoding()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentEncoding($expected);
        
        // Test
        $options->setBlobContentEncoding($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentEncoding());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setBlobContentLength
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getBlobContentLength
     */
    public function testSetContentLength()
    {
        // Setup
        $expected = 123;
        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentLength($expected);
        
        // Test
        $options->setBlobContentLength($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentLength());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setBlobContentLanguage
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getBlobContentLanguage
     */
    public function testSetBlobContentLanguage()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentLanguage($expected);
        
        // Test
        $options->setBlobContentLanguage($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentLanguage());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setBlobContentMD5
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getBlobContentMD5
     */
    public function testSetBlobContentMD5()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new SetBlobPropertiesOptions();
        $options->setBlobContentMD5($expected);
        
        // Test
        $options->setBlobContentMD5($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobContentMD5());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setBlobCacheControl
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getBlobCacheControl
     */
    public function testSetBlobCacheControl()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new SetBlobPropertiesOptions();
        $options->setBlobCacheControl($expected);
        
        // Test
        $options->setBlobCacheControl($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getBlobCacheControl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setLeaseId
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getLeaseId
     */
    public function testSetLeaseId()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new SetBlobPropertiesOptions();
        $options->setLeaseId($expected);
        
        // Test
        $options->setLeaseId($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getLeaseId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setSequenceNumberAction
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getSequenceNumberAction
     */
    public function testSetSequenceNumberAction()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $options = new SetBlobPropertiesOptions();
        $options->setSequenceNumberAction($expected);
        
        // Test
        $options->setSequenceNumberAction($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getSequenceNumberAction());
    }

    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setSequenceNumber
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getSequenceNumber
     */
    public function testSetSequenceNumber()
    {
        // Setup
        $expected = 123;
        $options = new SetBlobPropertiesOptions();
        $options->setSequenceNumber($expected);
        
        // Test
        $options->setSequenceNumber($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getSequenceNumber());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new SetBlobPropertiesOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new SetBlobPropertiesOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class SetBlobPropertiesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetBlobPropertiesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::setLastModified
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::getLastModified
     */
    public function testSetLastModified()
    {
        // Setup
        $expected = Utilities::rfc1123ToDateTime('Sun, 25 Sep 2011 19:42:18 GMT');
        $prooperties = new SetBlobPropertiesResult();
        $prooperties->setLastModified($expected);
        
        // Test
        $prooperties->setLastModified($expected);
        
        // Assert
        $this->assertEquals($expected, $prooperties->getLastModified());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::setETag
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::getETag
     */
    public function testSetETag()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $prooperties = new SetBlobPropertiesResult();
        $prooperties->setETag($expected);
        
        // Test
        $prooperties->setETag($expected);
        
        // Assert
        $this->assertEquals($expected, $prooperties->getETag());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::setSequenceNumber
     * @covers MicrosoftAzure\Storage\Blob\Models\SetBlobPropertiesResult::getSequenceNumber
     */
    public function testSetSequenceNumber()
    {
        // Setup
        $expected = 123;
        $prooperties = new SetBlobPropertiesResult();
        $prooperties->setSequenceNumber($expected);
        
        // Test
        $prooperties->setSequenceNumber($expected);
        
        // Assert
        $this->assertEquals($expected, $prooperties->getSequenceNumber());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\SetContainerMetadataOptions;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;

/**
 * Unit tests for class SetContainerMetadataOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SetContainerMetadataOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetContainerMetadataOptions::__construct
     */
    public function test__construct()
    {
        // Test
        $result = new SetContainerMetadataOptions();
        
        // Assert
        $this->assertNotNull($result->getAccessCondition());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetContainerMetadataOptions::getAccessCondition
     */
    public function testGetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new SetContainerMetadataOptions();
        $result->setAccessCondition($expected);
        
        // Test
        $actual = $result->getAccessCondition();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SetContainerMetadataOptions::setAccessCondition
     */
    public function testSetAccessCondition()
    {
        // Setup
        $expected = AccessCondition::none();
        $result = new SetContainerMetadataOptions();
        
        // Test
        $result->setAccessCondition($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccessCondition());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Blob\Models;
use MicrosoftAzure\Storage\Blob\Models\SignedIdentifier;
use MicrosoftAzure\Storage\Blob\Models\AccessPolicy;

/**
 * Unit tests for class SignedIdentifier
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Blob\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class SignedIdentifierTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SignedIdentifier::getId 
     */
    public function testGetId()
    {
        // Setup
        $signedIdentifier = new SignedIdentifier();
        $expected = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=';
        $signedIdentifier->setId($expected);
        
        // Test
        $actual = $signedIdentifier->getId();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SignedIdentifier::setId 
     */
    public function testSetId()
    {
        // Setup
        $signedIdentifier = new SignedIdentifier();
        $expected = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=';
        
        // Test
        $signedIdentifier->setId($expected);
        
        // Assert
        $this->assertEquals($expected, $signedIdentifier->getId());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SignedIdentifier::getAccessPolicy 
     */
    public function testGetAccessPolicy()
    {
        // Setup
        $signedIdentifier = new SignedIdentifier();
        $expected = new AccessPolicy();
        $expected->setExpiry(new \DateTime('2009-09-29T08:49:37'));
        $expected->setPermission('rwd');
        $expected->setStart(new \DateTime('2009-09-28T08:49:37'));
        $signedIdentifier->setAccessPolicy($expected);
        
        // Test
        $actual = $signedIdentifier->getAccessPolicy();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SignedIdentifier::setAccessPolicy
     */
    public function testSetAccessPolicy()
    {
        // Setup
        $signedIdentifier = new SignedIdentifier();
        $expected = new AccessPolicy();
        $expected->setExpiry(new \DateTime('2009-09-29T08:49:37'));
        $expected->setPermission('rwd');
        $expected->setStart(new \DateTime('2009-09-28T08:49:37'));
        
        // Test
        $signedIdentifier->setAccessPolicy($expected);
        
        // Assert
        $this->assertEquals($expected, $signedIdentifier->getAccessPolicy());
        
        return $signedIdentifier;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Blob\Models\SignedIdentifier::toArray
     * @depends testSetAccessPolicy
     */
    public function testToXml($signedIdentifier)
    {
        // Setup
        $id = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=';
        $signedIdentifier->setId($id);
        
        // Test
        $array = $signedIdentifier->toArray();
        
        // Assert
        $this->assertEquals($id, $array['SignedIdentifier']['Id']);
        $this->assertArrayHasKey('AccessPolicy', $array['SignedIdentifier']);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common;
use MicrosoftAzure\Storage\Common\CloudConfigurationManager;
use MicrosoftAzure\Storage\Common\Internal\ConnectionStringSource;

/**
 * Unit tests for class CloudConfigurationManager
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CloudConfigurationManagerTest extends \PHPUnit_Framework_TestCase
{
    private $_key = 'my_connection_string';
    private $_value = 'connection string value';
    
    public function setUp()
    {
        $isInitialized = new \ReflectionProperty('MicrosoftAzure\Storage\Common\CloudConfigurationManager', '_isInitialized');
        $isInitialized->setAccessible(true);
        $isInitialized->setValue(false);
        
        $sources = new \ReflectionProperty('MicrosoftAzure\Storage\Common\CloudConfigurationManager', '_sources');
        $sources->setAccessible(true);
        $sources->setValue(array());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::getConnectionString
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::_init
     */
    public function testGetConnectionStringFromEnvironmentVariable()
    {
        // Setup
        putenv("$this->_key=$this->_value");
        
        // Test
        $actual = CloudConfigurationManager::getConnectionString($this->_key);
        
        // Assert
        $this->assertEquals($this->_value, $actual);
        
        // Clean
        putenv($this->_key);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::getConnectionString
     */
    public function testGetConnectionStringDoesNotExist()
    {        
        // Test
        $actual = CloudConfigurationManager::getConnectionString('does not exist');
        
        // Assert
        $this->assertEmpty($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::registerSource
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::_init
     */
    public function testRegisterSource()
    {
        // Setup
        $expectedKey = $this->_key;
        $expectedValue = $this->_value . "extravalue";
        
        // Test
        CloudConfigurationManager::registerSource(
            'my_source',
            function ($key) use ($expectedKey, $expectedValue)
            {
                if ($key == $expectedKey) {
                    return $expectedValue;
                }
            }
        );
        
        // Assert
        $actual = CloudConfigurationManager::getConnectionString($expectedKey);
        $this->assertEquals($expectedValue, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::registerSource
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::_init
     */
    public function testRegisterSourceWithPrepend()
    {
        // Setup
        $expectedKey = $this->_key;
        $expectedValue = $this->_value . "extravalue2";
        putenv("$this->_key=wrongvalue");

        // Test
        CloudConfigurationManager::registerSource(
            'my_source',
            function ($key) use ($expectedKey, $expectedValue)
            {
                if ($key == $expectedKey) {
                    return $expectedValue;
                }
            },
            true
        );
        
        // Assert
        $actual = CloudConfigurationManager::getConnectionString($expectedKey);
        $this->assertEquals($expectedValue, $actual);
        
        // Clean
        putenv($this->_key);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::unregisterSource
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::_init
     */
    public function testUnRegisterSource()
    {
        // Setup
        $expectedKey = $this->_key;
        $expectedValue = $this->_value . "extravalue3";
        $name = 'my_source';
        CloudConfigurationManager::registerSource(
            $name,
            function ($key) use ($expectedKey, $expectedValue)
            {
                if ($key == $expectedKey) {
                    return $expectedValue;
                }
            }
        );
        
        // Test
        $callback = CloudConfigurationManager::unregisterSource($name);
        
        // Assert
        $actual = CloudConfigurationManager::getConnectionString($expectedKey);
        $this->assertEmpty($actual);
        $this->assertNotNull($callback);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::registerSource
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::_init
     */
    public function testRegisterSourceWithDefaultSource()
    {
        // Setup
        $expectedKey = $this->_key;
        $expectedValue = $this->_value . "extravalue5";
        CloudConfigurationManager::unregisterSource(ConnectionStringSource::ENVIRONMENT_SOURCE);
        putenv("$expectedKey=$expectedValue");
        
        // Test
        CloudConfigurationManager::registerSource(ConnectionStringSource::ENVIRONMENT_SOURCE);
        
        // Assert
        $actual = CloudConfigurationManager::getConnectionString($expectedKey);
        $this->assertEquals($expectedValue, $actual);
        
        // Clean
        putenv($expectedKey);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::unregisterSource
     * @covers MicrosoftAzure\Storage\Common\CloudConfigurationManager::_init
     */
    public function testUnRegisterSourceWithDefaultSource()
    {
        // Setup
        $expectedKey = $this->_key;
        $expectedValue = $this->_value . "extravalue4";
        $name = 'my_source';
        CloudConfigurationManager::registerSource(
            $name,
            function ($key) use ($expectedKey, $expectedValue)
            {
                if ($key == $expectedKey) {
                    return $expectedValue;
                }
            }
        );
        
        // Test
        $callback = CloudConfigurationManager::unregisterSource(ConnectionStringSource::ENVIRONMENT_SOURCE);
        
        // Assert
        $actual = CloudConfigurationManager::getConnectionString($expectedKey);
        $this->assertEquals($expectedValue, $actual);
        $this->assertNotNull($callback);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication\SharedKeyAuthSchemeMock;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for SharedKeyAuthScheme class.
 *
 * @package    MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class SharedKeyAuthSchemeTest extends \PHPUnit_Framework_TestCase
{
    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\SharedKeyAuthScheme::__construct
    */
    public function test__construct()
    {
        $expected = array();
        $expected[] = Resources::CONTENT_ENCODING;
        $expected[] = Resources::CONTENT_LANGUAGE;
        $expected[] = Resources::CONTENT_LENGTH;
        $expected[] = Resources::CONTENT_MD5;
        $expected[] = Resources::CONTENT_TYPE;
        $expected[] = Resources::DATE;
        $expected[] = Resources::IF_MODIFIED_SINCE;
        $expected[] = Resources::IF_MATCH;
        $expected[] = Resources::IF_NONE_MATCH;
        $expected[] = Resources::IF_UNMODIFIED_SINCE;
        $expected[] = Resources::RANGE;

        $mock = new SharedKeyAuthSchemeMock(TestResources::ACCOUNT_NAME, TestResources::KEY4);

        $this->assertEquals($expected, $mock->getIncludedHeaders());
    }

    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\SharedKeyAuthScheme::computeSignature
    */
    public function testComputeSignatureSimple()
    {
        $httpMethod = 'GET';
        $queryParams = array(Resources::QP_COMP => 'list');
        $url = TestResources::URI1;
        $date = TestResources::DATE1;
        $apiVersion = Resources::STORAGE_API_LATEST_VERSION;
        $accountName = TestResources::ACCOUNT_NAME;
        $headers = array(Resources::X_MS_DATE => $date, Resources::X_MS_VERSION => $apiVersion);
        $expected = "GET\n\n\n\n\n\n\n\n\n\n\n\n" . Resources::X_MS_DATE . ":$date\n" . Resources::X_MS_VERSION . 
                ":$apiVersion\n/$accountName" . parse_url($url, PHP_URL_PATH) . "\ncomp:list";
        $mock = new SharedKeyAuthSchemeMock($accountName, TestResources::KEY4);

        $actual = $mock->computeSignatureMock($headers, $url, $queryParams, $httpMethod);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\SharedKeyAuthScheme::getAuthorizationHeader
     */
    public function testGetAuthorizationHeaderSimple()
    {
        $accountName = TestResources::ACCOUNT_NAME;
        $apiVersion = Resources::STORAGE_API_LATEST_VERSION;
        $accountKey = TestResources::KEY4;
        $url = TestResources::URI2;
        $date1 = TestResources::DATE2;
        $headers = array(Resources::X_MS_VERSION => $apiVersion, Resources::X_MS_DATE => $date1);
        $queryParams = array(Resources::QP_COMP => 'list');
        $httpMethod = 'GET';
        $expected = 'SharedKey ' . $accountName . ':YDjZ61Lqt6HeMx+vv5QzFjW1juW7XEECVXJ4V9/pFgA=';

        $mock = new SharedKeyAuthSchemeMock($accountName, $accountKey);

        $actual = $mock->getAuthorizationHeader($headers, $url, $queryParams, $httpMethod);

        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme;
use MicrosoftAzure\Storage\Tests\Unit\Utilities;
use MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication\StorageAuthSchemeMock;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Resources;

/**
 * Unit tests for StorageAuthScheme class.
 *
 * @package    MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class StorageAuthSchemeTest extends \PHPUnit_Framework_TestCase
{
    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme::__construct
    */
    public function test__construct()
    {
        $mock = new StorageAuthSchemeMock(TestResources::ACCOUNT_NAME, TestResources::KEY4);
        $this->assertEquals(TestResources::ACCOUNT_NAME, $mock->getAccountName());
        $this->assertEquals(TestResources::KEY4, $mock->getAccountKey());
    }

    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme::computeCanonicalizedHeaders
    */
    public function testComputeCanonicalizedHeadersMock()
    {
        $date = TestResources::DATE1;
        $headers = array();
        $headers[Resources::X_MS_DATE] = $date;
        $headers[Resources::X_MS_VERSION] = Resources::STORAGE_API_LATEST_VERSION;
        $expected = array();
        $expected[] = Resources::X_MS_DATE . ':' . $date;
        $expected[] = Resources::X_MS_VERSION . ':' . Resources::STORAGE_API_LATEST_VERSION;
        $mock = new StorageAuthSchemeMock(TestResources::ACCOUNT_NAME, TestResources::KEY4);

        $actual = $mock->computeCanonicalizedHeadersMock($headers);

        $this->assertEquals($expected, $actual);
    }

    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme::computeCanonicalizedResource
    */
    public function testComputeCanonicalizedResourceMockSimple()
    {
        $queryVariables = array();
        $queryVariables['COMP'] = 'list';
        $accountName = TestResources::ACCOUNT_NAME;
        $url = TestResources::URI1;
        $expected = '/' . $accountName . parse_url($url, PHP_URL_PATH) . "\n" . 'comp:list';
        $mock = new StorageAuthSchemeMock($accountName, TestResources::KEY4);

        $actual = $mock->computeCanonicalizedResourceMock($url, $queryVariables);

        $this->assertEquals($expected, $actual);
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme::computeCanonicalizedResource
    */
    public function testComputeCanonicalizedResourceMockMultipleValues()
    {
        $queryVariables = array();
        $queryVariables['COMP'] = 'list';
        $queryVariables[Resources::QP_INCLUDE] = 'snapshots,metadata,uncommittedblobs';
        $expectedQueryPart = "comp:list\ninclude:metadata,snapshots,uncommittedblobs";
        $accountName = TestResources::ACCOUNT_NAME;
        $url = TestResources::URI1;
        $expected = '/' . $accountName . parse_url($url, PHP_URL_PATH) . "\n" . $expectedQueryPart;
        $mock = new StorageAuthSchemeMock($accountName, TestResources::KEY4);

        $actual = $mock->computeCanonicalizedResourceMock($url, $queryVariables);

        $this->assertEquals($expected, $actual);
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\StorageAuthScheme::computeCanonicalizedResourceForTable
    */
    public function testComputeCanonicalizedResourceForTableMock()
    {
        $queryVariables = array();
        $queryVariables['COMP'] = 'list';
        $accountName = TestResources::ACCOUNT_NAME;
        $url = TestResources::URI1;
        $expected = '/' . $accountName . parse_url($url, PHP_URL_PATH) . '?comp=list';
        $mock = new StorageAuthSchemeMock($accountName, TestResources::KEY4);

        $actual = $mock->computeCanonicalizedResourceForTableMock($url, $queryVariables);

        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication;
use MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Authentication\TableSharedKeyLiteAuthSchemeMock;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for TableSharedKeyLiteAuthScheme class.
 *
 * @package    MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Authentication
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class TableSharedKeyLiteAuthSchemeTest extends \PHPUnit_Framework_TestCase
{
    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\TableSharedKeyLiteAuthScheme::__construct
    */
    public function test__construct()
    {
        $expected = array();
        $expected[] = Resources::DATE;

        $mock = new TableSharedKeyLiteAuthSchemeMock(TestResources::ACCOUNT_NAME, TestResources::KEY4);

        $this->assertEquals($expected, $mock->getIncludedHeaders());
    }

    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\TableSharedKeyLiteAuthScheme::computeSignature
    */
    public function testComputeSignatureSimple()
    {
        $httpMethod = 'GET';
        $queryParams = array(Resources::QP_COMP => 'list');
        $url = TestResources::URI1;
        $date = TestResources::DATE1;
        $apiVersion = Resources::STORAGE_API_LATEST_VERSION;
        $accountName = TestResources::ACCOUNT_NAME;
        $headers = array(Resources::X_MS_DATE => $date, Resources::X_MS_VERSION => $apiVersion);
        $expected = "\n/$accountName" . parse_url($url, PHP_URL_PATH) . "?comp=list";
        $mock = new TableSharedKeyLiteAuthSchemeMock($accountName, TestResources::KEY4);

        $actual = $mock->computeSignatureMock($headers, $url, $queryParams, $httpMethod);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Authentication\TableSharedKeyLiteAuthScheme::getAuthorizationHeader
     */
    public function testGetAuthorizationHeaderSimple()
    {
        $accountName = TestResources::ACCOUNT_NAME;
        $apiVersion = Resources::STORAGE_API_LATEST_VERSION;
        $accountKey = TestResources::KEY4;
        $url = TestResources::URI2;
        $date1 = TestResources::DATE2;
        $headers = array(Resources::X_MS_VERSION => $apiVersion, Resources::X_MS_DATE => $date1);
        $queryParams = array(Resources::QP_COMP => 'list');
        $httpMethod = 'GET';
        $expected = 'SharedKeyLite ' . $accountName . ':KB+TK3FPHLADYwd0/b3PcZgK/fYXUSlwsoOIf80l2co=';

        $mock = new TableSharedKeyLiteAuthSchemeMock($accountName, $accountKey);

        $actual = $mock->getAuthorizationHeader($headers, $url, $queryParams, $httpMethod);

        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser;

/**
 * Unit tests for class ConnectionStringParser
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ConnectionStringParserTest extends \PHPUnit_Framework_TestCase
{
    private function _parseTest($connectionString)
    {
        // Setup
        $arguments = func_get_args();
        $count = func_num_args();
        $expected = array();
        for ($i = 1; $i < $count; $i += 2) {
            $expected[$arguments[$i]] = $arguments[$i + 1];
        }
        
        // Test
        $actual = ConnectionStringParser::parseConnectionString('connectionString', $connectionString);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    private function _parseTestFail($value)
    {
        // Setup
        $this->setExpectedException('\RuntimeException');
        
        // Test
        ConnectionStringParser::parseConnectionString('connectionString', $value);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::parseConnectionString 
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_parse
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_createException
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipWhiteSpaces
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractKey
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractString
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipOperator
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractValue
     */
    public function testKeyNames()
    {
        $this->_parseTest("a=b", "a", "b");
        $this->_parseTest(" a =b; c = d", "a", "b", "c", "d");
        $this->_parseTest("a b=c", "a b", "c");
        $this->_parseTest("'a b'=c", "a b", "c");
        $this->_parseTest("\"a b\"=c", "a b", "c");
        $this->_parseTest("\"a=b\"=c", "a=b", "c");
        $this->_parseTest("a=b=c", "a", "b=c");
        $this->_parseTest("'a='=b", "a=", "b");
        $this->_parseTest("\"a=\"=b", "a=", "b");
        $this->_parseTest("\"a'b\"=c", "a'b", "c");
        $this->_parseTest("'a\"b'=c", "a\"b", "c");
        $this->_parseTest("a'b=c", "a'b", "c");
        $this->_parseTest("a\"b=c", "a\"b", "c");
        $this->_parseTest("a'=b", "a'", "b");
        $this->_parseTest("a\"=b", "a\"", "b");
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::parseConnectionString 
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_parse
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_createException
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipWhiteSpaces
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractKey
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractString
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipOperator
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractValue
     */
    public function testAssignments()
    {
        $this->_parseTest("a=b", "a", "b");
        $this->_parseTest("a = b", "a", "b");
        $this->_parseTest("a==b", "a", "=b");
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::parseConnectionString 
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_parse
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_createException
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipWhiteSpaces
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractKey
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractString
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipOperator
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractValue
     */
    public function testValues()
    {
        $this->_parseTest("a=b", "a", "b");
        $this->_parseTest("a= b ", "a", "b");
        $this->_parseTest("a= b ;c= d;", "a", "b", "c", "d");
        $this->_parseTest("a=", "a", "");
        $this->_parseTest("a=;", "a", "");
        $this->_parseTest("a=;b=", "a", "", "b", "");
        $this->_parseTest("a==b", "a", "=b");
        $this->_parseTest("a=b=;c==d=", "a", "b=", "c", "=d=");
        $this->_parseTest("a='b c'", "a", "b c");
        $this->_parseTest("a=\"b c\"", "a", "b c");
        $this->_parseTest("a=\"b'c\"", "a", "b'c");
        $this->_parseTest("a='b\"c'", "a", "b\"c");
        $this->_parseTest("a='b=c'", "a", "b=c");
        $this->_parseTest("a=\"b=c\"", "a", "b=c");
        $this->_parseTest("a='b;c=d'", "a", "b;c=d");
        $this->_parseTest("a=\"b;c=d\"", "a", "b;c=d");
        $this->_parseTest("a='b c' ", "a", "b c");
        $this->_parseTest("a=\"b c\" ", "a", "b c");
        $this->_parseTest("a=b'c", "a", "b'c");
        $this->_parseTest("a=b\"c", "a", "b\"c");
        $this->_parseTest("a=b'", "a", "b'");
        $this->_parseTest("a=b\"", "a", "b\"");
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::parseConnectionString 
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_parse
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_createException
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipWhiteSpaces
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractKey
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractString
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipOperator
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractValue
     */
    public function testSeparators()
    {
        $this->_parseTest("a=b;", "a", "b");
        $this->_parseTest("a=b", "a", "b");
        $this->_parseTest("a=b;c=d", "a", "b", "c", "d");
        $this->_parseTest("a=b;c=d;", "a", "b", "c", "d");
        $this->_parseTest("a=b ; c=d", "a", "b", "c", "d");
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::parseConnectionString 
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_parse
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_createException
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipWhiteSpaces
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractKey
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractString
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_skipOperator
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringParser::_extractValue
     */
    public function testInvalidInputFail()
    {
        $this->_parseTestFail(";");           // Separator without an assignment;
        $this->_parseTestFail("=b");          // Missing key name;
        $this->_parseTestFail("''=b");        // Empty key name;
        $this->_parseTestFail("\"\"=b");      // Empty key name;
        $this->_parseTestFail("test");        // Missing assignment;
        $this->_parseTestFail(";a=b");        // Separator without key=value;
        $this->_parseTestFail("a=b;;");       // Two separators at the end;
        $this->_parseTestFail("a=b;;c=d");    // Two separators in the middle.
        $this->_parseTestFail("'a=b");        // Runaway single-quoted string at the beginning of the key name;
        $this->_parseTestFail("\"a=b");       // Runaway double-quoted string at the beginning of the key name;
        $this->_parseTestFail("'=b");         // Runaway single-quoted string in key name;
        $this->_parseTestFail("\"=b");        // Runaway double-quoted string in key name;
        $this->_parseTestFail("a='b");        // Runaway single-quoted string in value;
        $this->_parseTestFail("a=\"b");       // Runaway double-quoted string in value;
        $this->_parseTestFail("a='b'c");      // Extra character after single-quoted value;
        $this->_parseTestFail("a=\"b\"c");    // Extra character after double-quoted value;
        $this->_parseTestFail("'a'b=c");      // Extra character after single-quoted key;
        $this->_parseTestFail("\"a\"b=c");    // Extra character after double-quoted key;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\ConnectionStringSource;

/**
 * Unit tests for class ConnectionStringSource
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ConnectionStringSourceTest extends \PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        $property = new \ReflectionProperty('MicrosoftAzure\Storage\Common\Internal\ConnectionStringSource', '_isInitialized');
        $property->setAccessible(true);
        $property->setValue(null);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringSource::environmentSource
     */
    public function testEnvironmentSource()
    {
        // Setup
        $key = 'key';
        $value = 'value';
        putenv("$key=$value");
        
        // Test
        $actual = ConnectionStringSource::environmentSource($key);
        
        // Assert
        $this->assertEquals($value, $actual);
        
        // Clean
        putenv($key);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringSource::getDefaultSources
     * @covers MicrosoftAzure\Storage\Common\Internal\ConnectionStringSource::_init
     */
    public function testGetDefaultSources()
    {
        // Setup
        $expectedKeys = array(ConnectionStringSource::ENVIRONMENT_SOURCE);
        
        // Test
        $actual = ConnectionStringSource::getDefaultSources();
        
        // Assert
        $keys = array_keys($actual);
        $this->assertEquals(count($expectedKeys), count($keys));
        for ($index = 0; $index < count($expectedKeys); $index++) {
            $this->assertEquals($expectedKeys[$index], $keys[$index]);
        }
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters;
use MicrosoftAzure\Storage\Common\Internal\Filters\AuthenticationFilter;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Authentication\SharedKeyAuthScheme;
use MicrosoftAzure\Storage\Common\Internal\Authentication\TableSharedKeyLiteAuthScheme;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;

/**
 * Unit tests for class AuthenticationFilterTest
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AuthenticationFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\AuthenticationFilter::handleRequest
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\AuthenticationFilter::__construct
     */
    public function testHandleRequest()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $scheme = new SharedKeyAuthScheme('acount', 'key');
        $filter = new AuthenticationFilter($scheme);
        
        // Test
        $request = $filter->handleRequest($request);
        
        // Assert
        $this->assertArrayHasKey(strtolower(Resources::AUTHENTICATION), $request->getHeaders());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\AuthenticationFilter::handleRequest
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\AuthenticationFilter::__construct
     */
    public function testHandleRequestWithTable()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $scheme = new TableSharedKeyLiteAuthScheme('acount', 'key');
        $filter = new AuthenticationFilter($scheme);
        
        // Test
        $request = $filter->handleRequest($request);
        
        // Assert
        $this->assertArrayHasKey(strtolower(Resources::AUTHENTICATION), $request->getHeaders());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\AuthenticationFilter::handleResponse
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\AuthenticationFilter::__construct
     */
    public function testHandleResponse()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $response = null;
        $scheme = new SharedKeyAuthScheme('acount', 'key');
        $filter = new AuthenticationFilter($scheme);
        
        // Test
        $response = $filter->handleResponse($request, $response);
        
        // Assert
        $this->assertNull($response);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters;
use MicrosoftAzure\Storage\Common\Internal\Filters\DateFilter;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;

/**
 * Unit tests for class DateFilter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DateFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\DateFilter::handleRequest
     */
    public function testHandleRequest()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $filter = new DateFilter();
        
        // Test
        $request = $filter->handleRequest($request);
        
        // Assert
        $this->assertArrayHasKey(Resources::DATE, $request->getHeaders());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\DateFilter::handleResponse
     */
    public function testHandleResponse()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $response = null;
        $filter = new DateFilter();
        
        // Test
        $response = $filter->handleResponse($request, $response);
        
        // Assert
        $this->assertNull($response);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters;
use MicrosoftAzure\Storage\Common\Internal\Filters\ExponentialRetryPolicy;

/**
 * Unit tests for class ExponentialRetryPolicy
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ExponentialRetryPolicyTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\ExponentialRetryPolicy::__construct
     */
    public function test__construct()
    {
        // Setup
        $expectedRetryableStatusCodes = array(200, 201);
        
        // Test
        $actual = new ExponentialRetryPolicy($expectedRetryableStatusCodes);
        
        // Assert
        $this->assertNotNull($actual);
        
        return $actual;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\ExponentialRetryPolicy::shouldRetry
     * @depends test__construct
     */
    public function testShouldRetryFalse($retryPolicy)
    {
        // Setup
        $expected = false;
        
        // Test
        $actual = $retryPolicy->shouldRetry(1000, null);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\ExponentialRetryPolicy::calculateBackoff
     * @depends test__construct
     */
    public function testCalculateBackoff($retryPolicy)
    {
        // Test
        $actual = $retryPolicy->calculateBackoff(2, null);
        
        // Assert
        $this->assertTrue(is_integer($actual));
        
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters;
use MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Uri;

/**
 * Unit tests for class HeadersFilter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class HeadersFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter::handleRequest
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter::__construct
     */
    public function testHandleRequestEmptyHeaders()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $filter = new HeadersFilter(array());
        $expected = $request->getHeaders();
        
        // Test
        $request = $filter->handleRequest($request);
        
        // Assert
        $this->assertEquals($expected, $request->getHeaders());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter::handleRequest
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter::__construct
     */
    public function testHandleRequestOneHeader()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $header1 = 'header1';
        $value1 = 'value1';
        $expected = array($header1 => $value1);
        $filter = new HeadersFilter($expected);
        
        // Test
        $request = $filter->handleRequest($request);
        
        // Assert
        $headers = $request->getHeaders();
        $this->assertEquals($value1, $headers[$header1][0]);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter::handleRequest
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter::__construct
     */
    public function testHandleRequestMultipleHeaders()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $header1 = 'header1';
        $value1 = 'value1';
        $header2 = 'header2';
        $value2 = 'value2';
        $expected = array($header1 => $value1, $header2 => $value2);
        $filter = new HeadersFilter($expected);
        
        // Test
        $request = $filter->handleRequest($request);
        
        // Assert
        $headers = $request->getHeaders();
        $this->assertEquals($value1, $headers[$header1][0]);
        $this->assertEquals($value2, $headers[$header2][0]);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Filters\HeadersFilter::handleResponse
     */
    public function testHandleResponse()
    {
        // Setup
        $uri = new Uri('http://microsoft.com');
        $request = new Request('Get', $uri, array(), NULL);
        $response = null;
        $filter = new HeadersFilter(array());
        
        // Test
        $response = $filter->handleResponse($request, $response);
        
        // Assert
        $this->assertNull($response);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Http
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Http;
use MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext;

/**
 * Unit tests for class HttpCallContext
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Http
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class HttpCallContextTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::__construct
     */
    public function test__construct()
    {
        // Test
        $context = new HttpCallContext();

        // Assert
        $this->assertNull($context->getBody());
        $this->assertNull($context->getMethod());
        $this->assertNull($context->getPath());
        $this->assertNull($context->getUri());
        $this->assertTrue(is_array($context->getHeaders()));
        $this->assertTrue(is_array($context->getQueryParameters()));
        $this->assertTrue(is_array($context->getStatusCodes()));

        return $context;
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getMethod
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::setMethod
     * @depends test__construct
     */
    public function testSetMethod($context)
    {
        // Setup
        $expected = 'Method';

        // Test
        $context->setMethod($expected);

        // Assert
        $this->assertEquals($expected, $context->getMethod());
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getBody
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::setBody
     * @depends test__construct
     */
    public function testSetBody($context)
    {
        // Setup
        $expected = 'Body';

        // Test
        $context->setBody($expected);

        // Assert
        $this->assertEquals($expected, $context->getBody());
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getPath
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::setPath
     * @depends test__construct
     */
    public function testSetPath($context)
    {
        // Setup
        $expected = 'Path';

        // Test
        $context->setPath($expected);

        // Assert
        $this->assertEquals($expected, $context->getPath());
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getUri
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::setUri
     * @depends test__construct
     */
    public function testSetUri($context)
    {
        // Setup
        $expected = 'http://www.microsoft.com';

        // Test
        $context->setUri($expected);

        // Assert
        $this->assertEquals($expected, $context->getUri());
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getHeaders
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::setHeaders
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::addHeader
     * @depends test__construct
     */
    public function testSetHeaders($context)
    {
        // Setup
        $expected = array('value1', 'value2', 'value3');

        // Test
        $context->setHeaders($expected);

        // Assert
        $this->assertEquals($expected, $context->getHeaders());
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getQueryParameters
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::setQueryParameters
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::addQueryParameter
     * @depends test__construct
     */
    public function testSetQueryParameters($context)
    {
        // Setup
        $expected = array('value1', 'value2', 'value3');

        // Test
        $context->setQueryParameters($expected);

        // Assert
        $this->assertEquals($expected, $context->getQueryParameters());
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getStatusCodes
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::setStatusCodes
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::addStatusCode
     * @depends test__construct
     */
    public function testSetStatusCodes($context)
    {
        // Setup
        $expected = array(1, 2, 3);

        // Test
        $context->setStatusCodes($expected);

        // Assert
        $this->assertEquals($expected, $context->getStatusCodes());
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getHeader
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::addHeader
     * @depends test__construct
     */
    public function testAddHeader($context)
    {
        // Setup
        $expected = 'value';
        $key = 'key';

        // Test
        $context->addHeader($key, $expected);

        // Assert
        $this->assertEquals($expected, $context->getHeader($key));
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::removeHeader
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::getHeaders
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::addHeader
     * @depends test__construct
     */
    public function testRemoveHeader($context)
    {
        // Setup
        $value = 'value';
        $key = 'key';
        $context->addHeader($key, $value);

        // Test
        $context->removeHeader($key);

        // Assert
        $this->assertFalse(array_key_exists($key, $context->getHeaders()));
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Http\HttpCallContext::__toString
     * @depends test__construct
     */
    public function test__toString($context)
    {
        // Setup
        $headers = array('h1' => 'v1', 'h2' => 'v2');
        $method = 'GET';
        $uri = 'http://microsoft.com';
        $path = 'windowsazure/services';
        $body = 'The request body';
        $expected = "GET http://microsoft.com/windowsazure/services HTTP/1.1\nh1: v1\nh2: v2\n\nThe request body";
        $context->setHeaders($headers);
        $context->setMethod($method);
        $context->setUri($uri);
        $context->setPath($path);
        $context->setBody($body);

        // Test
        $actual = $context->__toString();

        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;

/**
 * Unit tests for class InvalidArgumentTypeException
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class InvalidArgumentTypeExceptionTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException::__construct
     */
    public function test__construct()
    {
        $e = new InvalidArgumentTypeException('string');
        
        $this->assertTrue(isset($e));
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\Logger;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Tests\Framework\VirtualFileSystem;

/**
 * Unit tests for class Logger
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class LoggerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Logger::log
     * @covers MicrosoftAzure\Storage\Common\Internal\Logger::setLogFile
     */
    public function testLogWithArray()
    {
        // Setup
        $virtualPath = VirtualFileSystem::newFile(Resources::EMPTY_STRING);
        $tip = 'This is array';
        $expected = "$tip\nArray\n(\n)\n";
        Logger::setLogFile($virtualPath);
        
        // Test
        Logger::log(array(), $tip);
        
        // Assert
        $actual = file_get_contents($virtualPath);
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Logger::log
     * @covers MicrosoftAzure\Storage\Common\Internal\Logger::setLogFile
     */
    public function testLogWithString()
    {
        // Setup
        $virtualPath = VirtualFileSystem::newFile(Resources::EMPTY_STRING);
        $tip = 'This is string';
        $expected = "$tip\nI'm a string\n";
        Logger::setLogFile($virtualPath);
        
        // Test
        Logger::log('I\'m a string', $tip);
        
        // Assert
        $actual = file_get_contents($virtualPath);
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;


/**
 * Dummy class for Xml Serializer. 
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DummyClass
{
    private $_attributes;

    public function __construct()
    {
        $this->_attributes = array();
    } 

    public function addAttribute($attributeKey, $attributeValue)
    {
        $this->_attributes[$attributeKey] = $attributeValue;
    }

    public function getAttributes() 
    {
        return $this->_attributes;
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;
use MicrosoftAzure\Storage\Common\Internal\Serialization\JsonSerializer;
use MicrosoftAzure\Storage\Common\Internal\Resources;


/**
 * Unit tests for class XmlSerializer
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class JsonSerializerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\JsonSerializer::objectSerialize
     */
    public function testObjectSerialize()
    {
        // Setup
        $testData = TestResources::getSimpleJson();
        $rootName = 'testRoot';
        $expected = "{\"{$rootName}\":{$testData['jsonObject']}}";

        // Test
        $actual = JsonSerializer::objectSerialize($testData['dataObject'], $rootName);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\JsonSerializer::unserialize
     */
    public function testUnserializeArray()
    {
        // Setup
        $jsonSerializer = new JsonSerializer();
        $testData = TestResources::getSimpleJson();
        $expected = $testData['dataArray'];

        // Test
        $actual = $jsonSerializer->unserialize($testData['jsonArray']);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\JsonSerializer::unserialize
     */
    public function testUnserializeObject()
    {
        // Setup
        $jsonSerializer = new JsonSerializer();
        $testData = TestResources::getSimpleJson();
        $expected = $testData['dataObject'];

        // Test
        $actual = $jsonSerializer->unserialize($testData['jsonObject']);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\JsonSerializer::unserialize
     */
    public function testUnserializeEmptyString()
    {
        // Setup
        $jsonSerializer = new JsonSerializer();
        $testData = "";
        $expected = null;

        // Test
        $actual = $jsonSerializer->unserialize($testData);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\JsonSerializer::unserialize
     */
    public function testUnserializeInvalidString()
    {
        // Setup
        $jsonSerializer = new JsonSerializer();
        $testData = "{]{{test]";
        $expected = null;

        // Test
        $actual = $jsonSerializer->unserialize($testData);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\JsonSerializer::serialize
     */
    public function testSerialize()
    {
        // Setup
        $jsonSerializer = new JsonSerializer();
        $testData = TestResources::getSimpleJson();
        $expected = $testData['jsonArray'];

        // Test
        $actual = $jsonSerializer->serialize($testData['dataArray']);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\JsonSerializer::serialize
     */
    public function testSerializeNull()
    {
        // Setup
        $jsonSerializer = new JsonSerializer();
        $testData = null;
        $expected = "";
        $this->setExpectedException('MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException', sprintf(Resources::INVALID_PARAM_MSG, 'array', 'array'));

        // Test
        $actual = $jsonSerializer->serialize($testData);
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;


/**
 * Unit tests for class XmlSerializer
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal\Serialization
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class XmlSerializerTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::unserialize
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::_sxml2arr
     */
    public function testUnserialize()
    {
        // Setup
        $xmlSerializer = new XmlSerializer();
        $propertiesSample = TestResources::getServicePropertiesSample();
        $properties = ServiceProperties::create($propertiesSample);
        $xml = $properties->toXml($xmlSerializer);
        $expected = $properties->toArray();
        
        // Test
        $actual = $xmlSerializer->unserialize($xml);
        
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::serialize
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::_arr2xml
     */
    public function testSerialize()
    {
        // Setup
        $xmlSerializer = new XmlSerializer();
        $propertiesSample = TestResources::getServicePropertiesSample();
        $properties = ServiceProperties::create($propertiesSample);
        $expected = $properties->toXml($xmlSerializer);
        $array = $properties->toArray();
        $serializerProperties = array(XmlSerializer::ROOT_NAME => ServiceProperties::$xmlRootName);
        
        // Test
        $actual = $xmlSerializer->serialize($array, $serializerProperties);
        
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::serialize
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::_arr2xml
     */
    public function testSerializeNoArray()
    {
        // Setup
        $xmlSerializer = new XmlSerializer();
        $expected = false;
        $array = 'not an array';
        $serializerProperties = array(XmlSerializer::ROOT_NAME => ServiceProperties::$xmlRootName);
        
        // Test
        $actual = $xmlSerializer->serialize($array, $serializerProperties);
        
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::serialize
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::_arr2xml
     */
    public function testSerializeAttribute()
    {
        // Setup
        $xmlSerializer = new XmlSerializer();
        $expected = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
            '<Object field1="value1" field2="value2"/>' . "\n";
        
        $object = array(
            '@attributes' => array(
                'field1' => 'value1',
                'field2' => 'value2'
            )
        );
        $serializerProperties = array(XmlSerializer::ROOT_NAME => 'Object');
        
        // Test
        $actual = $xmlSerializer->serialize($object, $serializerProperties);
        
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::objectSerialize
     */
    public function testObjectSerializeSucceess()
    {
        // Setup
        $expected = "<DummyClass/>\n";
        $target = new DummyClass();

        // Test
        $actual = XmlSerializer::objectSerialize($target, 'DummyClass');

        // Assert
        $this->assertEquals(
            $expected,
            $actual
        );
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::objectSerialize
     */
    public function testObjectSerializeSucceessWithAttributes()
    {
        // Setup 
        $expected = "<DummyClass testAttribute=\"testAttributeValue\"/>\n";
        $target = new DummyClass();
        $target->addAttribute('testAttribute', 'testAttributeValue');

        // Test
        $actual = XmlSerializer::objectSerialize($target, 'DummyClass');

        // Assert
        $this->assertEquals(
            $expected,
            $actual
        );
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer::objectSerialize
     */
    public function testObjectSerializeInvalidObject()
    {
        // Setup
        $this->setExpectedException(get_class(new \InvalidArgumentException()));
        // Test
        $actual = XmlSerializer::objectSerialize(null, null);
        // Assert
    }

}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Tests\Mock\Common\Internal\Filters\SimpleFilterMock;
use MicrosoftAzure\Storage\Blob\Models\AccessCondition;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * Unit tests for class ServiceRestProxy
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServiceRestProxyTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::generateMetadataHeaders
     */
    public function test__construct()
    {
        // Setup
        $uri     = 'http://www.microsoft.com';
        $accountName = 'myaccount';
        $dataSerializer = new XmlSerializer();

        // Test
        $proxy = new ServiceRestProxy($uri, $accountName, $dataSerializer);

        // Assert
        $this->assertNotNull($proxy);
        $this->assertEquals($accountName, $proxy->getAccountName());

        // Auto append an '/' at the end of uri.
        $this->assertEquals($uri . '/', $proxy->getUri());

        return $proxy;
    }

    /**
     * @covers  MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::withFilter
     * @depends test__construct
     */
    public function testWithFilter($restRestProxy)
    {
        // Setup
        $filter = new SimpleFilterMock('name', 'value');

        // Test
        $actual = $restRestProxy->withFilter($filter);

        // Assert
        $this->assertCount(1, $actual->getFilters());
        $this->assertCount(0, $restRestProxy->getFilters());
    }

    /**
     * @covers  MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::getFilters
     * @depends test__construct
     */
    public function testGetFilters($restRestProxy)
    {
        // Setup
        $filter = new SimpleFilterMock('name', 'value');
        $withFilter = $restRestProxy->withFilter($filter);

        // Test
        $actual1 = $withFilter->getFilters();
        $actual2 = $restRestProxy->getFilters();

        // Assert
        $this->assertCount(1, $actual1);
        $this->assertCount(0, $actual2);
    }

    /**
     * @covers  MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::addOptionalAccessConditionHeader
     * @depends test__construct
     */
    public function testAddOptionalAccessContitionHeader($restRestProxy)
    {
        // Setup
        $expectedHeader = Resources::IF_MATCH;
        $expectedValue = '0x8CAFB82EFF70C46';
        $accessCondition = AccessCondition::ifMatch($expectedValue);
        $headers = array('Header1' => 'Value1', 'Header2' => 'Value2');

        // Test
        $actual = $restRestProxy->addOptionalAccessConditionHeader($headers, $accessCondition);

        // Assert
        $this->assertCount(3, $actual);
        $this->assertEquals($expectedValue, $actual[$expectedHeader]);
    }

    /**
     * @covers  MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::addOptionalSourceAccessConditionHeader
     * @depends test__construct
     */
    public function testAddOptionalSourceAccessContitionHeader($restRestProxy)
    {
        // Setup
        $expectedHeader = Resources::X_MS_SOURCE_IF_MATCH;
        $expectedValue = '0x8CAFB82EFF70C46';
        $accessCondition = AccessCondition::ifMatch($expectedValue);
        $headers = array('Header1' => 'Value1', 'Header2' => 'Value2');

        // Test
        $actual = $restRestProxy->addOptionalSourceAccessConditionHeader($headers, $accessCondition);

        // Assert
        $this->assertCount(3, $actual);
        $this->assertEquals($expectedValue, $actual[$expectedHeader]);
    }

    /**
     * @covers  MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::groupQueryValues
     * @depends test__construct
     */
    public function testGroupQueryValues($restRestProxy)
    {
        // Setup
        $values = array('A', 'B', 'C');
        $expected = 'A,B,C';

        // Test
        $actual = $restRestProxy->groupQueryValues($values);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers  MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::groupQueryValues
     * @depends test__construct
     */
    public function testGroupQueryValuesWithNulls($restRestProxy)
    {
        // Setup
        $values = array(null, '', null);

        // Test
        $actual = $restRestProxy->groupQueryValues($values);

        // Assert
        $this->assertTrue(empty($actual));
    }

    /**
     * @covers  MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::groupQueryValues
     * @depends test__construct
     */
    public function testGroupQueryValuesWithMix($restRestProxy)
    {
        // Setup
        $values = array(null, 'B', 'C', '');
        $expected = 'B,C';

        // Test
        $actual = $restRestProxy->groupQueryValues($values);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
    * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::addPostParameter
    * @depends test__construct
    */
    public function testPostParameter($restRestProxy)
    {
        // Setup
        $postParameters = array();
        $key = 'a';
        $expected = 'b';

        // Test
        $processedPostParameters = $restRestProxy->addPostParameter($postParameters, $key, $expected);
        $actual = $processedPostParameters[$key];

        // Assert
        $this->assertEquals(
            $expected,
            $actual
        );
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::generateMetadataHeaders
     * @depends test__construct
     */
    public function testGenerateMetadataHeader($proxy)
    {
        // Setup
        $metadata = array('key1' => 'value1', 'MyName' => 'WindowsAzure', 'MyCompany' => 'Microsoft_');
        $expected = array();
        foreach ($metadata as $key => $value) {
            $expected[Resources::X_MS_META_HEADER_PREFIX . $key] = $value;
        }

        // Test
        $actual = $proxy->generateMetadataHeaders($metadata);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::generateMetadataHeaders
     * @depends test__construct
     */
    public function testGenerateMetadataHeaderInvalidNameFail($proxy)
    {
        // Setup
        $metadata = array('key1' => "value1\n", 'MyName' => "\rAzurr", 'MyCompany' => "Micr\r\nosoft_");
        $this->setExpectedException(get_class(new \InvalidArgumentException(Resources::INVALID_META_MSG)));

        // Test
        $proxy->generateMetadataHeaders($metadata);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::getMetadataArray
     * @depends test__construct
     */
    public function testGetMetadataArray($proxy)
    {
        // Setup
        $expected = array('key1' => 'value1', 'myname' => 'azure', 'mycompany' => 'microsoft_');
        $metadataHeaders = array();
        foreach ($expected as $key => $value) {
            $metadataHeaders[Resources::X_MS_META_HEADER_PREFIX . strtolower($key)] = $value;
        }

        // Test
        $actual = $proxy->getMetadataArray($metadataHeaders);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::getMetadataArray
     * @depends test__construct
     */
    public function testGetMetadataArrayWithMsHeaders($proxy)
    {
        // Setup
        $key = 'name';
        $validMetadataKey = Resources::X_MS_META_HEADER_PREFIX . $key;
        $value = 'correct';
        $metadataHeaders = array('x-ms-key1' => 'value1', 'myname' => 'x-ms-date',
                          $validMetadataKey => $value, 'mycompany' => 'microsoft_');

        // Test
        $actual = $proxy->getMetadataArray($metadataHeaders);

        // Assert
        $this->assertCount(1, $actual);
        $this->assertEquals($value, $actual[$key]);
    }

    /**
     * @expectedException \GuzzleHttp\Exception\RequestException
     * @expectedExceptionMessage foo
     */
    public function testSetGuzzleOptions()
    {
        $uri = 'http://www.microsoft.com';
        $accountName = 'myaccount';
        $dataSerializer = new XmlSerializer();
        $mockRequestHandler = new MockHandler(array(new RequestException('foo', new Request('GET', $uri))));

        $guzzleOptions = array('http' => array('handler' => HandlerStack::create($mockRequestHandler)));
        $proxy = new ServiceRestProxy($uri, $accountName, $dataSerializer, $guzzleOptions);
        $reflection = new \ReflectionClass($proxy);
        $method = $reflection->getMethod('send');
        $method->setAccessible(true);

        $method->invokeArgs($proxy, array(
            'GET',
            [],
            [],
            [],
            '/',
            null
        ));
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class StorageServiceSettings
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class StorageServiceSettingsTest extends \PHPUnit_Framework_TestCase
{
    private $_accountName = 'mytestaccount';
    
    public function setUp()
    {
        $property = new \ReflectionProperty('MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings', 'isInitialized');
        $property->setAccessible(true);
        $property->setValue(false);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::developmentStorageAccount
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDevelopmentStorageAccount
     */
    public function testCreateFromConnectionStringWithUseDevStore()
    {
        // Setup
        $connectionString = 'UseDevelopmentStorage=true';
        $expectedName = Resources::DEV_STORE_NAME;
        $expectedKey = Resources::DEV_STORE_KEY;
        $expectedBlobEndpoint = Resources::DEV_STORE_URI . ':10000/devstoreaccount1/';
        $expectedQueueEndpoint = Resources::DEV_STORE_URI . ':10001/devstoreaccount1/';
        $expectedTableEndpoint = Resources::DEV_STORE_URI . ':10002/devstoreaccount1/';
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::developmentStorageAccount
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDevelopmentStorageAccount
     */
    public function testCreateFromConnectionStringWithUseDevStoreUri()
    {
        // Setup
        $myProxyUri = 'http://222.3.5.6';
        $connectionString = "DevelopmentStorageProxyUri=$myProxyUri;UseDevelopmentStorage=true";
        $expectedName = Resources::DEV_STORE_NAME;
        $expectedKey = Resources::DEV_STORE_KEY;
        $expectedBlobEndpoint = $myProxyUri . ':10000/devstoreaccount1/';
        $expectedQueueEndpoint = $myProxyUri . ':10001/devstoreaccount1/';
        $expectedTableEndpoint = $myProxyUri . ':10002/devstoreaccount1/';
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::developmentStorageAccount
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDevelopmentStorageAccount
     */
    public function testCreateFromConnectionStringWithInvalidUseDevStoreFail()
    {
        // Setup
        $invalidValue = 'invalid_value';
        $connectionString = "UseDevelopmentStorage=$invalidValue";
        $expectedMsg = sprintf(
            Resources::INVALID_CONFIG_VALUE,
            $invalidValue,
            implode("\n", array('true'))
        );
        $this->setExpectedException('\RuntimeException', $expectedMsg);
        
        // Test
        StorageServiceSettings::createFromConnectionString($connectionString);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::developmentStorageAccount
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDevelopmentStorageAccount
     */
    public function testCreateFromConnectionStringWithEmptyConnectionStringFail()
    {
        // Setup
        $connectionString = '';
        $this->setExpectedException('\InvalidArgumentException');
        
        // Test
        StorageServiceSettings::createFromConnectionString($connectionString);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::getName
     */
    public function testGetName()
    {
        // Setup
        $expected = 'myname';
        $setting = new StorageServiceSettings($expected, null, null, null, null);
        
        // Test
        $actual = $setting->getName();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::getKey
     */
    public function testGetKey()
    {
        // Setup
        $expected = 'mykey';
        $setting = new StorageServiceSettings(null, $expected, null, null, null);
        
        // Test
        $actual = $setting->getKey();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::getBlobEndpointUri
     */
    public function testGetBlobEndpointUri()
    {
        // Setup
        $expected = 'myblobEndpointUri';
        $setting = new StorageServiceSettings(null, null, $expected, null, null);
        
        // Test
        $actual = $setting->getBlobEndpointUri();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::getQueueEndpointUri
     */
    public function testGetQueueEndpointUri()
    {
        // Setup
        $expected = 'myqueueEndpointUri';
        $setting = new StorageServiceSettings(null, null, null, $expected, null);
        
        // Test
        $actual = $setting->getQueueEndpointUri();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::getTableEndpointUri
     */
    public function testGetTableEndpointUri()
    {
        // Setup
        $expected = 'mytableEndpointUri';
        $setting = new StorageServiceSettings(null, null, null, null, $expected);
        
        // Test
        $actual = $setting->getTableEndpointUri();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithAutomatic()
    {
        // Setup
        $protocol = 'https';
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $connectionString  = "DefaultEndpointsProtocol=$protocol;AccountName=$expectedName;AccountKey=$expectedKey";
        $expectedBlobEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::BLOB_BASE_DNS_NAME);
        $expectedQueueEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::QUEUE_BASE_DNS_NAME);
        $expectedTableEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::TABLE_BASE_DNS_NAME);
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithTableEndpointSpecified()
    {
        // Setup
        $protocol = 'https';
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $expectedTableEndpoint = 'http://myprivatedns.com';
        $expectedBlobEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::BLOB_BASE_DNS_NAME);
        $expectedQueueEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::QUEUE_BASE_DNS_NAME);
        $connectionString  = "DefaultEndpointsProtocol=$protocol;AccountName=$expectedName;AccountKey=$expectedKey;TableEndpoint=$expectedTableEndpoint";
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithBlobEndpointSpecified()
    {
        // Setup
        $protocol = 'https';
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $expectedTableEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::TABLE_BASE_DNS_NAME);
        $expectedBlobEndpoint = 'http://myprivatedns.com';
        $expectedQueueEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::QUEUE_BASE_DNS_NAME);
        $connectionString  = "DefaultEndpointsProtocol=$protocol;BlobEndpoint=$expectedBlobEndpoint;AccountName=$expectedName;AccountKey=$expectedKey";
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithQueueEndpointSpecified()
    {
        // Setup
        $protocol = 'https';
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $expectedTableEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::TABLE_BASE_DNS_NAME);
        $expectedBlobEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::BLOB_BASE_DNS_NAME);
        $expectedQueueEndpoint = 'http://myprivatedns.com';
        $connectionString  = "QueueEndpoint=$expectedQueueEndpoint;DefaultEndpointsProtocol=$protocol;AccountName=$expectedName;AccountKey=$expectedKey";
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithQueueAndBlobEndpointSpecified()
    {
        // Setup
        $protocol = 'https';
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $expectedTableEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::TABLE_BASE_DNS_NAME);
        $expectedBlobEndpoint = 'http://myprivateblobdns.com';
        $expectedQueueEndpoint = 'http://myprivatequeuedns.com';
        $connectionString  = "QueueEndpoint=$expectedQueueEndpoint;DefaultEndpointsProtocol=$protocol;AccountName=$expectedName;AccountKey=$expectedKey;BlobEndpoint=$expectedBlobEndpoint";
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithAutomaticMissingProtocolFail()
    {
        // Setup
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $connectionString  = "AccountName=$expectedName;AccountKey=$expectedKey";
        $expectedMsg = sprintf(Resources::MISSING_CONNECTION_STRING_SETTINGS, $connectionString);
        $this->setExpectedException('\RuntimeException', $expectedMsg);
        
        // Test
        StorageServiceSettings::createFromConnectionString($connectionString);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithAutomaticMissingAccountNameFail()
    {
        // Setup
        $expectedKey = TestResources::KEY4;
        $connectionString  = "DefaultEndpointsProtocol=http;AccountKey=$expectedKey";
        $expectedMsg = sprintf(Resources::MISSING_CONNECTION_STRING_SETTINGS, $connectionString);
        $this->setExpectedException('\RuntimeException', $expectedMsg);
        
        // Test
        StorageServiceSettings::createFromConnectionString($connectionString);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithAutomaticCorruptedAccountKeyFail()
    {
        // Setup
        $expectedName = $this->_accountName;
        $invalidKey = '__A&*INVALID-@Key';
        $connectionString  = "DefaultEndpointsProtocol=http;AccountName=$expectedName;AccountKey=$invalidKey";
        $expectedMsg = sprintf(Resources::INVALID_ACCOUNT_KEY_FORMAT, $invalidKey);
        $this->setExpectedException('\RuntimeException', $expectedMsg);
        
        // Test
        StorageServiceSettings::createFromConnectionString($connectionString);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::atLeastOne
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithQueueEndpointSpecfied()
    {
        // Setup
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $expectedTableEndpoint = null;
        $expectedBlobEndpoint = null;
        $expectedQueueEndpoint = 'http://myprivatequeuedns.com';
        $connectionString  = "QueueEndpoint=$expectedQueueEndpoint;AccountName=$expectedName;AccountKey=$expectedKey";
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::atLeastOne
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithQueueAndBlobEndpointSpecfied()
    {
        // Setup
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $expectedTableEndpoint = null;
        $expectedBlobEndpoint = 'http://myprivateblobdns.com';;
        $expectedQueueEndpoint = 'http://myprivatequeuedns.com';
        $connectionString  = "QueueEndpoint=$expectedQueueEndpoint;BlobEndpoint=$expectedBlobEndpoint;AccountName=$expectedName;AccountKey=$expectedKey";
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::atLeastOne
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithQueueAndBlobAndTableEndpointSpecfied()
    {
        // Setup
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $expectedTableEndpoint = 'http://myprivatetabledns.com';
        $expectedBlobEndpoint = 'http://myprivateblobdns.com';;
        $expectedQueueEndpoint = 'http://myprivatequeuedns.com';
        $connectionString  = "TableEndpoint=$expectedTableEndpoint;QueueEndpoint=$expectedQueueEndpoint;BlobEndpoint=$expectedBlobEndpoint;AccountName=$expectedName;AccountKey=$expectedKey";
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::atLeastOne
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringMissingServicesEndpointsFail()
    {
        // Setup
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $connectionString  = "AccountName=$expectedName;AccountKey=$expectedKey";
        $expectedMsg = sprintf(Resources::MISSING_CONNECTION_STRING_SETTINGS, $connectionString);
        $this->setExpectedException('\RuntimeException', $expectedMsg);
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertNull($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithInvalidBlobEndpointUriFail()
    {
        // Setup
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $invalidUri = 'https://www.invalid_domain';
        $connectionString  = "BlobEndpoint=$invalidUri;DefaultEndpointsProtocol=http;AccountName=$expectedName;AccountKey=$expectedKey";
        $expectedMsg = sprintf(Resources::INVALID_CONFIG_URI, $invalidUri);
        $this->setExpectedException('\RuntimeException', $expectedMsg);
        
        // Test
        StorageServiceSettings::createFromConnectionString($connectionString);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithInvalidSettingKeyFail()
    {
        // Setup
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $validKeys = array();
        $validKeys[] = Resources::USE_DEVELOPMENT_STORAGE_NAME;
        $validKeys[] = Resources::DEVELOPMENT_STORAGE_PROXY_URI_NAME;
        $validKeys[] = Resources::DEFAULT_ENDPOINTS_PROTOCOL_NAME;
        $validKeys[] = Resources::ACCOUNT_NAME_NAME;
        $validKeys[] = Resources::ACCOUNT_KEY_NAME;
        $validKeys[] = Resources::BLOB_ENDPOINT_NAME;
        $validKeys[] = Resources::QUEUE_ENDPOINT_NAME;
        $validKeys[] = Resources::TABLE_ENDPOINT_NAME;
        $invalidKey = 'InvalidKey';
        $connectionString  = "DefaultEndpointsProtocol=http;$invalidKey=MyValue;AccountName=$expectedName;AccountKey=$expectedKey";
        $expectedMsg = sprintf(
            Resources::INVALID_CONNECTION_STRING_SETTING_KEY,
            $invalidKey,
            implode("\n", $validKeys)
        );
        $this->setExpectedException('\RuntimeException', $expectedMsg);
        
        // Test
        StorageServiceSettings::createFromConnectionString($connectionString);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::createFromConnectionString
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::init
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::__construct
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_getDefaultServiceEndpoint
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::getValidator
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::optional
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::allRequired
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::setting
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::settingWithFunc
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::matchedSpecification
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::parseAndValidateKeys
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceSettings::noMatch
     * @covers MicrosoftAzure\Storage\Common\Internal\StorageServiceSettings::_createStorageServiceSettings
     */
    public function testCreateFromConnectionStringWithCaseInsensitive()
    {
        // Setup
        $protocol = 'https';
        $expectedName = $this->_accountName;
        $expectedKey = TestResources::KEY4;
        $connectionString  = "defaultendpointsprotocol=$protocol;accountname=$expectedName;accountkey=$expectedKey";
        $expectedBlobEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::BLOB_BASE_DNS_NAME);
        $expectedQueueEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::QUEUE_BASE_DNS_NAME);
        $expectedTableEndpoint = sprintf(Resources::SERVICE_URI_FORMAT, $protocol, $expectedName, Resources::TABLE_BASE_DNS_NAME);
        
        // Test
        $actual = StorageServiceSettings::createFromConnectionString($connectionString);
        
        // Assert
        $this->assertEquals($expectedName, $actual->getName());
        $this->assertEquals($expectedKey, $actual->getKey());
        $this->assertEquals($expectedBlobEndpoint, $actual->getBlobEndpointUri());
        $this->assertEquals($expectedQueueEndpoint, $actual->getQueueEndpointUri());
        $this->assertEquals($expectedTableEndpoint, $actual->getTableEndpointUri());
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Tests\Framework\VirtualFileSystem;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;


/**
 * Unit tests for class Utilities
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class UtilitiesTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::tryGetValue
     */
    public function testTryGetValue()
    {
        // Setup
        $key = 0;
        $expected = 10;
        $data = array(10, 20, 30);

        // Test
        $actual = Utilities::tryGetValue($data, $key);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::tryGetValue
     */
    public function testTryGetValueUsingDefault()
    {
        // Setup
        $key = 10;
        $expected = 6;
        $data = array(10, 20, 30);

        // Test
        $actual = Utilities::tryGetValue($data, $key, $expected);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::tryGetValue
     */
    public function testTryGetValueWithNull()
    {
        // Setup
        $key = 10;
        $data = array(10, 20, 30);

        // Test
        $actual = Utilities::tryGetValue($data, $key);

        $this->assertNull($actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::tryGetKeysChainValue
     */
    public function testTryGetKeysChainValue()
    {
        // Setup
        $array = array();
        $array['a1'] = array();
        $array['a2'] = 'value1';
        $array['a1']['b1'] = array();
        $array['a1']['b2'] = 'value2';
        $array['a1']['b1']['c1'] = 'value3';

        // Test - Level 1
        $this->assertEquals('value1', Utilities::tryGetKeysChainValue($array, 'a2'));
        $this->assertEquals(null, Utilities::tryGetKeysChainValue($array, 'a3'));

        // Test - Level 2
        $this->assertEquals('value2', Utilities::tryGetKeysChainValue($array, 'a1', 'b2'));
        $this->assertEquals(null, Utilities::tryGetKeysChainValue($array, 'a1', 'b3'));

        // Test - Level 3
        $this->assertEquals('value3', Utilities::tryGetKeysChainValue($array, 'a1', 'b1', 'c1'));
        $this->assertEquals(null, Utilities::tryGetKeysChainValue($array, 'a1', 'b1', 'c2'));
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::startsWith
     */
    public function testStartsWith()
    {
        // Setup
        $string = 'myname';
        $prefix = 'my';

        // Test
        $actual = Utilities::startsWith($string, $prefix);

        $this->assertTrue($actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::startsWith
     */
    public function testStartsWithDoesNotStartWithPrefix()
    {
        // Setup
        $string = 'amyname';
        $prefix = 'my';

        // Test
        $actual = Utilities::startsWith($string, $prefix);

        $this->assertFalse($actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::getArray
     */
    public function testGetArray()
    {
        // Setup
        $expected = array(array(1, 2, 3, 4),  array(5, 6, 7, 8));

        // Test
        $actual = Utilities::getArray($expected);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::getArray
     */
    public function testGetArrayWithFlatValue()
    {
        // Setup
        $flat = array(1, 2, 3, 4, 5, 6, 7, 8);
        $expected = array(array(1, 2, 3, 4, 5, 6, 7, 8));

        // Test
        $actual = Utilities::getArray($flat);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::getArray
     */
    public function testGetArrayWithMixtureValue()
    {
        // Setup
        $flat = array(array(10, 2), 1, 2, 3, 4, 5, 6, 7, 8);
        $expected = array(array(array(10, 2), 1, 2, 3, 4, 5, 6, 7, 8));

        // Test
        $actual = Utilities::getArray($flat);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::getArray
     */
    public function testGetArrayWithEmptyValue()
    {
        // Setup
        $empty = Resources::EMPTY_STRING;
        $expected = array();

        // Test
        $actual = Utilities::getArray($empty);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::unserialize
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::_sxml2arr
     */
    public function testUnserialize()
    {
        // Setup
        $propertiesSample = TestResources::getServicePropertiesSample();
        $properties = ServiceProperties::create($propertiesSample);
        $xmlSerializer = new XmlSerializer();
        $xml = $properties->toXml($xmlSerializer);
        $expected = $properties->toArray();

        // Test
        $actual = Utilities::unserialize($xml);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::serialize
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::_arr2xml
     */
    public function testSerialize()
    {
        // Setup
        $propertiesSample = TestResources::getServicePropertiesSample();
        $properties = ServiceProperties::create($propertiesSample);
        $expected  = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
        $expected .= '<StorageServiceProperties><Logging><Version>1.0</Version><Delete>true</Delete>';
        $expected .= '<Read>false</Read><Write>true</Write><RetentionPolicy><Enabled>true</Enabled>';
        $expected .= '<Days>20</Days></RetentionPolicy></Logging><HourMetrics><Version>1.0</Version>';
        $expected .= '<Enabled>true</Enabled><IncludeAPIs>false</IncludeAPIs><RetentionPolicy>';
        $expected .= '<Enabled>true</Enabled><Days>20</Days></RetentionPolicy></HourMetrics></StorageServiceProperties>';
        $array = $properties->toArray();

        // Test
        $actual = Utilities::serialize($array, ServiceProperties::$xmlRootName);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::serialize
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::_arr2xml
     */
    public function testSerializeNoArray()
    {
        // Setup
        $expected = false;
        $array = 'not an array';

        // Test
        $actual = Utilities::serialize($array, ServiceProperties::$xmlRootName);

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::serialize
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::_arr2xml
     */
    public function testSerializeAttribute()
    {
        // Setup
        $expected = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
            '<Object field1="value1" field2="value2"/>';

        $object = array(
            '@attributes' => array(
                'field1' => 'value1',
                'field2' => 'value2'
            )
        );

        // Test
        $actual = Utilities::serialize($object, 'Object');

        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::toBoolean
     */
    public function testToBoolean()
    {
        // Setup
        $value = 'true';
        $expected = true;

        // Test
        $actual = Utilities::toBoolean($value);

        // Assert
        $this->assertTrue(is_bool($actual));
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::booleanToString
     */
    public function testBooleanToString()
    {
        // Setup
        $expected = 'true';
        $value = true;

        // Test
        $actual = Utilities::booleanToString($value);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::isoDate
     */
    public function testIsoDate()
    {
        // Test
        $date = Utilities::isoDate();

        // Assert
        $this->assertNotNull($date);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::convertToEdmDateTime
     */
    public function testConvertToEdmDateTime()
    {
        // Test
        $actual = Utilities::convertToEdmDateTime(new \DateTime());

        // Assert
        $this->assertNotNull($actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::convertToDateTime
     */
    public function testConvertToDateTime()
    {
        // Setup
        $date = '2008-10-01T15:26:13Z';

        // Test
        $actual = Utilities::convertToDateTime($date);

        // Assert
        $this->assertInstanceOf('\DateTime', $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::convertToDateTime
     */
    public function testConvertToDateTimeWithDate()
    {
        // Setup
        $date = new \DateTime();

        // Test
        $actual = Utilities::convertToDateTime($date);

        // Assert
        $this->assertEquals($date, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::stringToStream
     */
    public function testStringToStream()
    {
        $data = 'This is string';
        $expected = fopen('data://text/plain,' . $data, 'r');

        // Test
        $actual = Utilities::stringToStream($data);

        // Assert
        $this->assertEquals(stream_get_contents($expected), stream_get_contents($actual));
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::rfc1123ToDateTime
     */
    public function testWindowsAzureDateToDateTime()
    {
        // Setup
        $expected = 'Fri, 16 Oct 2009 21:04:30 GMT';

        // Test
        $actual = Utilities::rfc1123ToDateTime($expected);

        // Assert
        $this->assertEquals($expected, $actual->format('D, d M Y H:i:s T'));
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::tryAddUrlScheme
     */
    public function testTryAddUrlSchemeWithScheme()
    {
        // Setup
        $url = 'http://microsoft.com';

        // Test
        $actual = Utilities::tryAddUrlScheme($url);

        // Assert
        $this->assertEquals($url, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::tryAddUrlScheme
     */
    public function testTryAddUrlSchemeWithoutScheme()
    {
        // Setup
        $url = 'microsoft.com';
        $expected = 'http://microsoft.com';

        // Test
        $actual = Utilities::tryAddUrlScheme($url);

        // Assert
        $this->assertEquals($expected, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::startsWith
     */
    public function testStartsWithIgnoreCase()
    {
        // Setup
        $string = 'MYString';
        $prefix = 'mY';

        // Test
        $actual = Utilities::startsWith($string, $prefix, true);

        // Assert
        $this->assertTrue($actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::inArrayInsensitive
     */
    public function testInArrayInsensitive()
    {
        // Setup
        $value = 'CaseInsensitiVe';
        $array = array('caSeinSenSitivE');

        // Test
        $actual = Utilities::inArrayInsensitive($value, $array);

        // Assert
        $this->assertTrue($actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::inArrayInsensitive
     */
    public function testArrayKeyExistsInsensitive()
    {
        // Setup
        $key = 'CaseInsensitiVe';
        $array = array('caSeinSenSitivE' => '123');

        // Test
        $actual = Utilities::arrayKeyExistsInsensitive($key, $array);

        // Assert
        $this->assertTrue($actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::tryGetValueInsensitive
     */
    public function testTryGetValueInsensitive()
    {
        // Setup
        $key = 'KEy';
        $value = 1;
        $array = array($key => $value);

        // Test
        $actual = Utilities::tryGetValueInsensitive('keY', $array);

        // Assert
        $this->assertEquals($value, $actual);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::getGuid
     */
    public function testGetGuid()
    {
        // Test
        $actual1 = Utilities::getGuid();
        $actual2 = Utilities::getGuid();

        // Assert
        $this->assertNotNull($actual1);
        $this->assertNotNull($actual2);
        $this->assertInternalType('string', $actual1);
        $this->assertInternalType('string', $actual2);
        $this->assertNotEquals($actual1, $actual2);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::endsWith
     */
    public function testEndsWith()
    {
        // Setup
        $haystack = 'tesT';
        $needle = 't';
        $expected = true;

        // Test
        $actual = Utilities::endsWith($haystack, $needle, true);

        // Assert
        $this->assertEquals($expected, $actual);
    }

//     /**
//      * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::getEntityId
//      */
//     public function testGetEntityIdWithString(){

//         // Setup
//         $id = 'kjgdfg57';

//         // Test
//         $result = Utilities::GetEntityId($id, 'MicrosoftAzure\Storage\MediaServices\Models\Asset');

//         //Assert
//         $this->assertEquals($id, $result);
//     }

//     /**
//      * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::getEntityId
//      */
//     public function testGetEntityIdWithObject(){

//         // Setup
//         $idKey = 'Id';
//         $optionKey = 'Options';
//         $assetArray= array(
//                 $idKey                  => 'kjgdfg57',
//                 $optionKey             => Asset::OPTIONS_NONE,
//         );
//         $value = Asset::createFromOptions($assetArray);

//         // Test
//         $result = Utilities::GetEntityId($value,'MicrosoftAzure\Storage\MediaServices\Models\Asset');

//         //Assert
//         $this->assertEquals($assetArray[$idKey], $result);
//     }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::generateCryptoKey
     */
    public function testGenerateCryptoKey(){

        // Setup
        $length = 32;

        // Test
        $result = Utilities::generateCryptoKey($length);

        // Assert
        $this->assertEquals($length, strlen($result));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::ctrCrypt
     */
    public function testCtrCrypt(){
    
        // Setup
        $data = 'Test data more than 16 bytes';
        $key = Utilities::generateCryptoKey(32);
        $efectiveInitializationVector = Utilities::generateCryptoKey(8);
        $initializationVector = str_pad($efectiveInitializationVector, 16, chr(255));
    
        // Test
        $ecnrypted = Utilities::ctrCrypt($data, $key, $initializationVector);
        $decrypted = Utilities::ctrCrypt($ecnrypted, $key, $initializationVector);
    
        // Assert
        $this->assertEquals($data, $decrypted);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::ctrCrypt
     */
    public function testCtrCryptFixedKeys(){
    
        // Setup
        $data = 'Test data more than 16 bytes';
        $key = base64_decode('QNhZJajWRH3fmCKDJtMluj6PUBvkADwJ7dX4KQGI99o=');
        $efectiveInitializationVector = base64_decode('k3AmLEGFubw=');
        $expected = base64_decode('j3+9MFQVctoWlUvqbn/xReun0XnWqwJ3tpvbpw==');
        
        $initializationVector = str_pad($efectiveInitializationVector, 16, chr(255));
        
        // Test
        $actual = Utilities::ctrCrypt($data, $key, $initializationVector);
    
        // Assert
        $this->assertEquals($actual, $expected);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::ctrCrypt
     */
    public function testCtrCryptInvalidKeyLength(){
    
        // Setup
        $data = 'Test data more than 16 bytes';
        $key = '12345';
        $efectiveInitializationVector = Utilities::generateCryptoKey(8);
        $this->setExpectedException(get_class(new \InvalidArgumentException('')));
        
        $initializationVector = str_pad($efectiveInitializationVector, 16, chr(255));
        
        // Test
        $actual = Utilities::ctrCrypt($data, $key, $initializationVector);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::ctrCrypt
     */
    public function testCtrCryptInvalidInitializationVectorLength(){
    
        // Setup
        $data = 'Test data more than 16 bytes';
        $key = Utilities::generateCryptoKey(32);
        $initializationVector = '1234';
        $this->setExpectedException(get_class(new \InvalidArgumentException('')));
        
        // Test
        $actual = Utilities::ctrCrypt($data, $key, $initializationVector);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::base256ToDec
     */
    public function testBase256ToDecF(){
    
        // Setup
        $data = pack('C*', 255, 255, 255, 255);
        $expected = 4294967295;
    
        // Test
        $actual = Utilities::base256ToDec($data);
    
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::base256ToDec
     */
    public function testBase256ToDec0(){
    
        // Setup
        $data = pack('C*', 0, 0, 0, 0);
        $expected = 0;
    
        // Test
        $actual = Utilities::base256ToDec($data);
    
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::base256ToDec
     */
    public function testBase256ToDec(){
    
        // Setup
        $data = pack('C*', 34, 78, 27, 55);
        $expected = 575544119;
    
        // Test
        $actual = Utilities::base256ToDec($data);
    
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Utilities::base256ToDec
     */
    public function testBase256ToDecBig(){
    
        // Setup
        $data = pack('C*', 81, 35, 29, 39, 236, 104, 105, 144); //51 23 1D 27 EC 68 69 90
        $expected = '5846548798564231568';
    
        // Test
        $actual = Utilities::base256ToDec($data);
    
        // Assert
        $this->assertEquals($expected, $actual);
    }
}
<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Internal;
use MicrosoftAzure\Storage\Common\Internal\Validate;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class ValidateTest
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ValidateTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isArray
     */
    public function testIsArrayWithArray()
    {
        Validate::isArray(array(), 'array');

        $this->assertTrue(true);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isArray
     */
    public function testIsArrayWithNonArray()
    {
        $this->setExpectedException(get_class(new InvalidArgumentTypeException('')));
        Validate::isArray(123, 'array');
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isString
     */
    public function testIsStringWithString()
    {
        Validate::isString('I\'m a string', 'string');

        $this->assertTrue(true);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isString
     */
    public function testIsStringWithNonString()
    {
        $this->setExpectedException(get_class(new InvalidArgumentTypeException('')));
        Validate::isString(new \DateTime(), 'string');
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isBoolean
     */
    public function testIsBooleanWithBoolean()
    {
        Validate::isBoolean(true);

        $this->assertTrue(true);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInteger
     */
    public function testIsIntegerWithInteger()
    {
        Validate::isInteger(123, 'integer');

        $this->assertTrue(true);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInteger
     */
    public function testIsIntegerWithNonInteger()
    {
        $this->setExpectedException(get_class(new InvalidArgumentTypeException('')));
        Validate::isInteger(new \DateTime(), 'integer');
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isTrue
     */
    public function testIsTrueWithTrue()
    {
        Validate::isTrue(true, Resources::EMPTY_STRING);

        $this->assertTrue(true);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isTrue
     */
    public function testIsTrueWithFalse()
    {
        $this->setExpectedException('\InvalidArgumentException');
        Validate::isTrue(false, Resources::EMPTY_STRING);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isDate
     */
    public function testIsDateWithDate()
    {
        $date = Utilities::rfc1123ToDateTime('Fri, 09 Oct 2009 21:04:30 GMT');
        Validate::isDate($date);

        $this->assertTrue(true);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isDate
     */
    public function testIsDateWithNonDate()
    {
        $this->setExpectedException(get_class(new InvalidArgumentTypeException('DateTime')));
        Validate::isDate('not date');
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::notNullOrEmpty
     */
    public function testNotNullOrEmptyWithNonEmpty()
    {
        Validate::notNullOrEmpty(1234, 'not null');

        $this->assertTrue(true);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::notNullOrEmpty
     */
    public function testNotNullOrEmptyWithEmpty()
    {
        $this->setExpectedException('\InvalidArgumentException');
        Validate::notNullOrEmpty(Resources::EMPTY_STRING, 'variable');
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::notNull
     */
    public function testNotNullWithNull()
    {
        $this->setExpectedException('\InvalidArgumentException');
        Validate::notNullOrEmpty(null, 'variable');
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInstanceOf
     */
    public function testIsInstanceOfStringPasses()
    {
        // Setup
        $value = 'testString';
        $stringObject = 'stringObject';

        // Test
        $result = Validate::isInstanceOf($value, $stringObject, 'value');

        // Assert
        $this->assertTrue($result);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInstanceOf
     */
    public function testIsInstanceOfStringFail()
    {
        // Setup
        $this->setExpectedException('\InvalidArgumentException');
        $value = 'testString';
        $arrayObject = array();

        // Test
        $result = Validate::isInstanceOf($value, $arrayObject, 'value');

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInstanceOf
     */
    public function testIsInstanceOfArrayPasses()
    {
        // Setup
        $value = array();
        $arrayObject = array();

        // Test
        $result = Validate::isInstanceOf($value, $arrayObject, 'value');

        // Assert
        $this->assertTrue($result);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInstanceOf
     */
    public function testIsInstanceOfArrayFail()
    {
        // Setup
        $this->setExpectedException('\InvalidArgumentException');
        $value = array();
        $stringObject = 'testString';

        // Test
        $result = Validate::isInstanceOf($value, $stringObject, 'value');

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInstanceOf
     */
    public function testIsInstanceOfIntPasses()
    {
        // Setup
        $value = 38;
        $intObject = 83;

        // Test
        $result = Validate::isInstanceOf($value, $intObject, 'value');

        // Assert
        $this->assertTrue($result);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInstanceOf
     */
    public function testIsInstanceOfIntFail()
    {
        // Setup
        $this->setExpectedException('\InvalidArgumentException');
        $value = 38;
        $stringObject = 'testString';

        // Test
        $result = Validate::isInstanceOf($value, $stringObject, 'value');

        // Assert
    }


    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isInstanceOf
     */
    public function testIsInstanceOfNullValue()
    {
        // Setup
        $value = null;
        $arrayObject = array();

        // Test
        $result = Validate::isInstanceOf($value, $arrayObject, 'value');

        // Assert
        $this->assertTrue($result);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isDouble
     */
    public function testIsDoubleSuccess()
    {
        // Setup
        $value = 3.14159265;

        // Test
        Validate::isDouble($value, 'value');

        // Assert
        $this->assertTrue(true);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isDouble
     */
    public function testIsDoubleFail()
    {
        // Setup
        $this->setExpectedException('\InvalidArgumentException');
        $value = 'testInvalidDoubleValue';

        // Test
        Validate::isDouble($value, 'value');

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isDouble
     */
    public function testGetValidateUri()
    {
        // Test
        $function = Validate::getIsValidUri();

        // Assert
        $this->assertInternalType('object', $function);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isValidUri
     */
    public function testIsValidUriPass()
    {
        // Setup
        $value = 'http://test.com';

        // Test
        $result = Validate::isValidUri($value);

        // Assert
        $this->assertTrue($result);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isValidUri
     */
    public function testIsValidUriNull()
    {
        // Setup
        $this->setExpectedException(get_class(new \RuntimeException('')));
        $value = null;

        // Test
        $result = Validate::isValidUri($value);

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isValidUri
     */
    public function testIsValidUriNotUri()
    {
        // Setup
        $this->setExpectedException(get_class(new \RuntimeException('')));
        $value = 'test string';

        // Test
        $result = Validate::isValidUri($value);

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isObject
     */
    public function testIsObjectPass()
    {
        // Setup
        $value = new \stdClass();

        // Test
        $result = Validate::isObject($value, 'value');

        // Assert
        $this->assertTrue($result);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isObject
     */
    public function testIsObjectNull()
    {
        // Setup
        $this->setExpectedException(get_class(new InvalidArgumentTypeException('')));
        $value = null;

        // Test
        $result = Validate::isObject($value, 'value');

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isObject
     */
    public function testIsObjectNotObject()
    {
        // Setup
        $this->setExpectedException(get_class(new InvalidArgumentTypeException('')));
        $value = 'test string';

        // Test
        $result = Validate::isObject($value, 'value');

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isA
     */
    public function testIsAResourcesPasses()
    {
        // Setup
        $value = new Resources();
        $type = 'MicrosoftAzure\Storage\Common\Internal\Resources';

        // Test
        $result = Validate::isA($value, $type, 'value');

        // Assert
        $this->assertTrue($result);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isA
     */
    public function testIsANull()
    {
        // Setup
        $this->setExpectedException('\InvalidArgumentException');
        $value = null;
        $type = 'MicrosoftAzure\Storage\Common\Internal\Resources';

        // Test
        $result = Validate::isA($value, $type, 'value');

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isA
     */
    public function testIsAInvalidClass()
    {
        // Setup
        $this->setExpectedException('\InvalidArgumentException');
        $value = new Resources();
        $type = 'Some\Invalid\Class';

        // Test
        $result = Validate::isA($value, $type, 'value');

        // Assert
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isA
     */
    public function testIsANotAClass()
    {
        // Setup
        $this->setExpectedException(get_class(new InvalidArgumentTypeException('')));
        $value = 'test string';
        $type = 'MicrosoftAzure\Storage\Common\Internal\Resources';

        // Test
        $result = Validate::isA($value, $type, 'value');

        // Assert
    }

//     /**
//      * @covers MicrosoftAzure\Storage\Common\Internal\Validate::methodExists
//      */
//     public function testMethodExistsIfExists(){

//         // Setup
//         $asset = new Asset(Asset::OPTIONS_NONE);
//         $method = 'getState';

//         // Test
//         $result = Validate::methodExists($asset, $method, 'MicrosoftAzure\Storage\MediaServices\Models\Asset');

//         // Assert
//         $this->assertTrue($result);
//     }

//     /**
//      * @covers MicrosoftAzure\Storage\Common\Internal\Validate::methodExists
//      */
//     public function testMethodExistsIfNotExists(){

//         // Setup
//         $this->setExpectedException('\InvalidArgumentException');
//         $asset = new Asset(Asset::OPTIONS_NONE);
//         $method = 'setCreated';

//         // Test
//         $result = Validate::methodExists($asset, $method, 'MicrosoftAzure\Storage\MediaServices\Models\Asset');

//         // Assert
//     }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isDateString
     */
    public function testIsDateStringValid(){

        // Setup
        $value = '2013-11-25';

        // Test
        $result = Validate::isDateString($value, 'name');

        // Assert
        $this->assertTrue($result);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\Internal\Validate::isDateString
     */
    public function testIsDateStringNotValid(){

        // Setup
        $this->setExpectedException('\InvalidArgumentException');
        $value = 'not a date';

        // Test
        $result = Validate::isDateString($value, 'name');

        // Assert
    }
}

<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Models;
use MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class GetServicePropertiesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetServicePropertiesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult::create
     */
    public function testCreate()
    {
        // Test
        $result = GetServicePropertiesResult::create(TestResources::getServicePropertiesSample());
        
        // Assert
        $this->assertTrue(isset($result));
        
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult::getValue
     */
    public function testGetValue()
    {
        // Setup
        $result = GetServicePropertiesResult::create(TestResources::getServicePropertiesSample());
        $expected = ServiceProperties::create(TestResources::getServicePropertiesSample());
        
        // Test
        $actual = $result->getValue();
        
        // Assert
        $this->assertEquals($expected, $actual);
        
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult::setValue
     */
    public function testSetValue()
    {
        // Setup
        $result = new GetServicePropertiesResult();
        $expected = ServiceProperties::create(TestResources::getServicePropertiesSample());
        
        // Test
        $result->setValue($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getValue());
        
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Models;
use MicrosoftAzure\Storage\Common\Models\Logging;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class Logging
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class LoggingTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::create
     */
    public function testCreate()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        
        // Test
        $actual = Logging::create($sample['Logging']);
        
        // Assert
        $this->assertEquals(Utilities::toBoolean($sample['Logging']['Delete']), $actual->getDelete());
        $this->assertEquals(Utilities::toBoolean($sample['Logging']['Read']), $actual->getRead());
        $this->assertEquals(RetentionPolicy::create($sample['Logging']['RetentionPolicy']), $actual->getRetentionPolicy());
        $this->assertEquals($sample['Logging']['Version'], $actual->getVersion());
        $this->assertEquals(Utilities::toBoolean($sample['Logging']['Write']), $actual->getWrite());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::getRetentionPolicy
     */
    public function testGetRetentionPolicy()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = RetentionPolicy::create($sample['Logging']['RetentionPolicy']);
        $logging->setRetentionPolicy($expected);
        
        // Test
        $actual = $logging->getRetentionPolicy();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::setRetentionPolicy
     */
    public function testSetRetentionPolicy()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = RetentionPolicy::create($sample['Logging']['RetentionPolicy']);
        
        // Test
        $logging->setRetentionPolicy($expected);
        
        // Assert
        $actual = $logging->getRetentionPolicy();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::getWrite
     */
    public function testGetWrite()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = Utilities::toBoolean($sample['Logging']['Write']);
        $logging->setWrite($expected);
        
        // Test
        $actual = $logging->getWrite();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::setWrite
     */
    public function testSetWrite()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = Utilities::toBoolean($sample['Logging']['Write']);
        
        // Test
        $logging->setWrite($expected);
        
        // Assert
        $actual = $logging->getWrite();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::getRead
     */
    public function testGetRead()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = Utilities::toBoolean($sample['Logging']['Read']);
        $logging->setRead($expected);
        
        // Test
        $actual = $logging->getRead();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::setRead
     */
    public function testSetRead()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = Utilities::toBoolean($sample['Logging']['Read']);
        
        // Test
        $logging->setRead($expected);
        
        // Assert
        $actual = $logging->getRead();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::getDelete
     */
    public function testGetDelete()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = Utilities::toBoolean($sample['Logging']['Delete']);
        $logging->setDelete($expected);
        
        // Test
        $actual = $logging->getDelete();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::setDelete
     */
    public function testSetDelete()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = Utilities::toBoolean($sample['Logging']['Delete']);
        
        // Test
        $logging->setDelete($expected);
        
        // Assert
        $actual = $logging->getDelete();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::getVersion
     */
    public function testGetVersion()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = $sample['Logging']['Version'];
        $logging->setVersion($expected);
        
        // Test
        $actual = $logging->getVersion();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::setVersion
     */
    public function testSetVersion()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = new Logging();
        $expected = $sample['Logging']['Version'];
        
        // Test
        $logging->setVersion($expected);
        
        // Assert
        $actual = $logging->getVersion();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Logging::toArray
     */
    public function testToArray()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = Logging::create($sample['Logging']);
        $expected = array(
            'Version'         => $sample['Logging']['Version'],
            'Delete'          => $sample['Logging']['Delete'],
            'Read'            => $sample['Logging']['Read'],
            'Write'           => $sample['Logging']['Write'],
            'RetentionPolicy' => $logging->getRetentionPolicy()->toArray()
        );
        
        // Test
        $actual = $logging->toArray();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Models;
use MicrosoftAzure\Storage\Common\Models\Metrics;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;

/**
 * Unit tests for class Metrics
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class MetricsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::create
     */
    public function testCreate()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        
        // Test
        $actual = Metrics::create($sample['HourMetrics']);
        
        // Assert
        $this->assertEquals(Utilities::toBoolean($sample['HourMetrics']['Enabled']), $actual->getEnabled());
        $this->assertEquals(Utilities::toBoolean($sample['HourMetrics']['IncludeAPIs']), $actual->getIncludeAPIs());
        $this->assertEquals(RetentionPolicy::create($sample['HourMetrics']['RetentionPolicy']), $actual->getRetentionPolicy());
        $this->assertEquals($sample['HourMetrics']['Version'], $actual->getVersion());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::getRetentionPolicy
     */
    public function testGetRetentionPolicy()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = new Metrics();
        $expected = RetentionPolicy::create($sample['HourMetrics']['RetentionPolicy']);
        $metrics->setRetentionPolicy($expected);
        
        // Test
        $actual = $metrics->getRetentionPolicy();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::setRetentionPolicy
     */
    public function testSetRetentionPolicy()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = new Metrics();
        $expected = RetentionPolicy::create($sample['HourMetrics']['RetentionPolicy']);
        
        // Test
        $metrics->setRetentionPolicy($expected);
        
        // Assert
        $actual = $metrics->getRetentionPolicy();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::getVersion
     */
    public function testGetVersion()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = new Metrics();
        $expected = $sample['HourMetrics']['Version'];
        $metrics->setVersion($expected);
        
        // Test
        $actual = $metrics->getVersion();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::setVersion
     */
    public function testSetVersion()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = new Metrics();
        $expected = $sample['HourMetrics']['Version'];
        
        // Test
        $metrics->setVersion($expected);
        
        // Assert
        $actual = $metrics->getVersion();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::getEnabled
     */
    public function testGetEnabled()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = new Metrics();
        $expected = Utilities::toBoolean($sample['HourMetrics']['Enabled']);
        $metrics->setEnabled($expected);
        
        // Test
        $actual = $metrics->getEnabled();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::setEnabled
     */
    public function testSetEnabled()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = new Metrics();
        $expected = Utilities::toBoolean($sample['HourMetrics']['Enabled']);
        
        // Test
        $metrics->setEnabled($expected);
        
        // Assert
        $actual = $metrics->getEnabled();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::getIncludeAPIs
     */
    public function testGetIncludeAPIs()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = new Metrics();
        $expected = Utilities::toBoolean($sample['HourMetrics']['IncludeAPIs']);
        $metrics->setIncludeAPIs($expected);
        
        // Test
        $actual = $metrics->getIncludeAPIs();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::setIncludeAPIs
     */
    public function testSetIncludeAPIs()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = new Metrics();
        $expected = Utilities::toBoolean($sample['HourMetrics']['IncludeAPIs']);
        
        // Test
        $metrics->setIncludeAPIs($expected);
        
        // Assert
        $actual = $metrics->getIncludeAPIs();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::toArray
     */
    public function testToArray()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = Metrics::create($sample['HourMetrics']);
        $expected = array(
            'Version'         => $sample['HourMetrics']['Version'],
            'Enabled'         => $sample['HourMetrics']['Enabled'],
            'IncludeAPIs'     => $sample['HourMetrics']['IncludeAPIs'],
            'RetentionPolicy' => $metrics->getRetentionPolicy()->toArray()
        );
        
        // Test
        $actual = $metrics->toArray();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\Metrics::toArray
     */
    public function testToArrayWithNotEnabled()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $sample['HourMetrics']['Enabled'] = 'false';
        $metrics = Metrics::create($sample['HourMetrics']);
        $expected = array(
            'Version'         => $sample['HourMetrics']['Version'],
            'Enabled'         => $sample['HourMetrics']['Enabled'],
            'RetentionPolicy' => $metrics->getRetentionPolicy()->toArray()
        );
        
        // Test
        $actual = $metrics->toArray();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Models;
use MicrosoftAzure\Storage\Common\Models\RetentionPolicy;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class RetentionPolicy
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class RetentionPolicyTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\RetentionPolicy::create
     */
    public function testCreate()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $expectedEnabled = Utilities::toBoolean($sample['Logging']['RetentionPolicy']['Enabled']);
        $expectedDays = intval($sample['Logging']['RetentionPolicy']['Days']);
        
        // Test
        $actual = RetentionPolicy::create($sample['Logging']['RetentionPolicy']);
        
        // Assert
        $this->assertEquals($expectedEnabled, $actual->getEnabled());
        $this->assertEquals($expectedDays, $actual->getDays());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\RetentionPolicy::getEnabled
     */
    public function testGetEnabled()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $retentionPolicy = new RetentionPolicy();
        $expected = Utilities::toBoolean($sample['Logging']['RetentionPolicy']['Enabled']);
        $retentionPolicy->setEnabled($expected);
        
        // Test
        $actual = $retentionPolicy->getEnabled();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\RetentionPolicy::setEnabled
     */
    public function testSetEnabled()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $retentionPolicy = new RetentionPolicy();
        $expected = Utilities::toBoolean($sample['Logging']['RetentionPolicy']['Enabled']);
        
        // Test
        $retentionPolicy->setEnabled($expected);
        
        // Assert
        $actual = $retentionPolicy->getEnabled();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\RetentionPolicy::getDays
     */
    public function testGetDays()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $retentionPolicy = new RetentionPolicy();
        $expected = intval($sample['Logging']['RetentionPolicy']['Days']);
        $retentionPolicy->setDays($expected);
        
        // Test
        $actual = $retentionPolicy->getDays();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\RetentionPolicy::setDays
     */
    public function testSetDays()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $retentionPolicy = new RetentionPolicy();
        $expected = intval($sample['Logging']['RetentionPolicy']['Days']);
        
        // Test
        $retentionPolicy->setDays($expected);
        
        // Assert
        $actual = $retentionPolicy->getDays();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\RetentionPolicy::toArray
     */
    public function testToArray()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $retentionPolicy = RetentionPolicy::create($sample['Logging']['RetentionPolicy']);
        $expected = array(
            'Enabled' => $sample['Logging']['RetentionPolicy']['Enabled'], 
            'Days'    => $sample['Logging']['RetentionPolicy']['Days']
        );
        
        // Test
        $actual = $retentionPolicy->toArray();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\RetentionPolicy::toArray
     */
    public function testToArrayWithoutDays()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $retentionPolicy = RetentionPolicy::create($sample['Logging']['RetentionPolicy']);
        $expected = array('Enabled' => $sample['Logging']['RetentionPolicy']['Enabled']);
        $retentionPolicy->setDays(null);
        
        // Test
        $actual = $retentionPolicy->toArray();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common\Models;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\Models\Logging;
use MicrosoftAzure\Storage\Common\Models\Metrics;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Common\Models\GetServicePropertiesResult;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * Unit tests for class ServiceProperties
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServicePropertiesTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\ServiceProperties::create
     */
    public function testCreate()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = Logging::create($sample['Logging']);
        $metrics = Metrics::create($sample['HourMetrics']);
        
        // Test
        $result = ServiceProperties::create($sample);
        
        // Assert
        $this->assertEquals($logging, $result->getLogging());
        $this->assertEquals($metrics, $result->getMetrics());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\ServiceProperties::setLogging
     */
    public function testSetLogging()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = Logging::create($sample['Logging']);
        $result = new ServiceProperties();
        
        // Test
        $result->setLogging($logging);
        
        // Assert
        $this->assertEquals($logging, $result->getLogging());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\ServiceProperties::getLogging
     */
    public function testGetLogging()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $logging = Logging::create($sample['Logging']);
        $result = new ServiceProperties();
        $result->setLogging($logging);
        
        // Test
        $actual = $result->getLogging($logging);
        
        // Assert
        $this->assertEquals($logging, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\ServiceProperties::setMetrics
     */
    public function testSetMetrics()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = Metrics::create($sample['HourMetrics']);
        $result = new ServiceProperties();
        
        // Test
        $result->setMetrics($metrics);
        
        // Assert
        $this->assertEquals($metrics, $result->getMetrics());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\ServiceProperties::getMetrics
     */
    public function testGetMetrics()
    {
        // Setup
        $sample = TestResources::getServicePropertiesSample();
        $metrics = Metrics::create($sample['HourMetrics']);
        $result = new ServiceProperties();
        $result->setMetrics($metrics);
        
        // Test
        $actual = $result->getMetrics($metrics);
        
        // Assert
        $this->assertEquals($metrics, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\ServiceProperties::toArray
     */
    public function testToArray()
    {
        // Setup
        $properties = ServiceProperties::create(TestResources::getServicePropertiesSample());
        $expected = array(
            'Logging' => $properties->getLogging()->toArray(),
            'HourMetrics' => $properties->getMetrics()->toArray()
        );
        
        // Test
        $actual = $properties->toArray();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\Models\ServiceProperties::toXml
     */
    public function testToXml()
    {
        // Setup
        $properties = ServiceProperties::create(TestResources::getServicePropertiesSample());
        $xmlSerializer = new XmlSerializer();
        
        // Test
        $actual = $properties->toXml($xmlSerializer);
        
        // Assert
        $actualParsed = Utilities::unserialize($actual);
        $actualProperties = GetServicePropertiesResult::create($actualParsed);
        $this->assertEquals($actualProperties->getValue(), $properties);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common;
use MicrosoftAzure\Storage\Common\ServiceException;

/**
 * Unit tests for class ServiceException
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServiceExceptionTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\ServiceException::__construct
     */
    public function test__construct()
    {
        // Setup
        $code = '210';
        $error = 'Invalid value provided';
        $reason = 'Value can\'t be null';
        
        // Test
        $e = new ServiceException($code, $error, $reason);
        
        // Assert
        $this->assertEquals($code, $e->getCode());
        $this->assertEquals($error, $e->getErrorText());
        $this->assertEquals($reason, $e->getErrorReason());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\ServiceException::getErrorText
     */
    public function testGetErrorText()
    {
        // Setup
        $code = '210';
        $error = 'Invalid value provided';
        $reason = 'Value can\'t be null';
        $e = new ServiceException($code, $error, $reason);
        
        // Test
        $actualError = $e->getErrorText();
        // Assert
        $this->assertEquals($error, $actualError);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Common\ServiceException::getErrorReason
     */
    public function testGetErrorReason()
    {
        // Setup
        $code = '210';
        $error = 'Invalid value provided';
        $reason = 'Value can\'t be null';
        $e = new ServiceException($code, $error, $reason);

        // Test
        $actualErrorReason = $e->getErrorReason();
        
        // Assert
        $this->assertEquals($reason, $actualErrorReason);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Common;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\Internal\MediaServicesSettings;
use MicrosoftAzure\Storage\Common\ServicesBuilder;
use MicrosoftAzure\Storage\Common\Configuration;
use MicrosoftAzure\Storage\Common\Internal\InvalidArgumentTypeException;

/**
 * Unit tests for class ServicesBuilder
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Common
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ServicesBuilderTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::createQueueService
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::serializer
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::queueAuthenticationScheme
     */
    public function testBuildForQueue()
    {
        // Setup
        $builder = new ServicesBuilder();

        // Test
        $queueRestProxy = $builder->createQueueService(TestResources::getWindowsAzureStorageServicesConnectionString());

        // Assert
        $this->assertInstanceOf('MicrosoftAzure\Storage\Queue\Internal\IQueue', $queueRestProxy);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::createBlobService
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::serializer
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::blobAuthenticationScheme
     */
    public function testBuildForBlob()
    {
        // Setup
        $builder = new ServicesBuilder();

        // Test
        $blobRestProxy = $builder->createBlobService(TestResources::getWindowsAzureStorageServicesConnectionString());

        // Assert
        $this->assertInstanceOf('MicrosoftAzure\Storage\Blob\Internal\IBlob', $blobRestProxy);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::createTableService
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::serializer
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::mimeSerializer
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::atomSerializer
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::tableAuthenticationScheme
     */
    public function testBuildForTable()
    {
        // Setup
        $builder = new ServicesBuilder();

        // Test
        $tableRestProxy = $builder->createTableService(TestResources::getWindowsAzureStorageServicesConnectionString());

        // Assert
        $this->assertInstanceOf('MicrosoftAzure\Storage\Table\Internal\ITable', $tableRestProxy);
    }

    /**
     * @covers MicrosoftAzure\Storage\Common\ServicesBuilder::getInstance
     */
    public function testGetInstance()
    {
        // Test
        $actual = ServicesBuilder::getInstance();

        // Assert
        $this->assertInstanceOf('MicrosoftAzure\Storage\Common\ServicesBuilder', $actual);
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions;

/**
 * Unit tests for class CreateMessageOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateMessageOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions::getVisibilityTimeoutInSeconds
     */
    public function testGetVisibilityTimeoutInSeconds()
    {
        // Setup
        $createMessageOptions = new CreateMessageOptions();
        $expected = 1000;
        $createMessageOptions->setVisibilityTimeoutInSeconds($expected);
        
        // Test
        $actual = $createMessageOptions->getVisibilityTimeoutInSeconds();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions::setVisibilityTimeoutInSeconds
     */
    public function testSetVisibilityTimeoutInSeconds()
    {
        // Setup
        $createMessageOptions = new CreateMessageOptions();
        $expected = 1000;
        
        // Test
        $createMessageOptions->setVisibilityTimeoutInSeconds($expected);
        
        // Assert
        $actual = $createMessageOptions->getVisibilityTimeoutInSeconds();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions::getTimeToLiveInSeconds
     */
    public function testGetTimeToLiveInSeconds()
    {
        // Setup
        $createMessageOptions = new CreateMessageOptions();
        $expected = 20;
        $createMessageOptions->setTimeToLiveInSeconds($expected);
        
        // Test
        $actual = $createMessageOptions->getTimeToLiveInSeconds();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\CreateMessageOptions::setTimeToLiveInSeconds
     */
    public function testSetTimeToLiveInSeconds()
    {
        // Setup
        $createMessageOptions = new CreateMessageOptions();
        $expected = 20;
        
        // Test
        $createMessageOptions->setTimeToLiveInSeconds($expected);
        
        // Assert
        $actual = $createMessageOptions->getTimeToLiveInSeconds();
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;

/**
 * Unit tests for class CreateQueueOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class CreateQueueOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $queue = new CreateQueueOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $queue->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $queue->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $queue = new CreateQueueOptions();
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $queue->setMetadata($expected);
        
        // Test
        $actual = $queue->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions::addMetadata
     */
    public function testAddMetadata()
    {
        // Setup
        $queue = new CreateQueueOptions();
        $key = 'key1';
        $value = 'value1';
        $expected = array($key => $value);
        
        // Test
        $queue->addMetadata($key, $value);
        
        // Assert
        $this->assertEquals($expected, $queue->getMetadata());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult;

/**
 * Unit tests for class GetQueueMetadataResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetQueueMetadataResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult::__construct
     */
    public function test__construct()
    {
        // Setup
        $count = 10;
        $metadata = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $actual = new GetQueueMetadataResult($count, $metadata);
        
        // Assert
        $this->assertEquals($count, $actual->getApproximateMessageCount());
        $this->assertEquals($metadata, $actual->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult::getApproximateMessageCount
     */
    public function testGetApproximateMessageCount()
    {
        // Setup
        $expected = 10;
        $metadata = new GetQueueMetadataResult($expected, array());
        
        // Test
        $actual = $metadata->getApproximateMessageCount();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult::setApproximateMessageCount
     */
    public function testSetApproximateMessageCount()
    {
        // Setup
        $expected = 10;
        $metadata = new GetQueueMetadataResult(30, array());
        
        // Test
        $metadata->setApproximateMessageCount($expected);
        
        // Assert
        $this->assertEquals($expected, $metadata->getApproximateMessageCount());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $metadata = new GetQueueMetadataResult(0, $expected);
        
        // Test
        $actual = $metadata->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $metadata = new GetQueueMetadataResult(0, $expected);
        
        // Test
        $metadata->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $metadata->getMetadata());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions;

/**
 * Unit tests for class ListMessagesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListMessagesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions::getVisibilityTimeoutInSeconds
     */
    public function testGetVisibilityTimeoutInSeconds()
    {
        // Setup
        $listMessagesOptions = new ListMessagesOptions();
        $expected = 1000;
        $listMessagesOptions->setVisibilityTimeoutInSeconds($expected);
        
        // Test
        $actual = $listMessagesOptions->getVisibilityTimeoutInSeconds();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions::setVisibilityTimeoutInSeconds
     */
    public function testSetVisibilityTimeoutInSeconds()
    {
        // Setup
        $listMessagesOptions = new ListMessagesOptions();
        $expected = 1000;
        
        // Test
        $listMessagesOptions->setVisibilityTimeoutInSeconds($expected);
        
        // Assert
        $actual = $listMessagesOptions->getVisibilityTimeoutInSeconds();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions::getNumberOfMessages
     */
    public function testGetNumberOfMessages()
    {
        // Setup
        $listMessagesOptions = new ListMessagesOptions();
        $expected = 10;
        $listMessagesOptions->setNumberOfMessages($expected);
        
        // Test
        $actual = $listMessagesOptions->getNumberOfMessages();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions::setNumberOfMessages
     */
    public function testSetNumberOfMessages()
    {
        // Setup
        $listMessagesOptions = new ListMessagesOptions();
        $expected = 10;
        
        // Test
        $listMessagesOptions->setNumberOfMessages($expected);
        
        // Assert
        $actual = $listMessagesOptions->getNumberOfMessages();
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesResult;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class ListMessagesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListMessagesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListMessagesResult::create
     */
    public function testCreate()
    {
        // Setup
        $sample = TestResources::listMessagesSample();
        
        
        // Test
        $result = ListMessagesResult::create($sample);
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertCount(1, $actual);
        $this->assertEquals($sample['QueueMessage']['MessageId'] , $actual[0]->getMessageId());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage']['InsertionTime']) , $actual[0]->getInsertionDate());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage']['ExpirationTime']) , $actual[0]->getExpirationDate());
        $this->assertEquals($sample['QueueMessage']['PopReceipt'] , $actual[0]->getPopReceipt());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage']['TimeNextVisible']), $actual[0]->getTimeNextVisible());
        $this->assertEquals(intval($sample['QueueMessage']['DequeueCount']) , $actual[0]->getDequeueCount());
        $this->assertEquals($sample['QueueMessage']['MessageText'] , $actual[0]->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListMessagesResult::create
     */
    public function testCreateMultiple()
    {
        // Setup
        $sample = TestResources::listMessagesMultipleMessagesSample();
        
        // Test
        $result = ListMessagesResult::create($sample);
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertCount(2, $actual);
        $this->assertEquals($sample['QueueMessage'][0]['MessageId'] , $actual[0]->getMessageId());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][0]['InsertionTime']) , $actual[0]->getInsertionDate());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][0]['ExpirationTime']) , $actual[0]->getExpirationDate());
        $this->assertEquals($sample['QueueMessage'][0]['PopReceipt'] , $actual[0]->getPopReceipt());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][0]['TimeNextVisible']), $actual[0]->getTimeNextVisible());
        $this->assertEquals(intval($sample['QueueMessage'][0]['DequeueCount']) , $actual[0]->getDequeueCount());
        $this->assertEquals($sample['QueueMessage'][0]['MessageText'] , $actual[0]->getMessageText());
        
        $this->assertEquals($sample['QueueMessage'][1]['MessageId'] , $actual[1]->getMessageId());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][1]['InsertionTime']) , $actual[1]->getInsertionDate());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][1]['ExpirationTime']) , $actual[1]->getExpirationDate());
        $this->assertEquals($sample['QueueMessage'][1]['PopReceipt'] , $actual[1]->getPopReceipt());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][1]['TimeNextVisible']), $actual[1]->getTimeNextVisible());
        $this->assertEquals(intval($sample['QueueMessage'][1]['DequeueCount']) , $actual[1]->getDequeueCount());
        $this->assertEquals($sample['QueueMessage'][1]['MessageText'] , $actual[1]->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListMessagesResult::getQueueMessages
     */
    public function testGetQueueMessages()
    {
        // Setup
        $sample = TestResources::listMessagesSample();
        $expectedMessageId = '1234b585-0ac3-4e2a-ad0c-18e3992brca1';
        $result = ListMessagesResult::create($sample);
        $expected = $result->getQueueMessages();
        $expected[0]->setMessageId($expectedMessageId);
        $result->setQueueMessages($expected);
        
        // Test
        $actual = $result->getQueueMessages();
        
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListMessagesResult::setQueueMessages
     */
    public function testSetQueueMessages()
    {
        // Setup
        $sample = TestResources::listMessagesSample();
        $expectedMessageId = '1234b585-0ac3-4e2a-ad0c-18e3992brca1';
        $result = ListMessagesResult::create($sample);
        $expected = $result->getQueueMessages();
        $expected[0]->setMessageId($expectedMessageId);
        
        // Test
        $result->setQueueMessages($expected);
        
        $this->assertEquals($expected, $result->getQueueMessages());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class ListQueuesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListQueuesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions::setPrefix
     */
    public function testSetPrefix()
    {
        // Setup
        $options = new ListQueuesOptions();
        $expected = 'myprefix';
        
        // Test
        $options->setPrefix($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getPrefix());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions::getPrefix
     */
    public function testGetPrefix()
    {
        // Setup
        $options = new ListQueuesOptions();
        $expected = 'myprefix';
        $options->setPrefix($expected);
        
        // Test
        $actual = $options->getPrefix();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions::setMarker
     */
    public function testSetMarker()
    {
        // Setup
        $options = new ListQueuesOptions();
        $expected = 'mymarker';
        
        // Test
        $options->setMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions::getMarker
     */
    public function testGetMarker()
    {
        // Setup
        $options = new ListQueuesOptions();
        $expected = 'mymarker';
        $options->setMarker($expected);
        
        // Test
        $actual = $options->getMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions::setMaxResults
     */
    public function testSetMaxResults()
    {
        // Setup
        $options = new ListQueuesOptions();
        $expected = '3';
        
        // Test
        $options->setMaxResults($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getMaxResults());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions::getMaxResults
     */
    public function testGetMaxResults()
    {
        // Setup
        $options = new ListQueuesOptions();
        $expected = '3';
        $options->setMaxResults($expected);
        
        // Test
        $actual = $options->getMaxResults();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions::setIncludeMetadata
     */
    public function testSetIncludeMetadata()
    {
        // Setup
        $options = new ListQueuesOptions();
        $expected = true;
        
        // Test
        $options->setIncludeMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getIncludeMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions::getIncludeMetadata
     */
    public function testGetIncludeMetadata()
    {
        // Setup
        $options = new ListQueuesOptions();
        $expected = true;
        $options->setIncludeMetadata($expected);
        
        // Test
        $actual = $options->getIncludeMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesResult;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class ListQueuesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ListQueuesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::create 
     */
    public function testCreateWithEmpty()
    {
        // Setup
        $sample = TestResources::listQueuesEmpty();
        
        // Test
        $actual = ListQueuesResult::create($sample);
        
        // Assert
        $this->assertCount(0, $actual->getQueues());
        $this->assertTrue(empty($sample['NextMarker']));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::create 
     */
    public function testCreateWithOneEntry()
    {
        // Setup
        $sample = TestResources::listQueuesOneEntry();
        
        // Test
        $actual = ListQueuesResult::create($sample);
        
        // Assert
        $queues = $actual->getQueues();
        $this->assertCount(1, $queues);
        $this->assertEquals($sample['Queues']['Queue']['Name'], $queues[0]->getName());
        $this->assertEquals($sample['@attributes']['ServiceEndpoint'] . $sample['Queues']['Queue']['Name'], $queues[0]->getUrl());
        $this->assertEquals($sample['Marker'], $actual->getMarker());
        $this->assertEquals($sample['MaxResults'], $actual->getMaxResults());
        $this->assertEquals($sample['NextMarker'], $actual->getNextMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::create 
     */
    public function testCreateWithMultipleEntries()
    {
        // Setup
        $sample = TestResources::listQueuesMultipleEntries();
        
        // Test
        $actual = ListQueuesResult::create($sample);
        
        // Assert
        $queues = $actual->getQueues();
        $this->assertCount(2, $queues);
        $this->assertEquals($sample['Queues']['Queue'][0]['Name'], $queues[0]->getName());
        $this->assertEquals($sample['@attributes']['ServiceEndpoint'] . $sample['Queues']['Queue'][0]['Name'], $queues[0]->getUrl());
        $this->assertEquals($sample['Queues']['Queue'][1]['Name'], $queues[1]->getName());
        $this->assertEquals($sample['@attributes']['ServiceEndpoint'] . $sample['Queues']['Queue'][1]['Name'], $queues[1]->getUrl());
        $this->assertEquals($sample['MaxResults'], $actual->getMaxResults());
        $this->assertEquals($sample['NextMarker'], $actual->getNextMarker());
        
        return $actual;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::getQueues
     * @depends testCreateWithMultipleEntries
     */
    public function testGetQueues($result)
    {
        // Test
        $actual = $result->getQueues();
        
        // Assert
        $this->assertCount(2, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::setQueues
     * @depends testCreateWithMultipleEntries
     */
    public function testSetQueues($result)
    {
        // Setup
        $sample = new ListQueuesResult();
        $expected = $result->getQueues();
        
        // Test
        $sample->setQueues($expected);
        
        // Assert
        $this->assertEquals($expected, $sample->getQueues());
        $expected[0]->setName('test');
        $this->assertNotEquals($expected, $sample->getQueues());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::setPrefix
     */
    public function testSetPrefix()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = 'myprefix';
        
        // Test
        $result->setPrefix($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getPrefix());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::getPrefix
     */
    public function testGetPrefix()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = 'myprefix';
        $result->setPrefix($expected);
        
        // Test
        $actual = $result->getPrefix();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::setNextMarker
     */
    public function testSetNextMarker()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = 'mymarker';
        
        // Test
        $result->setNextMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getNextMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::getNextMarker
     */
    public function testGetNextMarker()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = 'mymarker';
        $result->setNextMarker($expected);
        
        // Test
        $actual = $result->getNextMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::setMarker
     */
    public function testSetMarker()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = 'mymarker';
        
        // Test
        $result->setMarker($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getMarker());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::getMarker
     */
    public function testGetMarker()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = 'mymarker';
        $result->setMarker($expected);
        
        // Test
        $actual = $result->getMarker();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::setMaxResults
     */
    public function testSetMaxResults()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = '3';
        
        // Test
        $result->setMaxResults($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getMaxResults());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::getMaxResults
     */
    public function testGetMaxResults()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = '3';
        $result->setMaxResults($expected);
        
        // Test
        $actual = $result->getMaxResults();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::setAccountName
     */
    public function testSetAccountName()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = 'name';
        
        // Test
        $result->setAccountName($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getAccountName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\ListQueuesResult::getAccountName
     */
    public function testGetAccountName()
    {
        // Setup
        $result = new ListQueuesResult();
        $expected = 'name';
        $result->setAccountName($expected);
        
        // Test
        $actual = $result->getAccountName();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class MicrosoftAzureQueueMessageTest
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class MicrosoftAzureQueueMessageTest extends \PHPUnit_Framework_TestCase
{
    /**
    * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::createFromListMessages
    */
    public function testCreateListMessages()
    {
        // Setup
        $sample = TestResources::listMessagesSample();
        $sample = $sample['QueueMessage'];
        
        // Test
        $actual = MicrosoftAzureQueueMessage::createFromListMessages($sample);
        
        // Assert
        $this->assertEquals($sample['MessageId'] , $actual->getMessageId());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['InsertionTime']) , $actual->getInsertionDate());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['ExpirationTime']) , $actual->getExpirationDate());
        $this->assertEquals($sample['PopReceipt'] , $actual->getPopReceipt());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['TimeNextVisible']), $actual->getTimeNextVisible());
        $this->assertEquals(intval($sample['DequeueCount']) , $actual->getDequeueCount());
        $this->assertEquals($sample['MessageText'] , $actual->getMessageText());
    }
    
    /**
    * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::createFromPeekMessages
    */
    public function testCreateFromPeekMessages()
    {
        // Setup
        $sample = TestResources::listMessagesSample();
        $sample = $sample['QueueMessage'];
        
        // Test
        $actual = MicrosoftAzureQueueMessage::createFromPeekMessages($sample);
        
        // Assert
        $this->assertEquals($sample['MessageId'] , $actual->getMessageId());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['InsertionTime']) , $actual->getInsertionDate());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['ExpirationTime']) , $actual->getExpirationDate());
        $this->assertEquals(intval($sample['DequeueCount']) , $actual->getDequeueCount());
        $this->assertEquals($sample['MessageText'] , $actual->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::getMessageText
     */
    public function testGetMessageText()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'PHRlc3Q+dGhpcyBpcyBhIHRlc3QgbWVzc2FnZTwvdGVzdD4=' ;
        $azureQueueMessage->setMessageText($expected);
        
        // Test
        $actual = $azureQueueMessage->getMessageText();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::setMessageText
     */
    public function testSetMessageText()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'PHRlc3Q+dGhpcyBpcyBhIHRlc3QgbWVzc2FnZTwvdGVzdD4=';
        
        // Test
        $azureQueueMessage->setMessageText($expected);
        
        // Assert
        $actual = $azureQueueMessage->getMessageText();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::getMessageId
     */
    public function testGetMessageId()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = '5974b586-0df3-4e2d-ad0c-18e3892bfca2';
        $azureQueueMessage->setMessageId($expected);
        
        // Test
        $actual = $azureQueueMessage->getMessageId();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::setMessageId
     */
    public function testSetMessageId()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = '5974b586-0df3-4e2d-ad0c-18e3892bfca2';
        
        // Test
        $azureQueueMessage->setMessageId($expected);
        
        // Assert
        $actual = $azureQueueMessage->getMessageId();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::getInsertionDate
     */
    public function testGetInsertionDate()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'Fri, 09 Oct 2009 21:04:30 GMT';
        $azureQueueMessage->setInsertionDate($expected);
        
        // Test
        $actual = $azureQueueMessage->getInsertionDate();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::setInsertionDate
     */
    public function testSetInsertionDate()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'Fri, 09 Oct 2009 21:04:30 GMT';
        
        // Test
        $azureQueueMessage->setInsertionDate($expected);
        
        // Assert
        $actual = $azureQueueMessage->getInsertionDate();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::getExpirationDate
     */
    public function testGetExpirationDate()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'Fri, 16 Oct 2009 21:04:30 GMT';
        $azureQueueMessage->setExpirationDate($expected);
        
        // Test
        $actual = $azureQueueMessage->getExpirationDate();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::setExpirationDate
     */
    public function testSetExpirationDate()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'Fri, 16 Oct 2009 21:04:30 GMT';
        
        // Test
        $azureQueueMessage->setExpirationDate($expected);
        
        // Assert
        $actual = $azureQueueMessage->getExpirationDate();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::getPopReceipt
     */
    public function testGetPopReceipt()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'YzQ4Yzg1MDItYTc0Ny00OWNjLTkxYTUtZGM0MDFiZDAwYzEw';
        $azureQueueMessage->setPopReceipt($expected);
        
        // Test
        $actual = $azureQueueMessage->getPopReceipt();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::setPopReceipt
     */
    public function testSetPopReceipt()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'YzQ4Yzg1MDItYTc0Ny00OWNjLTkxYTUtZGM0MDFiZDAwYzEw';
        
        // Test
        $azureQueueMessage->setPopReceipt($expected);
        
        // Assert
        $actual = $azureQueueMessage->getPopReceipt();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::getTimeNextVisible
     */
    public function testGetTimeNextVisible()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'Fri, 09 Oct 2009 23:29:20 GMT';
        $azureQueueMessage->setTimeNextVisible($expected);
        
        // Test
        $actual = $azureQueueMessage->getTimeNextVisible();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::setTimeNextVisible
     */
    public function testSetTimeNextVisible()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 'Fri, 09 Oct 2009 23:29:20 GMT';
        
        // Test
        $azureQueueMessage->setTimeNextVisible($expected);
        
        // Assert
        $actual = $azureQueueMessage->getTimeNextVisible();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::getDequeueCount
     */
    public function testGetDequeueCount()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 1;
        $azureQueueMessage->setDequeueCount($expected);
        
        // Test
        $actual = $azureQueueMessage->getDequeueCount();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\MicrosoftAzureQueueMessage::setDequeueCount
     */
    public function testSetDequeueCount()
    {
        // Setup
        $azureQueueMessage = new MicrosoftAzureQueueMessage();
        $expected = 1;
        
        // Test
        $azureQueueMessage->setDequeueCount($expected);
        
        // Assert
        $actual = $azureQueueMessage->getDequeueCount();
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;

/**
 * Unit tests for class PeekMessagesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PeekMessagesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions::getNumberOfMessages
     */
    public function testGetNumberOfMessages()
    {
        // Setup
        $peekMessagesOptions = new PeekMessagesOptions();
        $expected = 10;
        $peekMessagesOptions->setNumberOfMessages($expected);
        
        // Test
        $actual = $peekMessagesOptions->getNumberOfMessages();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions::setNumberOfMessages
     */
    public function testSetNumberOfMessages()
    {
        // Setup
        $peekMessagesOptions = new PeekMessagesOptions();
        $expected = 10;
        
        // Test
        $peekMessagesOptions->setNumberOfMessages($expected);
        
        // Assert
        $actual = $peekMessagesOptions->getNumberOfMessages();
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class PeekMessagesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PeekMessagesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult::create
     */
    public function testCreate()
    {
        // Setup
        $sample = TestResources::listMessagesSample();
        
        
        // Test
        $result = PeekMessagesResult::create($sample);
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertCount(1, $actual);
        $this->assertEquals($sample['QueueMessage']['MessageId'] , $actual[0]->getMessageId());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage']['InsertionTime']) , $actual[0]->getInsertionDate());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage']['ExpirationTime']) , $actual[0]->getExpirationDate());
        $this->assertEquals(intval($sample['QueueMessage']['DequeueCount']) , $actual[0]->getDequeueCount());
        $this->assertEquals($sample['QueueMessage']['MessageText'] , $actual[0]->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult::create
     */
    public function testCreateMultiple()
    {
        // Setup
        $sample = TestResources::listMessagesMultipleMessagesSample();
        
        // Test
        $result = PeekMessagesResult::create($sample);
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertCount(2, $actual);
        $this->assertEquals($sample['QueueMessage'][0]['MessageId'] , $actual[0]->getMessageId());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][0]['InsertionTime']) , $actual[0]->getInsertionDate());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][0]['ExpirationTime']) , $actual[0]->getExpirationDate());
        $this->assertEquals(intval($sample['QueueMessage'][0]['DequeueCount']) , $actual[0]->getDequeueCount());
        $this->assertEquals($sample['QueueMessage'][0]['MessageText'] , $actual[0]->getMessageText());
        
        $this->assertEquals($sample['QueueMessage'][1]['MessageId'] , $actual[1]->getMessageId());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][1]['InsertionTime']) , $actual[1]->getInsertionDate());
        $this->assertEquals(Utilities::rfc1123ToDateTime($sample['QueueMessage'][1]['ExpirationTime']) , $actual[1]->getExpirationDate());
        $this->assertEquals(intval($sample['QueueMessage'][1]['DequeueCount']) , $actual[1]->getDequeueCount());
        $this->assertEquals($sample['QueueMessage'][1]['MessageText'] , $actual[1]->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult::getQueueMessages
     */
    public function testGetQueueMessages()
    {
        // Setup
        $sample = TestResources::listMessagesSample();
        $expectedMessageId = '1234b585-0ac3-4e2a-ad0c-18e3992brca1';
        $result = PeekMessagesResult::create($sample);
        $expected = $result->getQueueMessages();
        $expected[0]->setMessageId($expectedMessageId);
        $result->setQueueMessages($expected);
        
        // Test
        $actual = $result->getQueueMessages();
        
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult::setQueueMessages
     */
    public function testSetQueueMessages()
    {
        // Setup
        $sample = TestResources::listMessagesSample();
        $expectedMessageId = '1234b585-0ac3-4e2a-ad0c-18e3992brca1';
        $result = PeekMessagesResult::create($sample);
        $expected = $result->getQueueMessages();
        $expected[0]->setMessageId($expectedMessageId);
        
        // Test
        $result->setQueueMessages($expected);
        
        $this->assertEquals($expected, $result->getQueueMessages());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Queue\Models\QueueMessage;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * Unit tests for class QueueMessage
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueueMessageTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\QueueMessage::getMessageText
     */
    public function testGetMessageText()
    {
        // Setup
        $queueMessage = new QueueMessage();
        $expected = 'this is message text';
        $queueMessage->setMessageText($expected);
        
        // Test
        $actual = $queueMessage->getMessageText();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\QueueMessage::setMessageText
     */
    public function testSetMessageText()
    {
        // Setup
        $queueMessage = new QueueMessage();
        $expected = 'this is message text';
        
        // Test
        $queueMessage->setMessageText($expected);
        
        // Assert
        $actual = $queueMessage->getMessageText();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\QueueMessage::toXml
     */
    public function testToXml()
    {
        // Setup
        $queueMessage = new QueueMessage();
        $messageText = 'this is message text';
        $array = array('MessageText' => $messageText);
        $queueMessage->setMessageText($messageText);
        $xmlSerializer = new XmlSerializer();
        $properties = array(XmlSerializer::ROOT_NAME => QueueMessage::$xmlRootName);
        $expected = $xmlSerializer->serialize($array, $properties);
        
        // Test
        $actual = $queueMessage->toXml($xmlSerializer);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions;

/**
 * Unit tests for class QueueServiceOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueueServiceOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions::setTimeout
     */
    public function testSetTimeout()
    {
        // Setup
        $options = new QueueServiceOptions();
        $value = 10;
        
        // Test
        $options->setTimeout($value);
        
        // Assert
        $this->assertEquals($value, $options->getTimeout());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions::getTimeout
     */
    public function testGetTimeout()
    {
        // Setup
        $options = new QueueServiceOptions();
        $value = 10;
        $options->setTimeout($value);
        
        // Test
        $actualValue = $options->getTimeout();
        
        // Assert
        $this->assertEquals($value, $actualValue);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\Queue;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;

/**
 * Unit tests for class Queue
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueueTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\Queue::__construct
     */
    public function test__construct()
    {
        // Setup
        $expectedName = TestResources::QUEUE1_NAME;
        $expectedUrl = TestResources::QUEUE_URI;
        
        // Test
        $queue = new Queue($expectedName, $expectedUrl);
        
        // Assert
        $this->assertEquals($expectedName, $queue->getName());
        $this->assertEquals($expectedUrl, $queue->getUrl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\Queue::setName
     */
    public function testSetName()
    {
        // Setup
        $queue = new Queue('myqueue', 'myurl');
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $queue->setName($expected);
        
        // Assert
        $this->assertEquals($expected, $queue->getName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\Queue::getName
     */
    public function testGetName()
    {
        // Setup
        $queue = new Queue('myqueue', 'myurl');
        $expected = TestResources::QUEUE1_NAME;
        $queue->setName($expected);
        
        // Test
        $actual = $queue->getName();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\Queue::setUrl
     */
    public function testSetUrl()
    {
        // Setup
        $queue = new Queue('myqueue', 'myurl');
        $expected = TestResources::QUEUE1_NAME;
        
        // Test
        $queue->setUrl($expected);
        
        // Assert
        $this->assertEquals($expected, $queue->getUrl());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\Queue::getUrl
     */
    public function testGetUrl()
    {
        // Setup
        $queue = new Queue('myqueue', 'myurl');
        $expected = TestResources::QUEUE_URI;
        $queue->setUrl($expected);
        
        // Test
        $actual = $queue->getUrl();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\Queue::setMetadata
     */
    public function testSetMetadata()
    {
        // Setup
        $queue = new Queue('myqueue', 'myurl');
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        
        // Test
        $queue->setMetadata($expected);
        
        // Assert
        $this->assertEquals($expected, $queue->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\Queue::getMetadata
     */
    public function testGetMetadata()
    {
        // Setup
        $queue = new Queue('myqueue', 'myurl');
        $expected = array('key1' => 'value1', 'key2' => 'value2');
        $queue->setMetadata($expected);
        
        // Test
        $actual = $queue->getMetadata();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue\Models;
use MicrosoftAzure\Storage\Queue\Models\UpdateMessageResult;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class UpdateMessageResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class UpdateMessageResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\UpdateMessageResult::getPopReceipt
     */
    public function testGetPopReceipt()
    {
        // Setup
        $updateMessageResult = new UpdateMessageResult();
        $expected = 'YzQ4Yzg1MDItYTc0Ny00OWNjLTkxYTUtZGM0MDFiZDAwYzEw';
        $updateMessageResult->setPopReceipt($expected);
        
        // Test
        $actual = $updateMessageResult->getPopReceipt();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\UpdateMessageResult::setPopReceipt
     */
    public function testSetPopReceipt()
    {
        // Setup
        $updateMessageResult = new UpdateMessageResult();
        $expected = 'YzQ4Yzg1MDItYTc0Ny00OWNjLTkxYTUtZGM0MDFiZDAwYzEw';
        
        // Test
        $updateMessageResult->setPopReceipt($expected);
        
        // Assert
        $actual = $updateMessageResult->getPopReceipt();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\UpdateMessageResult::getTimeNextVisible
     */
    public function testGetTimeNextVisible()
    {
        // Setup
        $updateMessageResult = new UpdateMessageResult();
        $expected = Utilities::rfc1123ToDateTime('Fri, 09 Oct 2009 23:29:20 GMT');
        $updateMessageResult->setTimeNextVisible($expected);
        
        // Test
        $actual = $updateMessageResult->getTimeNextVisible();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\Models\UpdateMessageResult::setTimeNextVisible
     */
    public function testSetTimeNextVisible()
    {
        // Setup
        $updateMessageResult = new UpdateMessageResult();
        $expected = Utilities::rfc1123ToDateTime('Fri, 09 Oct 2009 23:29:20 GMT');
        
        // Test
        $updateMessageResult->setTimeNextVisible($expected);
        
        // Assert
        $actual = $updateMessageResult->getTimeNextVisible();
        $this->assertEquals($expected, $actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Queue
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Queue;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Tests\Framework\QueueServiceRestProxyTestBase;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesOptions;
use MicrosoftAzure\Storage\Queue\Models\ListQueuesResult;
use MicrosoftAzure\Storage\Queue\Models\CreateQueueOptions;
use MicrosoftAzure\Storage\Queue\Models\GetQueueMetadataResult;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesResult;
use MicrosoftAzure\Storage\Queue\Models\ListMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesResult;
use MicrosoftAzure\Storage\Queue\Models\PeekMessagesOptions;
use MicrosoftAzure\Storage\Queue\Models\UpdateMessageResult;
use MicrosoftAzure\Storage\Queue\Models\QueueServiceOptions;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Common\ServiceException;

/**
 * Unit tests for QueueRestProxy class
 *
 * @package    MicrosoftAzure\Storage\Tests\Unit\Queue
 * @author     Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright  2016 Microsoft Corporation
 * @license    https://github.com/azure/azure-storage-php/LICENSE
 * @version    Release: 0.10.2
 * @link       https://github.com/azure/azure-storage-php
 */
class QueueRestProxyTest extends QueueServiceRestProxyTestBase
{
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::send
     */
    public function testListQueuesSimple()
    {
        // Setup
        $queue1 = 'listqueuesimple1';
        $queue2 = 'listqueuesimple2';
        $queue3 = 'listqueuesimple3';

        parent::createQueue($queue1);
        parent::createQueue($queue2);
        parent::createQueue($queue3);
        
        // Test
        $result = $this->restProxy->listQueues();

        // Assert
        $queues = $result->getQueues();
        $this->assertNotNull($result->getAccountName());
        $this->assertEquals($queue1, $queues[0]->getName());
        $this->assertEquals($queue2, $queues[1]->getName());
        $this->assertEquals($queue3, $queues[2]->getName());
    }

    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
     */
    public function testListQueuesWithOptions()
    {
        // Setup
        $queue1        = 'listqueuewithoptions1';
        $queue2        = 'listqueuewithoptions2';
        $queue3        = 'mlistqueuewithoptions3';
        $metadataName  = 'Mymetadataname';
        $metadataValue = 'MetadataValue';
        $options = new CreateQueueOptions();
        $options->addMetadata($metadataName, $metadataValue);
        parent::createQueue($queue1);
        parent::createQueue($queue2, $options);
        parent::createQueue($queue3);
        $options = new ListQueuesOptions();
        $options->setPrefix('list');
        $options->setIncludeMetadata(true);
        
        // Test
        $result = $this->restProxy->listQueues($options);
        
        // Assert
        $queues   = $result->getQueues();
        $metadata = $queues[1]->getMetadata();
        $this->assertEquals(2, count($queues));
        $this->assertEquals($queue1, $queues[0]->getName());
        $this->assertEquals($queue2, $queues[1]->getName());
        $this->assertEquals($metadataValue, $metadata[$metadataName]);
    }

    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
     */
    public function testListQueuesWithNextMarker()
    {
        // Setup
        $queue1 = 'listqueueswithnextmarker1';
        $queue2 = 'listqueueswithnextmarker2';
        $queue3 = 'listqueueswithnextmarker3';
        parent::createQueue($queue1);
        parent::createQueue($queue2);
        parent::createQueue($queue3);
        $options = new ListQueuesOptions();
        $options->setMaxResults(2);
        
        // Test
        $result = $this->restProxy->listQueues($options);
        
        // Assert
        $queues = $result->getQueues();
        $this->assertEquals(2, count($queues));
        $this->assertEquals($queue1, $queues[0]->getName());
        $this->assertEquals($queue2, $queues[1]->getName());
        
        // Test
        $options->setMarker($result->getNextMarker());
        $result = $this->restProxy->listQueues($options);
        $queues = $result->getQueues();

        // Assert
        $this->assertEquals(1, count($queues));
        $this->assertEquals($queue3, $queues[0]->getName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
     */
    public function testListQueuesWithInvalidNextMarkerFail()
    {
        $this->skipIfEmulated();
        
        // Setup
        $queue1 = 'listqueueswithinvalidnextmarker1';
        $queue2 = 'listqueueswithinvalidnextmarker2';
        $queue3 = 'listqueueswithinvalidnextmarker3';
        parent::createQueue($queue1);
        parent::createQueue($queue2);
        parent::createQueue($queue3);
        $options = new ListQueuesOptions();
        $options->setMaxResults(2);
        $this->setExpectedException(get_class(new ServiceException('409')));
        
        // Test
        $this->restProxy->listQueues($options);
        $options->setMarker('wrong marker');
        $this->restProxy->listQueues($options);
    }

    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
     */
    public function testListQueuesWithNoQueues()
    {
        // Test
        $result = $this->restProxy->listQueues();
        
        // Assert
        $queues = $result->getQueues();
        $this->assertTrue(empty($queues));
    }

    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listQueues
     */
    public function testListQueuesWithOneResult()
    {
        // Setup
        $queueName = 'listqueueswithoneresult';
        parent::createQueue($queueName);
        
        // Test
        $result = $this->restProxy->listQueues();
        $queues = $result->getQueues();

        // Assert
        $this->assertEquals(1, count($queues));
    }

    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
     */
    public function testCreateQueueSimple()
    {
        // Setup
        $queueName = 'createqueuesimple';
        
        // Test
        $this->createQueue($queueName);
        
        // Assert
        $result = $this->restProxy->listQueues();
        $queues = $result->getQueues();
        $this->assertEquals(1, count($queues));
        $this->assertEquals($queues[0]->getName(), $queueName);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
     */
    public function testCreateQueueWithExistingQueue()
    {
        // Setup
        $queueName = 'createqueuewithexistingqueue';
        $this->createQueue($queueName);
        
        // Test
        $this->createQueue($queueName);
        
        // Assert
        $result = $this->restProxy->listQueues();
        $queues = $result->getQueues();
        $this->assertEquals(1, count($queues));
        $this->assertEquals($queues[0]->getName(), $queueName);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
     */
    public function testCreateQueueWithMetadata()
    {
        $queueName     = 'createqueuewithmetadata';
        $metadataName  = 'Name';
        $metadataValue = 'MyName';
        $queueCreateOptions = new CreateQueueOptions();
        $queueCreateOptions->addMetadata($metadataName, $metadataValue);
        
        // Test
        $this->createQueue($queueName, $queueCreateOptions);

        // Assert
        $options = new ListQueuesOptions();
        $options->setIncludeMetadata(true);
        $result   = $this->restProxy->listQueues($options);
        $queues   = $result->getQueues();
        $metadata = $queues[0]->getMetadata();
        $this->assertEquals($metadataValue, $metadata[$metadataName]);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createQueue
     */
    public function testCreateQueueInvalidNameFail()
    {
        // Setup
        $queueName = 'CreateQueueInvalidNameFail';
        $this->setExpectedException(get_class(new ServiceException('400')));
        
        // Test
        $this->createQueue($queueName);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
     */
    public function testDeleteQueue()
    {
        // Setup
        $queueName = 'deletequeue';
        $this->restProxy->createQueue($queueName);
        
        // Test
        $this->restProxy->deleteQueue($queueName);
        
        // Assert
        $result = $this->restProxy->listQueues();
        $queues = $result->getQueues();
        $this->assertTrue(empty($queues));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteQueue
     */
    public function testDeleteQueueFail()
    {
        // Setup
        $queueName = 'deletequeuefail';
        $this->setExpectedException(get_class(new ServiceException('404')));
        
        // Test
        $this->restProxy->deleteQueue($queueName);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getServiceProperties
     */
    public function testGetServiceProperties()
    {
        $this->skipIfEmulated();
        
        // Test
        $result = $this->restProxy->getServiceProperties();
        
        // Assert
        $this->assertEquals($this->defaultProperties->toArray(), $result->getValue()->toArray());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setServiceProperties
     */
    public function testSetServiceProperties()
    {
        $this->skipIfEmulated();
        
        // Setup
        $expected = ServiceProperties::create(TestResources::setServicePropertiesSample());
        
        // Test
        $this->setServiceProperties($expected);
        $actual = $this->restProxy->getServiceProperties();
        
        // Assert
        $this->assertEquals($expected->toXml($this->xmlSerializer), $actual->getValue()->toXml($this->xmlSerializer));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::getQueueMetadata
     */
    public function testGetQueueMetadata()
    {
        // Setup
        $name     = 'getqueuemetadata';
        $expectedCount = 0;
        $options  = new CreateQueueOptions();
        $expected = array ('name1' => 'MyName1', 'mymetaname' => '12345', 'values' => 'Microsoft_');
        $options->setMetadata($expected);
        $this->createQueue($name, $options);
        
        // Test
        $result = $this->restProxy->getQueueMetadata($name);
        
        // Assert
        $this->assertEquals($expectedCount, $result->getApproximateMessageCount());
        $this->assertEquals($expected, $result->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::setQueueMetadata
     */
    public function testSetQueueMetadata()
    {
        // Setup
        $name = 'setqueuemetadata';
        $expected = array ('name1' => 'MyName1', 'mymetaname' => '12345', 'values' => 'Microsoft_');
        $this->createQueue($name);
        
        // Test
        $this->restProxy->setQueueMetadata($name, $expected);
        $actual = $this->restProxy->getQueueMetadata($name);
        
        // Assert
        $this->assertEquals($expected, $actual->getMetadata());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::createMessage
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::send
     */
    public function testCreateMessage()
    {
        // Setup
        $name = 'createmessage';
        $expected = 'this is message text';
        $this->createQueue($name);
        
        // Test
        $this->restProxy->createMessage($name, $expected);
        
        // Assert
        $result = $this->restProxy->listMessages($name);
        $messages = $result->getQueueMessages();
        $actual = $messages[0]->getMessageText();
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
     */
    public function testListMessagesEmpty()
    {
        // Setup
        $name = 'listmessagesempty';
        $this->createQueue($name);

        // Test
        $result = $this->restProxy->listMessages($name);        
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertEmpty($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
     */
    public function testListMessagesOneMessage()
    {
        // Setup
        $name = 'listmessagesonemessage';
        $this->createQueue($name);
        $expected = 'Message text';
        $this->restProxy->createMessage($name, $expected);
        
        // Test
        $result = $this->restProxy->listMessages($name);        
        
        // Assert
        $messages = $result->getQueueMessages();
        $actual = $messages[0];
        $this->assertCount(1, $messages);
        $this->assertEquals($expected, $actual->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
     */
    public function testListMessagesCreateMultiplesReturnOne()
    {
        // Setup
        $name = 'listmessagescreatemultiplesreturnone';
        $this->createQueue($name);
        $expected1 = 'Message #1 Text';
        $message2 = 'Message #2 Text';
        $message3 = 'Message #3 Text';
        $this->restProxy->createMessage($name, $expected1);
        $this->restProxy->createMessage($name, $message2);
        $this->restProxy->createMessage($name, $message3);
        
        // Test
        $result = $this->restProxy->listMessages($name);
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertCount(1, $actual);
        $this->assertEquals($expected1, $actual[0]->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::listMessages
     */
    public function testListMessagesMultiplesMessages()
    {
        // Setup
        $name = 'listmessagesmultiplesmessages';
        $this->createQueue($name);
        $expected1 = 'Message #1 Text';
        $expected2 = 'Message #2 Text';
        $expected3 = 'Message #3 Text';
        $this->restProxy->createMessage($name, $expected1);
        $this->restProxy->createMessage($name, $expected2);
        $this->restProxy->createMessage($name, $expected3);
        $options = new ListMessagesOptions();
        $options->setNumberOfMessages(10);
        
        // Test
        $result = $this->restProxy->listMessages($name, $options);
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertCount(3, $actual);
        $this->assertEquals($expected1, $actual[0]->getMessageText());
        $this->assertEquals($expected2, $actual[1]->getMessageText());
        $this->assertEquals($expected3, $actual[2]->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
     */
    public function testPeekMessagesEmpty()
    {
        // Setup
        $name = 'peekmessagesempty';
        $this->createQueue($name);

        // Test
        $result = $this->restProxy->peekMessages($name);        
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertEmpty($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
     */
    public function testPeekMessagesOneMessage()
    {
        // Setup
        $name = 'peekmessagesonemessage';
        $this->createQueue($name);
        $expected = 'Message text';
        $this->restProxy->createMessage($name, $expected);
        
        // Test
        $result = $this->restProxy->peekMessages($name);        
        
        // Assert
        $messages = $result->getQueueMessages();
        $actual = $messages[0];
        $this->assertCount(1, $messages);
        $this->assertEquals($expected, $actual->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
     */
    public function testPeekMessagesCreateMultiplesReturnOne()
    {
        // Setup
        $name = 'peekmessagescreatemultiplesreturnone';
        $this->createQueue($name);
        $expected1 = 'Message #1 Text';
        $message2 = 'Message #2 Text';
        $message3 = 'Message #3 Text';
        $this->restProxy->createMessage($name, $expected1);
        $this->restProxy->createMessage($name, $message2);
        $this->restProxy->createMessage($name, $message3);
        
        // Test
        $result = $this->restProxy->peekMessages($name);
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertCount(1, $actual);
        $this->assertEquals($expected1, $actual[0]->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::peekMessages
     */
    public function testPeekMessagesMultiplesMessages()
    {
        // Setup
        $name = 'peekmessagesmultiplesmessages';
        $this->createQueue($name);
        $expected1 = 'Message #1 Text';
        $expected2 = 'Message #2 Text';
        $expected3 = 'Message #3 Text';
        $this->restProxy->createMessage($name, $expected1);
        $this->restProxy->createMessage($name, $expected2);
        $this->restProxy->createMessage($name, $expected3);
        $options = new PeekMessagesOptions();
        $options->setNumberOfMessages(10);
        
        // Test
        $result = $this->restProxy->peekMessages($name, $options);
        
        // Assert
        $actual = $result->getQueueMessages();
        $this->assertCount(3, $actual);
        $this->assertEquals($expected1, $actual[0]->getMessageText());
        $this->assertEquals($expected2, $actual[1]->getMessageText());
        $this->assertEquals($expected3, $actual[2]->getMessageText());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::deleteMessage
     */
    public function testDeleteMessage()
    {
        // Setup
        $name = 'deletemessage';
        $expected = 'this is message text';
        $this->createQueue($name);
        $this->restProxy->createMessage($name, $expected);
        $result = $this->restProxy->listMessages($name);
        $messages   = $result->getQueueMessages();
        $messageId  = $messages[0]->getMessageId();
        $popReceipt = $messages[0]->getPopReceipt();
        
        // Test
        $this->restProxy->deleteMessage($name, $messageId, $popReceipt);
        
        // Assert
        $result   = $this->restProxy->listMessages($name);
        $messages = $result->getQueueMessages();
        $this->assertTrue(empty($messages));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::send
     */
    public function testClearMessagesWithOptions()
    {
        // Setup
        $name = 'clearmessageswithoptions';
        $msg1 = 'message #1';
        $msg2 = 'message #2';
        $msg3 = 'message #3';
        $options = new QueueServiceOptions();
        $options->setTimeout('10');
        $this->createQueue($name);
        $this->restProxy->createMessage($name, $msg1);
        $this->restProxy->createMessage($name, $msg2);
        $this->restProxy->createMessage($name, $msg3);
        
        // Test
        $this->restProxy->clearMessages($name, $options);
        
        // Assert
        $result   = $this->restProxy->listMessages($name);
        $messages = $result->getQueueMessages();
        $this->assertTrue(empty($messages));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::clearMessages
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::send
     */
    public function testClearMessages()
    {
        // Setup
        $name = 'clearmessages';
        $msg1 = 'message #1';
        $msg2 = 'message #2';
        $msg3 = 'message #3';
        $this->createQueue($name);
        $this->restProxy->createMessage($name, $msg1);
        $this->restProxy->createMessage($name, $msg2);
        $this->restProxy->createMessage($name, $msg3);
        
        // Test
        $this->restProxy->clearMessages($name);
        
        // Assert
        $result   = $this->restProxy->listMessages($name);
        $messages = $result->getQueueMessages();
        $this->assertTrue(empty($messages));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Queue\QueueRestProxy::updateMessage
     */
    public function testUpdateMessage()
    {
        // Setup
        $name = 'updatemessage';
        $expectedText = 'this is message text';
        $expectedVisibility = 10;
        $this->createQueue($name);
        $this->restProxy->createMessage($name, 'Text to change');
        $result = $this->restProxy->listMessages($name);
        $messages   = $result->getQueueMessages();
        $popReceipt = $messages[0]->getPopReceipt();
        $messageId = $messages[0]->getMessageId();
        
        // Test
        $result = $this->restProxy->UpdateMessage($name, $messageId, $popReceipt, 
            $expectedText, $expectedVisibility);
        
        // Assert
        $result   = $this->restProxy->listMessages($name);
        $messages = $result->getQueueMessages();
        $this->assertTrue(empty($messages));
        
        sleep($expectedVisibility);
        
        $result   = $this->restProxy->listMessages($name);
        $messages = $result->getQueueMessages();
        $actual   = $messages[0];
        $this->assertEquals($expectedText, $actual->getMessageText());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   Tests\Unit\MicrosoftAzure\Storage\Table\internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace Tests\Unit\MicrosoftAzure\Storage\Table\internal;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter;
use MicrosoftAzure\Storage\Table\Models\EdmType;

/**
 * Unit tests for class AtomReaderWriter
 *
 * @category  Microsoft
 * @package   Tests\Unit\MicrosoftAzure\Storage\Table\internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class AtomReaderWriterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getTable
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::__construct
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_serializeAtom
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_generateProperties
     */
    public function testGetTable()
    {
        // Setup
        $atomSerializer = new AtomReaderWriter();
        $expected = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n" .
                    '<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">' . "\n" .
                    ' <title/>' . "\n" .
                    ' <updated>' . Utilities::isoDate() . '</updated>' . "\n" .
                    ' <author>' . "\n" .
                    '  <name/>' . "\n" .
                    ' </author>' . "\n" .
                    ' <id/>' . "\n" .
                    ' <content type="application/xml">' . "\n" .
                    '  <m:properties>' . "\n" .
                    '   <d:TableName>customers</d:TableName>' . "\n" .
                    '  </m:properties>' . "\n" .
                    ' </content>' . "\n" .
                    '</entry>' . "\n";
        
        // Test
        $actual = $atomSerializer->getTable('customers');
        
        // Assert
        $this->assertEquals($expected, $actual);
        
        return $actual;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getEntity
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::__construct
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_serializeAtom
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_generateProperties
     */
    public function testGetEntity()
    {
        // Setup
        $atomSerializer = new AtomReaderWriter();
        $entity = TestResources::getTestEntity('123', '456');
        $entity->addProperty('Cost', EdmType::DOUBLE, 12.45);
        $expected = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n" .
                    '<entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">' . "\n" .
                    ' <title/>' . "\n" .
                    ' <updated>' . Utilities::isoDate() . '</updated>' . "\n" .
                    ' <author>' . "\n" .
                    '  <name/>' . "\n" .
                    ' </author>' . "\n" .
                    ' <id/>' . "\n" .
                    ' <content type="application/xml">' . "\n" .
                    '  <m:properties>' . "\n" .
                    '   <d:PartitionKey>123</d:PartitionKey>' . "\n" .
                    '   <d:RowKey>456</d:RowKey>' . "\n" .
                    '   <d:CustomerId m:type="Edm.Int32">890</d:CustomerId>' . "\n" .
                    '   <d:CustomerName>John</d:CustomerName>' . "\n" .
                    '   <d:IsNew m:type="Edm.Boolean">1</d:IsNew>' . "\n" .
                    // PHP DateTime only keeps 6 digits of microseconds.
                    '   <d:JoinDate m:type="Edm.DateTime">2012-01-26T18:26:19.0000470Z</d:JoinDate>' . "\n" .
                    '   <d:Cost m:type="Edm.Double">12.45</d:Cost>' . "\n" .
                    '  </m:properties>' . "\n" .
                    ' </content>' . "\n" .
                    '</entry>' . "\n";
        
        // Test
        $actual = $atomSerializer->getEntity($entity);
        
        // Assert
        $this->assertEquals($expected, $actual);
        
        return $actual;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTable
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::__construct
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_serializeAtom
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_generateProperties
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneTable
     * @depends testGetTable
     */
    public function testParseTable($tableAtom)
    {
        // Setup
        $atomSerializer = new AtomReaderWriter();
        $expected = 'customers';
        
        // Test
        $actual = $atomSerializer->parseTable($tableAtom);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTableEntries
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::__construct
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_serializeAtom
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_generateProperties
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneTable
     */
    public function testParseTables()
    {
        // Setup
        $atomSerializer = new AtomReaderWriter();
        $expected = array('querytablessimple1', 'querytablessimple2');
        $tablesAtom = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
                       <feed xml:base="http://aogail2.table.core.windows.net/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
                       <title type="text">Tables</title>
                       <id>http://aogail2.table.core.windows.net/Tables</id>
                       <updated>2012-05-17T00:30:14Z</updated>
                        <link rel="self" title="Tables" href="Tables" />
                        <entry>
                            <id>http://aogail2.table.core.windows.net/Tables(\'querytablessimple1\')</id>
                            <title type="text"></title>
                            <updated>2012-05-17T00:30:14Z</updated>
                            <author>
                            <name />
                            </author>
                            <link rel="edit" title="Tables" href="Tables(\'querytablessimple1\')" />
                            <category term="aogail2.Tables" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
                            <content type="application/xml">
                            <m:properties>
                                <d:TableName>querytablessimple1</d:TableName>
                            </m:properties>
                            </content>
                        </entry>
                        <entry>
                            <id>http://aogail2.table.core.windows.net/Tables(\'querytablessimple2\')</id>
                            <title type="text"></title>
                            <updated>2012-05-17T00:30:14Z</updated>
                            <author>
                            <name />
                            </author>
                            <link rel="edit" title="Tables" href="Tables(\'querytablessimple2\')" />
                            <category term="aogail2.Tables" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
                            <content type="application/xml">
                            <m:properties>
                                <d:TableName>querytablessimple2</d:TableName>
                            </m:properties>
                            </content>
                        </entry>
                        </feed>' . "\n";
        
        // Test
        $actual = $atomSerializer->parseTableEntries($tablesAtom);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntity
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::__construct
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_serializeAtom
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_generateProperties
     * @depends testGetEntity
     */
    public function testParseEntity($entityAtom)
    {
        // Setup
        $atomSerializer = new AtomReaderWriter();
        $expected = TestResources::getExpectedTestEntity('123', '456');
        $expected->addProperty('Cost', EdmType::DOUBLE, 12.45);
        
        // Test
        $actual = $atomSerializer->parseEntity($entityAtom);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    public function testParseEntities()
    {
        // Setup
        $atomSerializer = new AtomReaderWriter();
        $pk1 = '123';
        $pk2 = '124';
        $pk3 = '125';
        $e1 = TestResources::getExpectedTestEntity($pk1, '1');
        $e2 = TestResources::getExpectedTestEntity($pk2, '2');
        $e3 = TestResources::getExpectedTestEntity($pk3, '3');
        $e1->setETag('W/"datetime\'2012-05-17T00%3A59%3A32.1131734Z\'"');
        $e2->setETag('W/"datetime\'2012-05-17T00%3A59%3A32.4252358Z\'"');
        $e3->setETag('W/"datetime\'2012-05-17T00%3A59%3A32.7533014Z\'"');
        $e1->setTimestamp(Utilities::convertToDateTime('2012-05-17T00:59:32.1131734Z'));
        $e2->setTimestamp(Utilities::convertToDateTime('2012-05-17T00:59:32.4252358Z'));
        $e3->setTimestamp(Utilities::convertToDateTime('2012-05-17T00:59:32.7533014Z'));
        $expected = array($e1, $e2, $e3);
        $entitiesAtom = '<?xml version="1.0" encoding="utf-8" standalone="yes"?>
                        <feed xml:base="http://aogail2.table.core.windows.net/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
                        <title type="text">queryentitieswithmultipleentities</title>
                        <id>http://aogail2.table.core.windows.net/queryentitieswithmultipleentities</id>
                        <updated>2012-05-17T00:59:32Z</updated>
                        <link rel="self" title="queryentitieswithmultipleentities" href="queryentitieswithmultipleentities" />
                        <entry m:etag="W/&quot;datetime\'2012-05-17T00%3A59%3A32.1131734Z\'&quot;">
                            <id>http://aogail2.table.core.windows.net/queryentitieswithmultipleentities(PartitionKey=\'123\',RowKey=\'1\')</id>
                            <title type="text"></title>
                            <updated>2012-05-17T00:59:32Z</updated>
                            <author>
                            <name />
                            </author>
                            <link rel="edit" title="queryentitieswithmultipleentities" href="queryentitieswithmultipleentities(PartitionKey=\'123\',RowKey=\'1\')" />
                            <category term="aogail2.queryentitieswithmultipleentities" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
                            <content type="application/xml">
                            <m:properties>
                                <d:PartitionKey>123</d:PartitionKey>
                                <d:RowKey>1</d:RowKey>
                                <d:Timestamp m:type="Edm.DateTime">2012-05-17T00:59:32.1131734Z</d:Timestamp>
                                <d:CustomerId m:type="Edm.Int32">890</d:CustomerId>
                                <d:CustomerName>John</d:CustomerName>
                                <d:IsNew m:type="Edm.Boolean">true</d:IsNew>
                                <d:JoinDate m:type="Edm.DateTime">2012-01-26T18:26:19.0000473Z</d:JoinDate>
                            </m:properties>
                            </content>
                        </entry>
                        <entry m:etag="W/&quot;datetime\'2012-05-17T00%3A59%3A32.4252358Z\'&quot;">
                            <id>http://aogail2.table.core.windows.net/queryentitieswithmultipleentities(PartitionKey=\'124\',RowKey=\'2\')</id>
                            <title type="text"></title>
                            <updated>2012-05-17T00:59:32Z</updated>
                            <author>
                            <name />
                            </author>
                            <link rel="edit" title="queryentitieswithmultipleentities" href="queryentitieswithmultipleentities(PartitionKey=\'124\',RowKey=\'2\')" />
                            <category term="aogail2.queryentitieswithmultipleentities" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
                            <content type="application/xml">
                            <m:properties>
                                <d:PartitionKey>124</d:PartitionKey>
                                <d:RowKey>2</d:RowKey>
                                <d:Timestamp m:type="Edm.DateTime">2012-05-17T00:59:32.4252358Z</d:Timestamp>
                                <d:CustomerId m:type="Edm.Int32">890</d:CustomerId>
                                <d:CustomerName>John</d:CustomerName>
                                <d:IsNew m:type="Edm.Boolean">true</d:IsNew>
                                <d:JoinDate m:type="Edm.DateTime">2012-01-26T18:26:19.0000473Z</d:JoinDate>
                            </m:properties>
                            </content>
                        </entry>
                        <entry m:etag="W/&quot;datetime\'2012-05-17T00%3A59%3A32.7533014Z\'&quot;">
                            <id>http://aogail2.table.core.windows.net/queryentitieswithmultipleentities(PartitionKey=\'125\',RowKey=\'3\')</id>
                            <title type="text"></title>
                            <updated>2012-05-17T00:59:32Z</updated>
                            <author>
                            <name />
                            </author>
                            <link rel="edit" title="queryentitieswithmultipleentities" href="queryentitieswithmultipleentities(PartitionKey=\'125\',RowKey=\'3\')" />
                            <category term="aogail2.queryentitieswithmultipleentities" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
                            <content type="application/xml">
                            <m:properties>
                                <d:PartitionKey>125</d:PartitionKey>
                                <d:RowKey>3</d:RowKey>
                                <d:Timestamp m:type="Edm.DateTime">2012-05-17T00:59:32.7533014Z</d:Timestamp>
                                <d:CustomerId m:type="Edm.Int32">890</d:CustomerId>
                                <d:CustomerName>John</d:CustomerName>
                                <d:IsNew m:type="Edm.Boolean">true</d:IsNew>
                                <d:JoinDate m:type="Edm.DateTime">2012-01-26T18:26:19.0000473Z</d:JoinDate>
                            </m:properties>
                            </content>
                        </entry>
                        </feed>
                        ';
        
        // Test
        $actual = $atomSerializer->parseEntities($entitiesAtom);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
}



<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\BatchError;
use MicrosoftAzure\Storage\Common\ServiceException;

/**
 * Unit tests for class BatchError
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchErrorTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchError::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchError::getError
     * @covers MicrosoftAzure\Storage\Table\Models\BatchError::getContentId
     */
    public function testCreate()
    {
        // Setup
        $error = new ServiceException('200');
        $contentId = 1;
        $headers = array('content-id' => strval($contentId));
        
        // Test
        $batchError = BatchError::create($error, $headers);
        
        // Assert
        $this->assertEquals($error, $batchError->getError());
        $this->assertEquals($contentId, $batchError->getContentId());
        
        return $batchError;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchError::setError
     * @covers MicrosoftAzure\Storage\Table\Models\BatchError::getError
     * @depends testCreate
     */
    public function testSetError($batchError)
    {
        // Setup
        $error = new ServiceException('100');
        
        // Test
        $batchError->setError($error);
        
        // Assert
        $this->assertEquals($error, $batchError->getError());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchError::setContentId
     * @covers MicrosoftAzure\Storage\Table\Models\BatchError::getContentId
     * @depends testCreate
     */
    public function testSetContentId($batchError)
    {
        // Setup
        $contentId = 1;
        
        // Test
        $batchError->setContentId($contentId);
        
        // Assert
        $this->assertEquals($contentId, $batchError->getContentId());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\BatchOperationParameterName;

/**
 * Unit tests for class BatchOperationParameterName
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchOperationParameterNameTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperationParameterName::isValid
     */
    public function testIsValid()
    {
        // Setup
        $name = BatchOperationParameterName::BP_ETAG;
        
        // Test
        $actual = BatchOperationParameterName::isValid($name);
        
        // Assert
        $this->assertTrue($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperationParameterName::isValid
     */
    public function testIsValidWithInvalid()
    {
        // Setup
        $name = 'zeta el senen';
        
        // Test
        $actual = BatchOperationParameterName::isValid($name);
        
        // Assert
        $this->assertFalse($actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\BatchOperations;
use MicrosoftAzure\Storage\Table\Models\BatchOperation;
use MicrosoftAzure\Storage\Table\Models\Entity;

/**
 * Unit tests for class BatchOperations
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchOperationsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::getOperations
     */
    public function test__construct()
    {
        // Test
        $operations = new BatchOperations();
        
        // Assert
        $this->assertCount(0, $operations->getOperations());
        
        return $operations;
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::setOperations
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::getOperations
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addOperation
     * @depends test__construct
     */
    public function testSetOperations($operations)
    {
        // Setup
        $operation = new BatchOperation();
        $expected = array($operation);
        $operations->addOperation($operation);
        
        // Test
        $operations->setOperations($expected);
        
        // Assert
        $this->assertEquals($expected, $operations->getOperations());
    }
    
    /**
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addInsertEntity
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addOperation
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::getOperations
     */
    public function testAddInsertEntity()
    {
        // Setup
        $table = 'mytable';
        $entity = new Entity();
        $operations = new BatchOperations();
        
        // Test
        $operations->addInsertEntity($table, $entity);
        
        // Assert
        $this->assertCount(1, $operations->getOperations());
    }
    
    /**
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addUpdateEntity
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addOperation
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::getOperations
     */
    public function testAddUpdateEntity()
    {
        // Setup
        $table = 'mytable';
        $entity = new Entity();
        $operations = new BatchOperations();
        
        // Test
        $operations->addUpdateEntity($table, $entity);
        
        // Assert
        $this->assertCount(1, $operations->getOperations());
    }
    
    /**
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addMergeEntity
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addOperation
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::getOperations
     */
    public function testAddMergeEntity()
    {
        // Setup
        $table = 'mytable';
        $entity = new Entity();
        $operations = new BatchOperations();
        
        // Test
        $operations->addMergeEntity($table, $entity);
        
        // Assert
        $this->assertCount(1, $operations->getOperations());
    }
    
    /**
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addInsertOrReplaceEntity
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addOperation
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::getOperations
     */
    public function testAddInsertOrReplaceEntity()
    {
        // Setup
        $table = 'mytable';
        $entity = new Entity();
        $operations = new BatchOperations();
        
        // Test
        $operations->addInsertOrReplaceEntity($table, $entity);
        
        // Assert
        $this->assertCount(1, $operations->getOperations());
    }
    
    /**
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addInsertOrMergeEntity
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addOperation
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::getOperations
     */
    public function testAddInsertOrMergeEntity()
    {
        // Setup
        $table = 'mytable';
        $entity = new Entity();
        $operations = new BatchOperations();
        
        // Test
        $operations->addInsertOrMergeEntity($table, $entity);
        
        // Assert
        $this->assertCount(1, $operations->getOperations());
    }
    
    /**
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addDeleteEntity
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::addOperation
     *  @covers MicrosoftAzure\Storage\Table\Models\BatchOperations::getOperations
     */
    public function testAddDeleteEntity()
    {
        // Setup
        $table = 'mytable';
        $partitionKey = '123';
        $rowKey= '456';
        $etag = 'W/datetime:2009';
        $operations = new BatchOperations();
        
        // Test
        $operations->addDeleteEntity($table, $partitionKey, $rowKey, $etag);
        
        // Assert
        $this->assertCount(1, $operations->getOperations());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\BatchOperation;
use MicrosoftAzure\Storage\Table\Models\BatchOperationType;
use MicrosoftAzure\Storage\Table\Models\BatchOperationParameterName;

/**
 * Unit tests for class BatchOperation
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchOperationTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperation::setType
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperation::getType
     */
    public function testSetType()
    {
        // Setup
        $batchOperation = new BatchOperation();
        $expected = BatchOperationType::DELETE_ENTITY_OPERATION;
        
        // Test
        $batchOperation->setType($expected);
        
        // Assert
        $this->assertEquals($expected, $batchOperation->getType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperation::addParameter
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperation::getParameter
     */
    public function testAddParameter()
    {
        // Setup
        $batchOperation = new BatchOperation();
        $expected = 'param zeta';
        $name = BatchOperationParameterName::BP_ENTITY;
        
        // Test
        $batchOperation->addParameter($name, $expected);
        
        // Assert
        $this->assertEquals($expected, $batchOperation->getParameter($name));
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\BatchOperationType;

/**
 * Unit tests for class BatchOperationType
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchOperationTypeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperationType::isValid
     */
    public function testIsValid()
    {
        // Setup
        $name = BatchOperationType::DELETE_ENTITY_OPERATION;
        
        // Test
        $actual = BatchOperationType::isValid($name);
        
        // Assert
        $this->assertTrue($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchOperationType::isValid
     */
    public function testIsValidWithInvalid()
    {
        // Setup
        $name = 'zeta el senen';
        
        // Test
        $actual = BatchOperationType::isValid($name);
        
        // Assert
        $this->assertFalse($actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\BatchResult;

/**
 * Unit tests for class BatchResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BatchResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::setEntries
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::getEntries
     */
    public function testSetEntries()
    {
        // Setup
        $batchResult = new BatchResult();
        $entries = array();
        
        // Test
        $batchResult->setEntries($entries);
        
        // Assert
        $this->assertEquals($entries, $batchResult->getEntries());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions;
use MicrosoftAzure\Storage\Table\Models\ETag;

/**
 * Unit tests for class DeleteEntityOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class DeleteEntityOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions::setETag
     * @covers MicrosoftAzure\Storage\Table\Models\DeleteEntityOptions::getETag
     */
    public function testSetETag()
    {
        // Setup
        $options = new DeleteEntityOptions();
        $ETag = 'etag value';
        
        // Test
        $options->setETag($ETag);
        
        // Assert
        $this->assertEquals($ETag, $options->getETag());
        
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class EdmTypeTest
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class EdmTypeTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::processType
     */
    public function testProcessTypeWithNull()
    {
        // Setup
        $expected = EdmType::STRING;
        
        // Test
        $actual = EdmType::processType(null);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::processType
     */
    public function testProcessType()
    {
        // Setup
        $expected = EdmType::BINARY;
        
        // Test
        $actual = EdmType::processType($expected);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::unserializeQueryValue
     */
    public function testUnserializeQueryValueWithString()
    {
        // Setup
        $type = EdmType::STRING;
        $value = '1234';
        $expected = $value;
        
        // Test
        $actual = EdmType::unserializeQueryValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::unserializeQueryValue
     */
    public function testUnserializeQueryValueWithBinary()
    {
        // Setup
        $type = EdmType::BINARY;
        $value = 'MTIzNDU=';
        $expected = base64_decode($value);
        
        // Test
        $actual = EdmType::unserializeQueryValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::unserializeQueryValue
     */
    public function testUnserializeQueryValueWithDate()
    {
        // Setup
        $type = EdmType::DATETIME;
        $value = '2008-10-01T15:26:13Z';
        
        // Test
        $actual = EdmType::unserializeQueryValue($type, $value);
        
        // Assert
        $this->assertInstanceOf('\DateTime', $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::unserializeQueryValue
     */
    public function testUnserializeQueryValueWithInt()
    {
        // Setup
        $type = EdmType::INT64;
        $value = '123';
        $expected = 123;
        
        // Test
        $actual = EdmType::unserializeQueryValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::unserializeQueryValue
     */
    public function testUnserializeQueryValueWithBoolean()
    {
        // Setup
        $type = EdmType::BOOLEAN;
        $value = '1';
        $expected = true;
        
        // Test
        $actual = EdmType::unserializeQueryValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::unserializeQueryValue
     */
    public function testUnserializeQueryValueWithInvalid()
    {
        // Assert
        $this->setExpectedException('\InvalidArgumentException');
        
        // Test
        EdmType::unserializeQueryValue('7amada', '1233');
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::isValid
     */
    public function testIsValid()
    {
        // Setup
        $expected = true;
        
        // Test
        $actual = EdmType::isValid(EdmType::STRING);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::isValid
     */
    public function testIsValidWithInvalid()
    {
        // Setup
        $expected = false;
        
        // Test
        $actual = EdmType::isValid('hobba');
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::validateEdmValue
     */
    public function testValidateEdmValueWithBinary()
    {
        // Setup
        $type = EdmType::BINARY;
        $value = 'MTIzNDU=';
        $expected = true;
        
        // Test
        $actual = EdmType::validateEdmValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::validateEdmValue
     */
    public function testValidateEdmValueWithDate()
    {
        // Setup
        $type = EdmType::DATETIME;
        $value = new \DateTime();
        $expected = true;
        
        // Test
        $actual = EdmType::validateEdmValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::validateEdmValue
     */
    public function testValidateEdmValueWithInt()
    {
        // Setup
        $type = EdmType::INT64;
        $value = '123';
        $expected = true;
        
        // Test
        $actual = EdmType::validateEdmValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::validateEdmValue
     */
    public function testValidateEdmValueWithBoolean()
    {
        // Setup
        $type = EdmType::BOOLEAN;
        $value = false;
        $expected = true;
        
        // Test
        $actual = EdmType::validateEdmValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::validateEdmValue
     */
    public function testValidateEdmValueWithInvalid()
    {
        // Assert
        $this->setExpectedException('\InvalidArgumentException');
        
        // Test
        EdmType::validateEdmValue('7amada', '1233');
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     */
    public function testSerializeValueWithBinary()
    {
        // Setup
        $type = EdmType::BINARY;
        $value = '010101010111';
        $expected = base64_encode($value);
        
        // Test
        $actual = EdmType::serializeValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     */
    public function testSerializeValueWithDate()
    {
        // Setup
        $type = EdmType::DATETIME;
        $value = new \DateTime();
        $expected = Utilities::convertToEdmDateTime($value);
        
        // Test
        $actual = EdmType::serializeValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     */
    public function testSerializeValueWithInt()
    {
        // Setup
        $type = EdmType::INT64;
        $value = 123;
        $expected = htmlspecialchars($value);
        
        // Test
        $actual = EdmType::serializeValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     */
    public function testSerializeValueWithBoolean()
    {
        // Setup
        $type = EdmType::BOOLEAN;
        $value = true;
        $expected = ($value == true ? '1' : '0');
        
        // Test
        $actual = EdmType::serializeValue($type, $value);
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     */
    public function testSerializeValueWithInvalid()
    {
        // Assert
        $this->setExpectedException('\InvalidArgumentException');
        
        // Test
        EdmType::serializeValue('7amada', '1233');
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\Property;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Common\Internal\Utilities;

/**
 * Unit tests for class Entity
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class EntityTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::getPropertyValue
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::setPropertyValue
     */
    public function testGetPropertyValue()
    {
        // Setup
        $entity = new Entity();
        $name = 'name';
        $edmType = EdmType::STRING;
        $value = 'MyName';
        $expected = 'MyNewName';
        $entity->addProperty($name, $edmType, $value);
        $entity->setPropertyValue($name, $expected);
        
        // Test
        $actual = $entity->getPropertyValue($name);
        
        // Assert
        $this->assertEquals($actual, $expected);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::setETag
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::getETag
     */
    public function testSetETag()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $entity = new Entity();
        $entity->setETag($expected);
        
        // Test
        $entity->setETag($expected);
        
        // Assert
        $this->assertEquals($expected, $entity->getETag());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::setPartitionKey
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::getPartitionKey
     */
    public function testSetPartitionKey()
    {
        // Setup
        $entity = new Entity();
        $expected = '1234';
        
        // Test
        $entity->setPartitionKey($expected);
        
        // Assert
        $this->assertEquals($expected, $entity->getPartitionKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::setRowKey
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::getRowKey
     */
    public function testSetRowKey()
    {
        // Setup
        $entity = new Entity();
        $expected = '1234';
        
        // Test
        $entity->setRowKey($expected);
        
        // Assert
        $this->assertEquals($expected, $entity->getRowKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::setTimestamp
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::getTimestamp
     */
    public function testSetTimestamp()
    {
        // Setup
        $entity = new Entity();
        $expected = Utilities::convertToDateTime(Utilities::isoDate());
        
        // Test
        $entity->setTimestamp($expected);
        
        // Assert
        $this->assertEquals($expected, $entity->getTimestamp());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::setProperties
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::getProperties
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::_validateProperties
     */
    public function testSetProperties()
    {
        // Setup
        $entity = new Entity();
        $expected = array('name' => new Property(EdmType::STRING, null));
        
        // Test
        $entity->setProperties($expected);
        
        // Assert
        $this->assertEquals($expected, $entity->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::getProperty
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::setProperty
     */
    public function testSetProperty()
    {
        // Setup
        $entity = new Entity();
        $expected = new Property();
        $name = 'test';
        
        // Test
        $entity->setProperty($name, $expected);
        
        // Assert
        $this->assertEquals($expected, $entity->getProperty($name));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::addProperty
     */
    public function testAddProperty()
    {
        // Setup
        $entity = new Entity();
        $name = 'test';
        $expected = new Property();
        $edmType = EdmType::STRING;
        $value = '01231232290234210';
        $expected->setEdmType($edmType);
        $expected->setValue($value);
        
        // Test
        $entity->addProperty($name, $edmType, $value);
        
        // Assert
        $this->assertEquals($expected, $entity->getProperty($name));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::isValid
     */
    public function testIsValid()
    {
        // Setup
        $entity = new Entity();
        $entity->setPartitionKey('123');
        $entity->setRowKey('456');
        
        // Assert
        $actual = $entity->isValid();
        
        // Assert
        $this->assertTrue($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::isValid
     */
    public function testIsValidWithInvalid()
    {
        // Setup
        $entity = new Entity();
        
        // Assert
        $actual = $entity->isValid();
        
        // Assert
        $this->assertFalse($actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Entity::isValid
     */
    public function testIsValidWithEmptyPartitionKey()
    {
        // Setup
        $entity = new Entity();
        $entity->addProperty('name', EdmType::STRING, 'string');
        
        // Assert
        $actual = $entity->isValid();
        
        // Assert
        $this->assertFalse($actual);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters;
use MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter;

/**
 * Unit tests for class BinaryFilter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class BinaryFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter::getOperator
     */
    public function testGetOperator()
    {
        // Setup
        $expected = 'x';
        $filter = new BinaryFilter(null, $expected, null);
        
        // Assert
        $this->assertEquals($expected, $filter->getOperator());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter::getLeft
     */
    public function testGetLeft()
    {
        // Setup
        $expected = null;
        $filter = new BinaryFilter($expected, null, null);
        
        // Assert
        $this->assertEquals($expected, $filter->getLeft());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\BinaryFilter::getRight
     */
    public function testGetRight()
    {
        // Setup
        $expected = null;
        $filter = new BinaryFilter(null, null, $expected);
        
        // Assert
        $this->assertEquals($expected, $filter->getRight());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters;
use MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter;
use MicrosoftAzure\Storage\Table\Models\EdmType;

/**
 * Unit tests for class ConstantFilter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class ConstantFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter::getValue
     */
    public function testGetValue()
    {
        // Setup
        $expected = 'x';
        $filter = new ConstantFilter(null, $expected);
        
        // Assert
        $this->assertEquals($expected, $filter->getValue());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\ConstantFilter::getEdmType
     */
    public function testGetEdmType()
    {
        // Setup
        $expected = EdmType::BINARY;
        $filter = new ConstantFilter($expected, '1234');
        
        // Assert
        $this->assertEquals($expected, $filter->getEdmType());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\EdmType;

/**
 * Unit tests for class Filter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class FilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyAnd
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyPropertyName
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyQueryString
     */
    public function testApplyAnd()
    {
        // Setup
        $left = Filter::applyPropertyName('test');
        $right = Filter::applyQueryString('raw string');
        
        // Test
        $actual = Filter::applyAnd($left, $right);
        
        // Assert
        $this->assertEquals($left, $actual->getLeft());
        $this->assertEquals($right, $actual->getRight());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyNot
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyConstant
     */
    public function testApplyNot()
    {
        // Setup
        $operand = Filter::applyConstant('test', EdmType::STRING);
        
        // Test
        $actual = Filter::applyNot($operand);
        
        // Assert
        $this->assertEquals($operand, $actual->getOperand());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyOr
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyPropertyName
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyQueryString
     */
    public function testApplyOr()
    {
        // Setup
        $left = Filter::applyPropertyName('test');
        $right = Filter::applyQueryString('raw string');
        
        // Test
        $actual = Filter::applyOr($left, $right);
        
        // Assert
        $this->assertEquals($left, $actual->getLeft());
        $this->assertEquals($right, $actual->getRight());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyEq
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyPropertyName
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyQueryString
     */
    public function testApplyEq()
    {
        // Setup
        $left = Filter::applyPropertyName('test');
        $right = Filter::applyQueryString('raw string');
        
        // Test
        $actual = Filter::applyEq($left, $right);
        
        // Assert
        $this->assertEquals($left, $actual->getLeft());
        $this->assertEquals($right, $actual->getRight());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyNe
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyPropertyName
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyQueryString
     */
    public function testApplyNe()
    {
        // Setup
        $left = Filter::applyPropertyName('test');
        $right = Filter::applyQueryString('raw string');
        
        // Test
        $actual = Filter::applyNe($left, $right);
        
        // Assert
        $this->assertEquals($left, $actual->getLeft());
        $this->assertEquals($right, $actual->getRight());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyGe
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyPropertyName
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyQueryString
     */
    public function testApplyGe()
    {
        // Setup
        $left = Filter::applyPropertyName('test');
        $right = Filter::applyQueryString('raw string');
        
        // Test
        $actual = Filter::applyGe($left, $right);
        
        // Assert
        $this->assertEquals($left, $actual->getLeft());
        $this->assertEquals($right, $actual->getRight());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyGt
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyPropertyName
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyQueryString
     */
    public function testApplyGt()
    {
        // Setup
        $left = Filter::applyPropertyName('test');
        $right = Filter::applyQueryString('raw string');
        
        // Test
        $actual = Filter::applyGt($left, $right);
        
        // Assert
        $this->assertEquals($left, $actual->getLeft());
        $this->assertEquals($right, $actual->getRight());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyLt
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyPropertyName
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyQueryString
     */
    public function testApplyLt()
    {
        // Setup
        $left = Filter::applyPropertyName('test');
        $right = Filter::applyQueryString('raw string');
        
        // Test
        $actual = Filter::applyLt($left, $right);
        
        // Assert
        $this->assertEquals($left, $actual->getLeft());
        $this->assertEquals($right, $actual->getRight());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyLe
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyPropertyName
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\Filter::applyQueryString
     */
    public function testApplyLe()
    {
        // Setup
        $left = Filter::applyPropertyName('test');
        $right = Filter::applyQueryString('raw string');
        
        // Test
        $actual = Filter::applyLe($left, $right);
        
        // Assert
        $this->assertEquals($left, $actual->getLeft());
        $this->assertEquals($right, $actual->getRight());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters;
use MicrosoftAzure\Storage\Table\Models\Filters\PropertyNameFilter;

/**
 * Unit tests for class PropertyNameFilter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PropertyNameFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\PropertyNameFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\PropertyNameFilter::getPropertyName
     */
    public function testGetPropertyName()
    {
        // Setup
        $expected = 'x';
        $filter = new PropertyNameFilter($expected);
        
        // Assert
        $this->assertEquals($expected, $filter->getPropertyName());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters;
use MicrosoftAzure\Storage\Table\Models\Filters\QueryStringFilter;

/**
 * Unit tests for class QueryStringFilter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryStringFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\QueryStringFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\QueryStringFilter::getQueryString
     */
    public function testGetQueryString()
    {
        // Setup
        $expected = 'x';
        $filter = new QueryStringFilter($expected);
        
        // Assert
        $this->assertEquals($expected, $filter->getQueryString());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters;
use MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter;

/**
 * Unit tests for class UnaryFilter
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models\Filters
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class UnaryFilterTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter::getOperator
     */
    public function testGetOperator()
    {
        // Setup
        $expected = 'x';
        $filter = new UnaryFilter($expected, null);
        
        // Assert
        $this->assertEquals($expected, $filter->getOperator());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter::__construct
     * @covers MicrosoftAzure\Storage\Table\Models\Filters\UnaryFilter::getOperand
     */
    public function testGetOperand()
    {
        // Setup
        $expected = null;
        $filter = new UnaryFilter(null, $expected);
        
        // Assert
        $this->assertEquals($expected, $filter->getOperand());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\GetEntityResult;
use MicrosoftAzure\Storage\Table\Models\Entity;

/**
 * Unit tests for class GetEntityResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetEntityResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\GetEntityResult::setEntity
     * @covers MicrosoftAzure\Storage\Table\Models\GetEntityResult::getEntity
     */
    public function testSetEntity()
    {
        // Setup
        $result = new GetEntityResult();
        $entity = new Entity();
        
        // Test
        $result->setEntity($entity);
        
        // Assert
        $this->assertEquals($entity, $result->getEntity());
        
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\GetTableResult;

/**
 * Unit tests for class GetTableResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class GetTableResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\GetTableResult::setName
     * @covers MicrosoftAzure\Storage\Table\Models\GetTableResult::getName
     */
    public function testSetName()
    {
        // Setup
        $result = new GetTableResult();
        $name = 'myTable';
        
        // Test
        $result->setName($name);
        
        // Assert
        $this->assertEquals($name, $result->getName());
        
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\InsertEntityResult;
use MicrosoftAzure\Storage\Table\Models\Entity;

/**
 * Unit tests for class InsertEntityResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class InsertEntityResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\InsertEntityResult::setEntity
     * @covers MicrosoftAzure\Storage\Table\Models\InsertEntityResult::getEntity
     */
    public function testSetEntity()
    {
        // Setup
        $result = new InsertEntityResult();
        $entity = new Entity();
        
        // Test
        $result->setEntity($entity);
        
        // Assert
        $this->assertEquals($entity, $result->getEntity());
        
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\Property;
use MicrosoftAzure\Storage\Table\Models\EdmType;

/**
 * Unit tests for class Property
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class PropertyTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Property::setEdmType
     * @covers MicrosoftAzure\Storage\Table\Models\Property::getEdmType
     */
    public function testSetEdmType()
    {
        // Setup
        $pro = new Property();
        $expected = EdmType::BINARY;
        
        // Test
        $pro->setEdmType($expected);
        
        // Assert
        $this->assertEquals($expected, $pro->getEdmType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Property::setValue
     * @covers MicrosoftAzure\Storage\Table\Models\Property::getValue
     */
    public function testSetValue()
    {
        // Setup
        $pro = new Property();
        $expected = 'wal3a';
        
        // Test
        $pro->setValue($expected);
        
        // Assert
        $this->assertEquals($expected, $pro->getValue());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\Query;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\EdmType;

/**
 * Unit tests for class QueryEntitiesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryEntitiesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::setQuery
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::getQuery
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::__construct
     */
    public function testSetQuery()
    {
        // Setup
        $options = new QueryEntitiesOptions();
        $expected = new Query();
        
        // Test
        $options->setQuery($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getQuery());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::setNextPartitionKey
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::getNextPartitionKey
     */
    public function testSetNextPartitionKey()
    {
        // Setup
        $options = new QueryEntitiesOptions();
        $expected = 'parition';
        
        // Test
        $options->setNextPartitionKey($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getNextPartitionKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::setNextRowKey
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::getNextRowKey
     */
    public function testSetNextRowKey()
    {
        // Setup
        $options = new QueryEntitiesOptions();
        $expected = 'edelo';
        
        // Test
        $options->setNextRowKey($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getNextRowKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::setSelectFields
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::getSelectFields
     */
    public function testSetSelectFields()
    {
        // Setup
        $options = new QueryEntitiesOptions();
        $expected = array('customerId', 'customerName');
        
        // Test
        $options->setSelectFields($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getSelectFields());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::setTop
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::getTop
     */
    public function testSetTop()
    {
        // Setup
        $options = new QueryEntitiesOptions();
        $expected = 123;
        
        // Test
        $options->setTop($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getTop());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::setFilter
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::getFilter
     */
    public function testSetFilter()
    {
        // Setup
        $options = new QueryEntitiesOptions();
        $expected = Filter::applyConstant('constValue', EdmType::STRING);
        
        // Test
        $options->setFilter($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getFilter());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::addSelectField
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions::getSelectFields
     */
    public function testAddSelectField()
    {
        // Setup
        $options = new QueryEntitiesOptions();
        $field = 'customerId';
        $expected = array($field);
        
        // Test
        $options->addSelectField($field);
        
        // Assert
        $this->assertEquals($expected, $options->getSelectFields());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult;

/**
 * Unit tests for class QueryEntitiesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryEntitiesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult::create
     */
    public function testCreate()
    {
        // Test
        $result = QueryEntitiesResult::create(array(), array());
        
        // Assert
        $this->assertCount(0, $result->getEntities());
        $this->assertNull($result->getNextPartitionKey());
        $this->assertNull($result->getNextRowKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult::setNextPartitionKey
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult::getNextPartitionKey
     */
    public function testSetNextPartitionKey()
    {
        // Setup
        $result = new QueryEntitiesResult();
        $expected = 'parition';
        
        // Test
        $result->setNextPartitionKey($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getNextPartitionKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult::setNextRowKey
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult::getNextRowKey
     */
    public function testSetNextRowKey()
    {
        // Setup
        $result = new QueryEntitiesResult();
        $expected = 'edelo';
        
        // Test
        $result->setNextRowKey($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getNextRowKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult::setEntities
     * @covers MicrosoftAzure\Storage\Table\Models\QueryEntitiesResult::getEntities
     */
    public function testSetEntities()
    {
        // Setup
        $result = new QueryEntitiesResult();
        $expected = array();
        
        // Test
        $result->setEntities($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getEntities());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions;
use MicrosoftAzure\Storage\Table\Models\Query;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\EdmType;

/**
 * Unit tests for class QueryTablesOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryTablesOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::setNextTableName
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::getNextTableName
     */
    public function testSetNextTableName()
    {
        // Setup
        $options = new QueryTablesOptions();
        $expected = 'table';
        
        // Test
        $options->setNextTableName($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getNextTableName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::setPrefix
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::getPrefix
     */
    public function testSetPrefix()
    {
        // Setup
        $options = new QueryTablesOptions();
        $expected = 'prefix';
        
        // Test
        $options->setPrefix($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getPrefix());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::setTop
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::getTop
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::__construct
     */
    public function testSetTop()
    {
        // Setup
        $options = new QueryTablesOptions();
        $expected = 123;
        
        // Test
        $options->setTop($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getTop());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::getQuery
     */
    public function testGetQuery()
    {
        // Setup
        $options = new QueryTablesOptions();
        $expected = new Query();
        
        // Test
        $actual = $options->getQuery();
        
        // Assert
        $this->assertEquals($expected, $actual);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::setFilter
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesOptions::getFilter
     */
    public function testSetFilter()
    {
        // Setup
        $options = new QueryTablesOptions();
        $expected = Filter::applyConstant('constValue', EdmType::STRING);
        
        // Test
        $options->setFilter($expected);
        
        // Assert
        $this->assertEquals($expected, $options->getFilter());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\QueryTablesResult;

/**
 * Unit tests for class QueryTablesResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryTablesResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::setNextTableName
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::getNextTableName
     */
    public function testSetNextTableName()
    {
        // Setup
        $result = new QueryTablesResult();
        $expected = 'table';
        
        // Test
        $result->setNextTableName($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getNextTableName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::setTables
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::getTables
     */
    public function testSetTables()
    {
        // Setup
        $result = new QueryTablesResult();
        $expected = array(1, 2, 3, 4, 5);
        
        // Test
        $result->setTables($expected);
        
        // Assert
        $this->assertEquals($expected, $result->getTables());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\Query;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\EdmType;

/**
 * Unit tests for class Query
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class QueryTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Query::setSelectFields
     * @covers MicrosoftAzure\Storage\Table\Models\Query::getSelectFields
     */
    public function testSetSelectFields()
    {
        // Setup
        $query = new Query();
        $expected = array('customerId', 'customerName');
        
        // Test
        $query->setSelectFields($expected);
        
        // Assert
        $this->assertEquals($expected, $query->getSelectFields());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Query::setTop
     * @covers MicrosoftAzure\Storage\Table\Models\Query::getTop
     */
    public function testSetTop()
    {
        // Setup
        $query = new Query();
        $expected = 123;
        
        // Test
        $query->setTop($expected);
        
        // Assert
        $this->assertEquals($expected, $query->getTop());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Query::setFilter
     * @covers MicrosoftAzure\Storage\Table\Models\Query::getFilter
     */
    public function testSetFilter()
    {
        // Setup
        $query = new Query();
        $expected = Filter::applyConstant('constValue', EdmType::STRING);
        
        // Test
        $query->setFilter($expected);
        
        // Assert
        $this->assertEquals($expected, $query->getFilter());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\Query::addSelectField
     * @covers MicrosoftAzure\Storage\Table\Models\Query::getSelectFields
     */
    public function testAddSelectField()
    {
        // Setup
        $query = new Query();
        $field = 'customerId';
        $expected = array($field);
        
        // Test
        $query->addSelectField($field);
        
        // Assert
        $this->assertEquals($expected, $query->getSelectFields());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\TableServiceOptions;

/**
 * Unit tests for class TableServiceOptions
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class TableServiceOptionsTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\TableServiceOptions::setTimeout
     */
    public function testSetTimeout()
    {
        // Setup
        $options = new TableServiceOptions();
        $value = 10;
        
        // Test
        $options->setTimeout($value);
        
        // Assert
        $this->assertEquals($value, $options->getTimeout());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\TableServiceOptions::getTimeout
     */
    public function testGetTimeout()
    {
        // Setup
        $options = new TableServiceOptions();
        $value = 10;
        $options->setTimeout($value);
        
        // Test
        $actualValue = $options->getTimeout();
        
        // Assert
        $this->assertEquals($value, $actualValue);
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */
namespace MicrosoftAzure\Storage\Tests\Unit\Table\Models;
use MicrosoftAzure\Storage\Table\Models\UpdateEntityResult;

/**
 * Unit tests for class UpdateEntityResult
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\Models
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class UpdateEntityResultTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @covers MicrosoftAzure\Storage\Table\Models\UpdateEntityResult::setETag
     * @covers MicrosoftAzure\Storage\Table\Models\UpdateEntityResult::getETag
     */
    public function testSetETag()
    {
        // Setup
        $expected = '0x8CAFB82EFF70C46';
        $entity = new UpdateEntityResult();
        $entity->setETag($expected);
        
        // Test
        $entity->setETag($expected);
        
        // Assert
        $this->assertEquals($expected, $entity->getETag());
    }
}


<?php

/**
 * LICENSE: The MIT License (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://github.com/azure/azure-storage-php/LICENSE
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * PHP version 5
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @link      https://github.com/azure/azure-storage-php
 */

namespace MicrosoftAzure\Storage\Tests\Unit\Table\internal;
use MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter;
use MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter;
use MicrosoftAzure\Storage\Tests\Framework\TableServiceRestProxyTestBase;
use MicrosoftAzure\Storage\Common\Internal\Utilities;
use MicrosoftAzure\Storage\Common\ServiceException;
use MicrosoftAzure\Storage\Tests\Framework\TestResources;
use MicrosoftAzure\Storage\Common\Internal\Resources;
use MicrosoftAzure\Storage\Table\TableRestProxy;
use MicrosoftAzure\Storage\Common\Models\ServiceProperties;
use MicrosoftAzure\Storage\Table\Models\QueryTablesOptions;
use MicrosoftAzure\Storage\Table\Models\Query;
use MicrosoftAzure\Storage\Table\Models\Filters\Filter;
use MicrosoftAzure\Storage\Table\Models\Entity;
use MicrosoftAzure\Storage\Table\Models\EdmType;
use MicrosoftAzure\Storage\Table\Models\QueryEntitiesOptions;
use MicrosoftAzure\Storage\Table\Models\BatchOperations;
use MicrosoftAzure\Storage\Common\Internal\Serialization\XmlSerializer;

/**
 * Unit tests for class TableRestProxy
 *
 * @category  Microsoft
 * @package   MicrosoftAzure\Storage\Tests\Unit\Table\internal
 * @author    Azure Storage PHP SDK <dmsh@microsoft.com>
 * @copyright 2016 Microsoft Corporation
 * @license   https://github.com/azure/azure-storage-php/LICENSE
 * @version   Release: 0.10.2
 * @link      https://github.com/azure/azure-storage-php
 */
class TableRestProxyTest extends TableServiceRestProxyTestBase
{
    /**
     * @covers  MicrosoftAzure\Storage\Table\TableRestProxy::__construct
     */
    public function test__construct()
    {
        // Setup
        $atomSerializer = new AtomReaderWriter();
        $mimeSerializer = new MimeReaderWriter();
        $url = 'http://www.microsoft.com';
        
        // Test
        $tableRestProxy = new TableRestProxy($url, $atomSerializer, $mimeSerializer, null);
        
        // Assert
        $this->assertNotNull($tableRestProxy);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getServiceProperties
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testGetServiceProperties()
    {
        $this->skipIfEmulated();
        
        // Test
        $result = $this->restProxy->getServiceProperties();
        
        // Assert
        $this->assertEquals($this->defaultProperties->toArray(), $result->getValue()->toArray());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testSetServiceProperties()
    {
        $this->skipIfEmulated();
        
        // Setup
        $expected = ServiceProperties::create(TestResources::setServicePropertiesSample());
        
        // Test
        $this->setServiceProperties($expected);
        $actual = $this->restProxy->getServiceProperties();
        
        // Assert
        $this->assertEquals($expected->toXml($this->xmlSerializer), $actual->getValue()->toXml($this->xmlSerializer));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::setServiceProperties
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testSetServicePropertiesWithEmptyParts()
    {
        $this->skipIfEmulated();
        
        // Setup
        $xml = TestResources::setServicePropertiesSample();
        $xml['HourMetrics']['RetentionPolicy'] = null;
        $expected = ServiceProperties::create($xml);
        
        // Test
        $this->setServiceProperties($expected);
        $actual = $this->restProxy->getServiceProperties();
        
        // Assert
        $this->assertEquals($expected->toXml($this->xmlSerializer), $actual->getValue()->toXml($this->xmlSerializer));
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::createTable
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getTable
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testCreateTable()
    {
        // Setup
        $name = 'createtable';
        
        // Test
        $this->createTable($name);
        
        // Assert
        $result = $this->restProxy->queryTables();
        $this->assertCount(1, $result->getTables());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getTable
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTable
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Models\GetTableResult::create
     */
    public function testGetTable()
    {
        // Setup
        $name = 'gettable';
        $this->createTable($name);
        
        // Test
        $result = $this->restProxy->getTable($name);
        
        // Assert
        $this->assertEquals($name, $result->getName());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteTable
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testDeleteTable()
    {
        // Setup
        $name = 'deletetable';
        $this->restProxy->createTable($name);
        
        // Test
        $this->restProxy->deleteTable($name);
        
        // Assert
        $result = $this->restProxy->queryTables();
        $this->assertCount(0, $result->getTables());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpression
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpressionRec
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeQueryValue
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::create
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTableEntries
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryTablesSimple()
    {
        // Setup
        $name1 = 'querytablessimple1';
        $name2 = 'querytablessimple2';
        $this->createTable($name1);
        $this->createTable($name2);
        
        // Test
        $result = $this->restProxy->queryTables();
        
        // Assert
        $tables = $result->getTables();
        $this->assertCount(2, $tables);
        $this->assertEquals($name1, $tables[0]);
        $this->assertEquals($name2, $tables[1]);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpression
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpressionRec
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeQueryValue
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::create
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTableEntries
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryTablesOneTable()
    {
        // Setup
        $name1 = 'querytablesonetable';
        $this->createTable($name1);
        
        // Test
        $result = $this->restProxy->queryTables();
        
        // Assert
        $tables = $result->getTables();
        $this->assertCount(1, $tables);
        $this->assertEquals($name1, $tables[0]);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpression
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpressionRec
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeQueryValue
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::create
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTableEntries
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryTablesEmpty()
    {
        // Test
        $result = $this->restProxy->queryTables();
        
        // Assert
        $tables = $result->getTables();
        $this->assertCount(0, $tables);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpression
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpressionRec
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeQueryValue
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::create
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTableEntries
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryTablesWithPrefix()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name1 = 'wquerytableswithprefix1';
        $name2 = 'querytableswithprefix2';
        $name3 = 'querytableswithprefix3';
        $options = new QueryTablesOptions();
        $options->setPrefix('q');
        $this->createTable($name1);
        $this->createTable($name2);
        $this->createTable($name3);
        
        // Test
        $result = $this->restProxy->queryTables($options);
        
        // Assert
        $tables = $result->getTables();
        $this->assertCount(2, $tables);
        $this->assertEquals($name2, $tables[0]);
        $this->assertEquals($name3, $tables[1]);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpression
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpressionRec
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeQueryValue
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::create
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTableEntries
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryTablesWithStringOption()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name1 = 'wquerytableswithstringoption1';
        $name2 = 'querytableswithstringoption2';
        $name3 = 'querytableswithstringoption3';
        $prefix = 'q';
        $this->createTable($name1);
        $this->createTable($name2);
        $this->createTable($name3);
        
        // Test
        $result = $this->restProxy->queryTables($prefix);
        
        // Assert
        $tables = $result->getTables();
        $this->assertCount(2, $tables);
        $this->assertEquals($name2, $tables[0]);
        $this->assertEquals($name3, $tables[1]);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryTables
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpression
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_buildFilterExpressionRec
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeQueryValue
     * @covers MicrosoftAzure\Storage\Table\Models\QueryTablesResult::create
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseTableEntries
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryTablesWithFilterOption()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name1 = 'wquerytableswithfilteroption1';
        $name2 = 'querytableswithfilteroption2';
        $name3 = 'querytableswithfilteroption3';
        $prefix = 'q';
        $prefixFilter = Filter::applyAnd(
                Filter::applyGe(
                    Filter::applyPropertyName('TableName'),
                    Filter::applyConstant($prefix, EdmType::STRING)
                ),
                Filter::applyLe(
                    Filter::applyPropertyName('TableName'),
                    Filter::applyConstant($prefix . '{', EdmType::STRING)
                )
            );
        $this->createTable($name1);
        $this->createTable($name2);
        $this->createTable($name3);
        
        // Test
        $result = $this->restProxy->queryTables($prefixFilter);
        
        // Assert
        $tables = $result->getTables();
        $this->assertCount(2, $tables);
        $this->assertEquals($name2, $tables[0]);
        $this->assertEquals($name3, $tables[1]);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructInsertEntityContext
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getEntity
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntity
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Table\Models\InsertEntityResult::create
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testInsertEntity()
    {
        // Setup
        $name = 'insertentity';
        $this->createTable($name);
        $expected = TestResources::getTestEntity('123', '456');
        
        // Test
        $result = $this->restProxy->insertEntity($name, $expected);
        
        // Assert
        $actual = $result->getEntity();
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        // Add extra count for the properties because the Timestamp property
        $this->assertCount(count($expected->getProperties()) + 1, $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntities
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryEntitiesWithEmpty()
    {
        // Setup
        $name = 'queryentitieswithempty';
        $this->createTable($name);
        
        // Test
        $result = $this->restProxy->queryEntities($name);
        
        // Assert
        $entities = $result->getEntities();
        $this->assertCount(0, $entities);
        
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntities
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryEntitiesWithOneEntity()
    {
        // Setup
        $name = 'queryentitieswithoneentity';
        $pk1 = '123';
        $e1 = TestResources::getTestEntity($pk1, '1');
        $this->createTable($name);
        $this->restProxy->insertEntity($name, $e1);
        
        // Test
        $result = $this->restProxy->queryEntities($name);
        
        // Assert
        $entities = $result->getEntities();
        $this->assertCount(1, $entities);
        
        $actualEntity = $entities[0];
        $this->assertEquals($pk1, $actualEntity->getPartitionKey());
        $this->assertEquals(EdmType::STRING, $entities[0]->getProperty("CustomerName")->getEdmType());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntities
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryEntitiesQueryStringOption()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name = 'queryentitieswithquerystringoption';
        $pk1 = '123';
        $pk2 = '124';
        $pk3 = '125';
        $e1 = TestResources::getTestEntity($pk1, '1');
        $e2 = TestResources::getTestEntity($pk2, '2');
        $e3 = TestResources::getTestEntity($pk3, '3');
        $this->createTable($name);
        $this->restProxy->insertEntity($name, $e1);
        $this->restProxy->insertEntity($name, $e2);
        $this->restProxy->insertEntity($name, $e3);
        $queryString = "PartitionKey eq '123'";
        
        // Test
        $result = $this->restProxy->queryEntities($name, $queryString);
        
        // Assert
        $entities = $result->getEntities();
        $this->assertCount(1, $entities);
        $this->assertEquals($pk1, $entities[0]->getPartitionKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntities
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryEntitiesFilterOption()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name = 'queryentitieswithfilteroption';
        $pk1 = '123';
        $pk2 = '124';
        $pk3 = '125';
        $e1 = TestResources::getTestEntity($pk1, '1');
        $e2 = TestResources::getTestEntity($pk2, '2');
        $e3 = TestResources::getTestEntity($pk3, '3');
        $this->createTable($name);
        $this->restProxy->insertEntity($name, $e1);
        $this->restProxy->insertEntity($name, $e2);
        $this->restProxy->insertEntity($name, $e3);
        $queryString = "PartitionKey eq '123'";
        $filter = Filter::applyQueryString($queryString);
        
        // Test
        $result = $this->restProxy->queryEntities($name, $filter);
        
        // Assert
        $entities = $result->getEntities();
        $this->assertCount(1, $entities);
        $this->assertEquals($pk1, $entities[0]->getPartitionKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntities
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryEntitiesWithMultipleEntities()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name = 'queryentitieswithmultipleentities';
        $pk1 = '123';
        $pk2 = '124';
        $pk3 = '125';
        // This value is hard coded in TestResources::getTestEntity
        $expected = 890;
        $field = 'CustomerId';
        $e1 = TestResources::getTestEntity($pk1, '1');
        $e2 = TestResources::getTestEntity($pk2, '2');
        $e3 = TestResources::getTestEntity($pk3, '3');
        $this->createTable($name);
        $this->restProxy->insertEntity($name, $e1);
        $this->restProxy->insertEntity($name, $e2);
        $this->restProxy->insertEntity($name, $e3);
        $query = new Query();
        $query->addSelectField('CustomerId');
        $options = new QueryEntitiesOptions();
        $options->setQuery($query);
        
        // Test
        $result = $this->restProxy->queryEntities($name, $options);
        
        // Assert
        $entities = $result->getEntities();
        $this->assertCount(3, $entities);
        $this->assertEquals($expected, $entities[0]->getProperty($field)->getValue());
        $this->assertEquals($expected, $entities[1]->getProperty($field)->getValue());
        $this->assertEquals($expected, $entities[2]->getProperty($field)->getValue());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::queryEntities
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_addOptionalQuery
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValues
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_encodeODataUriValue
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntities
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testQueryEntitiesWithGetTop()
    {
        // Setup
        $name = 'queryentitieswithgettop';
        $pk1 = '123';
        $pk2 = '124';
        $pk3 = '125';
        $e1 = TestResources::getTestEntity($pk1, '1');
        $e2 = TestResources::getTestEntity($pk2, '2');
        $e3 = TestResources::getTestEntity($pk3, '3');
        $this->createTable($name);
        $this->restProxy->insertEntity($name, $e1);
        $this->restProxy->insertEntity($name, $e2);
        $this->restProxy->insertEntity($name, $e3);
        $query = new Query();
        $query->setTop(1);
        $options = new QueryEntitiesOptions();
        $options->setQuery($query);
        
        // Test
        $result = $this->restProxy->queryEntities($name, $options);
        
        // Assert
        $entities = $result->getEntities();
        $this->assertCount(1, $entities);
        $this->assertEquals($pk1, $entities[0]->getPartitionKey());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::updateEntity
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getEntityPath
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_putOrMergeEntityImpl
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getEntity
     * @covers MicrosoftAzure\Storage\Table\Models\UpdateEntityResult::create
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testUpdateEntity()
    {
        // Setup
        $name = 'updateentity';
        $this->createTable($name);
        $expected = TestResources::getTestEntity('123', '456');
        $this->restProxy->insertEntity($name, $expected);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $expected = $entities[0];
        $expected->addProperty('CustomerPlace', EdmType::STRING, 'Redmond');
        
        // Test
        $result = $this->restProxy->UpdateEntity($name, $expected);
        
        // Assert
        $this->assertNotNull($result);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $actual = $entities[0];
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        $this->assertCount(count($expected->getProperties()), $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertEntity
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructInsertEntityContext
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getEntity
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntity
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Table\Models\InsertEntityResult::create
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testUpdateEntityWithDeleteProperty()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name = 'updateentitywithdeleteproperty';
        $this->createTable($name);
        $expected = TestResources::getTestEntity('123', '456');
        $this->restProxy->insertEntity($name, $expected);
        $expected->setPropertyValue('CustomerId', null);
        
        // Test
        $result = $this->restProxy->updateEntity($name, $expected);
        
        // Assert
        $this->assertNotNull($result);
        $actual = $this->restProxy->getEntity($name, $expected->getPartitionKey(), $expected->getRowKey());
        $this->assertEquals($expected->getPartitionKey(), $actual->getEntity()->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getEntity()->getRowKey());
        // Add +1 to the count to include Timestamp property.
        $this->assertCount(count($expected->getProperties()), $actual->getEntity()->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::mergeEntity
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getEntityPath
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_putOrMergeEntityImpl
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Models\EdmType::serializeValue
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getEntity
     * @covers MicrosoftAzure\Storage\Table\Models\UpdateEntityResult::create
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testMergeEntity()
    {
        // Setup
        $name = 'mergeentity';
        $this->createTable($name);
        $expected = TestResources::getTestEntity('123', '456');
        $this->restProxy->insertEntity($name, $expected);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $expected = $entities[0];
        $expected->addProperty('CustomerPhone', EdmType::STRING, '99999999');
        
        // Test
        $result = $this->restProxy->mergeEntity($name, $expected);
        
        // Assert
        $this->assertNotNull($result);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $actual = $entities[0];
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        $this->assertCount(count($expected->getProperties()), $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::insertOrReplaceEntity
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getEntityPath
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_putOrMergeEntityImpl
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getEntity
     * @covers MicrosoftAzure\Storage\Table\Models\UpdateEntityResult::create
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testInsertOrReplaceEntity()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name = 'insertorreplaceentity';
        $this->createTable($name);
        $expected = TestResources::getTestEntity('123', '456');
        $this->restProxy->insertEntity($name, $expected);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $expected = $entities[0];
        $expected->addProperty('CustomerPlace', EdmType::STRING, 'Redmond');
        
        // Test
        $result = $this->restProxy->InsertOrReplaceEntity($name, $expected);
        
        // Assert
        $this->assertNotNull($result);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $actual = $entities[0];
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        $this->assertCount(count($expected->getProperties()), $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::InsertOrMergeEntity
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getEntityPath
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_putOrMergeEntityImpl
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::getEntity
     * @covers MicrosoftAzure\Storage\Table\Models\UpdateEntityResult::create
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testInsertOrMergeEntity()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name = 'insertormergeentity';
        $this->createTable($name);
        $expected = TestResources::getTestEntity('123', '456');
        $this->restProxy->insertEntity($name, $expected);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $expected = $entities[0];
        $expected->addProperty('CustomerPhone', EdmType::STRING, '99999999');
        
        // Test
        $result = $this->restProxy->InsertOrMergeEntity($name, $expected);
        
        // Assert
        $this->assertNotNull($result);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $actual = $entities[0];
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        $this->assertCount(count($expected->getProperties()), $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getEntityPath
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructDeleteEntityContext
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testDeleteEntity()
    {
        // Setup
        $name = 'deleteentity';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = '456';
        $entity = TestResources::getTestEntity($partitionKey, $rowKey);
        $result = $this->restProxy->insertEntity($name, $entity);
        
        // Test
        $this->restProxy->deleteEntity($name, $partitionKey, $rowKey);
        
        // Assert
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $this->assertCount(0, $entities);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::deleteEntity
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getEntityPath
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructDeleteEntityContext
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testDeleteEntityWithSpecialChars()
    {
        // Setup
        $name = 'deleteentitywithspecialchars';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = 'key with spaces';
        $entity = TestResources::getTestEntity($partitionKey, $rowKey);
        $result = $this->restProxy->insertEntity($name, $entity);
        
        // Test
        $this->restProxy->deleteEntity($name, $partitionKey, $rowKey);
        
        // Assert
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $this->assertCount(0, $entities);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::getEntity
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseBody
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::parseEntity
     * @covers MicrosoftAzure\Storage\Table\Internal\AtomReaderWriter::_parseOneEntity
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testGetEntity()
    {
        // Setup
        $name = 'getentity';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = '456';
        $expected = TestResources::getTestEntity($partitionKey, $rowKey);
        $this->restProxy->insertEntity($name, $expected);
        
        // Test
        $result = $this->restProxy->getEntity($name, $partitionKey, $rowKey);
        
        // Assert
        $actual = $result->getEntity();
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        // Increase thec properties count to incloude the Timestamp property.
        $this->assertCount(count($expected->getProperties()) + 1, $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createBatchRequestBody
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getOperationContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructInsertEntityContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createOperationsContexts
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::encodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::decodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_constructResponses
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_compareUsingContentId
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testBatchWithInsert()
    {
        // Setup
        $name = 'batchwithinsert';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = '456';
        $expected = TestResources::getTestEntity($partitionKey, $rowKey);
        $operations = new BatchOperations();
        $operations->addInsertEntity($name, $expected);
        
        // Test
        $result = $this->restProxy->batch($operations);
        
        // Assert
        $entries = $result->getEntries();
        $actual = $entries[0]->getEntity();
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        // Increase the properties count to include Timestamp property.
        $this->assertCount(count($expected->getProperties()) + 1, $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createBatchRequestBody
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getOperationContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createOperationsContexts
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructDeleteEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::encodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::decodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_constructResponses
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_compareUsingContentId
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testBatchWithDelete()
    {
        // Setup
        $name = 'batchwithdelete';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = '456';
        $expected = TestResources::getTestEntity($partitionKey, $rowKey);
        $this->restProxy->insertEntity($name, $expected);
        $operations = new BatchOperations();
        $operations->addDeleteEntity($name, $partitionKey, $rowKey);
        
        // Test
        $this->restProxy->batch($operations);
        
        // Assert
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $this->assertCount(0, $entities);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createBatchRequestBody
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getOperationContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createOperationsContexts
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::encodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::decodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_constructResponses
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_compareUsingContentId
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testBatchWithUpdate()
    {
        // Setup
        $name = 'batchwithupdate';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = '456';
        $expected = TestResources::getTestEntity($partitionKey, $rowKey);
        $this->restProxy->insertEntity($name, $expected);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $expected = $entities[0];
        $expected->addProperty('CustomerPlace', EdmType::STRING, 'Redmond');
        $operations = new BatchOperations();
        $operations->addUpdateEntity($name, $expected);
        
        // Test
        $result = $this->restProxy->batch($operations);
        
        // Assert
        $entries = $result->getEntries();
        $this->assertNotNull($entries[0]->getETag());
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $actual = $entities[0];
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        $this->assertCount(count($expected->getProperties()), $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createBatchRequestBody
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getOperationContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createOperationsContexts
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::encodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::decodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_constructResponses
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_compareUsingContentId
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testBatchWithMerge()
    {
        // Setup
        $name = 'batchwithmerge';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = '456';
        $expected = TestResources::getTestEntity($partitionKey, $rowKey);
        $this->restProxy->insertEntity($name, $expected);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $expected = $entities[0];
        $expected->addProperty('CustomerPlace', EdmType::STRING, 'Redmond');
        $operations = new BatchOperations();
        $operations->addMergeEntity($name, $expected);
        
        // Test
        $result = $this->restProxy->batch($operations);
        
        // Assert
        $entries = $result->getEntries();
        $this->assertNotNull($entries[0]->getETag());
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $actual = $entities[0];
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        $this->assertCount(count($expected->getProperties()), $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createBatchRequestBody
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getOperationContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createOperationsContexts
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::encodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::decodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_constructResponses
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_compareUsingContentId
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::send
     */
    public function testBatchWithInsertOrReplace()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name = 'batchwithinsertorreplace';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = '456';
        $expected = TestResources::getTestEntity($partitionKey, $rowKey);
        $this->restProxy->insertEntity($name, $expected);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $expected = $entities[0];
        $expected->addProperty('CustomerPlace', EdmType::STRING, 'Redmond');
        $operations = new BatchOperations();
        $operations->addInsertOrReplaceEntity($name, $expected);
        
        // Test
        $result = $this->restProxy->batch($operations);
        
        // Assert
        $entries = $result->getEntries();
        $this->assertNotNull($entries[0]->getETag());
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $actual = $entities[0];
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        $this->assertCount(count($expected->getProperties()), $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createBatchRequestBody
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getOperationContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createOperationsContexts
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::encodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::decodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_constructResponses
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_compareUsingContentId
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testBatchWithInsertOrMerge()
    {
        $this->skipIfEmulated();
        
        // Setup
        $name = 'batchwithinsertormerge';
        $this->createTable($name);
        $partitionKey = '123';
        $rowKey = '456';
        $expected = TestResources::getTestEntity($partitionKey, $rowKey);
        $this->restProxy->insertEntity($name, $expected);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $expected = $entities[0];
        $expected->addProperty('CustomerPlace', EdmType::STRING, 'Redmond');
        $operations = new BatchOperations();
        $operations->addInsertOrMergeEntity($name, $expected);
        
        // Test
        $result = $this->restProxy->batch($operations);
        
        // Assert
        $entries = $result->getEntries();
        $this->assertNotNull($entries[0]->getETag());
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $actual = $entities[0];
        $this->assertEquals($expected->getPartitionKey(), $actual->getPartitionKey());
        $this->assertEquals($expected->getRowKey(), $actual->getRowKey());
        $this->assertCount(count($expected->getProperties()), $actual->getProperties());
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createBatchRequestBody
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getOperationContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createOperationsContexts
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::encodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::decodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_constructResponses
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_compareUsingContentId
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testBatchWithMultipleOperations()
    {
        // Setup
        $name = 'batchwithwithmultipleoperations';
        $this->createTable($name);
        $partitionKey = '123';
        $rk1 = '456';
        $rk2 = '457';
        $rk3 = '458';
        $delete = TestResources::getTestEntity($partitionKey, $rk1);
        $insert = TestResources::getTestEntity($partitionKey, $rk2);
        $update = TestResources::getTestEntity($partitionKey, $rk3);
        $this->restProxy->insertEntity($name, $delete);
        $this->restProxy->insertEntity($name, $update);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $delete = $entities[0];
        $update = $entities[1];
        $update->addProperty('CustomerPlace', EdmType::STRING, 'Redmond');
        $operations = new BatchOperations();
        $operations->addInsertEntity($name, $insert);
        $operations->addUpdateEntity($name, $update);
        $operations->addDeleteEntity($name, $delete->getPartitionKey(), $delete->getRowKey(), $delete->getETag());
        
        // Test
        $result = $this->restProxy->batch($operations);
        
        // Assert
        $this->assertTrue(true);
    }
    
    /**
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::batch
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createBatchRequestBody
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_getOperationContext
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_createOperationsContexts
     * @covers MicrosoftAzure\Storage\Table\TableRestProxy::_constructPutOrMergeEntityContext
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::encodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Internal\MimeReaderWriter::decodeMimeMultipart
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::create
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_constructResponses
     * @covers MicrosoftAzure\Storage\Table\Models\BatchResult::_compareUsingContentId
     * @covers MicrosoftAzure\Storage\Common\Internal\ServiceRestProxy::sendContext
     */
    public function testBatchWithDifferentPKFail()
    {
        // Setup
        $name = 'batchwithwithdifferentpkfail';
        $this->createTable($name);
        $partitionKey = '123';
        $rk1 = '456';
        $rk3 = '458';
        $delete = TestResources::getTestEntity($partitionKey, $rk1);
        $update = TestResources::getTestEntity($partitionKey, $rk3);
        $this->restProxy->insertEntity($name, $delete);
        $this->restProxy->insertEntity($name, $update);
        $result = $this->restProxy->queryEntities($name);
        $entities = $result->getEntities();
        $delete = $entities[0];
        $update = $entities[1];
        $update->addProperty('CustomerPlace', EdmType::STRING, 'Redmond');
        $operations = new BatchOperations();
        $operations->addUpdateEntity($name, $update);
        $operations->addDeleteEntity($name, '125', $delete->getRowKey(), $delete->getETag());
        
        // Test
        $result = $this->restProxy->batch($operations);
        
        // Assert
        $this->assertTrue(true);
    }
}


<?php

$header = <<<EOF
This file is part of the Monolog package.

(c) Jordi Boggiano <j.boggiano@seld.be>

For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
EOF;

$finder = Symfony\CS\Finder::create()
    ->files()
    ->name('*.php')
    ->exclude('Fixtures')
    ->in(__DIR__.'/src')
    ->in(__DIR__.'/tests')
;

return Symfony\CS\Config::create()
    ->setUsingCache(true)
    //->setUsingLinter(false)
    ->setRiskyAllowed(true)
    ->setRules(array(
        '@PSR2' => true,
        'binary_operator_spaces' => true,
        'blank_line_before_return' => true,
        'header_comment' => array('header' => $header),
        'include' => true,
        'long_array_syntax' => true,
        'method_separation' => true,
        'no_blank_lines_after_class_opening' => true,
        'no_blank_lines_after_phpdoc' => true,
        'no_blank_lines_between_uses' => true,
        'no_duplicate_semicolons' => true,
        'no_extra_consecutive_blank_lines' => true,
        'no_leading_import_slash' => true,
        'no_leading_namespace_whitespace' => true,
        'no_trailing_comma_in_singleline_array' => true,
        'no_unused_imports' => true,
        'object_operator_without_whitespace' => true,
        'phpdoc_align' => true,
        'phpdoc_indent' => true,
        'phpdoc_no_access' => true,
        'phpdoc_no_package' => true,
        'phpdoc_order' => true,
        'phpdoc_scalar' => true,
        'phpdoc_trim' => true,
        'phpdoc_type_to_var' => true,
        'psr0' => true,
        'single_blank_line_before_namespace' => true,
        'spaces_cast' => true,
        'standardize_not_equals' => true,
        'ternary_operator_spaces' => true,
        'trailing_comma_in_multiline_array' => true,
        'whitespacy_lines' => true,
    ))
    ->finder($finder)
;
### 1.22.0 (2016-11-26)

  * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily
  * Added MercurialProcessor to add mercurial revision and branch names to log records
  * Added support for AWS SDK v3 in DynamoDbHandler
  * Fixed fatal errors occuring when normalizing generators that have been fully consumed
  * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix)
  * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore
  * Fixed SyslogUdpHandler to avoid sending empty frames
  * Fixed a few PHP 7.0 and 7.1 compatibility issues

### 1.21.0 (2016-07-29)

  * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues
  * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order
  * Added ability to format the main line of text the SlackHandler sends by explictly setting a formatter on the handler
  * Added information about SoapFault instances in NormalizerFormatter
  * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level

### 1.20.0 (2016-07-02)

  * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy
  * Added StreamHandler::getUrl to retrieve the stream's URL
  * Added ability to override addRow/addTitle in HtmlFormatter
  * Added the $context to context information when the ErrorHandler handles a regular php error
  * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d
  * Fixed WhatFailureGroupHandler to work with PHP7 throwables
  * Fixed a few minor bugs

### 1.19.0 (2016-04-12)

  * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed
  * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors
  * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler
  * Fixed HipChatHandler handling of long messages

### 1.18.2 (2016-04-02)

  * Fixed ElasticaFormatter to use more precise dates
  * Fixed GelfMessageFormatter sending too long messages

### 1.18.1 (2016-03-13)

  * Fixed SlackHandler bug where slack dropped messages randomly
  * Fixed RedisHandler issue when using with the PHPRedis extension
  * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension
  * Fixed BrowserConsoleHandler regression

### 1.18.0 (2016-03-01)

  * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond
  * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames
  * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name
  * Added FluentdFormatter for the Fluentd unix socket protocol
  * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed
  * Added support for replacing context sub-keys using `%context.*%` in LineFormatter
  * Added support for `payload` context value in RollbarHandler
  * Added setRelease to RavenHandler to describe the application version, sent with every log
  * Added support for `fingerprint` context value in RavenHandler
  * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed
  * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()`
  * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places

### 1.17.2 (2015-10-14)

  * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers
  * Fixed SlackHandler handling to use slack functionalities better
  * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id
  * Fixed 5.3 compatibility regression

### 1.17.1 (2015-08-31)

  * Fixed RollbarHandler triggering PHP notices

### 1.17.0 (2015-08-30)

  * Added support for `checksum` and `release` context/extra values in RavenHandler
  * Added better support for exceptions in RollbarHandler
  * Added UidProcessor::getUid
  * Added support for showing the resource type in NormalizedFormatter
  * Fixed IntrospectionProcessor triggering PHP notices

### 1.16.0 (2015-08-09)

  * Added IFTTTHandler to notify ifttt.com triggers
  * Added Logger::setHandlers() to allow setting/replacing all handlers
  * Added $capSize in RedisHandler to cap the log size
  * Fixed StreamHandler creation of directory to only trigger when the first log write happens
  * Fixed bug in the handling of curl failures
  * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler
  * Fixed missing fatal errors records with handlers that need to be closed to flush log records
  * Fixed TagProcessor::addTags support for associative arrays

### 1.15.0 (2015-07-12)

  * Added addTags and setTags methods to change a TagProcessor
  * Added automatic creation of directories if they are missing for a StreamHandler to open a log file
  * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure
  * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used
  * Fixed HTML/JS escaping in BrowserConsoleHandler
  * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only)

### 1.14.0 (2015-06-19)

  * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library
  * Added support for objects implementing __toString in the NormalizerFormatter
  * Added support for HipChat's v2 API in HipChatHandler
  * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app
  * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true)
  * Fixed curl errors being silently suppressed

### 1.13.1 (2015-03-09)

  * Fixed regression in HipChat requiring a new token to be created

### 1.13.0 (2015-03-05)

  * Added Registry::hasLogger to check for the presence of a logger instance
  * Added context.user support to RavenHandler
  * Added HipChat API v2 support in the HipChatHandler
  * Added NativeMailerHandler::addParameter to pass params to the mail() process
  * Added context data to SlackHandler when $includeContextAndExtra is true
  * Added ability to customize the Swift_Message per-email in SwiftMailerHandler
  * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided
  * Fixed serialization of INF and NaN values in Normalizer and LineFormatter

### 1.12.0 (2014-12-29)

  * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers.
  * Added PsrHandler to forward records to another PSR-3 logger
  * Added SamplingHandler to wrap around a handler and include only every Nth record
  * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now)
  * Added exception codes in the output of most formatters
  * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line)
  * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data
  * Added $host to HipChatHandler for users of private instances
  * Added $transactionName to NewRelicHandler and support for a transaction_name context value
  * Fixed MandrillHandler to avoid outputing API call responses
  * Fixed some non-standard behaviors in SyslogUdpHandler

### 1.11.0 (2014-09-30)

  * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names
  * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails
  * Added MandrillHandler to send emails via the Mandrillapp.com API
  * Added SlackHandler to log records to a Slack.com account
  * Added FleepHookHandler to log records to a Fleep.io account
  * Added LogglyHandler::addTag to allow adding tags to an existing handler
  * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end
  * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing
  * Added support for PhpAmqpLib in the AmqpHandler
  * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs
  * Added support for adding extra fields from $_SERVER in the WebProcessor
  * Fixed support for non-string values in PrsLogMessageProcessor
  * Fixed SwiftMailer messages being sent with the wrong date in long running scripts
  * Fixed minor PHP 5.6 compatibility issues
  * Fixed BufferHandler::close being called twice

### 1.10.0 (2014-06-04)

  * Added Logger::getHandlers() and Logger::getProcessors() methods
  * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached
  * Added support for extra data in NewRelicHandler
  * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines

### 1.9.1 (2014-04-24)

  * Fixed regression in RotatingFileHandler file permissions
  * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records
  * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative

### 1.9.0 (2014-04-20)

  * Added LogEntriesHandler to send logs to a LogEntries account
  * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler
  * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes
  * Added support for table formatting in FirePHPHandler via the table context key
  * Added a TagProcessor to add tags to records, and support for tags in RavenHandler
  * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files
  * Added sound support to the PushoverHandler
  * Fixed multi-threading support in StreamHandler
  * Fixed empty headers issue when ChromePHPHandler received no records
  * Fixed default format of the ErrorLogHandler

### 1.8.0 (2014-03-23)

  * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them
  * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output
  * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler
  * Added FlowdockHandler to send logs to a Flowdock account
  * Added RollbarHandler to send logs to a Rollbar account
  * Added HtmlFormatter to send prettier log emails with colors for each log level
  * Added GitProcessor to add the current branch/commit to extra record data
  * Added a Monolog\Registry class to allow easier global access to pre-configured loggers
  * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement
  * Added support for HHVM
  * Added support for Loggly batch uploads
  * Added support for tweaking the content type and encoding in NativeMailerHandler
  * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor
  * Fixed batch request support in GelfHandler

### 1.7.0 (2013-11-14)

  * Added ElasticSearchHandler to send logs to an Elastic Search server
  * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB
  * Added SyslogUdpHandler to send logs to a remote syslogd server
  * Added LogglyHandler to send logs to a Loggly account
  * Added $level to IntrospectionProcessor so it only adds backtraces when needed
  * Added $version to LogstashFormatter to allow using the new v1 Logstash format
  * Added $appName to NewRelicHandler
  * Added configuration of Pushover notification retries/expiry
  * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default
  * Added chainability to most setters for all handlers
  * Fixed RavenHandler batch processing so it takes the message from the record with highest priority
  * Fixed HipChatHandler batch processing so it sends all messages at once
  * Fixed issues with eAccelerator
  * Fixed and improved many small things

### 1.6.0 (2013-07-29)

  * Added HipChatHandler to send logs to a HipChat chat room
  * Added ErrorLogHandler to send logs to PHP's error_log function
  * Added NewRelicHandler to send logs to NewRelic's service
  * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler
  * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel
  * Added stack traces output when normalizing exceptions (json output & co)
  * Added Monolog\Logger::API constant (currently 1)
  * Added support for ChromePHP's v4.0 extension
  * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel
  * Added support for sending messages to multiple users at once with the PushoverHandler
  * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler)
  * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now
  * Fixed issue in RotatingFileHandler when an open_basedir restriction is active
  * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0
  * Fixed SyslogHandler issue when many were used concurrently with different facilities

### 1.5.0 (2013-04-23)

  * Added ProcessIdProcessor to inject the PID in log records
  * Added UidProcessor to inject a unique identifier to all log records of one request/run
  * Added support for previous exceptions in the LineFormatter exception serialization
  * Added Monolog\Logger::getLevels() to get all available levels
  * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle

### 1.4.1 (2013-04-01)

  * Fixed exception formatting in the LineFormatter to be more minimalistic
  * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0
  * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days
  * Fixed WebProcessor array access so it checks for data presence
  * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors

### 1.4.0 (2013-02-13)

  * Added RedisHandler to log to Redis via the Predis library or the phpredis extension
  * Added ZendMonitorHandler to log to the Zend Server monitor
  * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor
  * Added `$useSSL` option to the PushoverHandler which is enabled by default
  * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously
  * Fixed header injection capability in the NativeMailHandler

### 1.3.1 (2013-01-11)

  * Fixed LogstashFormatter to be usable with stream handlers
  * Fixed GelfMessageFormatter levels on Windows

### 1.3.0 (2013-01-08)

  * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface`
  * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance
  * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash)
  * Added PushoverHandler to send mobile notifications
  * Added CouchDBHandler and DoctrineCouchDBHandler
  * Added RavenHandler to send data to Sentry servers
  * Added support for the new MongoClient class in MongoDBHandler
  * Added microsecond precision to log records' timestamps
  * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing
    the oldest entries
  * Fixed normalization of objects with cyclic references

### 1.2.1 (2012-08-29)

  * Added new $logopts arg to SyslogHandler to provide custom openlog options
  * Fixed fatal error in SyslogHandler

### 1.2.0 (2012-08-18)

  * Added AmqpHandler (for use with AMQP servers)
  * Added CubeHandler
  * Added NativeMailerHandler::addHeader() to send custom headers in mails
  * Added the possibility to specify more than one recipient in NativeMailerHandler
  * Added the possibility to specify float timeouts in SocketHandler
  * Added NOTICE and EMERGENCY levels to conform with RFC 5424
  * Fixed the log records to use the php default timezone instead of UTC
  * Fixed BufferHandler not being flushed properly on PHP fatal errors
  * Fixed normalization of exotic resource types
  * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog

### 1.1.0 (2012-04-23)

  * Added Monolog\Logger::isHandling() to check if a handler will
    handle the given log level
  * Added ChromePHPHandler
  * Added MongoDBHandler
  * Added GelfHandler (for use with Graylog2 servers)
  * Added SocketHandler (for use with syslog-ng for example)
  * Added NormalizerFormatter
  * Added the possibility to change the activation strategy of the FingersCrossedHandler
  * Added possibility to show microseconds in logs
  * Added `server` and `referer` to WebProcessor output

### 1.0.2 (2011-10-24)

  * Fixed bug in IE with large response headers and FirePHPHandler

### 1.0.1 (2011-08-25)

  * Added MemoryPeakUsageProcessor and MemoryUsageProcessor
  * Added Monolog\Logger::getName() to get a logger's channel name

### 1.0.0 (2011-07-06)

  * Added IntrospectionProcessor to get info from where the logger was called
  * Fixed WebProcessor in CLI

### 1.0.0-RC1 (2011-07-01)

  * Initial release
{
    "name": "monolog/monolog",
    "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
    "keywords": ["log", "logging", "psr-3"],
    "homepage": "http://github.com/Seldaek/monolog",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Jordi Boggiano",
            "email": "j.boggiano@seld.be",
            "homepage": "http://seld.be"
        }
    ],
    "require": {
        "php": ">=5.3.0",
        "psr/log": "~1.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~4.5",
        "graylog2/gelf-php": "~1.0",
        "sentry/sentry": "^0.13",
        "ruflin/elastica": ">=0.90 <3.0",
        "doctrine/couchdb": "~1.0@dev",
        "aws/aws-sdk-php": "^2.4.9 || ^3.0",
        "php-amqplib/php-amqplib": "~2.4",
        "swiftmailer/swiftmailer": "~5.3",
        "php-console/php-console": "^3.1.3",
        "phpunit/phpunit-mock-objects": "2.3.0",
        "jakub-onderka/php-parallel-lint": "0.9"
    },
    "_": "phpunit/phpunit-mock-objects required in 2.3.0 due to https://github.com/sebastianbergmann/phpunit-mock-objects/issues/223 - needs hhvm 3.8+ on travis",
    "suggest": {
        "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
        "sentry/sentry": "Allow sending log messages to a Sentry server",
        "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
        "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
        "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
        "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
        "ext-mongo": "Allow sending log messages to a MongoDB server",
        "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
        "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
        "rollbar/rollbar": "Allow sending log messages to Rollbar",
        "php-console/php-console": "Allow sending log messages to Google Chrome"
    },
    "autoload": {
        "psr-4": {"Monolog\\": "src/Monolog"}
    },
    "autoload-dev": {
        "psr-4": {"Monolog\\": "tests/Monolog"}
    },
    "provide": {
        "psr/log-implementation": "1.0.0"
    },
    "extra": {
        "branch-alias": {
            "dev-master": "2.0.x-dev"
        }
    },
    "scripts": {
        "test": [
            "parallel-lint . --exclude vendor",
            "phpunit"
        ]
    }
}
# Using Monolog

- [Installation](#installation)
- [Core Concepts](#core-concepts)
- [Log Levels](#log-levels)
- [Configuring a logger](#configuring-a-logger)
- [Adding extra data in the records](#adding-extra-data-in-the-records)
- [Leveraging channels](#leveraging-channels)
- [Customizing the log format](#customizing-the-log-format)

## Installation

Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog))
and as such installable via [Composer](http://getcomposer.org/).

```bash
composer require monolog/monolog
```

If you do not use Composer, you can grab the code from GitHub, and use any
PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader))
to load Monolog classes.

## Core Concepts

Every `Logger` instance has a channel (name) and a stack of handlers. Whenever
you add a record to the logger, it traverses the handler stack. Each handler
decides whether it fully handled the record, and if so, the propagation of the
record ends there.

This allows for flexible logging setups, for example having a `StreamHandler` at
the bottom of the stack that will log anything to disk, and on top of that add
a `MailHandler` that will send emails only when an error message is logged.
Handlers also have a `$bubble` property which defines whether they block the
record or not if they handled it. In this example, setting the `MailHandler`'s
`$bubble` argument to false means that records handled by the `MailHandler` will
not propagate to the `StreamHandler` anymore.

You can create many `Logger`s, each defining a channel (e.g.: db, request,
router, ..) and each of them combining various handlers, which can be shared
or not. The channel is reflected in the logs and allows you to easily see or
filter records.

Each Handler also has a Formatter, a default one with settings that make sense
will be created if you don't set one. The formatters normalize and format
incoming records so that they can be used by the handlers to output useful
information.

Custom severity levels are not available. Only the eight
[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice,
warning, error, critical, alert, emergency) are present for basic filtering
purposes, but for sorting and other use cases that would require
flexibility, you should add Processors to the Logger that can add extra
information (tags, user ip, ..) to the records before they are handled.

## Log Levels

Monolog supports the logging levels described by [RFC 5424](http://tools.ietf.org/html/rfc5424).

- **DEBUG** (100): Detailed debug information.

- **INFO** (200): Interesting events. Examples: User logs in, SQL logs.

- **NOTICE** (250): Normal but significant events.

- **WARNING** (300): Exceptional occurrences that are not errors. Examples:
  Use of deprecated APIs, poor use of an API, undesirable things that are not
  necessarily wrong.

- **ERROR** (400): Runtime errors that do not require immediate action but
  should typically be logged and monitored.

- **CRITICAL** (500): Critical conditions. Example: Application component
  unavailable, unexpected exception.

- **ALERT** (550): Action must be taken immediately. Example: Entire website
  down, database unavailable, etc. This should trigger the SMS alerts and wake
  you up.

- **EMERGENCY** (600): Emergency: system is unusable.

## Configuring a logger

Here is a basic setup to log to a file and to firephp on the DEBUG level:

```php
<?php

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;

// Create the logger
$logger = new Logger('my_logger');
// Now add some handlers
$logger->pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG));
$logger->pushHandler(new FirePHPHandler());

// You can now use your logger
$logger->addInfo('My logger is now ready');
```

Let's explain it. The first step is to create the logger instance which will
be used in your code. The argument is a channel name, which is useful when
you use several loggers (see below for more details about it).

The logger itself does not know how to handle a record. It delegates it to
some handlers. The code above registers two handlers in the stack to allow
handling records in two different ways.

Note that the FirePHPHandler is called first as it is added on top of the
stack. This allows you to temporarily add a logger with bubbling disabled if
you want to override other configured loggers.

> If you use Monolog standalone and are looking for an easy way to
> configure many handlers, the [theorchard/monolog-cascade](https://github.com/theorchard/monolog-cascade)
> can help you build complex logging configs via PHP arrays, yaml or json configs.

## Adding extra data in the records

Monolog provides two different ways to add extra informations along the simple
textual message.

### Using the logging context

The first way is the context, allowing to pass an array of data along the
record:

```php
<?php

$logger->addInfo('Adding a new user', array('username' => 'Seldaek'));
```

Simple handlers (like the StreamHandler for instance) will simply format
the array to a string but richer handlers can take advantage of the context
(FirePHP is able to display arrays in pretty way for instance).

### Using processors

The second way is to add extra data for all records by using a processor.
Processors can be any callable. They will get the record as parameter and
must return it after having eventually changed the `extra` part of it. Let's
write a processor adding some dummy data in the record:

```php
<?php

$logger->pushProcessor(function ($record) {
    $record['extra']['dummy'] = 'Hello world!';

    return $record;
});
```

Monolog provides some built-in processors that can be used in your project.
Look at the [dedicated chapter](https://github.com/Seldaek/monolog/blob/master/doc/02-handlers-formatters-processors.md#processors) for the list.

> Tip: processors can also be registered on a specific handler instead of
  the logger to apply only for this handler.

## Leveraging channels

Channels are a great way to identify to which part of the application a record
is related. This is useful in big applications (and is leveraged by
MonologBundle in Symfony2).

Picture two loggers sharing a handler that writes to a single log file.
Channels would allow you to identify the logger that issued every record.
You can easily grep through the log files filtering this or that channel.

```php
<?php

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;

// Create some handlers
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$firephp = new FirePHPHandler();

// Create the main logger of the app
$logger = new Logger('my_logger');
$logger->pushHandler($stream);
$logger->pushHandler($firephp);

// Create a logger for the security-related stuff with a different channel
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
$securityLogger->pushHandler($firephp);

// Or clone the first one to only change the channel
$securityLogger = $logger->withName('security');
```

## Customizing the log format

In Monolog it's easy to customize the format of the logs written into files,
sockets, mails, databases and other handlers. Most of the handlers use the

```php
$record['formatted']
```

value to be automatically put into the log device. This value depends on the
formatter settings. You can choose between predefined formatter classes or
write your own (e.g. a multiline text file for human-readable output).

To configure a predefined formatter class, just set it as the handler's field:

```php
// the default date format is "Y-m-d H:i:s"
$dateFormat = "Y n j, g:i a";
// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
$output = "%datetime% > %level_name% > %message% %context% %extra%\n";
// finally, create a formatter
$formatter = new LineFormatter($output, $dateFormat);

// Create a handler
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// bind it to a logger object
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
```

You may also reuse the same formatter between multiple handlers and share those
handlers between multiple loggers.

[Handlers, Formatters and Processors](02-handlers-formatters-processors.md) &rarr;
# Handlers, Formatters and Processors

- [Handlers](#handlers)
  - [Log to files and syslog](#log-to-files-and-syslog)
  - [Send alerts and emails](#send-alerts-and-emails)
  - [Log specific servers and networked logging](#log-specific-servers-and-networked-logging)
  - [Logging in development](#logging-in-development)
  - [Log to databases](#log-to-databases)
  - [Wrappers / Special Handlers](#wrappers--special-handlers)
- [Formatters](#formatters)
- [Processors](#processors)
- [Third Party Packages](#third-party-packages)

## Handlers

### Log to files and syslog

- _StreamHandler_: Logs records into any PHP stream, use this for log files.
- _RotatingFileHandler_: Logs records to a file and creates one logfile per day.
  It will also delete files older than `$maxFiles`. You should use
  [logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile
  setups though, this is just meant as a quick and dirty solution.
- _SyslogHandler_: Logs records to the syslog.
- _ErrorLogHandler_: Logs records to PHP's
  [`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function.

### Send alerts and emails

- _NativeMailerHandler_: Sends emails using PHP's
  [`mail()`](http://php.net/manual/en/function.mail.php) function.
- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance.
- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API.
- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API.
- _FlowdockHandler_: Logs records to a [Flowdock](https://www.flowdock.com/) account.
- _SlackHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slack API.
- _SlackbotHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slackbot incoming hook.
- _SlackWebhookHandler_: Logs records to a [Slack](https://www.slack.com/) account using Slack Webhooks.
- _MandrillHandler_: Sends emails via the Mandrill API using a [`Swift_Message`](http://swiftmailer.org/) instance.
- _FleepHookHandler_: Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks.
- _IFTTTHandler_: Notifies an [IFTTT](https://ifttt.com/maker) trigger with the log channel, level name and message.

### Log specific servers and networked logging

- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this
  for UNIX and TCP sockets. See an [example](sockets.md).
- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible
  server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+).
- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server.
- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server.
- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using
  [raven](https://packagist.org/packages/raven/raven).
- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server.
- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application.
- _LogglyHandler_: Logs records to a [Loggly](http://www.loggly.com/) account.
- _RollbarHandler_: Logs records to a [Rollbar](https://rollbar.com/) account.
- _SyslogUdpHandler_: Logs records to a remote [Syslogd](http://www.rsyslog.com/) server.
- _LogEntriesHandler_: Logs records to a [LogEntries](http://logentries.com/) account.

### Logging in development

- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing
  inline `console` messages within [FireBug](http://getfirebug.com/).
- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing
  inline `console` messages within Chrome.
- _BrowserConsoleHandler_: Handler to send logs to browser's Javascript `console` with
  no browser extension required. Most browsers supporting `console` API are supported.
- _PHPConsoleHandler_: Handler for [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef), providing
  inline `console` and notification popup messages within Chrome.

### Log to databases

- _RedisHandler_: Logs records to a [redis](http://redis.io) server.
- _MongoDBHandler_: Handler to write records in MongoDB via a
  [Mongo](http://pecl.php.net/package/mongo) extension connection.
- _CouchDBHandler_: Logs records to a CouchDB server.
- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM.
- _ElasticSearchHandler_: Logs records to an Elastic Search server.
- _DynamoDbHandler_: Logs records to a DynamoDB table with the [AWS SDK](https://github.com/aws/aws-sdk-php).

### Wrappers / Special Handlers

- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as
  parameter and will accumulate log records of all levels until a record
  exceeds the defined severity level. At which point it delivers all records,
  including those of lower severity, to the handler it wraps. This means that
  until an error actually happens you will not see anything in your logs, but
  when it happens you will have the full information, including debug and info
  records. This provides you with all the information you need, but only when
  you need it.
- _DeduplicationHandler_: Useful if you are sending notifications or emails
  when critical errors occur. It takes a logger as parameter and will
  accumulate log records of all levels until the end of the request (or
  `flush()` is called). At that point it delivers all records to the handler
  it wraps, but only if the records are unique over a given time period
  (60seconds by default). If the records are duplicates they are simply
  discarded. The main use of this is in case of critical failure like if your
  database is unreachable for example all your requests will fail and that
  can result in a lot of notifications being sent. Adding this handler reduces
  the amount of notifications to a manageable level.
- _WhatFailureGroupHandler_: This handler extends the _GroupHandler_ ignoring
   exceptions raised by each child handler. This allows you to ignore issues
   where a remote tcp connection may have died but you do not want your entire
   application to crash and may wish to continue to log to other handlers.
- _BufferHandler_: This handler will buffer all the log records it receives
  until `close()` is called at which point it will call `handleBatch()` on the
  handler it wraps with all the log messages at once. This is very useful to
  send an email with all records at once for example instead of having one mail
  for every log record.
- _GroupHandler_: This handler groups other handlers. Every record received is
  sent to all the handlers it is configured with.
- _FilterHandler_: This handler only lets records of the given levels through
   to the wrapped handler.
- _SamplingHandler_: Wraps around another handler and lets you sample records
   if you only want to store some of them.
- _NullHandler_: Any record it can handle will be thrown away. This can be used
  to put on top of an existing handler stack to disable it temporarily.
- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger
- _TestHandler_: Used for testing, it records everything that is sent to it and
  has accessors to read out the information.
- _HandlerWrapper_: A simple handler wrapper you can inherit from to create
 your own wrappers easily.

## Formatters

- _LineFormatter_: Formats a log record into a one-line string.
- _HtmlFormatter_: Used to format log records into a human readable html table, mainly suitable for emails.
- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded.
- _ScalarFormatter_: Used to format log records into an associative array of scalar values.
- _JsonFormatter_: Encodes a log record into json.
- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler.
- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler.
- _GelfMessageFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler.
- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/latest).
- _ElasticaFormatter_: Used to format log records into an Elastica\Document object, only useful for the ElasticSearchHandler.
- _LogglyFormatter_: Used to format log records into Loggly messages, only useful for the LogglyHandler.
- _FlowdockFormatter_: Used to format log records into Flowdock messages, only useful for the FlowdockHandler.
- _MongoDBFormatter_: Converts \DateTime instances to \MongoDate and objects recursively to arrays, only useful with the MongoDBHandler.

## Processors

- _PsrLogMessageProcessor_: Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`.
- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated.
- _WebProcessor_: Adds the current request URI, request method and client IP to a log record.
- _MemoryUsageProcessor_: Adds the current memory usage to a log record.
- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record.
- _ProcessIdProcessor_: Adds the process id to a log record.
- _UidProcessor_: Adds a unique identifier to a log record.
- _GitProcessor_: Adds the current git branch and commit to a log record.
- _TagProcessor_: Adds an array of predefined tags to a log record.

## Third Party Packages

Third party handlers, formatters and processors are
[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You
can also add your own there if you publish one.

&larr; [Usage](01-usage.md) |  [Utility classes](03-utilities.md) &rarr;
# Utilities

- _Registry_: The `Monolog\Registry` class lets you configure global loggers that you
  can then statically access from anywhere. It is not really a best practice but can
  help in some older codebases or for ease of use.
- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register
  a Logger instance as an exception handler, error handler or fatal error handler.
- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log
  level is reached.
- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain
  log level is reached, depending on which channel received the log record.

&larr; [Handlers, Formatters and Processors](02-handlers-formatters-processors.md) |  [Extending Monolog](04-extending.md) &rarr;
# Extending Monolog

Monolog is fully extensible, allowing you to adapt your logger to your needs.

## Writing your own handler

Monolog provides many built-in handlers. But if the one you need does not
exist, you can write it and use it in your logger. The only requirement is
to implement `Monolog\Handler\HandlerInterface`.

Let's write a PDOHandler to log records to a database. We will extend the
abstract class provided by Monolog to keep things DRY.

```php
<?php

use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;

class PDOHandler extends AbstractProcessingHandler
{
    private $initialized = false;
    private $pdo;
    private $statement;

    public function __construct(PDO $pdo, $level = Logger::DEBUG, $bubble = true)
    {
        $this->pdo = $pdo;
        parent::__construct($level, $bubble);
    }

    protected function write(array $record)
    {
        if (!$this->initialized) {
            $this->initialize();
        }

        $this->statement->execute(array(
            'channel' => $record['channel'],
            'level' => $record['level'],
            'message' => $record['formatted'],
            'time' => $record['datetime']->format('U'),
        ));
    }

    private function initialize()
    {
        $this->pdo->exec(
            'CREATE TABLE IF NOT EXISTS monolog '
            .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)'
        );
        $this->statement = $this->pdo->prepare(
            'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)'
        );

        $this->initialized = true;
    }
}
```

You can now use this handler in your logger:

```php
<?php

$logger->pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite')));

// You can now use your logger
$logger->addInfo('My logger is now ready');
```

The `Monolog\Handler\AbstractProcessingHandler` class provides most of the
logic needed for the handler, including the use of processors and the formatting
of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``).

&larr; [Utility classes](03-utilities.md)
Sockets Handler
===============

This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen)
or [pfsockopen](http://php.net/pfsockopen).

Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening
the connections between requests.

You can use a `unix://` prefix to access unix sockets and `udp://` to open UDP sockets instead of the default TCP.

Basic Example
-------------

```php
<?php

use Monolog\Logger;
use Monolog\Handler\SocketHandler;

// Create the logger
$logger = new Logger('my_logger');

// Create the handler
$handler = new SocketHandler('unix:///var/log/httpd_app_log.socket');
$handler->setPersistent(true);

// Now add the handler
$logger->pushHandler($handler, Logger::DEBUG);

// You can now use your logger
$logger->addInfo('My logger is now ready');

```

In this example, using syslog-ng, you should see the log on the log server:

    cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] []

Copyright (c) 2011-2016 Jordi Boggiano

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.
<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="vendor/autoload.php" colors="true">
    <testsuites>
        <testsuite name="Monolog Test Suite">
            <directory>tests/Monolog/</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory suffix=".php">src/Monolog/</directory>
        </whitelist>
    </filter>

    <php>
        <ini name="date.timezone" value="UTC"/>
    </php>
</phpunit>
# Monolog - Logging for PHP [![Build Status](https://img.shields.io/travis/Seldaek/monolog.svg)](https://travis-ci.org/Seldaek/monolog)

[![Total Downloads](https://img.shields.io/packagist/dt/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Latest Stable Version](https://img.shields.io/packagist/v/monolog/monolog.svg)](https://packagist.org/packages/monolog/monolog)
[![Reference Status](https://www.versioneye.com/php/monolog:monolog/reference_badge.svg)](https://www.versioneye.com/php/monolog:monolog/references)


Monolog sends your logs to files, sockets, inboxes, databases and various
web services. See the complete list of handlers below. Special handlers
allow you to build advanced logging strategies.

This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
interface that you can type-hint against in your own libraries to keep
a maximum of interoperability. You can also use it in your applications to
make sure you can always use another compatible logger at a later time.
As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels.
Internally Monolog still uses its own level scheme since it predates PSR-3.

## Installation

Install the latest version with

```bash
$ composer require monolog/monolog
```

## Basic Usage

```php
<?php

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));

// add records to the log
$log->addWarning('Foo');
$log->addError('Bar');
```

## Documentation

- [Usage Instructions](doc/01-usage.md)
- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md)
- [Utility classes](doc/03-utilities.md)
- [Extending Monolog](doc/04-extending.md)

## Third Party Packages

Third party handlers, formatters and processors are
[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You
can also add your own there if you publish one.

## About

### Requirements

- Monolog works with PHP 5.3 or above, and is also tested to work with HHVM.

### Submitting bugs and feature requests

Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues)

### Framework Integrations

- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
  can be used very easily with Monolog since it implements the interface.
- [Symfony2](http://symfony.com) comes out of the box with Monolog.
- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog.
- [Laravel 4 & 5](http://laravel.com/) come out of the box with Monolog.
- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog.
- [PPI](http://www.ppi.io/) comes out of the box with Monolog.
- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin.
- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer.
- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog.
- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog.
- [Nette Framework](http://nette.org/en/) can be used with Monolog via [Kdyby/Monolog](https://github.com/Kdyby/Monolog) extension.
- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog.

### Author

Jordi Boggiano - <j.boggiano@seld.be> - <http://twitter.com/seldaek><br />
See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project.

### License

Monolog is licensed under the MIT License - see the `LICENSE` file for details

### Acknowledgements

This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/)
library, although most concepts have been adjusted to fit to the PHP world.
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Monolog\Handler\AbstractHandler;

/**
 * Monolog error handler
 *
 * A facility to enable logging of runtime errors, exceptions and fatal errors.
 *
 * Quick setup: <code>ErrorHandler::register($logger);</code>
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ErrorHandler
{
    private $logger;

    private $previousExceptionHandler;
    private $uncaughtExceptionLevel;

    private $previousErrorHandler;
    private $errorLevelMap;
    private $handleOnlyReportedErrors;

    private $hasFatalErrorHandler;
    private $fatalLevel;
    private $reservedMemory;
    private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    /**
     * Registers a new ErrorHandler for a given Logger
     *
     * By default it will handle errors, exceptions and fatal errors
     *
     * @param  LoggerInterface $logger
     * @param  array|false     $errorLevelMap  an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
     * @param  int|false       $exceptionLevel a LogLevel::* constant, or false to disable exception handling
     * @param  int|false       $fatalLevel     a LogLevel::* constant, or false to disable fatal error handling
     * @return ErrorHandler
     */
    public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null)
    {
        $handler = new static($logger);
        if ($errorLevelMap !== false) {
            $handler->registerErrorHandler($errorLevelMap);
        }
        if ($exceptionLevel !== false) {
            $handler->registerExceptionHandler($exceptionLevel);
        }
        if ($fatalLevel !== false) {
            $handler->registerFatalHandler($fatalLevel);
        }

        return $handler;
    }

    public function registerExceptionHandler($level = null, $callPrevious = true)
    {
        $prev = set_exception_handler(array($this, 'handleException'));
        $this->uncaughtExceptionLevel = $level;
        if ($callPrevious && $prev) {
            $this->previousExceptionHandler = $prev;
        }
    }

    public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true)
    {
        $prev = set_error_handler(array($this, 'handleError'), $errorTypes);
        $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
        if ($callPrevious) {
            $this->previousErrorHandler = $prev ?: true;
        }

        $this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
    }

    public function registerFatalHandler($level = null, $reservedMemorySize = 20)
    {
        register_shutdown_function(array($this, 'handleFatalError'));

        $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
        $this->fatalLevel = $level;
        $this->hasFatalErrorHandler = true;
    }

    protected function defaultErrorLevelMap()
    {
        return array(
            E_ERROR             => LogLevel::CRITICAL,
            E_WARNING           => LogLevel::WARNING,
            E_PARSE             => LogLevel::ALERT,
            E_NOTICE            => LogLevel::NOTICE,
            E_CORE_ERROR        => LogLevel::CRITICAL,
            E_CORE_WARNING      => LogLevel::WARNING,
            E_COMPILE_ERROR     => LogLevel::ALERT,
            E_COMPILE_WARNING   => LogLevel::WARNING,
            E_USER_ERROR        => LogLevel::ERROR,
            E_USER_WARNING      => LogLevel::WARNING,
            E_USER_NOTICE       => LogLevel::NOTICE,
            E_STRICT            => LogLevel::NOTICE,
            E_RECOVERABLE_ERROR => LogLevel::ERROR,
            E_DEPRECATED        => LogLevel::NOTICE,
            E_USER_DEPRECATED   => LogLevel::NOTICE,
        );
    }

    /**
     * @private
     */
    public function handleException($e)
    {
        $this->logger->log(
            $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel,
            sprintf('Uncaught Exception %s: "%s" at %s line %s', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()),
            array('exception' => $e)
        );

        if ($this->previousExceptionHandler) {
            call_user_func($this->previousExceptionHandler, $e);
        }

        exit(255);
    }

    /**
     * @private
     */
    public function handleError($code, $message, $file = '', $line = 0, $context = array())
    {
        if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
            return;
        }

        // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
        if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
            $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL;
            $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line));
        }

        if ($this->previousErrorHandler === true) {
            return false;
        } elseif ($this->previousErrorHandler) {
            return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context);
        }
    }

    /**
     * @private
     */
    public function handleFatalError()
    {
        $this->reservedMemory = null;

        $lastError = error_get_last();
        if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
            $this->logger->log(
                $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
                'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
                array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'])
            );

            if ($this->logger instanceof Logger) {
                foreach ($this->logger->getHandlers() as $handler) {
                    if ($handler instanceof AbstractHandler) {
                        $handler->close();
                    }
                }
            }
        }
    }

    private static function codeToString($code)
    {
        switch ($code) {
            case E_ERROR:
                return 'E_ERROR';
            case E_WARNING:
                return 'E_WARNING';
            case E_PARSE:
                return 'E_PARSE';
            case E_NOTICE:
                return 'E_NOTICE';
            case E_CORE_ERROR:
                return 'E_CORE_ERROR';
            case E_CORE_WARNING:
                return 'E_CORE_WARNING';
            case E_COMPILE_ERROR:
                return 'E_COMPILE_ERROR';
            case E_COMPILE_WARNING:
                return 'E_COMPILE_WARNING';
            case E_USER_ERROR:
                return 'E_USER_ERROR';
            case E_USER_WARNING:
                return 'E_USER_WARNING';
            case E_USER_NOTICE:
                return 'E_USER_NOTICE';
            case E_STRICT:
                return 'E_STRICT';
            case E_RECOVERABLE_ERROR:
                return 'E_RECOVERABLE_ERROR';
            case E_DEPRECATED:
                return 'E_DEPRECATED';
            case E_USER_DEPRECATED:
                return 'E_USER_DEPRECATED';
        }

        return 'Unknown PHP error';
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Logger;

/**
 * Formats a log message according to the ChromePHP array format
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class ChromePHPFormatter implements FormatterInterface
{
    /**
     * Translates Monolog log levels to Wildfire levels.
     */
    private $logLevels = array(
        Logger::DEBUG     => 'log',
        Logger::INFO      => 'info',
        Logger::NOTICE    => 'info',
        Logger::WARNING   => 'warn',
        Logger::ERROR     => 'error',
        Logger::CRITICAL  => 'error',
        Logger::ALERT     => 'error',
        Logger::EMERGENCY => 'error',
    );

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        // Retrieve the line and file if set and remove them from the formatted extra
        $backtrace = 'unknown';
        if (isset($record['extra']['file'], $record['extra']['line'])) {
            $backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
            unset($record['extra']['file'], $record['extra']['line']);
        }

        $message = array('message' => $record['message']);
        if ($record['context']) {
            $message['context'] = $record['context'];
        }
        if ($record['extra']) {
            $message['extra'] = $record['extra'];
        }
        if (count($message) === 1) {
            $message = reset($message);
        }

        return array(
            $record['channel'],
            $message,
            $backtrace,
            $this->logLevels[$record['level']],
        );
    }

    public function formatBatch(array $records)
    {
        $formatted = array();

        foreach ($records as $record) {
            $formatted[] = $this->format($record);
        }

        return $formatted;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Elastica\Document;

/**
 * Format a log message into an Elastica Document
 *
 * @author Jelle Vink <jelle.vink@gmail.com>
 */
class ElasticaFormatter extends NormalizerFormatter
{
    /**
     * @var string Elastic search index name
     */
    protected $index;

    /**
     * @var string Elastic search document type
     */
    protected $type;

    /**
     * @param string $index Elastic Search index name
     * @param string $type  Elastic Search document type
     */
    public function __construct($index, $type)
    {
        // elasticsearch requires a ISO 8601 format date with optional millisecond precision.
        parent::__construct('Y-m-d\TH:i:s.uP');

        $this->index = $index;
        $this->type = $type;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $record = parent::format($record);

        return $this->getDocument($record);
    }

    /**
     * Getter index
     * @return string
     */
    public function getIndex()
    {
        return $this->index;
    }

    /**
     * Getter type
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Convert a log message into an Elastica Document
     *
     * @param  array    $record Log message
     * @return Document
     */
    protected function getDocument($record)
    {
        $document = new Document();
        $document->setData($record);
        $document->setType($this->type);
        $document->setIndex($this->index);

        return $document;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * formats the record to be used in the FlowdockHandler
 *
 * @author Dominik Liebler <liebler.dominik@gmail.com>
 */
class FlowdockFormatter implements FormatterInterface
{
    /**
     * @var string
     */
    private $source;

    /**
     * @var string
     */
    private $sourceEmail;

    /**
     * @param string $source
     * @param string $sourceEmail
     */
    public function __construct($source, $sourceEmail)
    {
        $this->source = $source;
        $this->sourceEmail = $sourceEmail;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $tags = array(
            '#logs',
            '#' . strtolower($record['level_name']),
            '#' . $record['channel'],
        );

        foreach ($record['extra'] as $value) {
            $tags[] = '#' . $value;
        }

        $subject = sprintf(
            'in %s: %s - %s',
            $this->source,
            $record['level_name'],
            $this->getShortMessage($record['message'])
        );

        $record['flowdock'] = array(
            'source' => $this->source,
            'from_address' => $this->sourceEmail,
            'subject' => $subject,
            'content' => $record['message'],
            'tags' => $tags,
            'project' => $this->source,
        );

        return $record;
    }

    /**
     * {@inheritdoc}
     */
    public function formatBatch(array $records)
    {
        $formatted = array();

        foreach ($records as $record) {
            $formatted[] = $this->format($record);
        }

        return $formatted;
    }

    /**
     * @param string $message
     *
     * @return string
     */
    public function getShortMessage($message)
    {
        static $hasMbString;

        if (null === $hasMbString) {
            $hasMbString = function_exists('mb_strlen');
        }

        $maxLength = 45;

        if ($hasMbString) {
            if (mb_strlen($message, 'UTF-8') > $maxLength) {
                $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...';
            }
        } else {
            if (strlen($message) > $maxLength) {
                $message = substr($message, 0, $maxLength - 4) . ' ...';
            }
        }

        return $message;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Class FluentdFormatter
 *
 * Serializes a log message to Fluentd unix socket protocol
 *
 * Fluentd config:
 *
 * <source>
 *  type unix
 *  path /var/run/td-agent/td-agent.sock
 * </source>
 *
 * Monolog setup:
 *
 * $logger = new Monolog\Logger('fluent.tag');
 * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock');
 * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter());
 * $logger->pushHandler($fluentHandler);
 *
 * @author Andrius Putna <fordnox@gmail.com>
 */
class FluentdFormatter implements FormatterInterface
{
    /**
     * @var bool $levelTag should message level be a part of the fluentd tag
     */
    protected $levelTag = false;

    public function __construct($levelTag = false)
    {
        if (!function_exists('json_encode')) {
            throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter');
        }

        $this->levelTag = (bool) $levelTag;
    }

    public function isUsingLevelsInTag()
    {
        return $this->levelTag;
    }

    public function format(array $record)
    {
        $tag = $record['channel'];
        if ($this->levelTag) {
            $tag .= '.' . strtolower($record['level_name']);
        }

        $message = array(
            'message' => $record['message'],
            'extra' => $record['extra'],
        );

        if (!$this->levelTag) {
            $message['level'] = $record['level'];
            $message['level_name'] = $record['level_name'];
        }

        return json_encode(array($tag, $record['datetime']->getTimestamp(), $message));
    }

    public function formatBatch(array $records)
    {
        $message = '';
        foreach ($records as $record) {
            $message .= $this->format($record);
        }

        return $message;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Interface for formatters
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface FormatterInterface
{
    /**
     * Formats a log record.
     *
     * @param  array $record A record to format
     * @return mixed The formatted record
     */
    public function format(array $record);

    /**
     * Formats a set of log records.
     *
     * @param  array $records A set of records to format
     * @return mixed The formatted set of records
     */
    public function formatBatch(array $records);
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Logger;
use Gelf\Message;

/**
 * Serializes a log message to GELF
 * @see http://www.graylog2.org/about/gelf
 *
 * @author Matt Lehner <mlehner@gmail.com>
 */
class GelfMessageFormatter extends NormalizerFormatter
{
    const MAX_LENGTH = 32766;

    /**
     * @var string the name of the system for the Gelf log message
     */
    protected $systemName;

    /**
     * @var string a prefix for 'extra' fields from the Monolog record (optional)
     */
    protected $extraPrefix;

    /**
     * @var string a prefix for 'context' fields from the Monolog record (optional)
     */
    protected $contextPrefix;

    /**
     * Translates Monolog log levels to Graylog2 log priorities.
     */
    private $logLevels = array(
        Logger::DEBUG     => 7,
        Logger::INFO      => 6,
        Logger::NOTICE    => 5,
        Logger::WARNING   => 4,
        Logger::ERROR     => 3,
        Logger::CRITICAL  => 2,
        Logger::ALERT     => 1,
        Logger::EMERGENCY => 0,
    );

    public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_')
    {
        parent::__construct('U.u');

        $this->systemName = $systemName ?: gethostname();

        $this->extraPrefix = $extraPrefix;
        $this->contextPrefix = $contextPrefix;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $record = parent::format($record);

        if (!isset($record['datetime'], $record['message'], $record['level'])) {
            throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given');
        }

        $message = new Message();
        $message
            ->setTimestamp($record['datetime'])
            ->setShortMessage((string) $record['message'])
            ->setHost($this->systemName)
            ->setLevel($this->logLevels[$record['level']]);

        // start count with message length + system name length + 200 for padding / metadata
        $len = 200 + strlen((string) $record['message']) + strlen($this->systemName);

        if ($len > self::MAX_LENGTH) {
            $message->setShortMessage(substr($record['message'], 0, self::MAX_LENGTH - 200));

            return $message;
        }

        if (isset($record['channel'])) {
            $message->setFacility($record['channel']);
            $len += strlen($record['channel']);
        }
        if (isset($record['extra']['line'])) {
            $message->setLine($record['extra']['line']);
            $len += 10;
            unset($record['extra']['line']);
        }
        if (isset($record['extra']['file'])) {
            $message->setFile($record['extra']['file']);
            $len += strlen($record['extra']['file']);
            unset($record['extra']['file']);
        }

        foreach ($record['extra'] as $key => $val) {
            $val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
            $len += strlen($this->extraPrefix . $key . $val);
            if ($len > self::MAX_LENGTH) {
                $message->setAdditional($this->extraPrefix . $key, substr($val, 0, self::MAX_LENGTH - $len));
                break;
            }
            $message->setAdditional($this->extraPrefix . $key, $val);
        }

        foreach ($record['context'] as $key => $val) {
            $val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
            $len += strlen($this->contextPrefix . $key . $val);
            if ($len > self::MAX_LENGTH) {
                $message->setAdditional($this->contextPrefix . $key, substr($val, 0, self::MAX_LENGTH - $len));
                break;
            }
            $message->setAdditional($this->contextPrefix . $key, $val);
        }

        if (null === $message->getFile() && isset($record['context']['exception']['file'])) {
            if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) {
                $message->setFile($matches[1]);
                $message->setLine($matches[2]);
            }
        }

        return $message;
    }
}
<?php
/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Logger;

/**
 * Formats incoming records into an HTML table
 *
 * This is especially useful for html email logging
 *
 * @author Tiago Brito <tlfbrito@gmail.com>
 */
class HtmlFormatter extends NormalizerFormatter
{
    /**
     * Translates Monolog log levels to html color priorities.
     */
    protected $logLevels = array(
        Logger::DEBUG     => '#cccccc',
        Logger::INFO      => '#468847',
        Logger::NOTICE    => '#3a87ad',
        Logger::WARNING   => '#c09853',
        Logger::ERROR     => '#f0ad4e',
        Logger::CRITICAL  => '#FF7708',
        Logger::ALERT     => '#C12A19',
        Logger::EMERGENCY => '#000000',
    );

    /**
     * @param string $dateFormat The format of the timestamp: one supported by DateTime::format
     */
    public function __construct($dateFormat = null)
    {
        parent::__construct($dateFormat);
    }

    /**
     * Creates an HTML table row
     *
     * @param  string $th       Row header content
     * @param  string $td       Row standard cell content
     * @param  bool   $escapeTd false if td content must not be html escaped
     * @return string
     */
    protected function addRow($th, $td = ' ', $escapeTd = true)
    {
        $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8');
        if ($escapeTd) {
            $td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';
        }

        return "<tr style=\"padding: 4px;spacing: 0;text-align: left;\">\n<th style=\"background: #cccccc\" width=\"100px\">$th:</th>\n<td style=\"padding: 4px;spacing: 0;text-align: left;background: #eeeeee\">".$td."</td>\n</tr>";
    }

    /**
     * Create a HTML h1 tag
     *
     * @param  string $title Text to be in the h1
     * @param  int    $level Error level
     * @return string
     */
    protected function addTitle($title, $level)
    {
        $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');

        return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
    }

    /**
     * Formats a log record.
     *
     * @param  array $record A record to format
     * @return mixed The formatted record
     */
    public function format(array $record)
    {
        $output = $this->addTitle($record['level_name'], $record['level']);
        $output .= '<table cellspacing="1" width="100%" class="monolog-output">';

        $output .= $this->addRow('Message', (string) $record['message']);
        $output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat));
        $output .= $this->addRow('Channel', $record['channel']);
        if ($record['context']) {
            $embeddedTable = '<table cellspacing="1" width="100%">';
            foreach ($record['context'] as $key => $value) {
                $embeddedTable .= $this->addRow($key, $this->convertToString($value));
            }
            $embeddedTable .= '</table>';
            $output .= $this->addRow('Context', $embeddedTable, false);
        }
        if ($record['extra']) {
            $embeddedTable = '<table cellspacing="1" width="100%">';
            foreach ($record['extra'] as $key => $value) {
                $embeddedTable .= $this->addRow($key, $this->convertToString($value));
            }
            $embeddedTable .= '</table>';
            $output .= $this->addRow('Extra', $embeddedTable, false);
        }

        return $output.'</table>';
    }

    /**
     * Formats a set of log records.
     *
     * @param  array $records A set of records to format
     * @return mixed The formatted set of records
     */
    public function formatBatch(array $records)
    {
        $message = '';
        foreach ($records as $record) {
            $message .= $this->format($record);
        }

        return $message;
    }

    protected function convertToString($data)
    {
        if (null === $data || is_scalar($data)) {
            return (string) $data;
        }

        $data = $this->normalize($data);
        if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
            return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        return str_replace('\\/', '/', json_encode($data));
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Exception;
use Throwable;

/**
 * Encodes whatever record data is passed to it as json
 *
 * This can be useful to log to databases or remote APIs
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonFormatter extends NormalizerFormatter
{
    const BATCH_MODE_JSON = 1;
    const BATCH_MODE_NEWLINES = 2;

    protected $batchMode;
    protected $appendNewline;
    /**
     * @var bool
     */
    protected $includeStacktraces = false;

    /**
     * @param int $batchMode
     */
    public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true)
    {
        $this->batchMode = $batchMode;
        $this->appendNewline = $appendNewline;
    }

    /**
     * The batch mode option configures the formatting style for
     * multiple records. By default, multiple records will be
     * formatted as a JSON-encoded array. However, for
     * compatibility with some API endpoints, alternative styles
     * are available.
     *
     * @return int
     */
    public function getBatchMode()
    {
        return $this->batchMode;
    }

    /**
     * True if newlines are appended to every formatted record
     *
     * @return bool
     */
    public function isAppendingNewlines()
    {
        return $this->appendNewline;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
    }

    /**
     * {@inheritdoc}
     */
    public function formatBatch(array $records)
    {
        switch ($this->batchMode) {
            case static::BATCH_MODE_NEWLINES:
                return $this->formatBatchNewlines($records);

            case static::BATCH_MODE_JSON:
            default:
                return $this->formatBatchJson($records);
        }
    }

    /**
     * @param bool $include
     */
    public function includeStacktraces($include = true)
    {
        $this->includeStacktraces = $include;
    }

    /**
     * Return a JSON-encoded array of records.
     *
     * @param  array  $records
     * @return string
     */
    protected function formatBatchJson(array $records)
    {
        return $this->toJson($this->normalize($records), true);
    }

    /**
     * Use new lines to separate records instead of a
     * JSON-encoded array.
     *
     * @param  array  $records
     * @return string
     */
    protected function formatBatchNewlines(array $records)
    {
        $instance = $this;

        $oldNewline = $this->appendNewline;
        $this->appendNewline = false;
        array_walk($records, function (&$value, $key) use ($instance) {
            $value = $instance->format($value);
        });
        $this->appendNewline = $oldNewline;

        return implode("\n", $records);
    }

    /**
     * Normalizes given $data.
     *
     * @param mixed $data
     *
     * @return mixed
     */
    protected function normalize($data)
    {
        if (is_array($data) || $data instanceof \Traversable) {
            $normalized = array();

            $count = 1;
            foreach ($data as $key => $value) {
                if ($count++ >= 1000) {
                    $normalized['...'] = 'Over 1000 items, aborting normalization';
                    break;
                }
                $normalized[$key] = $this->normalize($value);
            }

            return $normalized;
        }

        if ($data instanceof Exception || $data instanceof Throwable) {
            return $this->normalizeException($data);
        }

        return $data;
    }

    /**
     * Normalizes given exception with or without its own stack trace based on
     * `includeStacktraces` property.
     *
     * @param Exception|Throwable $e
     *
     * @return array
     */
    protected function normalizeException($e)
    {
        // TODO 2.0 only check for Throwable
        if (!$e instanceof Exception && !$e instanceof Throwable) {
            throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
        }

        $data = array(
            'class' => get_class($e),
            'message' => $e->getMessage(),
            'code' => $e->getCode(),
            'file' => $e->getFile().':'.$e->getLine(),
        );

        if ($this->includeStacktraces) {
            $trace = $e->getTrace();
            foreach ($trace as $frame) {
                if (isset($frame['file'])) {
                    $data['trace'][] = $frame['file'].':'.$frame['line'];
                } elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
                    // We should again normalize the frames, because it might contain invalid items
                    $data['trace'][] = $frame['function'];
                } else {
                    // We should again normalize the frames, because it might contain invalid items
                    $data['trace'][] = $this->normalize($frame);
                }
            }
        }

        if ($previous = $e->getPrevious()) {
            $data['previous'] = $this->normalizeException($previous);
        }

        return $data;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Formats incoming records into a one-line string
 *
 * This is especially useful for logging to files
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Christophe Coevoet <stof@notk.org>
 */
class LineFormatter extends NormalizerFormatter
{
    const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";

    protected $format;
    protected $allowInlineLineBreaks;
    protected $ignoreEmptyContextAndExtra;
    protected $includeStacktraces;

    /**
     * @param string $format                     The format of the message
     * @param string $dateFormat                 The format of the timestamp: one supported by DateTime::format
     * @param bool   $allowInlineLineBreaks      Whether to allow inline line breaks in log entries
     * @param bool   $ignoreEmptyContextAndExtra
     */
    public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false)
    {
        $this->format = $format ?: static::SIMPLE_FORMAT;
        $this->allowInlineLineBreaks = $allowInlineLineBreaks;
        $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
        parent::__construct($dateFormat);
    }

    public function includeStacktraces($include = true)
    {
        $this->includeStacktraces = $include;
        if ($this->includeStacktraces) {
            $this->allowInlineLineBreaks = true;
        }
    }

    public function allowInlineLineBreaks($allow = true)
    {
        $this->allowInlineLineBreaks = $allow;
    }

    public function ignoreEmptyContextAndExtra($ignore = true)
    {
        $this->ignoreEmptyContextAndExtra = $ignore;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $vars = parent::format($record);

        $output = $this->format;

        foreach ($vars['extra'] as $var => $val) {
            if (false !== strpos($output, '%extra.'.$var.'%')) {
                $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
                unset($vars['extra'][$var]);
            }
        }


        foreach ($vars['context'] as $var => $val) {
            if (false !== strpos($output, '%context.'.$var.'%')) {
                $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
                unset($vars['context'][$var]);
            }
        }

        if ($this->ignoreEmptyContextAndExtra) {
            if (empty($vars['context'])) {
                unset($vars['context']);
                $output = str_replace('%context%', '', $output);
            }

            if (empty($vars['extra'])) {
                unset($vars['extra']);
                $output = str_replace('%extra%', '', $output);
            }
        }

        foreach ($vars as $var => $val) {
            if (false !== strpos($output, '%'.$var.'%')) {
                $output = str_replace('%'.$var.'%', $this->stringify($val), $output);
            }
        }

        // remove leftover %extra.xxx% and %context.xxx% if any
        if (false !== strpos($output, '%')) {
            $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
        }

        return $output;
    }

    public function formatBatch(array $records)
    {
        $message = '';
        foreach ($records as $record) {
            $message .= $this->format($record);
        }

        return $message;
    }

    public function stringify($value)
    {
        return $this->replaceNewlines($this->convertToString($value));
    }

    protected function normalizeException($e)
    {
        // TODO 2.0 only check for Throwable
        if (!$e instanceof \Exception && !$e instanceof \Throwable) {
            throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
        }

        $previousText = '';
        if ($previous = $e->getPrevious()) {
            do {
                $previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
            } while ($previous = $previous->getPrevious());
        }

        $str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')';
        if ($this->includeStacktraces) {
            $str .= "\n[stacktrace]\n".$e->getTraceAsString();
        }

        return $str;
    }

    protected function convertToString($data)
    {
        if (null === $data || is_bool($data)) {
            return var_export($data, true);
        }

        if (is_scalar($data)) {
            return (string) $data;
        }

        if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
            return $this->toJson($data, true);
        }

        return str_replace('\\/', '/', @json_encode($data));
    }

    protected function replaceNewlines($str)
    {
        if ($this->allowInlineLineBreaks) {
            return $str;
        }

        return str_replace(array("\r\n", "\r", "\n"), ' ', $str);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Encodes message information into JSON in a format compatible with Loggly.
 *
 * @author Adam Pancutt <adam@pancutt.com>
 */
class LogglyFormatter extends JsonFormatter
{
    /**
     * Overrides the default batch mode to new lines for compatibility with the
     * Loggly bulk API.
     *
     * @param int $batchMode
     */
    public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false)
    {
        parent::__construct($batchMode, $appendNewline);
    }

    /**
     * Appends the 'timestamp' parameter for indexing by Loggly.
     *
     * @see https://www.loggly.com/docs/automated-parsing/#json
     * @see \Monolog\Formatter\JsonFormatter::format()
     */
    public function format(array $record)
    {
        if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) {
            $record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO");
            // TODO 2.0 unset the 'datetime' parameter, retained for BC
        }

        return parent::format($record);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Serializes a log message to Logstash Event Format
 *
 * @see http://logstash.net/
 * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb
 *
 * @author Tim Mower <timothy.mower@gmail.com>
 */
class LogstashFormatter extends NormalizerFormatter
{
    const V0 = 0;
    const V1 = 1;

    /**
     * @var string the name of the system for the Logstash log message, used to fill the @source field
     */
    protected $systemName;

    /**
     * @var string an application name for the Logstash log message, used to fill the @type field
     */
    protected $applicationName;

    /**
     * @var string a prefix for 'extra' fields from the Monolog record (optional)
     */
    protected $extraPrefix;

    /**
     * @var string a prefix for 'context' fields from the Monolog record (optional)
     */
    protected $contextPrefix;

    /**
     * @var int logstash format version to use
     */
    protected $version;

    /**
     * @param string $applicationName the application that sends the data, used as the "type" field of logstash
     * @param string $systemName      the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
     * @param string $extraPrefix     prefix for extra keys inside logstash "fields"
     * @param string $contextPrefix   prefix for context keys inside logstash "fields", defaults to ctxt_
     * @param int    $version         the logstash format version to use, defaults to 0
     */
    public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0)
    {
        // logstash requires a ISO 8601 format date with optional millisecond precision.
        parent::__construct('Y-m-d\TH:i:s.uP');

        $this->systemName = $systemName ?: gethostname();
        $this->applicationName = $applicationName;
        $this->extraPrefix = $extraPrefix;
        $this->contextPrefix = $contextPrefix;
        $this->version = $version;
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        $record = parent::format($record);

        if ($this->version === self::V1) {
            $message = $this->formatV1($record);
        } else {
            $message = $this->formatV0($record);
        }

        return $this->toJson($message) . "\n";
    }

    protected function formatV0(array $record)
    {
        if (empty($record['datetime'])) {
            $record['datetime'] = gmdate('c');
        }
        $message = array(
            '@timestamp' => $record['datetime'],
            '@source' => $this->systemName,
            '@fields' => array(),
        );
        if (isset($record['message'])) {
            $message['@message'] = $record['message'];
        }
        if (isset($record['channel'])) {
            $message['@tags'] = array($record['channel']);
            $message['@fields']['channel'] = $record['channel'];
        }
        if (isset($record['level'])) {
            $message['@fields']['level'] = $record['level'];
        }
        if ($this->applicationName) {
            $message['@type'] = $this->applicationName;
        }
        if (isset($record['extra']['server'])) {
            $message['@source_host'] = $record['extra']['server'];
        }
        if (isset($record['extra']['url'])) {
            $message['@source_path'] = $record['extra']['url'];
        }
        if (!empty($record['extra'])) {
            foreach ($record['extra'] as $key => $val) {
                $message['@fields'][$this->extraPrefix . $key] = $val;
            }
        }
        if (!empty($record['context'])) {
            foreach ($record['context'] as $key => $val) {
                $message['@fields'][$this->contextPrefix . $key] = $val;
            }
        }

        return $message;
    }

    protected function formatV1(array $record)
    {
        if (empty($record['datetime'])) {
            $record['datetime'] = gmdate('c');
        }
        $message = array(
            '@timestamp' => $record['datetime'],
            '@version' => 1,
            'host' => $this->systemName,
        );
        if (isset($record['message'])) {
            $message['message'] = $record['message'];
        }
        if (isset($record['channel'])) {
            $message['type'] = $record['channel'];
            $message['channel'] = $record['channel'];
        }
        if (isset($record['level_name'])) {
            $message['level'] = $record['level_name'];
        }
        if ($this->applicationName) {
            $message['type'] = $this->applicationName;
        }
        if (!empty($record['extra'])) {
            foreach ($record['extra'] as $key => $val) {
                $message[$this->extraPrefix . $key] = $val;
            }
        }
        if (!empty($record['context'])) {
            foreach ($record['context'] as $key => $val) {
                $message[$this->contextPrefix . $key] = $val;
            }
        }

        return $message;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Formats a record for use with the MongoDBHandler.
 *
 * @author Florian Plattner <me@florianplattner.de>
 */
class MongoDBFormatter implements FormatterInterface
{
    private $exceptionTraceAsString;
    private $maxNestingLevel;

    /**
     * @param int  $maxNestingLevel        0 means infinite nesting, the $record itself is level 1, $record['context'] is 2
     * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings
     */
    public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true)
    {
        $this->maxNestingLevel = max($maxNestingLevel, 0);
        $this->exceptionTraceAsString = (bool) $exceptionTraceAsString;
    }

    /**
     * {@inheritDoc}
     */
    public function format(array $record)
    {
        return $this->formatArray($record);
    }

    /**
     * {@inheritDoc}
     */
    public function formatBatch(array $records)
    {
        foreach ($records as $key => $record) {
            $records[$key] = $this->format($record);
        }

        return $records;
    }

    protected function formatArray(array $record, $nestingLevel = 0)
    {
        if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) {
            foreach ($record as $name => $value) {
                if ($value instanceof \DateTime) {
                    $record[$name] = $this->formatDate($value, $nestingLevel + 1);
                } elseif ($value instanceof \Exception) {
                    $record[$name] = $this->formatException($value, $nestingLevel + 1);
                } elseif (is_array($value)) {
                    $record[$name] = $this->formatArray($value, $nestingLevel + 1);
                } elseif (is_object($value)) {
                    $record[$name] = $this->formatObject($value, $nestingLevel + 1);
                }
            }
        } else {
            $record = '[...]';
        }

        return $record;
    }

    protected function formatObject($value, $nestingLevel)
    {
        $objectVars = get_object_vars($value);
        $objectVars['class'] = get_class($value);

        return $this->formatArray($objectVars, $nestingLevel);
    }

    protected function formatException(\Exception $exception, $nestingLevel)
    {
        $formattedException = array(
            'class' => get_class($exception),
            'message' => $exception->getMessage(),
            'code' => $exception->getCode(),
            'file' => $exception->getFile() . ':' . $exception->getLine(),
        );

        if ($this->exceptionTraceAsString === true) {
            $formattedException['trace'] = $exception->getTraceAsString();
        } else {
            $formattedException['trace'] = $exception->getTrace();
        }

        return $this->formatArray($formattedException, $nestingLevel);
    }

    protected function formatDate(\DateTime $value, $nestingLevel)
    {
        return new \MongoDate($value->getTimestamp());
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Exception;

/**
 * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NormalizerFormatter implements FormatterInterface
{
    const SIMPLE_DATE = "Y-m-d H:i:s";

    protected $dateFormat;

    /**
     * @param string $dateFormat The format of the timestamp: one supported by DateTime::format
     */
    public function __construct($dateFormat = null)
    {
        $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE;
        if (!function_exists('json_encode')) {
            throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        return $this->normalize($record);
    }

    /**
     * {@inheritdoc}
     */
    public function formatBatch(array $records)
    {
        foreach ($records as $key => $record) {
            $records[$key] = $this->format($record);
        }

        return $records;
    }

    protected function normalize($data)
    {
        if (null === $data || is_scalar($data)) {
            if (is_float($data)) {
                if (is_infinite($data)) {
                    return ($data > 0 ? '' : '-') . 'INF';
                }
                if (is_nan($data)) {
                    return 'NaN';
                }
            }

            return $data;
        }

        if (is_array($data)) {
            $normalized = array();

            $count = 1;
            foreach ($data as $key => $value) {
                if ($count++ >= 1000) {
                    $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
                    break;
                }
                $normalized[$key] = $this->normalize($value);
            }

            return $normalized;
        }

        if ($data instanceof \DateTime) {
            return $data->format($this->dateFormat);
        }

        if (is_object($data)) {
            // TODO 2.0 only check for Throwable
            if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) {
                return $this->normalizeException($data);
            }

            // non-serializable objects that implement __toString stringified
            if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) {
                $value = $data->__toString();
            } else {
                // the rest is json-serialized in some way
                $value = $this->toJson($data, true);
            }

            return sprintf("[object] (%s: %s)", get_class($data), $value);
        }

        if (is_resource($data)) {
            return sprintf('[resource] (%s)', get_resource_type($data));
        }

        return '[unknown('.gettype($data).')]';
    }

    protected function normalizeException($e)
    {
        // TODO 2.0 only check for Throwable
        if (!$e instanceof Exception && !$e instanceof \Throwable) {
            throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.get_class($e));
        }

        $data = array(
            'class' => get_class($e),
            'message' => $e->getMessage(),
            'code' => $e->getCode(),
            'file' => $e->getFile().':'.$e->getLine(),
        );

        if ($e instanceof \SoapFault) {
            if (isset($e->faultcode)) {
                $data['faultcode'] = $e->faultcode;
            }

            if (isset($e->faultactor)) {
                $data['faultactor'] = $e->faultactor;
            }

            if (isset($e->detail)) {
                $data['detail'] = $e->detail;
            }
        }

        $trace = $e->getTrace();
        foreach ($trace as $frame) {
            if (isset($frame['file'])) {
                $data['trace'][] = $frame['file'].':'.$frame['line'];
            } elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
                // We should again normalize the frames, because it might contain invalid items
                $data['trace'][] = $frame['function'];
            } else {
                // We should again normalize the frames, because it might contain invalid items
                $data['trace'][] = $this->toJson($this->normalize($frame), true);
            }
        }

        if ($previous = $e->getPrevious()) {
            $data['previous'] = $this->normalizeException($previous);
        }

        return $data;
    }

    /**
     * Return the JSON representation of a value
     *
     * @param  mixed             $data
     * @param  bool              $ignoreErrors
     * @throws \RuntimeException if encoding fails and errors are not ignored
     * @return string
     */
    protected function toJson($data, $ignoreErrors = false)
    {
        // suppress json_encode errors since it's twitchy with some inputs
        if ($ignoreErrors) {
            return @$this->jsonEncode($data);
        }

        $json = $this->jsonEncode($data);

        if ($json === false) {
            $json = $this->handleJsonError(json_last_error(), $data);
        }

        return $json;
    }

    /**
     * @param  mixed  $data
     * @return string JSON encoded data or null on failure
     */
    private function jsonEncode($data)
    {
        if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
            return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        }

        return json_encode($data);
    }

    /**
     * Handle a json_encode failure.
     *
     * If the failure is due to invalid string encoding, try to clean the
     * input and encode again. If the second encoding attempt fails, the
     * inital error is not encoding related or the input can't be cleaned then
     * raise a descriptive exception.
     *
     * @param  int               $code return code of json_last_error function
     * @param  mixed             $data data that was meant to be encoded
     * @throws \RuntimeException if failure can't be corrected
     * @return string            JSON encoded data after error correction
     */
    private function handleJsonError($code, $data)
    {
        if ($code !== JSON_ERROR_UTF8) {
            $this->throwEncodeError($code, $data);
        }

        if (is_string($data)) {
            $this->detectAndCleanUtf8($data);
        } elseif (is_array($data)) {
            array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
        } else {
            $this->throwEncodeError($code, $data);
        }

        $json = $this->jsonEncode($data);

        if ($json === false) {
            $this->throwEncodeError(json_last_error(), $data);
        }

        return $json;
    }

    /**
     * Throws an exception according to a given code with a customized message
     *
     * @param  int               $code return code of json_last_error function
     * @param  mixed             $data data that was meant to be encoded
     * @throws \RuntimeException
     */
    private function throwEncodeError($code, $data)
    {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $msg = 'Maximum stack depth exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $msg = 'Underflow or the modes mismatch';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $msg = 'Unexpected control character found';
                break;
            case JSON_ERROR_UTF8:
                $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            default:
                $msg = 'Unknown error';
        }

        throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
    }

    /**
     * Detect invalid UTF-8 string characters and convert to valid UTF-8.
     *
     * Valid UTF-8 input will be left unmodified, but strings containing
     * invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed
     * original encoding of ISO-8859-15. This conversion may result in
     * incorrect output if the actual encoding was not ISO-8859-15, but it
     * will be clean UTF-8 output and will not rely on expensive and fragile
     * detection algorithms.
     *
     * Function converts the input in place in the passed variable so that it
     * can be used as a callback for array_walk_recursive.
     *
     * @param mixed &$data Input to check and convert if needed
     * @private
     */
    public function detectAndCleanUtf8(&$data)
    {
        if (is_string($data) && !preg_match('//u', $data)) {
            $data = preg_replace_callback(
                '/[\x80-\xFF]+/',
                function ($m) { return utf8_encode($m[0]); },
                $data
            );
            $data = str_replace(
                array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
                array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
                $data
            );
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

/**
 * Formats data into an associative array of scalar values.
 * Objects and arrays will be JSON encoded.
 *
 * @author Andrew Lawson <adlawson@gmail.com>
 */
class ScalarFormatter extends NormalizerFormatter
{
    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        foreach ($record as $key => $value) {
            $record[$key] = $this->normalizeValue($value);
        }

        return $record;
    }

    /**
     * @param  mixed $value
     * @return mixed
     */
    protected function normalizeValue($value)
    {
        $normalized = $this->normalize($value);

        if (is_array($normalized) || is_object($normalized)) {
            return $this->toJson($normalized, true);
        }

        return $normalized;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Formatter;

use Monolog\Logger;

/**
 * Serializes a log message according to Wildfire's header requirements
 *
 * @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
 * @author Christophe Coevoet <stof@notk.org>
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class WildfireFormatter extends NormalizerFormatter
{
    const TABLE = 'table';

    /**
     * Translates Monolog log levels to Wildfire levels.
     */
    private $logLevels = array(
        Logger::DEBUG     => 'LOG',
        Logger::INFO      => 'INFO',
        Logger::NOTICE    => 'INFO',
        Logger::WARNING   => 'WARN',
        Logger::ERROR     => 'ERROR',
        Logger::CRITICAL  => 'ERROR',
        Logger::ALERT     => 'ERROR',
        Logger::EMERGENCY => 'ERROR',
    );

    /**
     * {@inheritdoc}
     */
    public function format(array $record)
    {
        // Retrieve the line and file if set and remove them from the formatted extra
        $file = $line = '';
        if (isset($record['extra']['file'])) {
            $file = $record['extra']['file'];
            unset($record['extra']['file']);
        }
        if (isset($record['extra']['line'])) {
            $line = $record['extra']['line'];
            unset($record['extra']['line']);
        }

        $record = $this->normalize($record);
        $message = array('message' => $record['message']);
        $handleError = false;
        if ($record['context']) {
            $message['context'] = $record['context'];
            $handleError = true;
        }
        if ($record['extra']) {
            $message['extra'] = $record['extra'];
            $handleError = true;
        }
        if (count($message) === 1) {
            $message = reset($message);
        }

        if (isset($record['context'][self::TABLE])) {
            $type  = 'TABLE';
            $label = $record['channel'] .': '. $record['message'];
            $message = $record['context'][self::TABLE];
        } else {
            $type  = $this->logLevels[$record['level']];
            $label = $record['channel'];
        }

        // Create JSON object describing the appearance of the message in the console
        $json = $this->toJson(array(
            array(
                'Type'  => $type,
                'File'  => $file,
                'Line'  => $line,
                'Label' => $label,
            ),
            $message,
        ), $handleError);

        // The message itself is a serialization of the above JSON object + it's length
        return sprintf(
            '%s|%s|',
            strlen($json),
            $json
        );
    }

    public function formatBatch(array $records)
    {
        throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');
    }

    protected function normalize($data)
    {
        if (is_object($data) && !$data instanceof \DateTime) {
            return $data;
        }

        return parent::normalize($data);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;

/**
 * Base Handler class providing the Handler structure
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
abstract class AbstractHandler implements HandlerInterface
{
    protected $level = Logger::DEBUG;
    protected $bubble = true;

    /**
     * @var FormatterInterface
     */
    protected $formatter;
    protected $processors = array();

    /**
     * @param int     $level  The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($level = Logger::DEBUG, $bubble = true)
    {
        $this->setLevel($level);
        $this->bubble = $bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        return $record['level'] >= $this->level;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        foreach ($records as $record) {
            $this->handle($record);
        }
    }

    /**
     * Closes the handler.
     *
     * This will be called automatically when the object is destroyed
     */
    public function close()
    {
    }

    /**
     * {@inheritdoc}
     */
    public function pushProcessor($callback)
    {
        if (!is_callable($callback)) {
            throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
        }
        array_unshift($this->processors, $callback);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function popProcessor()
    {
        if (!$this->processors) {
            throw new \LogicException('You tried to pop from an empty processor stack.');
        }

        return array_shift($this->processors);
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        $this->formatter = $formatter;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        if (!$this->formatter) {
            $this->formatter = $this->getDefaultFormatter();
        }

        return $this->formatter;
    }

    /**
     * Sets minimum logging level at which this handler will be triggered.
     *
     * @param  int|string $level Level or level name
     * @return self
     */
    public function setLevel($level)
    {
        $this->level = Logger::toMonologLevel($level);

        return $this;
    }

    /**
     * Gets minimum logging level at which this handler will be triggered.
     *
     * @return int
     */
    public function getLevel()
    {
        return $this->level;
    }

    /**
     * Sets the bubbling behavior.
     *
     * @param  Boolean $bubble true means that this handler allows bubbling.
     *                         false means that bubbling is not permitted.
     * @return self
     */
    public function setBubble($bubble)
    {
        $this->bubble = $bubble;

        return $this;
    }

    /**
     * Gets the bubbling behavior.
     *
     * @return Boolean true means that this handler allows bubbling.
     *                 false means that bubbling is not permitted.
     */
    public function getBubble()
    {
        return $this->bubble;
    }

    public function __destruct()
    {
        try {
            $this->close();
        } catch (\Exception $e) {
            // do nothing
        } catch (\Throwable $e) {
            // do nothing
        }
    }

    /**
     * Gets the default formatter.
     *
     * @return FormatterInterface
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Base Handler class providing the Handler structure
 *
 * Classes extending it should (in most cases) only implement write($record)
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Christophe Coevoet <stof@notk.org>
 */
abstract class AbstractProcessingHandler extends AbstractHandler
{
    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if (!$this->isHandling($record)) {
            return false;
        }

        $record = $this->processRecord($record);

        $record['formatted'] = $this->getFormatter()->format($record);

        $this->write($record);

        return false === $this->bubble;
    }

    /**
     * Writes the record down to the log of the implementing handler
     *
     * @param  array $record
     * @return void
     */
    abstract protected function write(array $record);

    /**
     * Processes a record.
     *
     * @param  array $record
     * @return array
     */
    protected function processRecord(array $record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;

/**
 * Common syslog functionality
 */
abstract class AbstractSyslogHandler extends AbstractProcessingHandler
{
    protected $facility;

    /**
     * Translates Monolog log levels to syslog log priorities.
     */
    protected $logLevels = array(
        Logger::DEBUG     => LOG_DEBUG,
        Logger::INFO      => LOG_INFO,
        Logger::NOTICE    => LOG_NOTICE,
        Logger::WARNING   => LOG_WARNING,
        Logger::ERROR     => LOG_ERR,
        Logger::CRITICAL  => LOG_CRIT,
        Logger::ALERT     => LOG_ALERT,
        Logger::EMERGENCY => LOG_EMERG,
    );

    /**
     * List of valid log facility names.
     */
    protected $facilities = array(
        'auth'     => LOG_AUTH,
        'authpriv' => LOG_AUTHPRIV,
        'cron'     => LOG_CRON,
        'daemon'   => LOG_DAEMON,
        'kern'     => LOG_KERN,
        'lpr'      => LOG_LPR,
        'mail'     => LOG_MAIL,
        'news'     => LOG_NEWS,
        'syslog'   => LOG_SYSLOG,
        'user'     => LOG_USER,
        'uucp'     => LOG_UUCP,
    );

    /**
     * @param mixed   $facility
     * @param int     $level    The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble   Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);

        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
            $this->facilities['local0'] = LOG_LOCAL0;
            $this->facilities['local1'] = LOG_LOCAL1;
            $this->facilities['local2'] = LOG_LOCAL2;
            $this->facilities['local3'] = LOG_LOCAL3;
            $this->facilities['local4'] = LOG_LOCAL4;
            $this->facilities['local5'] = LOG_LOCAL5;
            $this->facilities['local6'] = LOG_LOCAL6;
            $this->facilities['local7'] = LOG_LOCAL7;
        } else {
            $this->facilities['local0'] = 128; // LOG_LOCAL0
            $this->facilities['local1'] = 136; // LOG_LOCAL1
            $this->facilities['local2'] = 144; // LOG_LOCAL2
            $this->facilities['local3'] = 152; // LOG_LOCAL3
            $this->facilities['local4'] = 160; // LOG_LOCAL4
            $this->facilities['local5'] = 168; // LOG_LOCAL5
            $this->facilities['local6'] = 176; // LOG_LOCAL6
            $this->facilities['local7'] = 184; // LOG_LOCAL7
        }

        // convert textual description of facility to syslog constant
        if (array_key_exists(strtolower($facility), $this->facilities)) {
            $facility = $this->facilities[strtolower($facility)];
        } elseif (!in_array($facility, array_values($this->facilities), true)) {
            throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
        }

        $this->facility = $facility;
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%');
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\JsonFormatter;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Channel\AMQPChannel;
use AMQPExchange;

class AmqpHandler extends AbstractProcessingHandler
{
    /**
     * @var AMQPExchange|AMQPChannel $exchange
     */
    protected $exchange;

    /**
     * @var string
     */
    protected $exchangeName;

    /**
     * @param AMQPExchange|AMQPChannel $exchange     AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use
     * @param string                   $exchangeName
     * @param int                      $level
     * @param bool                     $bubble       Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true)
    {
        if ($exchange instanceof AMQPExchange) {
            $exchange->setName($exchangeName);
        } elseif ($exchange instanceof AMQPChannel) {
            $this->exchangeName = $exchangeName;
        } else {
            throw new \InvalidArgumentException('PhpAmqpLib\Channel\AMQPChannel or AMQPExchange instance required');
        }
        $this->exchange = $exchange;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        $data = $record["formatted"];
        $routingKey = $this->getRoutingKey($record);

        if ($this->exchange instanceof AMQPExchange) {
            $this->exchange->publish(
                $data,
                $routingKey,
                0,
                array(
                    'delivery_mode' => 2,
                    'content_type' => 'application/json',
                )
            );
        } else {
            $this->exchange->basic_publish(
                $this->createAmqpMessage($data),
                $this->exchangeName,
                $routingKey
            );
        }
    }

    /**
     * {@inheritDoc}
     */
    public function handleBatch(array $records)
    {
        if ($this->exchange instanceof AMQPExchange) {
            parent::handleBatch($records);

            return;
        }

        foreach ($records as $record) {
            if (!$this->isHandling($record)) {
                continue;
            }

            $record = $this->processRecord($record);
            $data = $this->getFormatter()->format($record);

            $this->exchange->batch_basic_publish(
                $this->createAmqpMessage($data),
                $this->exchangeName,
                $this->getRoutingKey($record)
            );
        }

        $this->exchange->publish_batch();
    }

    /**
     * Gets the routing key for the AMQP exchange
     *
     * @param  array  $record
     * @return string
     */
    protected function getRoutingKey(array $record)
    {
        $routingKey = sprintf(
            '%s.%s',
            // TODO 2.0 remove substr call
            substr($record['level_name'], 0, 4),
            $record['channel']
        );

        return strtolower($routingKey);
    }

    /**
     * @param  string      $data
     * @return AMQPMessage
     */
    private function createAmqpMessage($data)
    {
        return new AMQPMessage(
            (string) $data,
            array(
                'delivery_mode' => 2,
                'content_type' => 'application/json',
            )
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;

/**
 * Handler sending logs to browser's javascript console with no browser extension required
 *
 * @author Olivier Poitrey <rs@dailymotion.com>
 */
class BrowserConsoleHandler extends AbstractProcessingHandler
{
    protected static $initialized = false;
    protected static $records = array();

    /**
     * {@inheritDoc}
     *
     * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
     *
     * Example of formatted string:
     *
     *     You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%');
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        // Accumulate records
        self::$records[] = $record;

        // Register shutdown handler if not already done
        if (!self::$initialized) {
            self::$initialized = true;
            $this->registerShutdownFunction();
        }
    }

    /**
     * Convert records to javascript console commands and send it to the browser.
     * This method is automatically called on PHP shutdown if output is HTML or Javascript.
     */
    public static function send()
    {
        $format = self::getResponseFormat();
        if ($format === 'unknown') {
            return;
        }

        if (count(self::$records)) {
            if ($format === 'html') {
                self::writeOutput('<script>' . self::generateScript() . '</script>');
            } elseif ($format === 'js') {
                self::writeOutput(self::generateScript());
            }
            self::reset();
        }
    }

    /**
     * Forget all logged records
     */
    public static function reset()
    {
        self::$records = array();
    }

    /**
     * Wrapper for register_shutdown_function to allow overriding
     */
    protected function registerShutdownFunction()
    {
        if (PHP_SAPI !== 'cli') {
            register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send'));
        }
    }

    /**
     * Wrapper for echo to allow overriding
     *
     * @param string $str
     */
    protected static function writeOutput($str)
    {
        echo $str;
    }

    /**
     * Checks the format of the response
     *
     * If Content-Type is set to application/javascript or text/javascript -> js
     * If Content-Type is set to text/html, or is unset -> html
     * If Content-Type is anything else -> unknown
     *
     * @return string One of 'js', 'html' or 'unknown'
     */
    protected static function getResponseFormat()
    {
        // Check content type
        foreach (headers_list() as $header) {
            if (stripos($header, 'content-type:') === 0) {
                // This handler only works with HTML and javascript outputs
                // text/javascript is obsolete in favour of application/javascript, but still used
                if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) {
                    return 'js';
                }
                if (stripos($header, 'text/html') === false) {
                    return 'unknown';
                }
                break;
            }
        }

        return 'html';
    }

    private static function generateScript()
    {
        $script = array();
        foreach (self::$records as $record) {
            $context = self::dump('Context', $record['context']);
            $extra = self::dump('Extra', $record['extra']);

            if (empty($context) && empty($extra)) {
                $script[] = self::call_array('log', self::handleStyles($record['formatted']));
            } else {
                $script = array_merge($script,
                    array(self::call_array('groupCollapsed', self::handleStyles($record['formatted']))),
                    $context,
                    $extra,
                    array(self::call('groupEnd'))
                );
            }
        }

        return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);";
    }

    private static function handleStyles($formatted)
    {
        $args = array(self::quote('font-weight: normal'));
        $format = '%c' . $formatted;
        preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);

        foreach (array_reverse($matches) as $match) {
            $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0]));
            $args[] = '"font-weight: normal"';

            $pos = $match[0][1];
            $format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0]));
        }

        array_unshift($args, self::quote($format));

        return $args;
    }

    private static function handleCustomStyles($style, $string)
    {
        static $colors = array('blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey');
        static $labels = array();

        return preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function ($m) use ($string, &$colors, &$labels) {
            if (trim($m[1]) === 'autolabel') {
                // Format the string as a label with consistent auto assigned background color
                if (!isset($labels[$string])) {
                    $labels[$string] = $colors[count($labels) % count($colors)];
                }
                $color = $labels[$string];

                return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px";
            }

            return $m[1];
        }, $style);
    }

    private static function dump($title, array $dict)
    {
        $script = array();
        $dict = array_filter($dict);
        if (empty($dict)) {
            return $script;
        }
        $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));
        foreach ($dict as $key => $value) {
            $value = json_encode($value);
            if (empty($value)) {
                $value = self::quote('');
            }
            $script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value);
        }

        return $script;
    }

    private static function quote($arg)
    {
        return '"' . addcslashes($arg, "\"\n\\") . '"';
    }

    private static function call()
    {
        $args = func_get_args();
        $method = array_shift($args);

        return self::call_array($method, $args);
    }

    private static function call_array($method, array $args)
    {
        return 'c.' . $method . '(' . implode(', ', $args) . ');';
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Buffers all records until closing the handler and then pass them as batch.
 *
 * This is useful for a MailHandler to send only one mail per request instead of
 * sending one per log message.
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class BufferHandler extends AbstractHandler
{
    protected $handler;
    protected $bufferSize = 0;
    protected $bufferLimit;
    protected $flushOnOverflow;
    protected $buffer = array();
    protected $initialized = false;

    /**
     * @param HandlerInterface $handler         Handler.
     * @param int              $bufferLimit     How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
     * @param int              $level           The minimum logging level at which this handler will be triggered
     * @param Boolean          $bubble          Whether the messages that are handled can bubble up the stack or not
     * @param Boolean          $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
     */
    public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false)
    {
        parent::__construct($level, $bubble);
        $this->handler = $handler;
        $this->bufferLimit = (int) $bufferLimit;
        $this->flushOnOverflow = $flushOnOverflow;
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($record['level'] < $this->level) {
            return false;
        }

        if (!$this->initialized) {
            // __destructor() doesn't get called on Fatal errors
            register_shutdown_function(array($this, 'close'));
            $this->initialized = true;
        }

        if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) {
            if ($this->flushOnOverflow) {
                $this->flush();
            } else {
                array_shift($this->buffer);
                $this->bufferSize--;
            }
        }

        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        $this->buffer[] = $record;
        $this->bufferSize++;

        return false === $this->bubble;
    }

    public function flush()
    {
        if ($this->bufferSize === 0) {
            return;
        }

        $this->handler->handleBatch($this->buffer);
        $this->clear();
    }

    public function __destruct()
    {
        // suppress the parent behavior since we already have register_shutdown_function()
        // to call close(), and the reference contained there will prevent this from being
        // GC'd until the end of the request
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        $this->flush();
    }

    /**
     * Clears the buffer without flushing any messages down to the wrapped handler.
     */
    public function clear()
    {
        $this->bufferSize = 0;
        $this->buffer = array();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\ChromePHPFormatter;
use Monolog\Logger;

/**
 * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
 *
 * This also works out of the box with Firefox 43+
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class ChromePHPHandler extends AbstractProcessingHandler
{
    /**
     * Version of the extension
     */
    const VERSION = '4.0';

    /**
     * Header name
     */
    const HEADER_NAME = 'X-ChromeLogger-Data';
    
    /**
     * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)
     */
    const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}';

    protected static $initialized = false;

    /**
     * Tracks whether we sent too much data
     *
     * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending
     *
     * @var Boolean
     */
    protected static $overflowed = false;

    protected static $json = array(
        'version' => self::VERSION,
        'columns' => array('label', 'log', 'backtrace', 'type'),
        'rows' => array(),
    );

    protected static $sendHeaders = true;

    /**
     * @param int     $level  The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);
        if (!function_exists('json_encode')) {
            throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s ChromePHPHandler');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $messages = array();

        foreach ($records as $record) {
            if ($record['level'] < $this->level) {
                continue;
            }
            $messages[] = $this->processRecord($record);
        }

        if (!empty($messages)) {
            $messages = $this->getFormatter()->formatBatch($messages);
            self::$json['rows'] = array_merge(self::$json['rows'], $messages);
            $this->send();
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new ChromePHPFormatter();
    }

    /**
     * Creates & sends header for a record
     *
     * @see sendHeader()
     * @see send()
     * @param array $record
     */
    protected function write(array $record)
    {
        self::$json['rows'][] = $record['formatted'];

        $this->send();
    }

    /**
     * Sends the log header
     *
     * @see sendHeader()
     */
    protected function send()
    {
        if (self::$overflowed || !self::$sendHeaders) {
            return;
        }

        if (!self::$initialized) {
            self::$initialized = true;

            self::$sendHeaders = $this->headersAccepted();
            if (!self::$sendHeaders) {
                return;
            }

            self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
        }

        $json = @json_encode(self::$json);
        $data = base64_encode(utf8_encode($json));
        if (strlen($data) > 240 * 1024) {
            self::$overflowed = true;

            $record = array(
                'message' => 'Incomplete logs, chrome header size limit reached',
                'context' => array(),
                'level' => Logger::WARNING,
                'level_name' => Logger::getLevelName(Logger::WARNING),
                'channel' => 'monolog',
                'datetime' => new \DateTime(),
                'extra' => array(),
            );
            self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
            $json = @json_encode(self::$json);
            $data = base64_encode(utf8_encode($json));
        }

        if (trim($data) !== '') {
            $this->sendHeader(self::HEADER_NAME, $data);
        }
    }

    /**
     * Send header string to the client
     *
     * @param string $header
     * @param string $content
     */
    protected function sendHeader($header, $content)
    {
        if (!headers_sent() && self::$sendHeaders) {
            header(sprintf('%s: %s', $header, $content));
        }
    }

    /**
     * Verifies if the headers are accepted by the current user agent
     *
     * @return Boolean
     */
    protected function headersAccepted()
    {
        if (empty($_SERVER['HTTP_USER_AGENT'])) {
            return false;
        }

        return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']);
    }

    /**
     * BC getter for the sendHeaders property that has been made static
     */
    public function __get($property)
    {
        if ('sendHeaders' !== $property) {
            throw new \InvalidArgumentException('Undefined property '.$property);
        }

        return static::$sendHeaders;
    }

    /**
     * BC setter for the sendHeaders property that has been made static
     */
    public function __set($property, $value)
    {
        if ('sendHeaders' !== $property) {
            throw new \InvalidArgumentException('Undefined property '.$property);
        }

        static::$sendHeaders = $value;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\JsonFormatter;
use Monolog\Logger;

/**
 * CouchDB handler
 *
 * @author Markus Bachmann <markus.bachmann@bachi.biz>
 */
class CouchDBHandler extends AbstractProcessingHandler
{
    private $options;

    public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true)
    {
        $this->options = array_merge(array(
            'host'     => 'localhost',
            'port'     => 5984,
            'dbname'   => 'logger',
            'username' => null,
            'password' => null,
        ), $options);

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        $basicAuth = null;
        if ($this->options['username']) {
            $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']);
        }

        $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname'];
        $context = stream_context_create(array(
            'http' => array(
                'method'        => 'POST',
                'content'       => $record['formatted'],
                'ignore_errors' => true,
                'max_redirects' => 0,
                'header'        => 'Content-type: application/json',
            ),
        ));

        if (false === @file_get_contents($url, null, $context)) {
            throw new \RuntimeException(sprintf('Could not connect to %s', $url));
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Logs to Cube.
 *
 * @link http://square.github.com/cube/
 * @author Wan Chen <kami@kamisama.me>
 */
class CubeHandler extends AbstractProcessingHandler
{
    private $udpConnection;
    private $httpConnection;
    private $scheme;
    private $host;
    private $port;
    private $acceptedSchemes = array('http', 'udp');

    /**
     * Create a Cube handler
     *
     * @throws \UnexpectedValueException when given url is not a valid url.
     *                                   A valid url must consist of three parts : protocol://host:port
     *                                   Only valid protocols used by Cube are http and udp
     */
    public function __construct($url, $level = Logger::DEBUG, $bubble = true)
    {
        $urlInfo = parse_url($url);

        if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {
            throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
        }

        if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
            throw new \UnexpectedValueException(
                'Invalid protocol (' . $urlInfo['scheme']  . ').'
                . ' Valid options are ' . implode(', ', $this->acceptedSchemes));
        }

        $this->scheme = $urlInfo['scheme'];
        $this->host = $urlInfo['host'];
        $this->port = $urlInfo['port'];

        parent::__construct($level, $bubble);
    }

    /**
     * Establish a connection to an UDP socket
     *
     * @throws \LogicException           when unable to connect to the socket
     * @throws MissingExtensionException when there is no socket extension
     */
    protected function connectUdp()
    {
        if (!extension_loaded('sockets')) {
            throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');
        }

        $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
        if (!$this->udpConnection) {
            throw new \LogicException('Unable to create a socket');
        }

        if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
            throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
        }
    }

    /**
     * Establish a connection to a http server
     * @throws \LogicException when no curl extension
     */
    protected function connectHttp()
    {
        if (!extension_loaded('curl')) {
            throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler');
        }

        $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');

        if (!$this->httpConnection) {
            throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
        }

        curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
        curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $date = $record['datetime'];

        $data = array('time' => $date->format('Y-m-d\TH:i:s.uO'));
        unset($record['datetime']);

        if (isset($record['context']['type'])) {
            $data['type'] = $record['context']['type'];
            unset($record['context']['type']);
        } else {
            $data['type'] = $record['channel'];
        }

        $data['data'] = $record['context'];
        $data['data']['level'] = $record['level'];

        if ($this->scheme === 'http') {
            $this->writeHttp(json_encode($data));
        } else {
            $this->writeUdp(json_encode($data));
        }
    }

    private function writeUdp($data)
    {
        if (!$this->udpConnection) {
            $this->connectUdp();
        }

        socket_send($this->udpConnection, $data, strlen($data), 0);
    }

    private function writeHttp($data)
    {
        if (!$this->httpConnection) {
            $this->connectHttp();
        }

        curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
        curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Content-Length: ' . strlen('['.$data.']'),
        ));

        Curl\Util::execute($this->httpConnection, 5, false);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\Curl;

class Util
{
    private static $retriableErrorCodes = array(
        CURLE_COULDNT_RESOLVE_HOST,
        CURLE_COULDNT_CONNECT,
        CURLE_HTTP_NOT_FOUND,
        CURLE_READ_ERROR,
        CURLE_OPERATION_TIMEOUTED,
        CURLE_HTTP_POST_ERROR,
        CURLE_SSL_CONNECT_ERROR,
    );

    /**
     * Executes a CURL request with optional retries and exception on failure
     *
     * @param  resource          $ch curl handler
     * @throws \RuntimeException
     */
    public static function execute($ch, $retries = 5, $closeAfterDone = true)
    {
        while ($retries--) {
            if (curl_exec($ch) === false) {
                $curlErrno = curl_errno($ch);

                if (false === in_array($curlErrno, self::$retriableErrorCodes, true) || !$retries) {
                    $curlError = curl_error($ch);

                    if ($closeAfterDone) {
                        curl_close($ch);
                    }

                    throw new \RuntimeException(sprintf('Curl error (code %s): %s', $curlErrno, $curlError));
                }

                continue;
            }

            if ($closeAfterDone) {
                curl_close($ch);
            }
            break;
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Simple handler wrapper that deduplicates log records across multiple requests
 *
 * It also includes the BufferHandler functionality and will buffer
 * all messages until the end of the request or flush() is called.
 *
 * This works by storing all log records' messages above $deduplicationLevel
 * to the file specified by $deduplicationStore. When further logs come in at the end of the
 * request (or when flush() is called), all those above $deduplicationLevel are checked
 * against the existing stored logs. If they match and the timestamps in the stored log is
 * not older than $time seconds, the new log record is discarded. If no log record is new, the
 * whole data set is discarded.
 *
 * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers
 * that send messages to people, to avoid spamming with the same message over and over in case of
 * a major component failure like a database server being down which makes all requests fail in the
 * same way.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DeduplicationHandler extends BufferHandler
{
    /**
     * @var string
     */
    protected $deduplicationStore;

    /**
     * @var int
     */
    protected $deduplicationLevel;

    /**
     * @var int
     */
    protected $time;

    /**
     * @var bool
     */
    private $gc = false;

    /**
     * @param HandlerInterface $handler            Handler.
     * @param string           $deduplicationStore The file/path where the deduplication log should be kept
     * @param int              $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
     * @param int              $time               The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
     * @param Boolean          $bubble             Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true)
    {
        parent::__construct($handler, 0, Logger::DEBUG, $bubble, false);

        $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;
        $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);
        $this->time = $time;
    }

    public function flush()
    {
        if ($this->bufferSize === 0) {
            return;
        }

        $passthru = null;

        foreach ($this->buffer as $record) {
            if ($record['level'] >= $this->deduplicationLevel) {

                $passthru = $passthru || !$this->isDuplicate($record);
                if ($passthru) {
                    $this->appendRecord($record);
                }
            }
        }

        // default of null is valid as well as if no record matches duplicationLevel we just pass through
        if ($passthru === true || $passthru === null) {
            $this->handler->handleBatch($this->buffer);
        }

        $this->clear();

        if ($this->gc) {
            $this->collectLogs();
        }
    }

    private function isDuplicate(array $record)
    {
        if (!file_exists($this->deduplicationStore)) {
            return false;
        }

        $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (!is_array($store)) {
            return false;
        }

        $yesterday = time() - 86400;
        $timestampValidity = $record['datetime']->getTimestamp() - $this->time;
        $expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']);

        for ($i = count($store) - 1; $i >= 0; $i--) {
            list($timestamp, $level, $message) = explode(':', $store[$i], 3);

            if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) {
                return true;
            }

            if ($timestamp < $yesterday) {
                $this->gc = true;
            }
        }

        return false;
    }

    private function collectLogs()
    {
        if (!file_exists($this->deduplicationStore)) {
            return false;
        }

        $handle = fopen($this->deduplicationStore, 'rw+');
        flock($handle, LOCK_EX);
        $validLogs = array();

        $timestampValidity = time() - $this->time;

        while (!feof($handle)) {
            $log = fgets($handle);
            if (substr($log, 0, 10) >= $timestampValidity) {
                $validLogs[] = $log;
            }
        }

        ftruncate($handle, 0);
        rewind($handle);
        foreach ($validLogs as $log) {
            fwrite($handle, $log);
        }

        flock($handle, LOCK_UN);
        fclose($handle);

        $this->gc = false;
    }

    private function appendRecord(array $record)
    {
        file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
use Doctrine\CouchDB\CouchDBClient;

/**
 * CouchDB handler for Doctrine CouchDB ODM
 *
 * @author Markus Bachmann <markus.bachmann@bachi.biz>
 */
class DoctrineCouchDBHandler extends AbstractProcessingHandler
{
    private $client;

    public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true)
    {
        $this->client = $client;
        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        $this->client->postDocument($record['formatted']);
    }

    protected function getDefaultFormatter()
    {
        return new NormalizerFormatter;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Aws\Common\Aws;
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Marshaler;
use Monolog\Formatter\ScalarFormatter;
use Monolog\Logger;

/**
 * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/)
 *
 * @link https://github.com/aws/aws-sdk-php/
 * @author Andrew Lawson <adlawson@gmail.com>
 */
class DynamoDbHandler extends AbstractProcessingHandler
{
    const DATE_FORMAT = 'Y-m-d\TH:i:s.uO';

    /**
     * @var DynamoDbClient
     */
    protected $client;

    /**
     * @var string
     */
    protected $table;

    /**
     * @var int
     */
    protected $version;

    /**
     * @var Marshaler
     */
    protected $marshaler;

    /**
     * @param DynamoDbClient $client
     * @param string         $table
     * @param int            $level
     * @param bool           $bubble
     */
    public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true)
    {
        if (defined('Aws\Common\Aws::VERSION') && version_compare(Aws::VERSION, '3.0', '>=')) {
            $this->version = 3;
            $this->marshaler = new Marshaler;
        } else {
            $this->version = 2;
        }

        $this->client = $client;
        $this->table = $table;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $filtered = $this->filterEmptyFields($record['formatted']);
        if ($this->version === 3) {
            $formatted = $this->marshaler->marshalItem($filtered);
        } else {
            $formatted = $this->client->formatAttributes($filtered);
        }

        $this->client->putItem(array(
            'TableName' => $this->table,
            'Item' => $formatted,
        ));
    }

    /**
     * @param  array $record
     * @return array
     */
    protected function filterEmptyFields(array $record)
    {
        return array_filter($record, function ($value) {
            return !empty($value) || false === $value || 0 === $value;
        });
    }

    /**
     * {@inheritdoc}
     */
    protected function getDefaultFormatter()
    {
        return new ScalarFormatter(self::DATE_FORMAT);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\ElasticaFormatter;
use Monolog\Logger;
use Elastica\Client;
use Elastica\Exception\ExceptionInterface;

/**
 * Elastic Search handler
 *
 * Usage example:
 *
 *    $client = new \Elastica\Client();
 *    $options = array(
 *        'index' => 'elastic_index_name',
 *        'type' => 'elastic_doc_type',
 *    );
 *    $handler = new ElasticSearchHandler($client, $options);
 *    $log = new Logger('application');
 *    $log->pushHandler($handler);
 *
 * @author Jelle Vink <jelle.vink@gmail.com>
 */
class ElasticSearchHandler extends AbstractProcessingHandler
{
    /**
     * @var Client
     */
    protected $client;

    /**
     * @var array Handler config options
     */
    protected $options = array();

    /**
     * @param Client  $client  Elastica Client object
     * @param array   $options Handler configuration
     * @param int     $level   The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble  Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);
        $this->client = $client;
        $this->options = array_merge(
            array(
                'index'          => 'monolog',      // Elastic index name
                'type'           => 'record',       // Elastic document type
                'ignore_error'   => false,          // Suppress Elastica exceptions
            ),
            $options
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        $this->bulkSend(array($record['formatted']));
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        if ($formatter instanceof ElasticaFormatter) {
            return parent::setFormatter($formatter);
        }
        throw new \InvalidArgumentException('ElasticSearchHandler is only compatible with ElasticaFormatter');
    }

    /**
     * Getter options
     * @return array
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new ElasticaFormatter($this->options['index'], $this->options['type']);
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $documents = $this->getFormatter()->formatBatch($records);
        $this->bulkSend($documents);
    }

    /**
     * Use Elasticsearch bulk API to send list of documents
     * @param  array             $documents
     * @throws \RuntimeException
     */
    protected function bulkSend(array $documents)
    {
        try {
            $this->client->addDocuments($documents);
        } catch (ExceptionInterface $e) {
            if (!$this->options['ignore_error']) {
                throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e);
            }
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;
use Monolog\Logger;

/**
 * Stores to PHP error_log() handler.
 *
 * @author Elan Ruusamäe <glen@delfi.ee>
 */
class ErrorLogHandler extends AbstractProcessingHandler
{
    const OPERATING_SYSTEM = 0;
    const SAPI = 4;

    protected $messageType;
    protected $expandNewlines;

    /**
     * @param int     $messageType    Says where the error should go.
     * @param int     $level          The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble         Whether the messages that are handled can bubble up the stack or not
     * @param Boolean $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
     */
    public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false)
    {
        parent::__construct($level, $bubble);

        if (false === in_array($messageType, self::getAvailableTypes())) {
            $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
            throw new \InvalidArgumentException($message);
        }

        $this->messageType = $messageType;
        $this->expandNewlines = $expandNewlines;
    }

    /**
     * @return array With all available types
     */
    public static function getAvailableTypes()
    {
        return array(
            self::OPERATING_SYSTEM,
            self::SAPI,
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%');
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        if ($this->expandNewlines) {
            $lines = preg_split('{[\r\n]+}', (string) $record['formatted']);
            foreach ($lines as $line) {
                error_log($line, $this->messageType);
            }
        } else {
            error_log((string) $record['formatted'], $this->messageType);
        }
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Simple handler wrapper that filters records based on a list of levels
 *
 * It can be configured with an exact list of levels to allow, or a min/max level.
 *
 * @author Hennadiy Verkh
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FilterHandler extends AbstractHandler
{
    /**
     * Handler or factory callable($record, $this)
     *
     * @var callable|\Monolog\Handler\HandlerInterface
     */
    protected $handler;

    /**
     * Minimum level for logs that are passed to handler
     *
     * @var int[]
     */
    protected $acceptedLevels;

    /**
     * Whether the messages that are handled can bubble up the stack or not
     *
     * @var Boolean
     */
    protected $bubble;

    /**
     * @param callable|HandlerInterface $handler        Handler or factory callable($record, $this).
     * @param int|array                 $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
     * @param int                       $maxLevel       Maximum level to accept, only used if $minLevelOrList is not an array
     * @param Boolean                   $bubble         Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true)
    {
        $this->handler  = $handler;
        $this->bubble   = $bubble;
        $this->setAcceptedLevels($minLevelOrList, $maxLevel);

        if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
            throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
        }
    }

    /**
     * @return array
     */
    public function getAcceptedLevels()
    {
        return array_flip($this->acceptedLevels);
    }

    /**
     * @param int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided
     * @param int|string       $maxLevel       Maximum level or level name to accept, only used if $minLevelOrList is not an array
     */
    public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY)
    {
        if (is_array($minLevelOrList)) {
            $acceptedLevels = array_map('Monolog\Logger::toMonologLevel', $minLevelOrList);
        } else {
            $minLevelOrList = Logger::toMonologLevel($minLevelOrList);
            $maxLevel = Logger::toMonologLevel($maxLevel);
            $acceptedLevels = array_values(array_filter(Logger::getLevels(), function ($level) use ($minLevelOrList, $maxLevel) {
                return $level >= $minLevelOrList && $level <= $maxLevel;
            }));
        }
        $this->acceptedLevels = array_flip($acceptedLevels);
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        return isset($this->acceptedLevels[$record['level']]);
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if (!$this->isHandling($record)) {
            return false;
        }

        // The same logic as in FingersCrossedHandler
        if (!$this->handler instanceof HandlerInterface) {
            $this->handler = call_user_func($this->handler, $record, $this);
            if (!$this->handler instanceof HandlerInterface) {
                throw new \RuntimeException("The factory callable should return a HandlerInterface");
            }
        }

        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        $this->handler->handle($record);

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $filtered = array();
        foreach ($records as $record) {
            if ($this->isHandling($record)) {
                $filtered[] = $record;
            }
        }

        $this->handler->handleBatch($filtered);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\FingersCrossed;

/**
 * Interface for activation strategies for the FingersCrossedHandler.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
interface ActivationStrategyInterface
{
    /**
     * Returns whether the given record activates the handler.
     *
     * @param  array   $record
     * @return Boolean
     */
    public function isHandlerActivated(array $record);
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\FingersCrossed;

use Monolog\Logger;

/**
 * Channel and Error level based monolog activation strategy. Allows to trigger activation
 * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except
 * for records of the 'sql' channel; those should trigger activation on level 'WARN'.
 *
 * Example:
 *
 * <code>
 *   $activationStrategy = new ChannelLevelActivationStrategy(
 *       Logger::CRITICAL,
 *       array(
 *           'request' => Logger::ALERT,
 *           'sensitive' => Logger::ERROR,
 *       )
 *   );
 *   $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy);
 * </code>
 *
 * @author Mike Meessen <netmikey@gmail.com>
 */
class ChannelLevelActivationStrategy implements ActivationStrategyInterface
{
    private $defaultActionLevel;
    private $channelToActionLevel;

    /**
     * @param int   $defaultActionLevel   The default action level to be used if the record's category doesn't match any
     * @param array $channelToActionLevel An array that maps channel names to action levels.
     */
    public function __construct($defaultActionLevel, $channelToActionLevel = array())
    {
        $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel);
        $this->channelToActionLevel = array_map('Monolog\Logger::toMonologLevel', $channelToActionLevel);
    }

    public function isHandlerActivated(array $record)
    {
        if (isset($this->channelToActionLevel[$record['channel']])) {
            return $record['level'] >= $this->channelToActionLevel[$record['channel']];
        }

        return $record['level'] >= $this->defaultActionLevel;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\FingersCrossed;

use Monolog\Logger;

/**
 * Error level based activation strategy.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ErrorLevelActivationStrategy implements ActivationStrategyInterface
{
    private $actionLevel;

    public function __construct($actionLevel)
    {
        $this->actionLevel = Logger::toMonologLevel($actionLevel);
    }

    public function isHandlerActivated(array $record)
    {
        return $record['level'] >= $this->actionLevel;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;

/**
 * Buffers all records until a certain level is reached
 *
 * The advantage of this approach is that you don't get any clutter in your log files.
 * Only requests which actually trigger an error (or whatever your actionLevel is) will be
 * in the logs, but they will contain all records, not only those above the level threshold.
 *
 * You can find the various activation strategies in the
 * Monolog\Handler\FingersCrossed\ namespace.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FingersCrossedHandler extends AbstractHandler
{
    protected $handler;
    protected $activationStrategy;
    protected $buffering = true;
    protected $bufferSize;
    protected $buffer = array();
    protected $stopBuffering;
    protected $passthruLevel;

    /**
     * @param callable|HandlerInterface       $handler            Handler or factory callable($record, $fingersCrossedHandler).
     * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action
     * @param int                             $bufferSize         How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
     * @param Boolean                         $bubble             Whether the messages that are handled can bubble up the stack or not
     * @param Boolean                         $stopBuffering      Whether the handler should stop buffering after being triggered (default true)
     * @param int                             $passthruLevel      Minimum level to always flush to handler on close, even if strategy not triggered
     */
    public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null)
    {
        if (null === $activationStrategy) {
            $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING);
        }

        // convert simple int activationStrategy to an object
        if (!$activationStrategy instanceof ActivationStrategyInterface) {
            $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy);
        }

        $this->handler = $handler;
        $this->activationStrategy = $activationStrategy;
        $this->bufferSize = $bufferSize;
        $this->bubble = $bubble;
        $this->stopBuffering = $stopBuffering;

        if ($passthruLevel !== null) {
            $this->passthruLevel = Logger::toMonologLevel($passthruLevel);
        }

        if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
            throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
        }
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        return true;
    }

    /**
     * Manually activate this logger regardless of the activation strategy
     */
    public function activate()
    {
        if ($this->stopBuffering) {
            $this->buffering = false;
        }
        if (!$this->handler instanceof HandlerInterface) {
            $record = end($this->buffer) ?: null;

            $this->handler = call_user_func($this->handler, $record, $this);
            if (!$this->handler instanceof HandlerInterface) {
                throw new \RuntimeException("The factory callable should return a HandlerInterface");
            }
        }
        $this->handler->handleBatch($this->buffer);
        $this->buffer = array();
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        if ($this->buffering) {
            $this->buffer[] = $record;
            if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) {
                array_shift($this->buffer);
            }
            if ($this->activationStrategy->isHandlerActivated($record)) {
                $this->activate();
            }
        } else {
            $this->handler->handle($record);
        }

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        if (null !== $this->passthruLevel) {
            $level = $this->passthruLevel;
            $this->buffer = array_filter($this->buffer, function ($record) use ($level) {
                return $record['level'] >= $level;
            });
            if (count($this->buffer) > 0) {
                $this->handler->handleBatch($this->buffer);
                $this->buffer = array();
            }
        }
    }

    /**
     * Resets the state of the handler. Stops forwarding records to the wrapped handler.
     */
    public function reset()
    {
        $this->buffering = true;
    }

    /**
     * Clears the buffer without flushing any messages down to the wrapped handler.
     *
     * It also resets the handler to its initial buffering state.
     */
    public function clear()
    {
        $this->buffer = array();
        $this->reset();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\WildfireFormatter;

/**
 * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol.
 *
 * @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
 */
class FirePHPHandler extends AbstractProcessingHandler
{
    /**
     * WildFire JSON header message format
     */
    const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';

    /**
     * FirePHP structure for parsing messages & their presentation
     */
    const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';

    /**
     * Must reference a "known" plugin, otherwise headers won't display in FirePHP
     */
    const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3';

    /**
     * Header prefix for Wildfire to recognize & parse headers
     */
    const HEADER_PREFIX = 'X-Wf';

    /**
     * Whether or not Wildfire vendor-specific headers have been generated & sent yet
     */
    protected static $initialized = false;

    /**
     * Shared static message index between potentially multiple handlers
     * @var int
     */
    protected static $messageIndex = 1;

    protected static $sendHeaders = true;

    /**
     * Base header creation function used by init headers & record headers
     *
     * @param  array  $meta    Wildfire Plugin, Protocol & Structure Indexes
     * @param  string $message Log message
     * @return array  Complete header string ready for the client as key and message as value
     */
    protected function createHeader(array $meta, $message)
    {
        $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta));

        return array($header => $message);
    }

    /**
     * Creates message header from record
     *
     * @see createHeader()
     * @param  array  $record
     * @return string
     */
    protected function createRecordHeader(array $record)
    {
        // Wildfire is extensible to support multiple protocols & plugins in a single request,
        // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake.
        return $this->createHeader(
            array(1, 1, 1, self::$messageIndex++),
            $record['formatted']
        );
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new WildfireFormatter();
    }

    /**
     * Wildfire initialization headers to enable message parsing
     *
     * @see createHeader()
     * @see sendHeader()
     * @return array
     */
    protected function getInitHeaders()
    {
        // Initial payload consists of required headers for Wildfire
        return array_merge(
            $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI),
            $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI),
            $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI)
        );
    }

    /**
     * Send header string to the client
     *
     * @param string $header
     * @param string $content
     */
    protected function sendHeader($header, $content)
    {
        if (!headers_sent() && self::$sendHeaders) {
            header(sprintf('%s: %s', $header, $content));
        }
    }

    /**
     * Creates & sends header for a record, ensuring init headers have been sent prior
     *
     * @see sendHeader()
     * @see sendInitHeaders()
     * @param array $record
     */
    protected function write(array $record)
    {
        if (!self::$sendHeaders) {
            return;
        }

        // WildFire-specific headers must be sent prior to any messages
        if (!self::$initialized) {
            self::$initialized = true;

            self::$sendHeaders = $this->headersAccepted();
            if (!self::$sendHeaders) {
                return;
            }

            foreach ($this->getInitHeaders() as $header => $content) {
                $this->sendHeader($header, $content);
            }
        }

        $header = $this->createRecordHeader($record);
        if (trim(current($header)) !== '') {
            $this->sendHeader(key($header), current($header));
        }
    }

    /**
     * Verifies if the headers are accepted by the current user agent
     *
     * @return Boolean
     */
    protected function headersAccepted()
    {
        if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) {
            return true;
        }

        return isset($_SERVER['HTTP_X_FIREPHP_VERSION']);
    }

    /**
     * BC getter for the sendHeaders property that has been made static
     */
    public function __get($property)
    {
        if ('sendHeaders' !== $property) {
            throw new \InvalidArgumentException('Undefined property '.$property);
        }

        return static::$sendHeaders;
    }

    /**
     * BC setter for the sendHeaders property that has been made static
     */
    public function __set($property, $value)
    {
        if ('sendHeaders' !== $property) {
            throw new \InvalidArgumentException('Undefined property '.$property);
        }

        static::$sendHeaders = $value;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;
use Monolog\Logger;

/**
 * Sends logs to Fleep.io using Webhook integrations
 *
 * You'll need a Fleep.io account to use this handler.
 *
 * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation
 * @author Ando Roots <ando@sqroot.eu>
 */
class FleepHookHandler extends SocketHandler
{
    const FLEEP_HOST = 'fleep.io';

    const FLEEP_HOOK_URI = '/hook/';

    /**
     * @var string Webhook token (specifies the conversation where logs are sent)
     */
    protected $token;

    /**
     * Construct a new Fleep.io Handler.
     *
     * For instructions on how to create a new web hook in your conversations
     * see https://fleep.io/integrations/webhooks/
     *
     * @param  string                    $token  Webhook token
     * @param  bool|int                  $level  The minimum logging level at which this handler will be triggered
     * @param  bool                      $bubble Whether the messages that are handled can bubble up the stack or not
     * @throws MissingExtensionException
     */
    public function __construct($token, $level = Logger::DEBUG, $bubble = true)
    {
        if (!extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler');
        }

        $this->token = $token;

        $connectionString = 'ssl://' . self::FLEEP_HOST . ':443';
        parent::__construct($connectionString, $level, $bubble);
    }

    /**
     * Returns the default formatter to use with this handler
     *
     * Overloaded to remove empty context and extra arrays from the end of the log message.
     *
     * @return LineFormatter
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter(null, null, true, true);
    }

    /**
     * Handles a log record
     *
     * @param array $record
     */
    public function write(array $record)
    {
        parent::write($record);
        $this->closeSocket();
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    /**
     * Builds the header of the API Call
     *
     * @param  string $content
     * @return string
     */
    private function buildHeader($content)
    {
        $header = "POST " . self::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n";
        $header .= "Host: " . self::FLEEP_HOST . "\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }

    /**
     * Builds the body of API call
     *
     * @param  array  $record
     * @return string
     */
    private function buildContent($record)
    {
        $dataArray = array(
            'message' => $record['formatted'],
        );

        return http_build_query($dataArray);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\FlowdockFormatter;
use Monolog\Formatter\FormatterInterface;

/**
 * Sends notifications through the Flowdock push API
 *
 * This must be configured with a FlowdockFormatter instance via setFormatter()
 *
 * Notes:
 * API token - Flowdock API token
 *
 * @author Dominik Liebler <liebler.dominik@gmail.com>
 * @see https://www.flowdock.com/api/push
 */
class FlowdockHandler extends SocketHandler
{
    /**
     * @var string
     */
    protected $apiToken;

    /**
     * @param string   $apiToken
     * @param bool|int $level    The minimum logging level at which this handler will be triggered
     * @param bool     $bubble   Whether the messages that are handled can bubble up the stack or not
     *
     * @throws MissingExtensionException if OpenSSL is missing
     */
    public function __construct($apiToken, $level = Logger::DEBUG, $bubble = true)
    {
        if (!extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler');
        }

        parent::__construct('ssl://api.flowdock.com:443', $level, $bubble);
        $this->apiToken = $apiToken;
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        if (!$formatter instanceof FlowdockFormatter) {
            throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
        }

        return parent::setFormatter($formatter);
    }

    /**
     * Gets the default formatter.
     *
     * @return FormatterInterface
     */
    protected function getDefaultFormatter()
    {
        throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        parent::write($record);

        $this->closeSocket();
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    /**
     * Builds the body of API call
     *
     * @param  array  $record
     * @return string
     */
    private function buildContent($record)
    {
        return json_encode($record['formatted']['flowdock']);
    }

    /**
     * Builds the header of the API Call
     *
     * @param  string $content
     * @return string
     */
    private function buildHeader($content)
    {
        $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n";
        $header .= "Host: api.flowdock.com\r\n";
        $header .= "Content-Type: application/json\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Gelf\IMessagePublisher;
use Gelf\PublisherInterface;
use Gelf\Publisher;
use InvalidArgumentException;
use Monolog\Logger;
use Monolog\Formatter\GelfMessageFormatter;

/**
 * Handler to send messages to a Graylog2 (http://www.graylog2.org) server
 *
 * @author Matt Lehner <mlehner@gmail.com>
 * @author Benjamin Zikarsky <benjamin@zikarsky.de>
 */
class GelfHandler extends AbstractProcessingHandler
{
    /**
     * @var Publisher the publisher object that sends the message to the server
     */
    protected $publisher;

    /**
     * @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object
     * @param int                                            $level     The minimum logging level at which this handler will be triggered
     * @param bool                                           $bubble    Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($publisher, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);

        if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) {
            throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance');
        }

        $this->publisher = $publisher;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        $this->publisher = null;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $this->publisher->publish($record['formatted']);
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new GelfMessageFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;

/**
 * Forwards records to multiple handlers
 *
 * @author Lenar Lõhmus <lenar@city.ee>
 */
class GroupHandler extends AbstractHandler
{
    protected $handlers;

    /**
     * @param array   $handlers Array of Handlers.
     * @param Boolean $bubble   Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(array $handlers, $bubble = true)
    {
        foreach ($handlers as $handler) {
            if (!$handler instanceof HandlerInterface) {
                throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.');
            }
        }

        $this->handlers = $handlers;
        $this->bubble = $bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        foreach ($this->handlers as $handler) {
            if ($handler->isHandling($record)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        foreach ($this->handlers as $handler) {
            $handler->handle($record);
        }

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        if ($this->processors) {
            $processed = array();
            foreach ($records as $record) {
                foreach ($this->processors as $processor) {
                    $processed[] = call_user_func($processor, $record);
                }
            }
            $records = $processed;
        }

        foreach ($this->handlers as $handler) {
            $handler->handleBatch($records);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        foreach ($this->handlers as $handler) {
            $handler->setFormatter($formatter);
        }

        return $this;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;

/**
 * Interface that all Monolog Handlers must implement
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface HandlerInterface
{
    /**
     * Checks whether the given record will be handled by this handler.
     *
     * This is mostly done for performance reasons, to avoid calling processors for nothing.
     *
     * Handlers should still check the record levels within handle(), returning false in isHandling()
     * is no guarantee that handle() will not be called, and isHandling() might not be called
     * for a given record.
     *
     * @param array $record Partial log record containing only a level key
     *
     * @return Boolean
     */
    public function isHandling(array $record);

    /**
     * Handles a record.
     *
     * All records may be passed to this method, and the handler should discard
     * those that it does not want to handle.
     *
     * The return value of this function controls the bubbling process of the handler stack.
     * Unless the bubbling is interrupted (by returning true), the Logger class will keep on
     * calling further handlers in the stack with a given log record.
     *
     * @param  array   $record The record to handle
     * @return Boolean true means that this handler handled the record, and that bubbling is not permitted.
     *                        false means the record was either not processed or that this handler allows bubbling.
     */
    public function handle(array $record);

    /**
     * Handles a set of records at once.
     *
     * @param array $records The records to handle (an array of record arrays)
     */
    public function handleBatch(array $records);

    /**
     * Adds a processor in the stack.
     *
     * @param  callable $callback
     * @return self
     */
    public function pushProcessor($callback);

    /**
     * Removes the processor on top of the stack and returns it.
     *
     * @return callable
     */
    public function popProcessor();

    /**
     * Sets the formatter.
     *
     * @param  FormatterInterface $formatter
     * @return self
     */
    public function setFormatter(FormatterInterface $formatter);

    /**
     * Gets the formatter.
     *
     * @return FormatterInterface
     */
    public function getFormatter();
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;

/**
 * This simple wrapper class can be used to extend handlers functionality.
 *
 * Example: A filtering handle. Inherit from this class, override isHandling() like this
 *
 * public function isHandling(array $record)
 * {
 *      if ($record meets certain conditions) {
 *          return false;
 *      }
 *      return $this->handler->isHandling($record);
 * }
 *
 * @author Alexey Karapetov <alexey@karapetov.com>
 */
class HandlerWrapper implements HandlerInterface
{
    /**
     * @var HandlerInterface
     */
    protected $handler;

    /**
     * HandlerWrapper constructor.
     * @param HandlerInterface $handler
     */
    public function __construct(HandlerInterface $handler)
    {
        $this->handler = $handler;
    }

    /**
     * {@inheritdoc}
     */
    public function isHandling(array $record)
    {
        return $this->handler->isHandling($record);
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        return $this->handler->handle($record);
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        return $this->handler->handleBatch($records);
    }

    /**
     * {@inheritdoc}
     */
    public function pushProcessor($callback)
    {
        $this->handler->pushProcessor($callback);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function popProcessor()
    {
        return $this->handler->popProcessor();
    }

    /**
     * {@inheritdoc}
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        $this->handler->setFormatter($formatter);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getFormatter()
    {
        return $this->handler->getFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Sends notifications through the hipchat api to a hipchat room
 *
 * Notes:
 * API token - HipChat API token
 * Room      - HipChat Room Id or name, where messages are sent
 * Name      - Name used to send the message (from)
 * notify    - Should the message trigger a notification in the clients
 * version   - The API version to use (HipChatHandler::API_V1 | HipChatHandler::API_V2)
 *
 * @author Rafael Dohms <rafael@doh.ms>
 * @see    https://www.hipchat.com/docs/api
 */
class HipChatHandler extends SocketHandler
{
    /**
     * Use API version 1
     */
    const API_V1 = 'v1';

    /**
     * Use API version v2
     */
    const API_V2 = 'v2';

    /**
     * The maximum allowed length for the name used in the "from" field.
     */
    const MAXIMUM_NAME_LENGTH = 15;

    /**
     * The maximum allowed length for the message.
     */
    const MAXIMUM_MESSAGE_LENGTH = 9500;

    /**
     * @var string
     */
    private $token;

    /**
     * @var string
     */
    private $room;

    /**
     * @var string
     */
    private $name;

    /**
     * @var bool
     */
    private $notify;

    /**
     * @var string
     */
    private $format;

    /**
     * @var string
     */
    private $host;

    /**
     * @var string
     */
    private $version;

    /**
     * @param string $token   HipChat API Token
     * @param string $room    The room that should be alerted of the message (Id or Name)
     * @param string $name    Name used in the "from" field.
     * @param bool   $notify  Trigger a notification in clients or not
     * @param int    $level   The minimum logging level at which this handler will be triggered
     * @param bool   $bubble  Whether the messages that are handled can bubble up the stack or not
     * @param bool   $useSSL  Whether to connect via SSL.
     * @param string $format  The format of the messages (default to text, can be set to html if you have html in the messages)
     * @param string $host    The HipChat server hostname.
     * @param string $version The HipChat API version (default HipChatHandler::API_V1)
     */
    public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1)
    {
        if ($version == self::API_V1 && !$this->validateStringLength($name, static::MAXIMUM_NAME_LENGTH)) {
            throw new \InvalidArgumentException('The supplied name is too long. HipChat\'s v1 API supports names up to 15 UTF-8 characters.');
        }

        $connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80';
        parent::__construct($connectionString, $level, $bubble);

        $this->token = $token;
        $this->name = $name;
        $this->notify = $notify;
        $this->room = $room;
        $this->format = $format;
        $this->host = $host;
        $this->version = $version;
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    /**
     * Builds the body of API call
     *
     * @param  array  $record
     * @return string
     */
    private function buildContent($record)
    {
        $dataArray = array(
            'notify' => $this->version == self::API_V1 ?
                ($this->notify ? 1 : 0) :
                ($this->notify ? 'true' : 'false'),
            'message' => $record['formatted'],
            'message_format' => $this->format,
            'color' => $this->getAlertColor($record['level']),
        );

        if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) {
            if (function_exists('mb_substr')) {
                $dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
            } else {
                $dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
            }
        }

        // if we are using the legacy API then we need to send some additional information
        if ($this->version == self::API_V1) {
            $dataArray['room_id'] = $this->room;
        }

        // append the sender name if it is set
        // always append it if we use the v1 api (it is required in v1)
        if ($this->version == self::API_V1 || $this->name !== null) {
            $dataArray['from'] = (string) $this->name;
        }

        return http_build_query($dataArray);
    }

    /**
     * Builds the header of the API Call
     *
     * @param  string $content
     * @return string
     */
    private function buildHeader($content)
    {
        if ($this->version == self::API_V1) {
            $header = "POST /v1/rooms/message?format=json&auth_token={$this->token} HTTP/1.1\r\n";
        } else {
            // needed for rooms with special (spaces, etc) characters in the name
            $room = rawurlencode($this->room);
            $header = "POST /v2/room/{$room}/notification?auth_token={$this->token} HTTP/1.1\r\n";
        }

        $header .= "Host: {$this->host}\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }

    /**
     * Assigns a color to each level of log records.
     *
     * @param  int    $level
     * @return string
     */
    protected function getAlertColor($level)
    {
        switch (true) {
            case $level >= Logger::ERROR:
                return 'red';
            case $level >= Logger::WARNING:
                return 'yellow';
            case $level >= Logger::INFO:
                return 'green';
            case $level == Logger::DEBUG:
                return 'gray';
            default:
                return 'yellow';
        }
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        parent::write($record);
        $this->closeSocket();
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        if (count($records) == 0) {
            return true;
        }

        $batchRecords = $this->combineRecords($records);

        $handled = false;
        foreach ($batchRecords as $batchRecord) {
            if ($this->isHandling($batchRecord)) {
                $this->write($batchRecord);
                $handled = true;
            }
        }

        if (!$handled) {
            return false;
        }

        return false === $this->bubble;
    }

    /**
     * Combines multiple records into one. Error level of the combined record
     * will be the highest level from the given records. Datetime will be taken
     * from the first record.
     *
     * @param $records
     * @return array
     */
    private function combineRecords($records)
    {
        $batchRecord = null;
        $batchRecords = array();
        $messages = array();
        $formattedMessages = array();
        $level = 0;
        $levelName = null;
        $datetime = null;

        foreach ($records as $record) {
            $record = $this->processRecord($record);

            if ($record['level'] > $level) {
                $level = $record['level'];
                $levelName = $record['level_name'];
            }

            if (null === $datetime) {
                $datetime = $record['datetime'];
            }

            $messages[] = $record['message'];
            $messageStr = implode(PHP_EOL, $messages);
            $formattedMessages[] = $this->getFormatter()->format($record);
            $formattedMessageStr = implode('', $formattedMessages);

            $batchRecord = array(
                'message'   => $messageStr,
                'formatted' => $formattedMessageStr,
                'context'   => array(),
                'extra'     => array(),
            );

            if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) {
                // Pop the last message and implode the remaining messages
                $lastMessage = array_pop($messages);
                $lastFormattedMessage = array_pop($formattedMessages);
                $batchRecord['message'] = implode(PHP_EOL, $messages);
                $batchRecord['formatted'] = implode('', $formattedMessages);

                $batchRecords[] = $batchRecord;
                $messages = array($lastMessage);
                $formattedMessages = array($lastFormattedMessage);

                $batchRecord = null;
            }
        }

        if (null !== $batchRecord) {
            $batchRecords[] = $batchRecord;
        }

        // Set the max level and datetime for all records
        foreach ($batchRecords as &$batchRecord) {
            $batchRecord = array_merge(
                $batchRecord,
                array(
                    'level'      => $level,
                    'level_name' => $levelName,
                    'datetime'   => $datetime,
                )
            );
        }

        return $batchRecords;
    }

    /**
     * Validates the length of a string.
     *
     * If the `mb_strlen()` function is available, it will use that, as HipChat
     * allows UTF-8 characters. Otherwise, it will fall back to `strlen()`.
     *
     * Note that this might cause false failures in the specific case of using
     * a valid name with less than 16 characters, but 16 or more bytes, on a
     * system where `mb_strlen()` is unavailable.
     *
     * @param string $str
     * @param int    $length
     *
     * @return bool
     */
    private function validateStringLength($str, $length)
    {
        if (function_exists('mb_strlen')) {
            return (mb_strlen($str) <= $length);
        }

        return (strlen($str) <= $length);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * IFTTTHandler uses cURL to trigger IFTTT Maker actions
 *
 * Register a secret key and trigger/event name at https://ifttt.com/maker
 *
 * value1 will be the channel from monolog's Logger constructor,
 * value2 will be the level name (ERROR, WARNING, ..)
 * value3 will be the log record's message
 *
 * @author Nehal Patel <nehal@nehalpatel.me>
 */
class IFTTTHandler extends AbstractProcessingHandler
{
    private $eventName;
    private $secretKey;

    /**
     * @param string  $eventName The name of the IFTTT Maker event that should be triggered
     * @param string  $secretKey A valid IFTTT secret key
     * @param int     $level     The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble    Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true)
    {
        $this->eventName = $eventName;
        $this->secretKey = $secretKey;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritdoc}
     */
    public function write(array $record)
    {
        $postData = array(
            "value1" => $record["channel"],
            "value2" => $record["level_name"],
            "value3" => $record["message"],
        );
        $postString = json_encode($postData);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postString);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            "Content-Type: application/json",
        ));

        Curl\Util::execute($ch);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * @author Robert Kaufmann III <rok3@rok3.me>
 */
class LogEntriesHandler extends SocketHandler
{
    /**
     * @var string
     */
    protected $logToken;

    /**
     * @param string $token  Log token supplied by LogEntries
     * @param bool   $useSSL Whether or not SSL encryption should be used.
     * @param int    $level  The minimum logging level to trigger this handler
     * @param bool   $bubble Whether or not messages that are handled should bubble up the stack.
     *
     * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
     */
    public function __construct($token, $useSSL = true, $level = Logger::DEBUG, $bubble = true)
    {
        if ($useSSL && !extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
        }

        $endpoint = $useSSL ? 'ssl://data.logentries.com:443' : 'data.logentries.com:80';
        parent::__construct($endpoint, $level, $bubble);
        $this->logToken = $token;
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        return $this->logToken . ' ' . $record['formatted'];
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\LogglyFormatter;

/**
 * Sends errors to Loggly.
 *
 * @author Przemek Sobstel <przemek@sobstel.org>
 * @author Adam Pancutt <adam@pancutt.com>
 * @author Gregory Barchard <gregory@barchard.net>
 */
class LogglyHandler extends AbstractProcessingHandler
{
    const HOST = 'logs-01.loggly.com';
    const ENDPOINT_SINGLE = 'inputs';
    const ENDPOINT_BATCH = 'bulk';

    protected $token;

    protected $tag = array();

    public function __construct($token, $level = Logger::DEBUG, $bubble = true)
    {
        if (!extension_loaded('curl')) {
            throw new \LogicException('The curl extension is needed to use the LogglyHandler');
        }

        $this->token = $token;

        parent::__construct($level, $bubble);
    }

    public function setTag($tag)
    {
        $tag = !empty($tag) ? $tag : array();
        $this->tag = is_array($tag) ? $tag : array($tag);
    }

    public function addTag($tag)
    {
        if (!empty($tag)) {
            $tag = is_array($tag) ? $tag : array($tag);
            $this->tag = array_unique(array_merge($this->tag, $tag));
        }
    }

    protected function write(array $record)
    {
        $this->send($record["formatted"], self::ENDPOINT_SINGLE);
    }

    public function handleBatch(array $records)
    {
        $level = $this->level;

        $records = array_filter($records, function ($record) use ($level) {
            return ($record['level'] >= $level);
        });

        if ($records) {
            $this->send($this->getFormatter()->formatBatch($records), self::ENDPOINT_BATCH);
        }
    }

    protected function send($data, $endpoint)
    {
        $url = sprintf("https://%s/%s/%s/", self::HOST, $endpoint, $this->token);

        $headers = array('Content-Type: application/json');

        if (!empty($this->tag)) {
            $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag);
        }

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        Curl\Util::execute($ch);
    }

    protected function getDefaultFormatter()
    {
        return new LogglyFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Base class for all mail handlers
 *
 * @author Gyula Sallai
 */
abstract class MailHandler extends AbstractProcessingHandler
{
    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $messages = array();

        foreach ($records as $record) {
            if ($record['level'] < $this->level) {
                continue;
            }
            $messages[] = $this->processRecord($record);
        }

        if (!empty($messages)) {
            $this->send((string) $this->getFormatter()->formatBatch($messages), $messages);
        }
    }

    /**
     * Send a mail with the given content
     *
     * @param string $content formatted email body to be sent
     * @param array  $records the array of log records that formed this content
     */
    abstract protected function send($content, array $records);

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $this->send((string) $record['formatted'], array($record));
    }

    protected function getHighestRecord(array $records)
    {
        $highestRecord = null;
        foreach ($records as $record) {
            if ($highestRecord === null || $highestRecord['level'] < $record['level']) {
                $highestRecord = $record;
            }
        }

        return $highestRecord;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * MandrillHandler uses cURL to send the emails to the Mandrill API
 *
 * @author Adam Nicholson <adamnicholson10@gmail.com>
 */
class MandrillHandler extends MailHandler
{
    protected $message;
    protected $apiKey;

    /**
     * @param string                  $apiKey  A valid Mandrill API key
     * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
     * @param int                     $level   The minimum logging level at which this handler will be triggered
     * @param Boolean                 $bubble  Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true)
    {
        parent::__construct($level, $bubble);

        if (!$message instanceof \Swift_Message && is_callable($message)) {
            $message = call_user_func($message);
        }
        if (!$message instanceof \Swift_Message) {
            throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it');
        }
        $this->message = $message;
        $this->apiKey = $apiKey;
    }

    /**
     * {@inheritdoc}
     */
    protected function send($content, array $records)
    {
        $message = clone $this->message;
        $message->setBody($content);
        $message->setDate(time());

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json');
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
            'key' => $this->apiKey,
            'raw_message' => (string) $message,
            'async' => false,
        )));

        Curl\Util::execute($ch);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Exception can be thrown if an extension for an handler is missing
 *
 * @author  Christian Bergau <cbergau86@gmail.com>
 */
class MissingExtensionException extends \Exception
{
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;

/**
 * Logs to a MongoDB database.
 *
 * usage example:
 *
 *   $log = new Logger('application');
 *   $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod");
 *   $log->pushHandler($mongodb);
 *
 * @author Thomas Tourlourat <thomas@tourlourat.com>
 */
class MongoDBHandler extends AbstractProcessingHandler
{
    protected $mongoCollection;

    public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true)
    {
        if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) {
            throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required');
        }

        $this->mongoCollection = $mongo->selectCollection($database, $collection);

        parent::__construct($level, $bubble);
    }

    protected function write(array $record)
    {
        if ($this->mongoCollection instanceof \MongoDB\Collection) {
            $this->mongoCollection->insertOne($record["formatted"]);
        } else {
            $this->mongoCollection->save($record["formatted"]);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new NormalizerFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;

/**
 * NativeMailerHandler uses the mail() function to send the emails
 *
 * @author Christophe Coevoet <stof@notk.org>
 * @author Mark Garrett <mark@moderndeveloperllc.com>
 */
class NativeMailerHandler extends MailHandler
{
    /**
     * The email addresses to which the message will be sent
     * @var array
     */
    protected $to;

    /**
     * The subject of the email
     * @var string
     */
    protected $subject;

    /**
     * Optional headers for the message
     * @var array
     */
    protected $headers = array();

    /**
     * Optional parameters for the message
     * @var array
     */
    protected $parameters = array();

    /**
     * The wordwrap length for the message
     * @var int
     */
    protected $maxColumnWidth;

    /**
     * The Content-type for the message
     * @var string
     */
    protected $contentType = 'text/plain';

    /**
     * The encoding for the message
     * @var string
     */
    protected $encoding = 'utf-8';

    /**
     * @param string|array $to             The receiver of the mail
     * @param string       $subject        The subject of the mail
     * @param string       $from           The sender of the mail
     * @param int          $level          The minimum logging level at which this handler will be triggered
     * @param bool         $bubble         Whether the messages that are handled can bubble up the stack or not
     * @param int          $maxColumnWidth The maximum column width that the message lines will have
     */
    public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70)
    {
        parent::__construct($level, $bubble);
        $this->to = is_array($to) ? $to : array($to);
        $this->subject = $subject;
        $this->addHeader(sprintf('From: %s', $from));
        $this->maxColumnWidth = $maxColumnWidth;
    }

    /**
     * Add headers to the message
     *
     * @param  string|array $headers Custom added headers
     * @return self
     */
    public function addHeader($headers)
    {
        foreach ((array) $headers as $header) {
            if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) {
                throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons');
            }
            $this->headers[] = $header;
        }

        return $this;
    }

    /**
     * Add parameters to the message
     *
     * @param  string|array $parameters Custom added parameters
     * @return self
     */
    public function addParameter($parameters)
    {
        $this->parameters = array_merge($this->parameters, (array) $parameters);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    protected function send($content, array $records)
    {
        $content = wordwrap($content, $this->maxColumnWidth);
        $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n");
        $headers .= 'Content-type: ' . $this->getContentType() . '; charset=' . $this->getEncoding() . "\r\n";
        if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) {
            $headers .= 'MIME-Version: 1.0' . "\r\n";
        }

        $subject = $this->subject;
        if ($records) {
            $subjectFormatter = new LineFormatter($this->subject);
            $subject = $subjectFormatter->format($this->getHighestRecord($records));
        }

        $parameters = implode(' ', $this->parameters);
        foreach ($this->to as $to) {
            mail($to, $subject, $content, $headers, $parameters);
        }
    }

    /**
     * @return string $contentType
     */
    public function getContentType()
    {
        return $this->contentType;
    }

    /**
     * @return string $encoding
     */
    public function getEncoding()
    {
        return $this->encoding;
    }

    /**
     * @param  string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML
     *                             messages.
     * @return self
     */
    public function setContentType($contentType)
    {
        if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) {
            throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection');
        }

        $this->contentType = $contentType;

        return $this;
    }

    /**
     * @param  string $encoding
     * @return self
     */
    public function setEncoding($encoding)
    {
        if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) {
            throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection');
        }

        $this->encoding = $encoding;

        return $this;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;

/**
 * Class to record a log on a NewRelic application.
 * Enabling New Relic High Security mode may prevent capture of useful information.
 *
 * @see https://docs.newrelic.com/docs/agents/php-agent
 * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security
 */
class NewRelicHandler extends AbstractProcessingHandler
{
    /**
     * Name of the New Relic application that will receive logs from this handler.
     *
     * @var string
     */
    protected $appName;

    /**
     * Name of the current transaction
     *
     * @var string
     */
    protected $transactionName;

    /**
     * Some context and extra data is passed into the handler as arrays of values. Do we send them as is
     * (useful if we are using the API), or explode them for display on the NewRelic RPM website?
     *
     * @var bool
     */
    protected $explodeArrays;

    /**
     * {@inheritDoc}
     *
     * @param string $appName
     * @param bool   $explodeArrays
     * @param string $transactionName
     */
    public function __construct(
        $level = Logger::ERROR,
        $bubble = true,
        $appName = null,
        $explodeArrays = false,
        $transactionName = null
    ) {
        parent::__construct($level, $bubble);

        $this->appName       = $appName;
        $this->explodeArrays = $explodeArrays;
        $this->transactionName = $transactionName;
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        if (!$this->isNewRelicEnabled()) {
            throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler');
        }

        if ($appName = $this->getAppName($record['context'])) {
            $this->setNewRelicAppName($appName);
        }

        if ($transactionName = $this->getTransactionName($record['context'])) {
            $this->setNewRelicTransactionName($transactionName);
            unset($record['formatted']['context']['transaction_name']);
        }

        if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
            newrelic_notice_error($record['message'], $record['context']['exception']);
            unset($record['formatted']['context']['exception']);
        } else {
            newrelic_notice_error($record['message']);
        }

        if (isset($record['formatted']['context']) && is_array($record['formatted']['context'])) {
            foreach ($record['formatted']['context'] as $key => $parameter) {
                if (is_array($parameter) && $this->explodeArrays) {
                    foreach ($parameter as $paramKey => $paramValue) {
                        $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue);
                    }
                } else {
                    $this->setNewRelicParameter('context_' . $key, $parameter);
                }
            }
        }

        if (isset($record['formatted']['extra']) && is_array($record['formatted']['extra'])) {
            foreach ($record['formatted']['extra'] as $key => $parameter) {
                if (is_array($parameter) && $this->explodeArrays) {
                    foreach ($parameter as $paramKey => $paramValue) {
                        $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue);
                    }
                } else {
                    $this->setNewRelicParameter('extra_' . $key, $parameter);
                }
            }
        }
    }

    /**
     * Checks whether the NewRelic extension is enabled in the system.
     *
     * @return bool
     */
    protected function isNewRelicEnabled()
    {
        return extension_loaded('newrelic');
    }

    /**
     * Returns the appname where this log should be sent. Each log can override the default appname, set in this
     * handler's constructor, by providing the appname in it's context.
     *
     * @param  array       $context
     * @return null|string
     */
    protected function getAppName(array $context)
    {
        if (isset($context['appname'])) {
            return $context['appname'];
        }

        return $this->appName;
    }

    /**
     * Returns the name of the current transaction. Each log can override the default transaction name, set in this
     * handler's constructor, by providing the transaction_name in it's context
     *
     * @param array $context
     *
     * @return null|string
     */
    protected function getTransactionName(array $context)
    {
        if (isset($context['transaction_name'])) {
            return $context['transaction_name'];
        }

        return $this->transactionName;
    }

    /**
     * Sets the NewRelic application that should receive this log.
     *
     * @param string $appName
     */
    protected function setNewRelicAppName($appName)
    {
        newrelic_set_appname($appName);
    }

    /**
     * Overwrites the name of the current transaction
     *
     * @param string $transactionName
     */
    protected function setNewRelicTransactionName($transactionName)
    {
        newrelic_name_transaction($transactionName);
    }

    /**
     * @param string $key
     * @param mixed  $value
     */
    protected function setNewRelicParameter($key, $value)
    {
        if (null === $value || is_scalar($value)) {
            newrelic_add_custom_parameter($key, $value);
        } else {
            newrelic_add_custom_parameter($key, @json_encode($value));
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new NormalizerFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Blackhole
 *
 * Any record it can handle will be thrown away. This can be used
 * to put on top of an existing stack to override it temporarily.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NullHandler extends AbstractHandler
{
    /**
     * @param int $level The minimum logging level at which this handler will be triggered
     */
    public function __construct($level = Logger::DEBUG)
    {
        parent::__construct($level, false);
    }

    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($record['level'] < $this->level) {
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Exception;
use Monolog\Formatter\LineFormatter;
use Monolog\Logger;
use PhpConsole\Connector;
use PhpConsole\Handler;
use PhpConsole\Helper;

/**
 * Monolog handler for Google Chrome extension "PHP Console"
 *
 * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely
 *
 * Usage:
 * 1. Install Google Chrome extension https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef
 * 2. See overview https://github.com/barbushin/php-console#overview
 * 3. Install PHP Console library https://github.com/barbushin/php-console#installation
 * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png)
 *
 *      $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler()));
 *      \Monolog\ErrorHandler::register($logger);
 *      echo $undefinedVar;
 *      $logger->addDebug('SELECT * FROM users', array('db', 'time' => 0.012));
 *      PC::debug($_SERVER); // PHP Console debugger for any type of vars
 *
 * @author Sergey Barbushin https://www.linkedin.com/in/barbushin
 */
class PHPConsoleHandler extends AbstractProcessingHandler
{
    private $options = array(
        'enabled' => true, // bool Is PHP Console server enabled
        'classesPartialsTraceIgnore' => array('Monolog\\'), // array Hide calls of classes started with...
        'debugTagsKeysInContext' => array(0, 'tag'), // bool Is PHP Console server enabled
        'useOwnErrorsHandler' => false, // bool Enable errors handling
        'useOwnExceptionsHandler' => false, // bool Enable exceptions handling
        'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths
        'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s')
        'serverEncoding' => null, // string|null Server internal encoding
        'headersLimit' => null, // int|null Set headers size limit for your web-server
        'password' => null, // string|null Protect PHP Console connection by password
        'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed
        'ipMasks' => array(), // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1')
        'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required)
        'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings
        'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level
        'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number
        'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item
        'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON
        'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug
        'dataStorage' => null, // PhpConsole\Storage|null Fixes problem with custom $_SESSION handler(see http://goo.gl/Ne8juJ)
    );

    /** @var Connector */
    private $connector;

    /**
     * @param  array          $options   See \Monolog\Handler\PHPConsoleHandler::$options for more details
     * @param  Connector|null $connector Instance of \PhpConsole\Connector class (optional)
     * @param  int            $level
     * @param  bool           $bubble
     * @throws Exception
     */
    public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true)
    {
        if (!class_exists('PhpConsole\Connector')) {
            throw new Exception('PHP Console library not found. See https://github.com/barbushin/php-console#installation');
        }
        parent::__construct($level, $bubble);
        $this->options = $this->initOptions($options);
        $this->connector = $this->initConnector($connector);
    }

    private function initOptions(array $options)
    {
        $wrongOptions = array_diff(array_keys($options), array_keys($this->options));
        if ($wrongOptions) {
            throw new Exception('Unknown options: ' . implode(', ', $wrongOptions));
        }

        return array_replace($this->options, $options);
    }

    private function initConnector(Connector $connector = null)
    {
        if (!$connector) {
            if ($this->options['dataStorage']) {
                Connector::setPostponeStorage($this->options['dataStorage']);
            }
            $connector = Connector::getInstance();
        }

        if ($this->options['registerHelper'] && !Helper::isRegistered()) {
            Helper::register();
        }

        if ($this->options['enabled'] && $connector->isActiveClient()) {
            if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) {
                $handler = Handler::getInstance();
                $handler->setHandleErrors($this->options['useOwnErrorsHandler']);
                $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']);
                $handler->start();
            }
            if ($this->options['sourcesBasePath']) {
                $connector->setSourcesBasePath($this->options['sourcesBasePath']);
            }
            if ($this->options['serverEncoding']) {
                $connector->setServerEncoding($this->options['serverEncoding']);
            }
            if ($this->options['password']) {
                $connector->setPassword($this->options['password']);
            }
            if ($this->options['enableSslOnlyMode']) {
                $connector->enableSslOnlyMode();
            }
            if ($this->options['ipMasks']) {
                $connector->setAllowedIpMasks($this->options['ipMasks']);
            }
            if ($this->options['headersLimit']) {
                $connector->setHeadersLimit($this->options['headersLimit']);
            }
            if ($this->options['detectDumpTraceAndSource']) {
                $connector->getDebugDispatcher()->detectTraceAndSource = true;
            }
            $dumper = $connector->getDumper();
            $dumper->levelLimit = $this->options['dumperLevelLimit'];
            $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit'];
            $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit'];
            $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit'];
            $dumper->detectCallbacks = $this->options['dumperDetectCallbacks'];
            if ($this->options['enableEvalListener']) {
                $connector->startEvalRequestsListener();
            }
        }

        return $connector;
    }

    public function getConnector()
    {
        return $this->connector;
    }

    public function getOptions()
    {
        return $this->options;
    }

    public function handle(array $record)
    {
        if ($this->options['enabled'] && $this->connector->isActiveClient()) {
            return parent::handle($record);
        }

        return !$this->bubble;
    }

    /**
     * Writes the record down to the log of the implementing handler
     *
     * @param  array $record
     * @return void
     */
    protected function write(array $record)
    {
        if ($record['level'] < Logger::NOTICE) {
            $this->handleDebugRecord($record);
        } elseif (isset($record['context']['exception']) && $record['context']['exception'] instanceof Exception) {
            $this->handleExceptionRecord($record);
        } else {
            $this->handleErrorRecord($record);
        }
    }

    private function handleDebugRecord(array $record)
    {
        $tags = $this->getRecordTags($record);
        $message = $record['message'];
        if ($record['context']) {
            $message .= ' ' . json_encode($this->connector->getDumper()->dump(array_filter($record['context'])));
        }
        $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']);
    }

    private function handleExceptionRecord(array $record)
    {
        $this->connector->getErrorsDispatcher()->dispatchException($record['context']['exception']);
    }

    private function handleErrorRecord(array $record)
    {
        $context = $record['context'];

        $this->connector->getErrorsDispatcher()->dispatchError(
            isset($context['code']) ? $context['code'] : null,
            isset($context['message']) ? $context['message'] : $record['message'],
            isset($context['file']) ? $context['file'] : null,
            isset($context['line']) ? $context['line'] : null,
            $this->options['classesPartialsTraceIgnore']
        );
    }

    private function getRecordTags(array &$record)
    {
        $tags = null;
        if (!empty($record['context'])) {
            $context = & $record['context'];
            foreach ($this->options['debugTagsKeysInContext'] as $key) {
                if (!empty($context[$key])) {
                    $tags = $context[$key];
                    if ($key === 0) {
                        array_shift($context);
                    } else {
                        unset($context[$key]);
                    }
                    break;
                }
            }
        }

        return $tags ?: strtolower($record['level_name']);
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('%message%');
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Psr\Log\LoggerInterface;

/**
 * Proxies log messages to an existing PSR-3 compliant logger.
 *
 * @author Michael Moussa <michael.moussa@gmail.com>
 */
class PsrHandler extends AbstractHandler
{
    /**
     * PSR-3 compliant logger
     *
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied
     * @param int             $level  The minimum logging level at which this handler will be triggered
     * @param Boolean         $bubble Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->logger = $logger;
    }

    /**
     * {@inheritDoc}
     */
    public function handle(array $record)
    {
        if (!$this->isHandling($record)) {
            return false;
        }

        $this->logger->log(strtolower($record['level_name']), $record['message'], $record['context']);

        return false === $this->bubble;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Sends notifications through the pushover api to mobile phones
 *
 * @author Sebastian Göttschkes <sebastian.goettschkes@googlemail.com>
 * @see    https://www.pushover.net/api
 */
class PushoverHandler extends SocketHandler
{
    private $token;
    private $users;
    private $title;
    private $user;
    private $retry;
    private $expire;

    private $highPriorityLevel;
    private $emergencyLevel;
    private $useFormattedMessage = false;

    /**
     * All parameters that can be sent to Pushover
     * @see https://pushover.net/api
     * @var array
     */
    private $parameterNames = array(
        'token' => true,
        'user' => true,
        'message' => true,
        'device' => true,
        'title' => true,
        'url' => true,
        'url_title' => true,
        'priority' => true,
        'timestamp' => true,
        'sound' => true,
        'retry' => true,
        'expire' => true,
        'callback' => true,
    );

    /**
     * Sounds the api supports by default
     * @see https://pushover.net/api#sounds
     * @var array
     */
    private $sounds = array(
        'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming',
        'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb',
        'persistent', 'echo', 'updown', 'none',
    );

    /**
     * @param string       $token             Pushover api token
     * @param string|array $users             Pushover user id or array of ids the message will be sent to
     * @param string       $title             Title sent to the Pushover API
     * @param int          $level             The minimum logging level at which this handler will be triggered
     * @param Boolean      $bubble            Whether the messages that are handled can bubble up the stack or not
     * @param Boolean      $useSSL            Whether to connect via SSL. Required when pushing messages to users that are not
     *                                        the pushover.net app owner. OpenSSL is required for this option.
     * @param int          $highPriorityLevel The minimum logging level at which this handler will start
     *                                        sending "high priority" requests to the Pushover API
     * @param int          $emergencyLevel    The minimum logging level at which this handler will start
     *                                        sending "emergency" requests to the Pushover API
     * @param int          $retry             The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user.
     * @param int          $expire            The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds).
     */
    public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200)
    {
        $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80';
        parent::__construct($connectionString, $level, $bubble);

        $this->token = $token;
        $this->users = (array) $users;
        $this->title = $title ?: gethostname();
        $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel);
        $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel);
        $this->retry = $retry;
        $this->expire = $expire;
    }

    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    private function buildContent($record)
    {
        // Pushover has a limit of 512 characters on title and message combined.
        $maxMessageLength = 512 - strlen($this->title);

        $message = ($this->useFormattedMessage) ? $record['formatted'] : $record['message'];
        $message = substr($message, 0, $maxMessageLength);

        $timestamp = $record['datetime']->getTimestamp();

        $dataArray = array(
            'token' => $this->token,
            'user' => $this->user,
            'message' => $message,
            'title' => $this->title,
            'timestamp' => $timestamp,
        );

        if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) {
            $dataArray['priority'] = 2;
            $dataArray['retry'] = $this->retry;
            $dataArray['expire'] = $this->expire;
        } elseif (isset($record['level']) && $record['level'] >= $this->highPriorityLevel) {
            $dataArray['priority'] = 1;
        }

        // First determine the available parameters
        $context = array_intersect_key($record['context'], $this->parameterNames);
        $extra = array_intersect_key($record['extra'], $this->parameterNames);

        // Least important info should be merged with subsequent info
        $dataArray = array_merge($extra, $context, $dataArray);

        // Only pass sounds that are supported by the API
        if (isset($dataArray['sound']) && !in_array($dataArray['sound'], $this->sounds)) {
            unset($dataArray['sound']);
        }

        return http_build_query($dataArray);
    }

    private function buildHeader($content)
    {
        $header = "POST /1/messages.json HTTP/1.1\r\n";
        $header .= "Host: api.pushover.net\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }

    protected function write(array $record)
    {
        foreach ($this->users as $user) {
            $this->user = $user;

            parent::write($record);
            $this->closeSocket();
        }

        $this->user = null;
    }

    public function setHighPriorityLevel($value)
    {
        $this->highPriorityLevel = $value;
    }

    public function setEmergencyLevel($value)
    {
        $this->emergencyLevel = $value;
    }

    /**
     * Use the formatted message?
     * @param bool $value
     */
    public function useFormattedMessage($value)
    {
        $this->useFormattedMessage = (boolean) $value;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Raven_Client;

/**
 * Handler to send messages to a Sentry (https://github.com/getsentry/sentry) server
 * using raven-php (https://github.com/getsentry/raven-php)
 *
 * @author Marc Abramowitz <marc@marc-abramowitz.com>
 */
class RavenHandler extends AbstractProcessingHandler
{
    /**
     * Translates Monolog log levels to Raven log levels.
     */
    private $logLevels = array(
        Logger::DEBUG     => Raven_Client::DEBUG,
        Logger::INFO      => Raven_Client::INFO,
        Logger::NOTICE    => Raven_Client::INFO,
        Logger::WARNING   => Raven_Client::WARNING,
        Logger::ERROR     => Raven_Client::ERROR,
        Logger::CRITICAL  => Raven_Client::FATAL,
        Logger::ALERT     => Raven_Client::FATAL,
        Logger::EMERGENCY => Raven_Client::FATAL,
    );

    /**
     * @var string should represent the current version of the calling
     *             software. Can be any string (git commit, version number)
     */
    private $release;

    /**
     * @var Raven_Client the client object that sends the message to the server
     */
    protected $ravenClient;

    /**
     * @var LineFormatter The formatter to use for the logs generated via handleBatch()
     */
    protected $batchFormatter;

    /**
     * @param Raven_Client $ravenClient
     * @param int          $level       The minimum logging level at which this handler will be triggered
     * @param Boolean      $bubble      Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->ravenClient = $ravenClient;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        $level = $this->level;

        // filter records based on their level
        $records = array_filter($records, function ($record) use ($level) {
            return $record['level'] >= $level;
        });

        if (!$records) {
            return;
        }

        // the record with the highest severity is the "main" one
        $record = array_reduce($records, function ($highest, $record) {
            if ($record['level'] > $highest['level']) {
                return $record;
            }

            return $highest;
        });

        // the other ones are added as a context item
        $logs = array();
        foreach ($records as $r) {
            $logs[] = $this->processRecord($r);
        }

        if ($logs) {
            $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs);
        }

        $this->handle($record);
    }

    /**
     * Sets the formatter for the logs generated by handleBatch().
     *
     * @param FormatterInterface $formatter
     */
    public function setBatchFormatter(FormatterInterface $formatter)
    {
        $this->batchFormatter = $formatter;
    }

    /**
     * Gets the formatter for the logs generated by handleBatch().
     *
     * @return FormatterInterface
     */
    public function getBatchFormatter()
    {
        if (!$this->batchFormatter) {
            $this->batchFormatter = $this->getDefaultBatchFormatter();
        }

        return $this->batchFormatter;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $previousUserContext = false;
        $options = array();
        $options['level'] = $this->logLevels[$record['level']];
        $options['tags'] = array();
        if (!empty($record['extra']['tags'])) {
            $options['tags'] = array_merge($options['tags'], $record['extra']['tags']);
            unset($record['extra']['tags']);
        }
        if (!empty($record['context']['tags'])) {
            $options['tags'] = array_merge($options['tags'], $record['context']['tags']);
            unset($record['context']['tags']);
        }
        if (!empty($record['context']['fingerprint'])) {
            $options['fingerprint'] = $record['context']['fingerprint'];
            unset($record['context']['fingerprint']);
        }
        if (!empty($record['context']['logger'])) {
            $options['logger'] = $record['context']['logger'];
            unset($record['context']['logger']);
        } else {
            $options['logger'] = $record['channel'];
        }
        foreach ($this->getExtraParameters() as $key) {
            foreach (array('extra', 'context') as $source) {
                if (!empty($record[$source][$key])) {
                    $options[$key] = $record[$source][$key];
                    unset($record[$source][$key]);
                }
            }
        }
        if (!empty($record['context'])) {
            $options['extra']['context'] = $record['context'];
            if (!empty($record['context']['user'])) {
                $previousUserContext = $this->ravenClient->context->user;
                $this->ravenClient->user_context($record['context']['user']);
                unset($options['extra']['context']['user']);
            }
        }
        if (!empty($record['extra'])) {
            $options['extra']['extra'] = $record['extra'];
        }

        if (!empty($this->release) && !isset($options['release'])) {
            $options['release'] = $this->release;
        }

        if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
            $options['extra']['message'] = $record['formatted'];
            $this->ravenClient->captureException($record['context']['exception'], $options);
        } else {
            $this->ravenClient->captureMessage($record['formatted'], array(), $options);
        }

        if ($previousUserContext !== false) {
            $this->ravenClient->user_context($previousUserContext);
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter('[%channel%] %message%');
    }

    /**
     * Gets the default formatter for the logs generated by handleBatch().
     *
     * @return FormatterInterface
     */
    protected function getDefaultBatchFormatter()
    {
        return new LineFormatter();
    }

    /**
     * Gets extra parameters supported by Raven that can be found in "extra" and "context"
     *
     * @return array
     */
    protected function getExtraParameters()
    {
        return array('checksum', 'release');
    }

    /**
     * @param string $value
     */
    public function setRelease($value)
    {
        $this->release = $value;

        return $this;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\LineFormatter;
use Monolog\Logger;

/**
 * Logs to a Redis key using rpush
 *
 * usage example:
 *
 *   $log = new Logger('application');
 *   $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod");
 *   $log->pushHandler($redis);
 *
 * @author Thomas Tourlourat <thomas@tourlourat.com>
 */
class RedisHandler extends AbstractProcessingHandler
{
    private $redisClient;
    private $redisKey;
    protected $capSize;

    /**
     * @param \Predis\Client|\Redis $redis   The redis instance
     * @param string                $key     The key name to push records to
     * @param int                   $level   The minimum logging level at which this handler will be triggered
     * @param bool                  $bubble  Whether the messages that are handled can bubble up the stack or not
     * @param int                   $capSize Number of entries to limit list size to
     */
    public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false)
    {
        if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) {
            throw new \InvalidArgumentException('Predis\Client or Redis instance required');
        }

        $this->redisClient = $redis;
        $this->redisKey = $key;
        $this->capSize = $capSize;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritDoc}
     */
    protected function write(array $record)
    {
        if ($this->capSize) {
            $this->writeCapped($record);
        } else {
            $this->redisClient->rpush($this->redisKey, $record["formatted"]);
        }
    }

    /**
     * Write and cap the collection
     * Writes the record to the redis list and caps its
     *
     * @param  array $record associative record array
     * @return void
     */
    protected function writeCapped(array $record)
    {
        if ($this->redisClient instanceof \Redis) {
            $this->redisClient->multi()
                ->rpush($this->redisKey, $record["formatted"])
                ->ltrim($this->redisKey, -$this->capSize, -1)
                ->exec();
        } else {
            $redisKey = $this->redisKey;
            $capSize = $this->capSize;
            $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) {
                $tx->rpush($redisKey, $record["formatted"]);
                $tx->ltrim($redisKey, -$capSize, -1);
            });
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultFormatter()
    {
        return new LineFormatter();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use RollbarNotifier;
use Exception;
use Monolog\Logger;

/**
 * Sends errors to Rollbar
 *
 * If the context data contains a `payload` key, that is used as an array
 * of payload options to RollbarNotifier's report_message/report_exception methods.
 *
 * Rollbar's context info will contain the context + extra keys from the log record
 * merged, and then on top of that a few keys:
 *
 *  - level (rollbar level name)
 *  - monolog_level (monolog level name, raw level, as rollbar only has 5 but monolog 8)
 *  - channel
 *  - datetime (unix timestamp)
 *
 * @author Paul Statezny <paulstatezny@gmail.com>
 */
class RollbarHandler extends AbstractProcessingHandler
{
    /**
     * Rollbar notifier
     *
     * @var RollbarNotifier
     */
    protected $rollbarNotifier;

    protected $levelMap = array(
        Logger::DEBUG     => 'debug',
        Logger::INFO      => 'info',
        Logger::NOTICE    => 'info',
        Logger::WARNING   => 'warning',
        Logger::ERROR     => 'error',
        Logger::CRITICAL  => 'critical',
        Logger::ALERT     => 'critical',
        Logger::EMERGENCY => 'critical',
    );

    /**
     * Records whether any log records have been added since the last flush of the rollbar notifier
     *
     * @var bool
     */
    private $hasRecords = false;

    protected $initialized = false;

    /**
     * @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token
     * @param int             $level           The minimum logging level at which this handler will be triggered
     * @param bool            $bubble          Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true)
    {
        $this->rollbarNotifier = $rollbarNotifier;

        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        if (!$this->initialized) {
            // __destructor() doesn't get called on Fatal errors
            register_shutdown_function(array($this, 'close'));
            $this->initialized = true;
        }

        $context = $record['context'];
        $payload = array();
        if (isset($context['payload'])) {
            $payload = $context['payload'];
            unset($context['payload']);
        }
        $context = array_merge($context, $record['extra'], array(
            'level' => $this->levelMap[$record['level']],
            'monolog_level' => $record['level_name'],
            'channel' => $record['channel'],
            'datetime' => $record['datetime']->format('U'),
        ));

        if (isset($context['exception']) && $context['exception'] instanceof Exception) {
            $exception = $context['exception'];
            unset($context['exception']);

            $this->rollbarNotifier->report_exception($exception, $context, $payload);
        } else {
            $this->rollbarNotifier->report_message(
                $record['message'],
                $context['level'],
                $context,
                $payload
            );
        }

        $this->hasRecords = true;
    }

    public function flush()
    {
        if ($this->hasRecords) {
            $this->rollbarNotifier->flush();
            $this->hasRecords = false;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        $this->flush();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Stores logs to files that are rotated every day and a limited number of files are kept.
 *
 * This rotation is only intended to be used as a workaround. Using logrotate to
 * handle the rotation is strongly encouraged when you can use it.
 *
 * @author Christophe Coevoet <stof@notk.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RotatingFileHandler extends StreamHandler
{
    const FILE_PER_DAY = 'Y-m-d';
    const FILE_PER_MONTH = 'Y-m';
    const FILE_PER_YEAR = 'Y';

    protected $filename;
    protected $maxFiles;
    protected $mustRotate;
    protected $nextRotation;
    protected $filenameFormat;
    protected $dateFormat;

    /**
     * @param string   $filename
     * @param int      $maxFiles       The maximal amount of files to keep (0 means unlimited)
     * @param int      $level          The minimum logging level at which this handler will be triggered
     * @param Boolean  $bubble         Whether the messages that are handled can bubble up the stack or not
     * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write)
     * @param Boolean  $useLocking     Try to lock log file before doing any writes
     */
    public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false)
    {
        $this->filename = $filename;
        $this->maxFiles = (int) $maxFiles;
        $this->nextRotation = new \DateTime('tomorrow');
        $this->filenameFormat = '{filename}-{date}';
        $this->dateFormat = 'Y-m-d';

        parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking);
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        parent::close();

        if (true === $this->mustRotate) {
            $this->rotate();
        }
    }

    public function setFilenameFormat($filenameFormat, $dateFormat)
    {
        if (!preg_match('{^Y(([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) {
            trigger_error(
                'Invalid date format - format must be one of '.
                'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '.
                'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '.
                'date formats using slashes, underscores and/or dots instead of dashes.',
                E_USER_DEPRECATED
            );
        }
        if (substr_count($filenameFormat, '{date}') === 0) {
            trigger_error(
                'Invalid filename format - format should contain at least `{date}`, because otherwise rotating is impossible.',
                E_USER_DEPRECATED
            );
        }
        $this->filenameFormat = $filenameFormat;
        $this->dateFormat = $dateFormat;
        $this->url = $this->getTimedFilename();
        $this->close();
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        // on the first record written, if the log is new, we should rotate (once per day)
        if (null === $this->mustRotate) {
            $this->mustRotate = !file_exists($this->url);
        }

        if ($this->nextRotation < $record['datetime']) {
            $this->mustRotate = true;
            $this->close();
        }

        parent::write($record);
    }

    /**
     * Rotates the files.
     */
    protected function rotate()
    {
        // update filename
        $this->url = $this->getTimedFilename();
        $this->nextRotation = new \DateTime('tomorrow');

        // skip GC of old logs if files are unlimited
        if (0 === $this->maxFiles) {
            return;
        }

        $logFiles = glob($this->getGlobPattern());
        if ($this->maxFiles >= count($logFiles)) {
            // no files to remove
            return;
        }

        // Sorting the files by name to remove the older ones
        usort($logFiles, function ($a, $b) {
            return strcmp($b, $a);
        });

        foreach (array_slice($logFiles, $this->maxFiles) as $file) {
            if (is_writable($file)) {
                // suppress errors here as unlink() might fail if two processes
                // are cleaning up/rotating at the same time
                set_error_handler(function ($errno, $errstr, $errfile, $errline) {});
                unlink($file);
                restore_error_handler();
            }
        }

        $this->mustRotate = false;
    }

    protected function getTimedFilename()
    {
        $fileInfo = pathinfo($this->filename);
        $timedFilename = str_replace(
            array('{filename}', '{date}'),
            array($fileInfo['filename'], date($this->dateFormat)),
            $fileInfo['dirname'] . '/' . $this->filenameFormat
        );

        if (!empty($fileInfo['extension'])) {
            $timedFilename .= '.'.$fileInfo['extension'];
        }

        return $timedFilename;
    }

    protected function getGlobPattern()
    {
        $fileInfo = pathinfo($this->filename);
        $glob = str_replace(
            array('{filename}', '{date}'),
            array($fileInfo['filename'], '*'),
            $fileInfo['dirname'] . '/' . $this->filenameFormat
        );
        if (!empty($fileInfo['extension'])) {
            $glob .= '.'.$fileInfo['extension'];
        }

        return $glob;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Sampling handler
 *
 * A sampled event stream can be useful for logging high frequency events in
 * a production environment where you only need an idea of what is happening
 * and are not concerned with capturing every occurrence. Since the decision to
 * handle or not handle a particular event is determined randomly, the
 * resulting sampled log is not guaranteed to contain 1/N of the events that
 * occurred in the application, but based on the Law of large numbers, it will
 * tend to be close to this ratio with a large number of attempts.
 *
 * @author Bryan Davis <bd808@wikimedia.org>
 * @author Kunal Mehta <legoktm@gmail.com>
 */
class SamplingHandler extends AbstractHandler
{
    /**
     * @var callable|HandlerInterface $handler
     */
    protected $handler;

    /**
     * @var int $factor
     */
    protected $factor;

    /**
     * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler).
     * @param int                       $factor  Sample factor
     */
    public function __construct($handler, $factor)
    {
        parent::__construct();
        $this->handler = $handler;
        $this->factor = $factor;

        if (!$this->handler instanceof HandlerInterface && !is_callable($this->handler)) {
            throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
        }
    }

    public function isHandling(array $record)
    {
        return $this->handler->isHandling($record);
    }

    public function handle(array $record)
    {
        if ($this->isHandling($record) && mt_rand(1, $this->factor) === 1) {
            // The same logic as in FingersCrossedHandler
            if (!$this->handler instanceof HandlerInterface) {
                $this->handler = call_user_func($this->handler, $record, $this);
                if (!$this->handler instanceof HandlerInterface) {
                    throw new \RuntimeException("The factory callable should return a HandlerInterface");
                }
            }

            if ($this->processors) {
                foreach ($this->processors as $processor) {
                    $record = call_user_func($processor, $record);
                }
            }

            $this->handler->handle($record);
        }

        return false === $this->bubble;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\Slack;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
use Monolog\Formatter\FormatterInterface;

/**
 * Slack record utility helping to log to Slack webhooks or API.
 *
 * @author Greg Kedzierski <greg@gregkedzierski.com>
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 * @see    https://api.slack.com/incoming-webhooks
 * @see    https://api.slack.com/docs/message-attachments
 */
class SlackRecord
{
    const COLOR_DANGER = 'danger';

    const COLOR_WARNING = 'warning';

    const COLOR_GOOD = 'good';

    const COLOR_DEFAULT = '#e3e4e6';

    /**
     * Slack channel (encoded ID or name)
     * @var string|null
     */
    private $channel;

    /**
     * Name of a bot
     * @var string
     */
    private $username;

    /**
     * Emoji icon name
     * @var string
     */
    private $iconEmoji;

    /**
     * Whether the message should be added to Slack as attachment (plain text otherwise)
     * @var bool
     */
    private $useAttachment;

    /**
     * Whether the the context/extra messages added to Slack as attachments are in a short style
     * @var bool
     */
    private $useShortAttachment;

    /**
     * Whether the attachment should include context and extra data
     * @var bool
     */
    private $includeContextAndExtra;

    /**
     * @var FormatterInterface
     */
    private $formatter;

    /**
     * @var LineFormatter
     */
    private $lineFormatter;

    public function __construct($channel = null, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, FormatterInterface $formatter = null)
    {
        $this->channel = $channel;
        $this->username = $username;
        $this->iconEmoji = trim($iconEmoji, ':');
        $this->useAttachment = $useAttachment;
        $this->useShortAttachment = $useShortAttachment;
        $this->includeContextAndExtra = $includeContextAndExtra;
        $this->formatter = $formatter;

        if ($this->includeContextAndExtra) {
            $this->lineFormatter = new LineFormatter();
        }
    }

    public function getSlackData(array $record)
    {
        $dataArray = array(
            'username'    => $this->username,
            'text'        => '',
        );

        if ($this->channel) {
            $dataArray['channel'] = $this->channel;
        }

        if ($this->formatter) {
            $message = $this->formatter->format($record);
        } else {
            $message = $record['message'];
        }

        if ($this->useAttachment) {
            $attachment = array(
                'fallback' => $message,
                'text'     => $message,
                'color'    => $this->getAttachmentColor($record['level']),
                'fields'   => array(),
            );

            if ($this->useShortAttachment) {
                $attachment['title'] = $record['level_name'];
            } else {
                $attachment['title'] = 'Message';
                $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name'], true);
            }

            if ($this->includeContextAndExtra) {
                foreach (array('extra', 'context') as $key) {
                    if (empty($record[$key])) {
                        continue;
                    }

                    if ($this->useShortAttachment) {
                        $attachment['fields'][] = $this->generateAttachmentField(
                            ucfirst($key),
                            $this->stringify($record[$key]),
                            true
                        );
                    } else {
                        // Add all extra fields as individual fields in attachment
                        $attachment['fields'] = array_merge(
                            $attachment['fields'],
                            $this->generateAttachmentFields($record[$key])
                        );
                    }
                }
            }

            $dataArray['attachments'] = array($attachment);
        } else {
            $dataArray['text'] = $message;
        }

        if ($this->iconEmoji) {
            $dataArray['icon_emoji'] = ":{$this->iconEmoji}:";
        }

        return $dataArray;
    }

    /**
     * Returned a Slack message attachment color associated with
     * provided level.
     *
     * @param  int    $level
     * @return string
     */
    public function getAttachmentColor($level)
    {
        switch (true) {
            case $level >= Logger::ERROR:
                return self::COLOR_DANGER;
            case $level >= Logger::WARNING:
                return self::COLOR_WARNING;
            case $level >= Logger::INFO:
                return self::COLOR_GOOD;
            default:
                return self::COLOR_DEFAULT;
        }
    }

    /**
     * Stringifies an array of key/value pairs to be used in attachment fields
     *
     * @param  array  $fields
     * @return string|null
     */
    public function stringify($fields)
    {
        if (!$this->lineFormatter) {
            return null;
        }

        $string = '';
        foreach ($fields as $var => $val) {
            $string .= $var.': '.$this->lineFormatter->stringify($val)." | ";
        }

        $string = rtrim($string, " |");

        return $string;
    }

    /**
     * Sets the formatter
     *
     * @param FormatterInterface $formatter
     */
    public function setFormatter(FormatterInterface $formatter)
    {
        $this->formatter = $formatter;
    }

    /**
     * Generates attachment field
     *
     * @param string $title
     * @param string|array $value
     * @param bool $short
     * @return array
     */
    private function generateAttachmentField($title, $value, $short)
    {
        return array(
            'title' => $title,
            'value' => is_array($value) ? $this->lineFormatter->stringify($value) : $value,
            'short' => $short
        );
    }

    /**
     * Generates a collection of attachment fields from array
     *
     * @param array $data
     * @return array
     */
    private function generateAttachmentFields(array $data)
    {
        $fields = array();
        foreach ($data as $key => $value) {
            $fields[] = $this->generateAttachmentField($key, $value, false);
        }

        return $fields;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Sends notifications through Slack's Slackbot
 *
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 * @see    https://slack.com/apps/A0F81R8ET-slackbot
 */
class SlackbotHandler extends AbstractProcessingHandler
{
    /**
     * The slug of the Slack team
     * @var string
     */
    private $slackTeam;

    /**
     * Slackbot token
     * @var string
     */
    private $token;

    /**
     * Slack channel name
     * @var string
     */
    private $channel;

    /**
     * @param  string $slackTeam Slack team slug
     * @param  string $token     Slackbot token
     * @param  string $channel   Slack channel (encoded ID or name)
     * @param  int    $level     The minimum logging level at which this handler will be triggered
     * @param  bool   $bubble    Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->slackTeam = $slackTeam;
        $this->token = $token;
        $this->channel = $channel;
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        $slackbotUrl = sprintf(
            'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s',
            $this->slackTeam,
            $this->token,
            $this->channel
        );

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $slackbotUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']);

        Curl\Util::execute($ch);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Handler\Slack\SlackRecord;

/**
 * Sends notifications through Slack API
 *
 * @author Greg Kedzierski <greg@gregkedzierski.com>
 * @see    https://api.slack.com/
 */
class SlackHandler extends SocketHandler
{
    /**
     * Slack API token
     * @var string
     */
    private $token;

    /**
     * Instance of the SlackRecord util class preparing data for Slack API.
     * @var SlackRecord
     */
    private $slackRecord;

    /**
     * @param  string                    $token                  Slack API token
     * @param  string                    $channel                Slack channel (encoded ID or name)
     * @param  string                    $username               Name of a bot
     * @param  bool                      $useAttachment          Whether the message should be added to Slack as attachment (plain text otherwise)
     * @param  string|null               $iconEmoji              The emoji name to use (or null)
     * @param  int                       $level                  The minimum logging level at which this handler will be triggered
     * @param  bool                      $bubble                 Whether the messages that are handled can bubble up the stack or not
     * @param  bool                      $useShortAttachment     Whether the the context/extra messages added to Slack as attachments are in a short style
     * @param  bool                      $includeContextAndExtra Whether the attachment should include context and extra data
     * @throws MissingExtensionException If no OpenSSL PHP extension configured
     */
    public function __construct($token, $channel, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false)
    {
        if (!extension_loaded('openssl')) {
            throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler');
        }

        parent::__construct('ssl://slack.com:443', $level, $bubble);

        $this->slackRecord = new SlackRecord(
            $channel,
            $username,
            $useAttachment,
            $iconEmoji,
            $useShortAttachment,
            $includeContextAndExtra,
            $this->formatter
        );

        $this->token = $token;
    }

    public function getSlackRecord()
    {
        return $this->slackRecord;
    }

    /**
     * {@inheritdoc}
     *
     * @param  array  $record
     * @return string
     */
    protected function generateDataStream($record)
    {
        $content = $this->buildContent($record);

        return $this->buildHeader($content) . $content;
    }

    /**
     * Builds the body of API call
     *
     * @param  array  $record
     * @return string
     */
    private function buildContent($record)
    {
        $dataArray = $this->prepareContentData($record);

        return http_build_query($dataArray);
    }

    /**
     * Prepares content data
     *
     * @param  array $record
     * @return array
     */
    protected function prepareContentData($record)
    {
        $dataArray = $this->slackRecord->getSlackData($record);
        $dataArray['token'] = $this->token;

        if (!empty($dataArray['attachments'])) {
            $dataArray['attachments'] = json_encode($dataArray['attachments']);
        }

        return $dataArray;
    }

    /**
     * Builds the header of the API Call
     *
     * @param  string $content
     * @return string
     */
    private function buildHeader($content)
    {
        $header = "POST /api/chat.postMessage HTTP/1.1\r\n";
        $header .= "Host: slack.com\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($content) . "\r\n";
        $header .= "\r\n";

        return $header;
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        parent::write($record);
        $res = $this->getResource();
        if (is_resource($res)) {
            @fread($res, 2048);
        }
        $this->closeSocket();
    }

    /**
     * Returned a Slack message attachment color associated with
     * provided level.
     *
     * @param  int    $level
     * @return string
     * @deprecated Use underlying SlackRecord instead
     */
    protected function getAttachmentColor($level)
    {
        trigger_error(
            'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.',
            E_USER_DEPRECATED
        );

        return $this->slackRecord->getAttachmentColor($level);
    }

    /**
     * Stringifies an array of key/value pairs to be used in attachment fields
     *
     * @param  array  $fields
     * @return string
     * @deprecated Use underlying SlackRecord instead
     */
    protected function stringify($fields)
    {
        trigger_error(
            'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.',
            E_USER_DEPRECATED
        );

        return $this->slackRecord->stringify($fields);
    }

    public function setFormatter(FormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->slackRecord->setFormatter($formatter);

        return $this;
    }

    public function getFormatter()
    {
        $formatter = parent::getFormatter();
        $this->slackRecord->setFormatter($formatter);

        return $formatter;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Handler\Slack\SlackRecord;

/**
 * Sends notifications through Slack Webhooks
 *
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 * @see    https://api.slack.com/incoming-webhooks
 */
class SlackWebhookHandler extends AbstractProcessingHandler
{
    /**
     * Slack Webhook token
     * @var string
     */
    private $webhookUrl;

    /**
     * Instance of the SlackRecord util class preparing data for Slack API.
     * @var SlackRecord
     */
    private $slackRecord;

    /**
     * @param  string      $webhookUrl             Slack Webhook URL
     * @param  string|null $channel                Slack channel (encoded ID or name)
     * @param  string      $username               Name of a bot
     * @param  bool        $useAttachment          Whether the message should be added to Slack as attachment (plain text otherwise)
     * @param  string|null $iconEmoji              The emoji name to use (or null)
     * @param  bool        $useShortAttachment     Whether the the context/extra messages added to Slack as attachments are in a short style
     * @param  bool        $includeContextAndExtra Whether the attachment should include context and extra data
     * @param  int         $level                  The minimum logging level at which this handler will be triggered
     * @param  bool        $bubble                 Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($webhookUrl, $channel = null, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->webhookUrl = $webhookUrl;

        $this->slackRecord = new SlackRecord(
            $channel,
            $username,
            $useAttachment,
            $iconEmoji,
            $useShortAttachment,
            $includeContextAndExtra,
            $this->formatter
        );
    }

    public function getSlackRecord()
    {
        return $this->slackRecord;
    }

    /**
     * {@inheritdoc}
     *
     * @param array $record
     */
    protected function write(array $record)
    {
        $postData = $this->slackRecord->getSlackData($record);
        $postString = json_encode($postData);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->webhookUrl);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        if (defined('CURLOPT_SAFE_UPLOAD')) {
            curl_setopt($ch, CURLOPT_SAFE_UPLOAD, true);
        }
        curl_setopt($ch, CURLOPT_POSTFIELDS, array('payload' => $postString));

        Curl\Util::execute($ch);
    }

    public function setFormatter(FormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->slackRecord->setFormatter($formatter);

        return $this;
    }

    public function getFormatter()
    {
        $formatter = parent::getFormatter();
        $this->slackRecord->setFormatter($formatter);

        return $formatter;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Stores to any socket - uses fsockopen() or pfsockopen().
 *
 * @author Pablo de Leon Belloc <pablolb@gmail.com>
 * @see    http://php.net/manual/en/function.fsockopen.php
 */
class SocketHandler extends AbstractProcessingHandler
{
    private $connectionString;
    private $connectionTimeout;
    private $resource;
    private $timeout = 0;
    private $writingTimeout = 10;
    private $lastSentBytes = null;
    private $persistent = false;
    private $errno;
    private $errstr;
    private $lastWritingAt;

    /**
     * @param string  $connectionString Socket connection string
     * @param int     $level            The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble           Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($level, $bubble);
        $this->connectionString = $connectionString;
        $this->connectionTimeout = (float) ini_get('default_socket_timeout');
    }

    /**
     * Connect (if necessary) and write to the socket
     *
     * @param array $record
     *
     * @throws \UnexpectedValueException
     * @throws \RuntimeException
     */
    protected function write(array $record)
    {
        $this->connectIfNotConnected();
        $data = $this->generateDataStream($record);
        $this->writeToSocket($data);
    }

    /**
     * We will not close a PersistentSocket instance so it can be reused in other requests.
     */
    public function close()
    {
        if (!$this->isPersistent()) {
            $this->closeSocket();
        }
    }

    /**
     * Close socket, if open
     */
    public function closeSocket()
    {
        if (is_resource($this->resource)) {
            fclose($this->resource);
            $this->resource = null;
        }
    }

    /**
     * Set socket connection to nbe persistent. It only has effect before the connection is initiated.
     *
     * @param bool $persistent
     */
    public function setPersistent($persistent)
    {
        $this->persistent = (boolean) $persistent;
    }

    /**
     * Set connection timeout.  Only has effect before we connect.
     *
     * @param float $seconds
     *
     * @see http://php.net/manual/en/function.fsockopen.php
     */
    public function setConnectionTimeout($seconds)
    {
        $this->validateTimeout($seconds);
        $this->connectionTimeout = (float) $seconds;
    }

    /**
     * Set write timeout. Only has effect before we connect.
     *
     * @param float $seconds
     *
     * @see http://php.net/manual/en/function.stream-set-timeout.php
     */
    public function setTimeout($seconds)
    {
        $this->validateTimeout($seconds);
        $this->timeout = (float) $seconds;
    }

    /**
     * Set writing timeout. Only has effect during connection in the writing cycle.
     *
     * @param float $seconds 0 for no timeout
     */
    public function setWritingTimeout($seconds)
    {
        $this->validateTimeout($seconds);
        $this->writingTimeout = (float) $seconds;
    }

    /**
     * Get current connection string
     *
     * @return string
     */
    public function getConnectionString()
    {
        return $this->connectionString;
    }

    /**
     * Get persistent setting
     *
     * @return bool
     */
    public function isPersistent()
    {
        return $this->persistent;
    }

    /**
     * Get current connection timeout setting
     *
     * @return float
     */
    public function getConnectionTimeout()
    {
        return $this->connectionTimeout;
    }

    /**
     * Get current in-transfer timeout
     *
     * @return float
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Get current local writing timeout
     *
     * @return float
     */
    public function getWritingTimeout()
    {
        return $this->writingTimeout;
    }

    /**
     * Check to see if the socket is currently available.
     *
     * UDP might appear to be connected but might fail when writing.  See http://php.net/fsockopen for details.
     *
     * @return bool
     */
    public function isConnected()
    {
        return is_resource($this->resource)
            && !feof($this->resource);  // on TCP - other party can close connection.
    }

    /**
     * Wrapper to allow mocking
     */
    protected function pfsockopen()
    {
        return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);
    }

    /**
     * Wrapper to allow mocking
     */
    protected function fsockopen()
    {
        return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);
    }

    /**
     * Wrapper to allow mocking
     *
     * @see http://php.net/manual/en/function.stream-set-timeout.php
     */
    protected function streamSetTimeout()
    {
        $seconds = floor($this->timeout);
        $microseconds = round(($this->timeout - $seconds) * 1e6);

        return stream_set_timeout($this->resource, $seconds, $microseconds);
    }

    /**
     * Wrapper to allow mocking
     */
    protected function fwrite($data)
    {
        return @fwrite($this->resource, $data);
    }

    /**
     * Wrapper to allow mocking
     */
    protected function streamGetMetadata()
    {
        return stream_get_meta_data($this->resource);
    }

    private function validateTimeout($value)
    {
        $ok = filter_var($value, FILTER_VALIDATE_FLOAT);
        if ($ok === false || $value < 0) {
            throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)");
        }
    }

    private function connectIfNotConnected()
    {
        if ($this->isConnected()) {
            return;
        }
        $this->connect();
    }

    protected function generateDataStream($record)
    {
        return (string) $record['formatted'];
    }

    /**
     * @return resource|null
     */
    protected function getResource()
    {
        return $this->resource;
    }

    private function connect()
    {
        $this->createSocketResource();
        $this->setSocketTimeout();
    }

    private function createSocketResource()
    {
        if ($this->isPersistent()) {
            $resource = $this->pfsockopen();
        } else {
            $resource = $this->fsockopen();
        }
        if (!$resource) {
            throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)");
        }
        $this->resource = $resource;
    }

    private function setSocketTimeout()
    {
        if (!$this->streamSetTimeout()) {
            throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()");
        }
    }

    private function writeToSocket($data)
    {
        $length = strlen($data);
        $sent = 0;
        $this->lastSentBytes = $sent;
        while ($this->isConnected() && $sent < $length) {
            if (0 == $sent) {
                $chunk = $this->fwrite($data);
            } else {
                $chunk = $this->fwrite(substr($data, $sent));
            }
            if ($chunk === false) {
                throw new \RuntimeException("Could not write to socket");
            }
            $sent += $chunk;
            $socketInfo = $this->streamGetMetadata();
            if ($socketInfo['timed_out']) {
                throw new \RuntimeException("Write timed-out");
            }

            if ($this->writingIsTimedOut($sent)) {
                throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)");
            }
        }
        if (!$this->isConnected() && $sent < $length) {
            throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)");
        }
    }

    private function writingIsTimedOut($sent)
    {
        $writingTimeout = (int) floor($this->writingTimeout);
        if (0 === $writingTimeout) {
            return false;
        }

        if ($sent !== $this->lastSentBytes) {
            $this->lastWritingAt = time();
            $this->lastSentBytes = $sent;

            return false;
        } else {
            usleep(100);
        }

        if ((time() - $this->lastWritingAt) >= $writingTimeout) {
            $this->closeSocket();

            return true;
        }

        return false;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Stores to any stream resource
 *
 * Can be used to store into php://stderr, remote and local files, etc.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class StreamHandler extends AbstractProcessingHandler
{
    protected $stream;
    protected $url;
    private $errorMessage;
    protected $filePermission;
    protected $useLocking;
    private $dirCreated;

    /**
     * @param resource|string $stream
     * @param int             $level          The minimum logging level at which this handler will be triggered
     * @param Boolean         $bubble         Whether the messages that are handled can bubble up the stack or not
     * @param int|null        $filePermission Optional file permissions (default (0644) are only for owner read/write)
     * @param Boolean         $useLocking     Try to lock log file before doing any writes
     *
     * @throws \Exception                If a missing directory is not buildable
     * @throws \InvalidArgumentException If stream is not a resource or string
     */
    public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false)
    {
        parent::__construct($level, $bubble);
        if (is_resource($stream)) {
            $this->stream = $stream;
        } elseif (is_string($stream)) {
            $this->url = $stream;
        } else {
            throw new \InvalidArgumentException('A stream must either be a resource or a string.');
        }

        $this->filePermission = $filePermission;
        $this->useLocking = $useLocking;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        if ($this->url && is_resource($this->stream)) {
            fclose($this->stream);
        }
        $this->stream = null;
    }

    /**
     * Return the currently active stream if it is open
     *
     * @return resource|null
     */
    public function getStream()
    {
        return $this->stream;
    }

    /**
     * Return the stream URL if it was configured with a URL and not an active resource
     *
     * @return string|null
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        if (!is_resource($this->stream)) {
            if (null === $this->url || '' === $this->url) {
                throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
            }
            $this->createDir();
            $this->errorMessage = null;
            set_error_handler(array($this, 'customErrorHandler'));
            $this->stream = fopen($this->url, 'a');
            if ($this->filePermission !== null) {
                @chmod($this->url, $this->filePermission);
            }
            restore_error_handler();
            if (!is_resource($this->stream)) {
                $this->stream = null;
                throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url));
            }
        }

        if ($this->useLocking) {
            // ignoring errors here, there's not much we can do about them
            flock($this->stream, LOCK_EX);
        }

        $this->streamWrite($this->stream, $record);

        if ($this->useLocking) {
            flock($this->stream, LOCK_UN);
        }
    }

    /**
     * Write to stream
     * @param resource $stream
     * @param array $record
     */
    protected function streamWrite($stream, array $record)
    {
        fwrite($stream, (string) $record['formatted']);
    }

    private function customErrorHandler($code, $msg)
    {
        $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
    }

    /**
     * @param string $stream
     *
     * @return null|string
     */
    private function getDirFromStream($stream)
    {
        $pos = strpos($stream, '://');
        if ($pos === false) {
            return dirname($stream);
        }

        if ('file://' === substr($stream, 0, 7)) {
            return dirname(substr($stream, 7));
        }

        return;
    }

    private function createDir()
    {
        // Do not try to create dir if it has already been tried.
        if ($this->dirCreated) {
            return;
        }

        $dir = $this->getDirFromStream($this->url);
        if (null !== $dir && !is_dir($dir)) {
            $this->errorMessage = null;
            set_error_handler(array($this, 'customErrorHandler'));
            $status = mkdir($dir, 0777, true);
            restore_error_handler();
            if (false === $status) {
                throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir));
            }
        }
        $this->dirCreated = true;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Formatter\LineFormatter;

/**
 * SwiftMailerHandler uses Swift_Mailer to send the emails
 *
 * @author Gyula Sallai
 */
class SwiftMailerHandler extends MailHandler
{
    protected $mailer;
    private $messageTemplate;

    /**
     * @param \Swift_Mailer           $mailer  The mailer to use
     * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
     * @param int                     $level   The minimum logging level at which this handler will be triggered
     * @param Boolean                 $bubble  Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true)
    {
        parent::__construct($level, $bubble);

        $this->mailer = $mailer;
        $this->messageTemplate = $message;
    }

    /**
     * {@inheritdoc}
     */
    protected function send($content, array $records)
    {
        $this->mailer->send($this->buildMessage($content, $records));
    }

    /**
     * Creates instance of Swift_Message to be sent
     *
     * @param  string         $content formatted email body to be sent
     * @param  array          $records Log records that formed the content
     * @return \Swift_Message
     */
    protected function buildMessage($content, array $records)
    {
        $message = null;
        if ($this->messageTemplate instanceof \Swift_Message) {
            $message = clone $this->messageTemplate;
            $message->generateId();
        } elseif (is_callable($this->messageTemplate)) {
            $message = call_user_func($this->messageTemplate, $content, $records);
        }

        if (!$message instanceof \Swift_Message) {
            throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it');
        }

        if ($records) {
            $subjectFormatter = new LineFormatter($message->getSubject());
            $message->setSubject($subjectFormatter->format($this->getHighestRecord($records)));
        }

        $message->setBody($content);
        $message->setDate(time());

        return $message;
    }

    /**
     * BC getter, to be removed in 2.0
     */
    public function __get($name)
    {
        if ($name === 'message') {
            trigger_error('SwiftMailerHandler->message is deprecated, use ->buildMessage() instead to retrieve the message', E_USER_DEPRECATED);

            return $this->buildMessage(null, array());
        }

        throw new \InvalidArgumentException('Invalid property '.$name);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;

/**
 * Logs to syslog service.
 *
 * usage example:
 *
 *   $log = new Logger('application');
 *   $syslog = new SyslogHandler('myfacility', 'local6');
 *   $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
 *   $syslog->setFormatter($formatter);
 *   $log->pushHandler($syslog);
 *
 * @author Sven Paulus <sven@karlsruhe.org>
 */
class SyslogHandler extends AbstractSyslogHandler
{
    protected $ident;
    protected $logopts;

    /**
     * @param string  $ident
     * @param mixed   $facility
     * @param int     $level    The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble   Whether the messages that are handled can bubble up the stack or not
     * @param int     $logopts  Option flags for the openlog() call, defaults to LOG_PID
     */
    public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID)
    {
        parent::__construct($facility, $level, $bubble);

        $this->ident = $ident;
        $this->logopts = $logopts;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        closelog();
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        if (!openlog($this->ident, $this->logopts, $this->facility)) {
            throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"');
        }
        syslog($this->logLevels[$record['level']], (string) $record['formatted']);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler\SyslogUdp;

class UdpSocket
{
    const DATAGRAM_MAX_LENGTH = 65023;

    protected $ip;
    protected $port;
    protected $socket;

    public function __construct($ip, $port = 514)
    {
        $this->ip = $ip;
        $this->port = $port;
        $this->socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
    }

    public function write($line, $header = "")
    {
        $this->send($this->assembleMessage($line, $header));
    }

    public function close()
    {
        if (is_resource($this->socket)) {
            socket_close($this->socket);
            $this->socket = null;
        }
    }

    protected function send($chunk)
    {
        if (!is_resource($this->socket)) {
            throw new \LogicException('The UdpSocket to '.$this->ip.':'.$this->port.' has been closed and can not be written to anymore');
        }
        socket_sendto($this->socket, $chunk, strlen($chunk), $flags = 0, $this->ip, $this->port);
    }

    protected function assembleMessage($line, $header)
    {
        $chunkSize = self::DATAGRAM_MAX_LENGTH - strlen($header);

        return $header . substr($line, 0, $chunkSize);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Logger;
use Monolog\Handler\SyslogUdp\UdpSocket;

/**
 * A Handler for logging to a remote syslogd server.
 *
 * @author Jesper Skovgaard Nielsen <nulpunkt@gmail.com>
 */
class SyslogUdpHandler extends AbstractSyslogHandler
{
    protected $socket;

    /**
     * @param string  $host
     * @param int     $port
     * @param mixed   $facility
     * @param int     $level    The minimum logging level at which this handler will be triggered
     * @param Boolean $bubble   Whether the messages that are handled can bubble up the stack or not
     */
    public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
    {
        parent::__construct($facility, $level, $bubble);

        $this->socket = new UdpSocket($host, $port ?: 514);
    }

    protected function write(array $record)
    {
        $lines = $this->splitMessageIntoLines($record['formatted']);

        $header = $this->makeCommonSyslogHeader($this->logLevels[$record['level']]);

        foreach ($lines as $line) {
            $this->socket->write($line, $header);
        }
    }

    public function close()
    {
        $this->socket->close();
    }

    private function splitMessageIntoLines($message)
    {
        if (is_array($message)) {
            $message = implode("\n", $message);
        }

        return preg_split('/$\R?^/m', $message, -1, PREG_SPLIT_NO_EMPTY);
    }

    /**
     * Make common syslog header (see rfc5424)
     */
    protected function makeCommonSyslogHeader($severity)
    {
        $priority = $severity + $this->facility;

        return "<$priority>1 ";
    }

    /**
     * Inject your own socket, mainly used for testing
     */
    public function setSocket($socket)
    {
        $this->socket = $socket;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Used for testing purposes.
 *
 * It records all records and gives you access to them for verification.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @method bool hasEmergency($record)
 * @method bool hasAlert($record)
 * @method bool hasCritical($record)
 * @method bool hasError($record)
 * @method bool hasWarning($record)
 * @method bool hasNotice($record)
 * @method bool hasInfo($record)
 * @method bool hasDebug($record)
 *
 * @method bool hasEmergencyRecords()
 * @method bool hasAlertRecords()
 * @method bool hasCriticalRecords()
 * @method bool hasErrorRecords()
 * @method bool hasWarningRecords()
 * @method bool hasNoticeRecords()
 * @method bool hasInfoRecords()
 * @method bool hasDebugRecords()
 *
 * @method bool hasEmergencyThatContains($message)
 * @method bool hasAlertThatContains($message)
 * @method bool hasCriticalThatContains($message)
 * @method bool hasErrorThatContains($message)
 * @method bool hasWarningThatContains($message)
 * @method bool hasNoticeThatContains($message)
 * @method bool hasInfoThatContains($message)
 * @method bool hasDebugThatContains($message)
 *
 * @method bool hasEmergencyThatMatches($message)
 * @method bool hasAlertThatMatches($message)
 * @method bool hasCriticalThatMatches($message)
 * @method bool hasErrorThatMatches($message)
 * @method bool hasWarningThatMatches($message)
 * @method bool hasNoticeThatMatches($message)
 * @method bool hasInfoThatMatches($message)
 * @method bool hasDebugThatMatches($message)
 *
 * @method bool hasEmergencyThatPasses($message)
 * @method bool hasAlertThatPasses($message)
 * @method bool hasCriticalThatPasses($message)
 * @method bool hasErrorThatPasses($message)
 * @method bool hasWarningThatPasses($message)
 * @method bool hasNoticeThatPasses($message)
 * @method bool hasInfoThatPasses($message)
 * @method bool hasDebugThatPasses($message)
 */
class TestHandler extends AbstractProcessingHandler
{
    protected $records = array();
    protected $recordsByLevel = array();

    public function getRecords()
    {
        return $this->records;
    }

    public function clear()
    {
        $this->records = array();
        $this->recordsByLevel = array();
    }

    public function hasRecords($level)
    {
        return isset($this->recordsByLevel[$level]);
    }

    public function hasRecord($record, $level)
    {
        if (is_array($record)) {
            $record = $record['message'];
        }

        return $this->hasRecordThatPasses(function ($rec) use ($record) {
            return $rec['message'] === $record;
        }, $level);
    }

    public function hasRecordThatContains($message, $level)
    {
        return $this->hasRecordThatPasses(function ($rec) use ($message) {
            return strpos($rec['message'], $message) !== false;
        }, $level);
    }

    public function hasRecordThatMatches($regex, $level)
    {
        return $this->hasRecordThatPasses(function ($rec) use ($regex) {
            return preg_match($regex, $rec['message']) > 0;
        }, $level);
    }

    public function hasRecordThatPasses($predicate, $level)
    {
        if (!is_callable($predicate)) {
            throw new \InvalidArgumentException("Expected a callable for hasRecordThatSucceeds");
        }

        if (!isset($this->recordsByLevel[$level])) {
            return false;
        }

        foreach ($this->recordsByLevel[$level] as $i => $rec) {
            if (call_user_func($predicate, $rec, $i)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $this->recordsByLevel[$record['level']][] = $record;
        $this->records[] = $record;
    }

    public function __call($method, $args)
    {
        if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) {
            $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3];
            $level = constant('Monolog\Logger::' . strtoupper($matches[2]));
            if (method_exists($this, $genericMethod)) {
                $args[] = $level;

                return call_user_func_array(array($this, $genericMethod), $args);
            }
        }

        throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()');
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

/**
 * Forwards records to multiple handlers suppressing failures of each handler
 * and continuing through to give every handler a chance to succeed.
 *
 * @author Craig D'Amelio <craig@damelio.ca>
 */
class WhatFailureGroupHandler extends GroupHandler
{
    /**
     * {@inheritdoc}
     */
    public function handle(array $record)
    {
        if ($this->processors) {
            foreach ($this->processors as $processor) {
                $record = call_user_func($processor, $record);
            }
        }

        foreach ($this->handlers as $handler) {
            try {
                $handler->handle($record);
            } catch (\Exception $e) {
                // What failure?
            } catch (\Throwable $e) {
                // What failure?
            }
        }

        return false === $this->bubble;
    }

    /**
     * {@inheritdoc}
     */
    public function handleBatch(array $records)
    {
        foreach ($this->handlers as $handler) {
            try {
                $handler->handleBatch($records);
            } catch (\Exception $e) {
                // What failure?
            } catch (\Throwable $e) {
                // What failure?
            }
        }
    }
}
<?php
/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Handler;

use Monolog\Formatter\NormalizerFormatter;
use Monolog\Logger;

/**
 * Handler sending logs to Zend Monitor
 *
 * @author  Christian Bergau <cbergau86@gmail.com>
 */
class ZendMonitorHandler extends AbstractProcessingHandler
{
    /**
     * Monolog level / ZendMonitor Custom Event priority map
     *
     * @var array
     */
    protected $levelMap = array(
        Logger::DEBUG     => 1,
        Logger::INFO      => 2,
        Logger::NOTICE    => 3,
        Logger::WARNING   => 4,
        Logger::ERROR     => 5,
        Logger::CRITICAL  => 6,
        Logger::ALERT     => 7,
        Logger::EMERGENCY => 0,
    );

    /**
     * Construct
     *
     * @param  int                       $level
     * @param  bool                      $bubble
     * @throws MissingExtensionException
     */
    public function __construct($level = Logger::DEBUG, $bubble = true)
    {
        if (!function_exists('zend_monitor_custom_event')) {
            throw new MissingExtensionException('You must have Zend Server installed in order to use this handler');
        }
        parent::__construct($level, $bubble);
    }

    /**
     * {@inheritdoc}
     */
    protected function write(array $record)
    {
        $this->writeZendMonitorCustomEvent(
            $this->levelMap[$record['level']],
            $record['message'],
            $record['formatted']
        );
    }

    /**
     * Write a record to Zend Monitor
     *
     * @param int    $level
     * @param string $message
     * @param array  $formatted
     */
    protected function writeZendMonitorCustomEvent($level, $message, $formatted)
    {
        zend_monitor_custom_event($level, $message, $formatted);
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultFormatter()
    {
        return new NormalizerFormatter();
    }

    /**
     * Get the level map
     *
     * @return array
     */
    public function getLevelMap()
    {
        return $this->levelMap;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

use Monolog\Handler\HandlerInterface;
use Monolog\Handler\StreamHandler;
use Psr\Log\LoggerInterface;
use Psr\Log\InvalidArgumentException;

/**
 * Monolog log channel
 *
 * It contains a stack of Handlers and a stack of Processors,
 * and uses them to store records that are added to it.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Logger implements LoggerInterface
{
    /**
     * Detailed debug information
     */
    const DEBUG = 100;

    /**
     * Interesting events
     *
     * Examples: User logs in, SQL logs.
     */
    const INFO = 200;

    /**
     * Uncommon events
     */
    const NOTICE = 250;

    /**
     * Exceptional occurrences that are not errors
     *
     * Examples: Use of deprecated APIs, poor use of an API,
     * undesirable things that are not necessarily wrong.
     */
    const WARNING = 300;

    /**
     * Runtime errors
     */
    const ERROR = 400;

    /**
     * Critical conditions
     *
     * Example: Application component unavailable, unexpected exception.
     */
    const CRITICAL = 500;

    /**
     * Action must be taken immediately
     *
     * Example: Entire website down, database unavailable, etc.
     * This should trigger the SMS alerts and wake you up.
     */
    const ALERT = 550;

    /**
     * Urgent alert.
     */
    const EMERGENCY = 600;

    /**
     * Monolog API version
     *
     * This is only bumped when API breaks are done and should
     * follow the major version of the library
     *
     * @var int
     */
    const API = 1;

    /**
     * Logging levels from syslog protocol defined in RFC 5424
     *
     * @var array $levels Logging levels
     */
    protected static $levels = array(
        self::DEBUG     => 'DEBUG',
        self::INFO      => 'INFO',
        self::NOTICE    => 'NOTICE',
        self::WARNING   => 'WARNING',
        self::ERROR     => 'ERROR',
        self::CRITICAL  => 'CRITICAL',
        self::ALERT     => 'ALERT',
        self::EMERGENCY => 'EMERGENCY',
    );

    /**
     * @var \DateTimeZone
     */
    protected static $timezone;

    /**
     * @var string
     */
    protected $name;

    /**
     * The handler stack
     *
     * @var HandlerInterface[]
     */
    protected $handlers;

    /**
     * Processors that will process all log records
     *
     * To process records of a single handler instead, add the processor on that specific handler
     *
     * @var callable[]
     */
    protected $processors;

    /**
     * @var bool
     */
    protected $microsecondTimestamps = true;

    /**
     * @param string             $name       The logging channel
     * @param HandlerInterface[] $handlers   Optional stack of handlers, the first one in the array is called first, etc.
     * @param callable[]         $processors Optional array of processors
     */
    public function __construct($name, array $handlers = array(), array $processors = array())
    {
        $this->name = $name;
        $this->handlers = $handlers;
        $this->processors = $processors;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Return a new cloned instance with the name changed
     *
     * @return static
     */
    public function withName($name)
    {
        $new = clone $this;
        $new->name = $name;

        return $new;
    }

    /**
     * Pushes a handler on to the stack.
     *
     * @param  HandlerInterface $handler
     * @return $this
     */
    public function pushHandler(HandlerInterface $handler)
    {
        array_unshift($this->handlers, $handler);

        return $this;
    }

    /**
     * Pops a handler from the stack
     *
     * @return HandlerInterface
     */
    public function popHandler()
    {
        if (!$this->handlers) {
            throw new \LogicException('You tried to pop from an empty handler stack.');
        }

        return array_shift($this->handlers);
    }

    /**
     * Set handlers, replacing all existing ones.
     *
     * If a map is passed, keys will be ignored.
     *
     * @param  HandlerInterface[] $handlers
     * @return $this
     */
    public function setHandlers(array $handlers)
    {
        $this->handlers = array();
        foreach (array_reverse($handlers) as $handler) {
            $this->pushHandler($handler);
        }

        return $this;
    }

    /**
     * @return HandlerInterface[]
     */
    public function getHandlers()
    {
        return $this->handlers;
    }

    /**
     * Adds a processor on to the stack.
     *
     * @param  callable $callback
     * @return $this
     */
    public function pushProcessor($callback)
    {
        if (!is_callable($callback)) {
            throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
        }
        array_unshift($this->processors, $callback);

        return $this;
    }

    /**
     * Removes the processor on top of the stack and returns it.
     *
     * @return callable
     */
    public function popProcessor()
    {
        if (!$this->processors) {
            throw new \LogicException('You tried to pop from an empty processor stack.');
        }

        return array_shift($this->processors);
    }

    /**
     * @return callable[]
     */
    public function getProcessors()
    {
        return $this->processors;
    }

    /**
     * Control the use of microsecond resolution timestamps in the 'datetime'
     * member of new records.
     *
     * Generating microsecond resolution timestamps by calling
     * microtime(true), formatting the result via sprintf() and then parsing
     * the resulting string via \DateTime::createFromFormat() can incur
     * a measurable runtime overhead vs simple usage of DateTime to capture
     * a second resolution timestamp in systems which generate a large number
     * of log events.
     *
     * @param bool $micro True to use microtime() to create timestamps
     */
    public function useMicrosecondTimestamps($micro)
    {
        $this->microsecondTimestamps = (bool) $micro;
    }

    /**
     * Adds a log record.
     *
     * @param  int     $level   The logging level
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addRecord($level, $message, array $context = array())
    {
        if (!$this->handlers) {
            $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG));
        }

        $levelName = static::getLevelName($level);

        // check if any handler will handle this message so we can return early and save cycles
        $handlerKey = null;
        reset($this->handlers);
        while ($handler = current($this->handlers)) {
            if ($handler->isHandling(array('level' => $level))) {
                $handlerKey = key($this->handlers);
                break;
            }

            next($this->handlers);
        }

        if (null === $handlerKey) {
            return false;
        }

        if (!static::$timezone) {
            static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC');
        }

        // php7.1+ always has microseconds enabled, so we do not need this hack
        if ($this->microsecondTimestamps && PHP_VERSION_ID < 70100) {
            $ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone);
        } else {
            $ts = new \DateTime(null, static::$timezone);
        }
        $ts->setTimezone(static::$timezone);

        $record = array(
            'message' => (string) $message,
            'context' => $context,
            'level' => $level,
            'level_name' => $levelName,
            'channel' => $this->name,
            'datetime' => $ts,
            'extra' => array(),
        );

        foreach ($this->processors as $processor) {
            $record = call_user_func($processor, $record);
        }

        while ($handler = current($this->handlers)) {
            if (true === $handler->handle($record)) {
                break;
            }

            next($this->handlers);
        }

        return true;
    }

    /**
     * Adds a log record at the DEBUG level.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addDebug($message, array $context = array())
    {
        return $this->addRecord(static::DEBUG, $message, $context);
    }

    /**
     * Adds a log record at the INFO level.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addInfo($message, array $context = array())
    {
        return $this->addRecord(static::INFO, $message, $context);
    }

    /**
     * Adds a log record at the NOTICE level.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addNotice($message, array $context = array())
    {
        return $this->addRecord(static::NOTICE, $message, $context);
    }

    /**
     * Adds a log record at the WARNING level.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addWarning($message, array $context = array())
    {
        return $this->addRecord(static::WARNING, $message, $context);
    }

    /**
     * Adds a log record at the ERROR level.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addError($message, array $context = array())
    {
        return $this->addRecord(static::ERROR, $message, $context);
    }

    /**
     * Adds a log record at the CRITICAL level.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addCritical($message, array $context = array())
    {
        return $this->addRecord(static::CRITICAL, $message, $context);
    }

    /**
     * Adds a log record at the ALERT level.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addAlert($message, array $context = array())
    {
        return $this->addRecord(static::ALERT, $message, $context);
    }

    /**
     * Adds a log record at the EMERGENCY level.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function addEmergency($message, array $context = array())
    {
        return $this->addRecord(static::EMERGENCY, $message, $context);
    }

    /**
     * Gets all supported logging levels.
     *
     * @return array Assoc array with human-readable level names => level codes.
     */
    public static function getLevels()
    {
        return array_flip(static::$levels);
    }

    /**
     * Gets the name of the logging level.
     *
     * @param  int    $level
     * @return string
     */
    public static function getLevelName($level)
    {
        if (!isset(static::$levels[$level])) {
            throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels)));
        }

        return static::$levels[$level];
    }

    /**
     * Converts PSR-3 levels to Monolog ones if necessary
     *
     * @param string|int Level number (monolog) or name (PSR-3)
     * @return int
     */
    public static function toMonologLevel($level)
    {
        if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) {
            return constant(__CLASS__.'::'.strtoupper($level));
        }

        return $level;
    }

    /**
     * Checks whether the Logger has a handler that listens on the given level
     *
     * @param  int     $level
     * @return Boolean
     */
    public function isHandling($level)
    {
        $record = array(
            'level' => $level,
        );

        foreach ($this->handlers as $handler) {
            if ($handler->isHandling($record)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Adds a log record at an arbitrary level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  mixed   $level   The log level
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function log($level, $message, array $context = array())
    {
        $level = static::toMonologLevel($level);

        return $this->addRecord($level, $message, $context);
    }

    /**
     * Adds a log record at the DEBUG level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function debug($message, array $context = array())
    {
        return $this->addRecord(static::DEBUG, $message, $context);
    }

    /**
     * Adds a log record at the INFO level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function info($message, array $context = array())
    {
        return $this->addRecord(static::INFO, $message, $context);
    }

    /**
     * Adds a log record at the NOTICE level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function notice($message, array $context = array())
    {
        return $this->addRecord(static::NOTICE, $message, $context);
    }

    /**
     * Adds a log record at the WARNING level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function warn($message, array $context = array())
    {
        return $this->addRecord(static::WARNING, $message, $context);
    }

    /**
     * Adds a log record at the WARNING level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function warning($message, array $context = array())
    {
        return $this->addRecord(static::WARNING, $message, $context);
    }

    /**
     * Adds a log record at the ERROR level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function err($message, array $context = array())
    {
        return $this->addRecord(static::ERROR, $message, $context);
    }

    /**
     * Adds a log record at the ERROR level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function error($message, array $context = array())
    {
        return $this->addRecord(static::ERROR, $message, $context);
    }

    /**
     * Adds a log record at the CRITICAL level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function crit($message, array $context = array())
    {
        return $this->addRecord(static::CRITICAL, $message, $context);
    }

    /**
     * Adds a log record at the CRITICAL level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function critical($message, array $context = array())
    {
        return $this->addRecord(static::CRITICAL, $message, $context);
    }

    /**
     * Adds a log record at the ALERT level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function alert($message, array $context = array())
    {
        return $this->addRecord(static::ALERT, $message, $context);
    }

    /**
     * Adds a log record at the EMERGENCY level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function emerg($message, array $context = array())
    {
        return $this->addRecord(static::EMERGENCY, $message, $context);
    }

    /**
     * Adds a log record at the EMERGENCY level.
     *
     * This method allows for compatibility with common interfaces.
     *
     * @param  string  $message The log message
     * @param  array   $context The log context
     * @return Boolean Whether the record has been processed
     */
    public function emergency($message, array $context = array())
    {
        return $this->addRecord(static::EMERGENCY, $message, $context);
    }

    /**
     * Set the timezone to be used for the timestamp of log records.
     *
     * This is stored globally for all Logger instances
     *
     * @param \DateTimeZone $tz Timezone object
     */
    public static function setTimezone(\DateTimeZone $tz)
    {
        self::$timezone = $tz;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

use Monolog\Logger;

/**
 * Injects Git branch and Git commit SHA in all records
 *
 * @author Nick Otter
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitProcessor
{
    private $level;
    private static $cache;

    public function __construct($level = Logger::DEBUG)
    {
        $this->level = Logger::toMonologLevel($level);
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // return if the level is not high enough
        if ($record['level'] < $this->level) {
            return $record;
        }

        $record['extra']['git'] = self::getGitInfo();

        return $record;
    }

    private static function getGitInfo()
    {
        if (self::$cache) {
            return self::$cache;
        }

        $branches = `git branch -v --no-abbrev`;
        if (preg_match('{^\* (.+?)\s+([a-f0-9]{40})(?:\s|$)}m', $branches, $matches)) {
            return self::$cache = array(
                'branch' => $matches[1],
                'commit' => $matches[2],
            );
        }

        return self::$cache = array();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

use Monolog\Logger;

/**
 * Injects line/file:class/function where the log message came from
 *
 * Warning: This only works if the handler processes the logs directly.
 * If you put the processor on a handler that is behind a FingersCrossedHandler
 * for example, the processor will only be called once the trigger level is reached,
 * and all the log records will have the same file/line/.. data from the call that
 * triggered the FingersCrossedHandler.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class IntrospectionProcessor
{
    private $level;

    private $skipClassesPartials;

    private $skipStackFramesCount;

    private $skipFunctions = array(
        'call_user_func',
        'call_user_func_array',
    );

    public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0)
    {
        $this->level = Logger::toMonologLevel($level);
        $this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials);
        $this->skipStackFramesCount = $skipStackFramesCount;
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // return if the level is not high enough
        if ($record['level'] < $this->level) {
            return $record;
        }

        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

        // skip first since it's always the current method
        array_shift($trace);
        // the call_user_func call is also skipped
        array_shift($trace);

        $i = 0;

        while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
            if (isset($trace[$i]['class'])) {
                foreach ($this->skipClassesPartials as $part) {
                    if (strpos($trace[$i]['class'], $part) !== false) {
                        $i++;
                        continue 2;
                    }
                }
            } elseif (in_array($trace[$i]['function'], $this->skipFunctions)) {
                $i++;
                continue;
            }

            break;
        }

        $i += $this->skipStackFramesCount;

        // we should have the call source now
        $record['extra'] = array_merge(
            $record['extra'],
            array(
                'file'      => isset($trace[$i - 1]['file']) ? $trace[$i - 1]['file'] : null,
                'line'      => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null,
                'class'     => isset($trace[$i]['class']) ? $trace[$i]['class'] : null,
                'function'  => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
            )
        );

        return $record;
    }

    private function isTraceClassOrSkippedFunction(array $trace, $index)
    {
        if (!isset($trace[$index])) {
            return false;
        }

        return isset($trace[$index]['class']) || in_array($trace[$index]['function'], $this->skipFunctions);
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Injects memory_get_peak_usage in all records
 *
 * @see Monolog\Processor\MemoryProcessor::__construct() for options
 * @author Rob Jensen
 */
class MemoryPeakUsageProcessor extends MemoryProcessor
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        $bytes = memory_get_peak_usage($this->realUsage);
        $formatted = $this->formatBytes($bytes);

        $record['extra']['memory_peak_usage'] = $formatted;

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Some methods that are common for all memory processors
 *
 * @author Rob Jensen
 */
abstract class MemoryProcessor
{
    /**
     * @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported.
     */
    protected $realUsage;

    /**
     * @var bool If true, then format memory size to human readable string (MB, KB, B depending on size)
     */
    protected $useFormatting;

    /**
     * @param bool $realUsage     Set this to true to get the real size of memory allocated from system.
     * @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size)
     */
    public function __construct($realUsage = true, $useFormatting = true)
    {
        $this->realUsage = (boolean) $realUsage;
        $this->useFormatting = (boolean) $useFormatting;
    }

    /**
     * Formats bytes into a human readable string if $this->useFormatting is true, otherwise return $bytes as is
     *
     * @param  int        $bytes
     * @return string|int Formatted string if $this->useFormatting is true, otherwise return $bytes as is
     */
    protected function formatBytes($bytes)
    {
        $bytes = (int) $bytes;

        if (!$this->useFormatting) {
            return $bytes;
        }

        if ($bytes > 1024 * 1024) {
            return round($bytes / 1024 / 1024, 2).' MB';
        } elseif ($bytes > 1024) {
            return round($bytes / 1024, 2).' KB';
        }

        return $bytes . ' B';
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Injects memory_get_usage in all records
 *
 * @see Monolog\Processor\MemoryProcessor::__construct() for options
 * @author Rob Jensen
 */
class MemoryUsageProcessor extends MemoryProcessor
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        $bytes = memory_get_usage($this->realUsage);
        $formatted = $this->formatBytes($bytes);

        $record['extra']['memory_usage'] = $formatted;

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jonathan A. Schweder <jonathanschweder@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

use Monolog\Logger;

/**
 * Injects Hg branch and Hg revision number in all records
 *
 * @author Jonathan A. Schweder <jonathanschweder@gmail.com>
 */
class MercurialProcessor
{
    private $level;
    private static $cache;

    public function __construct($level = Logger::DEBUG)
    {
        $this->level = Logger::toMonologLevel($level);
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // return if the level is not high enough
        if ($record['level'] < $this->level) {
            return $record;
        }

        $record['extra']['hg'] = self::getMercurialInfo();

        return $record;
    }

    private static function getMercurialInfo()
    {
        if (self::$cache) {
            return self::$cache;
        }

        $result = explode(' ', trim(`hg id -nb`));
        if (count($result) >= 3) {
            return self::$cache = array(
                'branch' => $result[1],
                'revision' => $result[2],
            );
        }

        return self::$cache = array();
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Adds value of getmypid into records
 *
 * @author Andreas Hörnicke
 */
class ProcessIdProcessor
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        $record['extra']['process_id'] = getmypid();

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Processes a record's message according to PSR-3 rules
 *
 * It replaces {foo} with the value from $context['foo']
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PsrLogMessageProcessor
{
    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        if (false === strpos($record['message'], '{')) {
            return $record;
        }

        $replacements = array();
        foreach ($record['context'] as $key => $val) {
            if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) {
                $replacements['{'.$key.'}'] = $val;
            } elseif (is_object($val)) {
                $replacements['{'.$key.'}'] = '[object '.get_class($val).']';
            } else {
                $replacements['{'.$key.'}'] = '['.gettype($val).']';
            }
        }

        $record['message'] = strtr($record['message'], $replacements);

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Adds a tags array into record
 *
 * @author Martijn Riemers
 */
class TagProcessor
{
    private $tags;

    public function __construct(array $tags = array())
    {
        $this->setTags($tags);
    }

    public function addTags(array $tags = array())
    {
        $this->tags = array_merge($this->tags, $tags);
    }

    public function setTags(array $tags = array())
    {
        $this->tags = $tags;
    }

    public function __invoke(array $record)
    {
        $record['extra']['tags'] = $this->tags;

        return $record;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Adds a unique identifier into records
 *
 * @author Simon Mönch <sm@webfactory.de>
 */
class UidProcessor
{
    private $uid;

    public function __construct($length = 7)
    {
        if (!is_int($length) || $length > 32 || $length < 1) {
            throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32');
        }

        $this->uid = substr(hash('md5', uniqid('', true)), 0, $length);
    }

    public function __invoke(array $record)
    {
        $record['extra']['uid'] = $this->uid;

        return $record;
    }

    /**
     * @return string
     */
    public function getUid()
    {
        return $this->uid;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog\Processor;

/**
 * Injects url/method and remote IP of the current web request in all records
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class WebProcessor
{
    /**
     * @var array|\ArrayAccess
     */
    protected $serverData;

    /**
     * Default fields
     *
     * Array is structured as [key in record.extra => key in $serverData]
     *
     * @var array
     */
    protected $extraFields = array(
        'url'         => 'REQUEST_URI',
        'ip'          => 'REMOTE_ADDR',
        'http_method' => 'REQUEST_METHOD',
        'server'      => 'SERVER_NAME',
        'referrer'    => 'HTTP_REFERER',
    );

    /**
     * @param array|\ArrayAccess $serverData  Array or object w/ ArrayAccess that provides access to the $_SERVER data
     * @param array|null         $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer
     */
    public function __construct($serverData = null, array $extraFields = null)
    {
        if (null === $serverData) {
            $this->serverData = &$_SERVER;
        } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) {
            $this->serverData = $serverData;
        } else {
            throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.');
        }

        if (null !== $extraFields) {
            if (isset($extraFields[0])) {
                foreach (array_keys($this->extraFields) as $fieldName) {
                    if (!in_array($fieldName, $extraFields)) {
                        unset($this->extraFields[$fieldName]);
                    }
                }
            } else {
                $this->extraFields = $extraFields;
            }
        }
    }

    /**
     * @param  array $record
     * @return array
     */
    public function __invoke(array $record)
    {
        // skip processing if for some reason request data
        // is not present (CLI or wonky SAPIs)
        if (!isset($this->serverData['REQUEST_URI'])) {
            return $record;
        }

        $record['extra'] = $this->appendExtraFields($record['extra']);

        return $record;
    }

    /**
     * @param  string $extraName
     * @param  string $serverName
     * @return $this
     */
    public function addExtraField($extraName, $serverName)
    {
        $this->extraFields[$extraName] = $serverName;

        return $this;
    }

    /**
     * @param  array $extra
     * @return array
     */
    private function appendExtraFields(array $extra)
    {
        foreach ($this->extraFields as $extraName => $serverName) {
            $extra[$extraName] = isset($this->serverData[$serverName]) ? $this->serverData[$serverName] : null;
        }

        if (isset($this->serverData['UNIQUE_ID'])) {
            $extra['unique_id'] = $this->serverData['UNIQUE_ID'];
        }

        return $extra;
    }
}
<?php

/*
 * This file is part of the Monolog package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Monolog;

use InvalidArgumentException;

/**
 * Monolog log registry
 *
 * Allows to get `Logger` instances in the global scope
 * via static method calls on this class.
 *
 * <code>
 * $application = new Monolog\Logger('application');
 * $api = new Monolog\Logger('api');
 *
 * Monolog\Registry::addLogger($application);
 * Monolog\Registry::addLogger($api);
 *
 * function testLogger()
 * {
 *     Monolog\Registry::api()->addError('Sent to $api Logger instance');
 *     Monolog\Registry::application()->addError('Sent to $application Logger instance');
 * }
 * </code>
 *
 * @author Tomas Tatarko <tomas@tatarko.sk>
 */
class Registry
{
    /**
     * List of all loggers in the registry (by named indexes)
     *
     * @var Logger[]
     */
    private static $loggers = array();

    /**
     * Adds new logging channel to the registry
     *
     * @param  Logger                    $logger    Instance of the logging channel
     * @param  string|null               $name      Name of the logging channel ($logger->getName() by default)
     * @param  bool                      $overwrite Overwrite instance in the registry if the given name already exists?
     * @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists
     */
    public static function addLogger(Logger $logger, $name = null, $overwrite = false)
    {
        $name = $name ?: $logger->getName();

        if (isset(self::$loggers[$name]) && !$overwrite) {
            throw new InvalidArgumentException('Logger with the given name already exists');
        }

        self::$loggers[$name] = $logger;
    }

    /**
     * Checks if such logging channel exists by name or instance
     *
     * @param string|Logger $logger Name or logger instance
     */
    public static function hasLogger($logger)
    {
        if ($logger instanceof Logger) {
            $index = array_search($logger, self::$loggers, true);

            return false !== $index;
        } else {
            return isset(self::$loggers[$logger]);
        }
    }

    /**
     * Removes instance from registry by name or instance
     *
     * @param string|Logger $logger Name or logger instance
     */
    public static function removeLogger($logger)
    {
        if ($logger instanceof Logger) {
            if (false !== ($idx = array_search($logger, self::$loggers, true))) {
                unset(self::$loggers[$idx]);
            }
        } else {
            unset(self::$loggers[$logger]);
        }
    }

    /**
     * Clears the registry
     */
    public static function clear()
    {
        self::$loggers = array();
    }

    /**
     * Gets Logger instance from the registry
     *
     * @param  string                    $name Name of the requested Logger instance
     * @throws \InvalidArgumentException If named Logger instance is not in the registry
     * @return Logger                    Requested instance of Logger
     */
    public static function getInstance($name)
    {
        if (!isset(self::$loggers[$name])) {
            throw new InvalidArgumentException(sprintf('Requested "%s" logger instance is not in the registry', $name));
        }

        return self::$loggers[$name];
    }

    /**
     * Gets Logger instance from the registry via static method call
     *
     * @param  string                    $name      Name of the requested Logger instance
     * @param  array                     $arguments Arguments passed to static method call
     * @throws \InvalidArgumentException If named Logger instance is not in the registry
     * @return Logger                    Requested instance of Logger
     */
    public static function __callStatic($name, $arguments)
    {
        return self::getInstance($name);
    }
}
vendor
composer.lock
phpunit.xml
compiled
artifacts/
language: php

php:
  - 5.4
  - 5.5
  - 5.6
  - hhvm

before_script:
  - composer install

script: make test

after_script:
  - make perf
  - JP_PHP_COMPILE=on make perf
  - JP_PHP_COMPILE=on CACHE=on make perf
#!/usr/bin/env php
<?php
require __DIR__ . '/../vendor/autoload.php';

$dir = isset($argv[1]) ? $argv[1] : __DIR__ . '/../tests/compliance/perf';
is_dir($dir) or die('Dir not found: ' . $dir);
// Warm up the runner
\JmesPath\Env::search('foo', []);

$total = 0;
foreach (glob($dir . '/*.json') as $file) {
    $total += runSuite($file);
}
echo "\nTotal time: {$total}\n";

function runSuite($file)
{
    $contents = file_get_contents($file);
    $json = json_decode($contents, true);
    $total = 0;
    foreach ($json as $suite) {
        foreach ($suite['cases'] as $case) {
            $total += runCase(
                $suite['given'],
                $case['expression'],
                $case['name']
            );
        }
    }
    return $total;
}

function runCase($given, $expression, $name)
{
    $best = 99999;
    $runtime = \JmesPath\Env::createRuntime();

    for ($i = 0; $i < 100; $i++) {
        $t = microtime(true);
        $runtime($expression, $given);
        $tryTime = (microtime(true) - $t) * 1000;
        if ($tryTime < $best) {
            $best = $tryTime;
        }
        if (!getenv('CACHE')) {
            $runtime = \JmesPath\Env::createRuntime();
            // Delete compiled scripts if not caching.
            if ($runtime instanceof \JmesPath\CompilerRuntime) {
                array_map('unlink', glob(sys_get_temp_dir() . '/jmespath_*.php'));
            }
        }
    }

    printf("time: %07.4fms name: %s\n", $best, $name);

    return $best;
}
# CHANGELOG

## 2.4.0 - 2016-12-03

* Added support for floats when interpreting data.
* Added a function_exists check to work around redeclaration issues.

## 2.3.0 - 2016-01-05

* Added support for [JEP-9](https://github.com/jmespath/jmespath.site/blob/master/docs/proposals/improved-filters.rst),
  including unary filter expressions, and `&&` filter expressions.
* Fixed various parsing issues, including not removing escaped single quotes
  from raw string literals.
* Added support for the `map` function.
* Fixed several issues with code generation.

## 2.2.0 - 2015-05-27

* Added support for [JEP-12](https://github.com/jmespath/jmespath.site/blob/master/docs/proposals/raw-string-literals.rst)
  and raw string literals (e.g., `'foo'`).

## 2.1.0 - 2014-01-13

* Added `JmesPath\Env::cleanCompileDir()` to delete any previously compiled
  JMESPath expressions.

## 2.0.0 - 2014-01-11

* Moving to a flattened namespace structure.
* Runtimes are now only PHP callables.
* Fixed an error in the way empty JSON literals are parsed so that they now
  return an empty string to match the Python and JavaScript implementations.
* Removed functions from runtimes. Instead there is now a function dispatcher
  class, FnDispatcher, that provides function implementations behind a single
  dispatch function.
* Removed ExprNode in lieu of just using a PHP callable with bound variables.
* Removed debug methods from runtimes and instead into a new Debugger class.
* Heavily cleaned up function argument validation.
* Slice syntax is now properly validated (i.e., colons are followed by the
  appropriate value).
* Lots of code cleanup and performance improvements.
* Added a convenient `JmesPath\search()` function.
* **IMPORTANT**: Relocating the project to https://github.com/jmespath/jmespath.php

## 1.1.1 - 2014-10-08

* Added support for using ArrayAccess and Countable as arrays and objects.

## 1.1.0 - 2014-08-06

* Added the ability to search data returned from json_decode() where JSON
  objects are returned as stdClass objects.
{
  "name": "mtdowling/jmespath.php",
  "description": "Declaratively specify how to extract elements from a JSON document",
  "keywords": ["json", "jsonpath"],
  "license": "MIT",

  "authors": [
    {
      "name": "Michael Dowling",
      "email": "mtdowling@gmail.com",
      "homepage": "https://github.com/mtdowling"
    }
  ],

  "require": {
    "php": ">=5.4.0"
  },

  "require-dev": {
    "phpunit/phpunit": "~4.0"
  },

  "autoload": {
    "psr-4": {
      "JmesPath\\": "src/"
    },
    "files": ["src/JmesPath.php"]
  },

  "bin": ["bin/jp.php"],

  "extra": {
    "branch-alias": {
      "dev-master": "2.0-dev"
    }
  }
}
Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling

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.
all: clean coverage

test:
	vendor/bin/phpunit

coverage:
	vendor/bin/phpunit --coverage-html=artifacts/coverage

view-coverage:
	open artifacts/coverage/index.html

clean:
	rm -rf artifacts/*
	rm -rf compiled/*

perf:
	php bin/perf.php

.PHONY: test coverage perf
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php"
         colors="true">

    <testsuites>
        <testsuite>
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <filter>
        <whitelist>
            <directory suffix=".php">src</directory>
        </whitelist>
    </filter>

</phpunit>
============
jmespath.php
============

JMESPath (pronounced "jaymz path") allows you to declaratively specify how to
extract elements from a JSON document. *jmespath.php* allows you to use
JMESPath in PHP applications with PHP data structures. It requires PHP 5.4 or
greater and can be installed through `Composer <http://getcomposer.org/doc/00-intro.md>`_
using the ``mtdowling/jmespath.php`` package.

.. code-block:: php

    require 'vendor/autoload.php';

    $expression = 'foo.*.baz';

    $data = [
        'foo' => [
            'bar' => ['baz' => 1],
            'bam' => ['baz' => 2],
            'boo' => ['baz' => 3]
        ]
    ];

    JmesPath\search($expression, $data);
    // Returns: [1, 2, 3]

- `JMESPath Tutorial <http://jmespath.org/tutorial.html>`_
- `JMESPath Grammar <http://jmespath.org/specification.html#grammar>`_
- `JMESPath Python library <https://github.com/jmespath/jmespath.py>`_

PHP Usage
=========

The ``JmesPath\search`` function can be used in most cases when using the
library. This function utilizes a JMESPath runtime based on your environment.
The runtime utilized can be configured using environment variables and may at
some point in the future automatically utilize a C extension if available.

.. code-block:: php

    $result = JmesPath\search($expression, $data);

    // or, if you require PSR-4 compliance.
    $result = JmesPath\Env::search($expression, $data);

Runtimes
--------

jmespath.php utilizes *runtimes*. There are currently two runtimes:
AstRuntime and CompilerRuntime.

AstRuntime is utilized by ``JmesPath\search()`` and ``JmesPath\Env::search()``
by default.

AstRuntime
~~~~~~~~~~

The AstRuntime will parse an expression, cache the resulting AST in memory,
and interpret the AST using an external tree visitor. AstRuntime provides a
good general approach for interpreting JMESPath expressions that have a low to
moderate level of reuse.

.. code-block:: php

    $runtime = new JmesPath\AstRuntime();
    $runtime('foo.bar', ['foo' => ['bar' => 'baz']]);
    // > 'baz'

CompilerRuntime
~~~~~~~~~~~~~~~

``JmesPath\CompilerRuntime`` provides the most performance for
applications that have a moderate to high level of reuse of JMESPath
expressions. The CompilerRuntime will walk a JMESPath AST and emit PHP source
code, resulting in anywhere from 7x to 60x speed improvements.

Compiling JMESPath expressions to source code is a slower process than just
walking and interpreting a JMESPath AST (via the AstRuntime). However,
running the compiled JMESPath code results in much better performance than
walking an AST. This essentially means that there is a warm-up period when
using the ``CompilerRuntime``, but after the warm-up period, it will provide
much better performance.

Use the CompilerRuntime if you know that you will be executing JMESPath
expressions more than once or if you can pre-compile JMESPath expressions
before executing them (for example, server-side applications).

.. code-block:: php

    // Note: The cache directory argument is optional.
    $runtime = new JmesPath\CompilerRuntime('/path/to/compile/folder');
    $runtime('foo.bar', ['foo' => ['bar' => 'baz']]);
    // > 'baz'

Environment Variables
^^^^^^^^^^^^^^^^^^^^^

You can utilize the CompilerRuntime in ``JmesPath\search()`` by setting
the ``JP_PHP_COMPILE`` environment variable to "on" or to a directory
on disk used to store cached expressions.

Testing
=======

A comprehensive list of test cases can be found at
https://github.com/jmespath/jmespath.php/tree/master/tests/compliance.
These compliance tests are utilized by jmespath.php to ensure consistency with
other implementations, and can serve as examples of the language.

jmespath.php is tested using PHPUnit. In order to run the tests, you need to
first install the dependencies using Composer as described in the *Installation*
section. Next you just need to run the tests via make:

.. code-block:: bash

    make test

You can run a suite of performance tests as well:

.. code-block:: bash

    make perf
<?php
namespace JmesPath;

/**
 * Uses an external tree visitor to interpret an AST.
 */
class AstRuntime
{
    private $parser;
    private $interpreter;
    private $cache = [];
    private $cachedCount = 0;

    public function __construct(
        Parser $parser = null,
        callable $fnDispatcher = null
    ) {
        $fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance();
        $this->interpreter = new TreeInterpreter($fnDispatcher);
        $this->parser = $parser ?: new Parser();
    }

    /**
     * Returns data from the provided input that matches a given JMESPath
     * expression.
     *
     * @param string $expression JMESPath expression to evaluate
     * @param mixed  $data       Data to search. This data should be data that
     *                           is similar to data returned from json_decode
     *                           using associative arrays rather than objects.
     *
     * @return mixed|null Returns the matching data or null
     */
    public function __invoke($expression, $data)
    {
        if (!isset($this->cache[$expression])) {
            // Clear the AST cache when it hits 1024 entries
            if (++$this->cachedCount > 1024) {
                $this->cache = [];
                $this->cachedCount = 0;
            }
            $this->cache[$expression] = $this->parser->parse($expression);
        }

        return $this->interpreter->visit($this->cache[$expression], $data);
    }
}
<?php
namespace JmesPath;

/**
 * Compiles JMESPath expressions to PHP source code and executes it.
 *
 * JMESPath file names are stored in the cache directory using the following
 * logic to determine the filename:
 *
 * 1. Start with the string "jmespath_"
 * 2. Append the MD5 checksum of the expression.
 * 3. Append ".php"
 */
class CompilerRuntime
{
    private $parser;
    private $compiler;
    private $cacheDir;
    private $interpreter;

    /**
     * @param string $dir Directory used to store compiled PHP files.
     * @param Parser $parser JMESPath parser to utilize
     * @throws \RuntimeException if the cache directory cannot be created
     */
    public function __construct($dir = null, Parser $parser = null)
    {
        $this->parser = $parser ?: new Parser();
        $this->compiler = new TreeCompiler();
        $dir = $dir ?: sys_get_temp_dir();

        if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
            throw new \RuntimeException("Unable to create cache directory: $dir");
        }

        $this->cacheDir = realpath($dir);
        $this->interpreter = new TreeInterpreter();
    }

    /**
     * Returns data from the provided input that matches a given JMESPath
     * expression.
     *
     * @param string $expression JMESPath expression to evaluate
     * @param mixed  $data       Data to search. This data should be data that
     *                           is similar to data returned from json_decode
     *                           using associative arrays rather than objects.
     *
     * @return mixed|null Returns the matching data or null
     * @throws \RuntimeException
     */
    public function __invoke($expression, $data)
    {
        $functionName = 'jmespath_' . md5($expression);

        if (!function_exists($functionName)) {
            $filename = "{$this->cacheDir}/{$functionName}.php";
            if (!file_exists($filename)) {
                $this->compile($filename, $expression, $functionName);
            }
            require $filename;
        }

        return $functionName($this->interpreter, $data);
    }

    private function compile($filename, $expression, $functionName)
    {
        $code = $this->compiler->visit(
            $this->parser->parse($expression),
            $functionName,
            $expression
        );

        if (!file_put_contents($filename, $code)) {
            throw new \RuntimeException(sprintf(
                'Unable to write the compiled PHP code to: %s (%s)',
                $filename,
                var_export(error_get_last(), true)
            ));
        }
    }
}
<?php
namespace JmesPath;

/**
 * Provides CLI debugging information for the AST and Compiler runtimes.
 */
class DebugRuntime
{
    private $runtime;
    private $out;
    private $lexer;
    private $parser;

    public function __construct(callable $runtime, $output = null)
    {
        $this->runtime = $runtime;
        $this->out = $output ?: STDOUT;
        $this->lexer = new Lexer();
        $this->parser = new Parser($this->lexer);
    }

    public function __invoke($expression, $data)
    {
        if ($this->runtime instanceof CompilerRuntime) {
            return $this->debugCompiled($expression, $data);
        }

        return $this->debugInterpreted($expression, $data);
    }

    private function debugInterpreted($expression, $data)
    {
        return $this->debugCallback(
            function () use ($expression, $data) {
                $runtime = $this->runtime;
                return $runtime($expression, $data);
            },
            $expression,
            $data
        );
    }

    private function debugCompiled($expression, $data)
    {
        $result = $this->debugCallback(
            function () use ($expression, $data) {
                $runtime = $this->runtime;
                return $runtime($expression, $data);
            },
            $expression,
            $data
        );
        $this->dumpCompiledCode($expression);

        return $result;
    }

    private function dumpTokens($expression)
    {
        $lexer = new Lexer();
        fwrite($this->out, "Tokens\n======\n\n");
        $tokens = $lexer->tokenize($expression);

        foreach ($tokens as $t) {
            fprintf(
                $this->out,
                "%3d  %-13s  %s\n", $t['pos'], $t['type'],
                json_encode($t['value'])
            );
        }

        fwrite($this->out, "\n");
    }

    private function dumpAst($expression)
    {
        $parser = new Parser();
        $ast = $parser->parse($expression);
        fwrite($this->out, "AST\n========\n\n");
        fwrite($this->out, json_encode($ast, JSON_PRETTY_PRINT) . "\n");
    }

    private function dumpCompiledCode($expression)
    {
        fwrite($this->out, "Code\n========\n\n");
        $dir = sys_get_temp_dir();
        $hash = md5($expression);
        $functionName = "jmespath_{$hash}";
        $filename = "{$dir}/{$functionName}.php";
        fwrite($this->out, "File: {$filename}\n\n");
        fprintf($this->out, file_get_contents($filename));
    }

    private function debugCallback(callable $debugFn, $expression, $data)
    {
        fprintf($this->out, "Expression\n==========\n\n%s\n\n", $expression);
        $this->dumpTokens($expression);
        $this->dumpAst($expression);
        fprintf($this->out, "\nData\n====\n\n%s\n\n", json_encode($data, JSON_PRETTY_PRINT));
        $startTime = microtime(true);
        $result = $debugFn();
        $total = microtime(true) - $startTime;
        fprintf($this->out, "\nResult\n======\n\n%s\n\n", json_encode($result, JSON_PRETTY_PRINT));
        fwrite($this->out, "Time\n====\n\n");
        fprintf($this->out, "Total time:     %f ms\n\n", $total);

        return $result;
    }
}
<?php
namespace JmesPath;

/**
 * Provides a simple environment based search.
 *
 * The runtime utilized by the Env class can be customized via environment
 * variables. If the JP_PHP_COMPILE environment variable is specified, then the
 * CompilerRuntime will be utilized. If set to "on", JMESPath expressions will
 * be cached to the system's temp directory. Set the environment variable to
 * a string to cache expressions to a specific directory.
 */
final class Env
{
    const COMPILE_DIR = 'JP_PHP_COMPILE';

    /**
     * Returns data from the input array that matches a JMESPath expression.
     *
     * @param string $expression JMESPath expression to evaluate
     * @param mixed  $data       JSON-like data to search
     *
     * @return mixed|null Returns the matching data or null
     */
    public static function search($expression, $data)
    {
        static $runtime;
        if (!$runtime) {
            $runtime = Env::createRuntime();
        }
        return $runtime($expression, $data);
    }

    /**
     * Creates a JMESPath runtime based on environment variables and extensions
     * available on a system.
     *
     * @return callable
     */
    public static function createRuntime()
    {
        switch ($compileDir = getenv(self::COMPILE_DIR)) {
            case false: return new AstRuntime();
            case 'on': return new CompilerRuntime();
            default: return new CompilerRuntime($compileDir);
        }
    }

    /**
     * Delete all previously compiled JMESPath files from the JP_COMPILE_DIR
     * directory or sys_get_temp_dir().
     *
     * @return int Returns the number of deleted files.
     */
    public static function cleanCompileDir()
    {
        $total = 0;
        $compileDir = getenv(self::COMPILE_DIR) ?: sys_get_temp_dir();
        foreach (glob("{$compileDir}/jmespath_*.php") as $file) {
            $total++;
            unlink($file);
        }

        return $total;
    }
}
<?php
namespace JmesPath;

/**
 * Dispatches to named JMESPath functions using a single function that has the
 * following signature:
 *
 *     mixed $result = fn(string $function_name, array $args)
 */
class FnDispatcher
{
    /**
     * Gets a cached instance of the default function implementations.
     *
     * @return FnDispatcher
     */
    public static function getInstance()
    {
        static $instance = null;
        if (!$instance) {
            $instance = new self();
        }

        return $instance;
    }

    /**
     * @param string $fn   Function name.
     * @param array  $args Function arguments.
     *
     * @return mixed
     */
    public function __invoke($fn, array $args)
    {
        return $this->{'fn_' . $fn}($args);
    }

    private function fn_abs(array $args)
    {
        $this->validate('abs', $args, [['number']]);
        return abs($args[0]);
    }

    private function fn_avg(array $args)
    {
        $this->validate('avg', $args, [['array']]);
        $sum = $this->reduce('avg:0', $args[0], ['number'], function ($a, $b) {
            return $a + $b;
        });
        return $args[0] ? ($sum / count($args[0])) : null;
    }

    private function fn_ceil(array $args)
    {
        $this->validate('ceil', $args, [['number']]);
        return ceil($args[0]);
    }

    private function fn_contains(array $args)
    {
        $this->validate('contains', $args, [['string', 'array'], ['any']]);
        if (is_array($args[0])) {
            return in_array($args[1], $args[0]);
        } elseif (is_string($args[1])) {
            return strpos($args[0], $args[1]) !== false;
        } else {
            return null;
        }
    }

    private function fn_ends_with(array $args)
    {
        $this->validate('ends_with', $args, [['string'], ['string']]);
        list($search, $suffix) = $args;
        return $suffix === '' || substr($search, -strlen($suffix)) === $suffix;
    }

    private function fn_floor(array $args)
    {
        $this->validate('floor', $args, [['number']]);
        return floor($args[0]);
    }

    private function fn_not_null(array $args)
    {
        if (!$args) {
            throw new \RuntimeException(
                "not_null() expects 1 or more arguments, 0 were provided"
            );
        }

        return array_reduce($args, function ($carry, $item) {
            return $carry !== null ? $carry : $item;
        });
    }

    private function fn_join(array $args)
    {
        $this->validate('join', $args, [['string'], ['array']]);
        $fn = function ($a, $b, $i) use ($args) {
            return $i ? ($a . $args[0] . $b) : $b;
        };
        return $this->reduce('join:0', $args[1], ['string'], $fn);
    }

    private function fn_keys(array $args)
    {
        $this->validate('keys', $args, [['object']]);
        return array_keys((array) $args[0]);
    }

    private function fn_length(array $args)
    {
        $this->validate('length', $args, [['string', 'array', 'object']]);
        return is_string($args[0]) ? strlen($args[0]) : count((array) $args[0]);
    }

    private function fn_max(array $args)
    {
        $this->validate('max', $args, [['array']]);
        $fn = function ($a, $b) { return $a >= $b ? $a : $b; };
        return $this->reduce('max:0', $args[0], ['number', 'string'], $fn);
    }

    private function fn_max_by(array $args)
    {
        $this->validate('max_by', $args, [['array'], ['expression']]);
        $expr = $this->wrapExpression('max_by:1', $args[1], ['number', 'string']);
        $fn = function ($carry, $item, $index) use ($expr) {
            return $index
                ? ($expr($carry) >= $expr($item) ? $carry : $item)
                : $item;
        };
        return $this->reduce('max_by:1', $args[0], ['any'], $fn);
    }

    private function fn_min(array $args)
    {
        $this->validate('min', $args, [['array']]);
        $fn = function ($a, $b, $i) { return $i && $a <= $b ? $a : $b; };
        return $this->reduce('min:0', $args[0], ['number', 'string'], $fn);
    }

    private function fn_min_by(array $args)
    {
        $this->validate('min_by', $args, [['array'], ['expression']]);
        $expr = $this->wrapExpression('min_by:1', $args[1], ['number', 'string']);
        $i = -1;
        $fn = function ($a, $b) use ($expr, &$i) {
            return ++$i ? ($expr($a) <= $expr($b) ? $a : $b) : $b;
        };
        return $this->reduce('min_by:1', $args[0], ['any'], $fn);
    }

    private function fn_reverse(array $args)
    {
        $this->validate('reverse', $args, [['array', 'string']]);
        if (is_array($args[0])) {
            return array_reverse($args[0]);
        } elseif (is_string($args[0])) {
            return strrev($args[0]);
        } else {
            throw new \RuntimeException('Cannot reverse provided argument');
        }
    }

    private function fn_sum(array $args)
    {
        $this->validate('sum', $args, [['array']]);
        $fn = function ($a, $b) { return $a + $b; };
        return $this->reduce('sum:0', $args[0], ['number'], $fn);
    }

    private function fn_sort(array $args)
    {
        $this->validate('sort', $args, [['array']]);
        $valid = ['string', 'number'];
        return Utils::stableSort($args[0], function ($a, $b) use ($valid) {
            $this->validateSeq('sort:0', $valid, $a, $b);
            return strnatcmp($a, $b);
        });
    }

    private function fn_sort_by(array $args)
    {
        $this->validate('sort_by', $args, [['array'], ['expression']]);
        $expr = $args[1];
        $valid = ['string', 'number'];
        return Utils::stableSort(
            $args[0],
            function ($a, $b) use ($expr, $valid) {
                $va = $expr($a);
                $vb = $expr($b);
                $this->validateSeq('sort_by:0', $valid, $va, $vb);
                return strnatcmp($va, $vb);
            }
        );
    }

    private function fn_starts_with(array $args)
    {
        $this->validate('starts_with', $args, [['string'], ['string']]);
        list($search, $prefix) = $args;
        return $prefix === '' || strpos($search, $prefix) === 0;
    }

    private function fn_type(array $args)
    {
        $this->validateArity('type', count($args), 1);
        return Utils::type($args[0]);
    }

    private function fn_to_string(array $args)
    {
        $this->validateArity('to_string', count($args), 1);
        $v = $args[0];
        if (is_string($v)) {
            return $v;
        } elseif (is_object($v)
            && !($v instanceof \JsonSerializable)
            && method_exists($v, '__toString')
        ) {
            return (string) $v;
        }

        return json_encode($v);
    }

    private function fn_to_number(array $args)
    {
        $this->validateArity('to_number', count($args), 1);
        $value = $args[0];
        $type = Utils::type($value);
        if ($type == 'number') {
            return $value;
        } elseif ($type == 'string' && is_numeric($value)) {
            return strpos($value, '.') ? (float) $value : (int) $value;
        } else {
            return null;
        }
    }

    private function fn_values(array $args)
    {
        $this->validate('values', $args, [['array', 'object']]);
        return array_values((array) $args[0]);
    }

    private function fn_merge(array $args)
    {
        if (!$args) {
            throw new \RuntimeException(
                "merge() expects 1 or more arguments, 0 were provided"
            );
        }

        return call_user_func_array('array_replace', $args);
    }

    private function fn_to_array(array $args)
    {
        $this->validate('to_array', $args, [['any']]);

        return Utils::isArray($args[0]) ? $args[0] : [$args[0]];
    }

    private function fn_map(array $args)
    {
        $this->validate('map', $args, [['expression'], ['any']]);
        $result = [];
        foreach ($args[1] as $a) {
            $result[] = $args[0]($a);
        }
        return $result;
    }

    private function typeError($from, $msg)
    {
        if (strpos($from, ':')) {
            list($fn, $pos) = explode(':', $from);
            throw new \RuntimeException(
                sprintf('Argument %d of %s %s', $pos, $fn, $msg)
            );
        } else {
            throw new \RuntimeException(
                sprintf('Type error: %s %s', $from, $msg)
            );
        }
    }

    private function validateArity($from, $given, $expected)
    {
        if ($given != $expected) {
            $err = "%s() expects {$expected} arguments, {$given} were provided";
            throw new \RuntimeException(sprintf($err, $from));
        }
    }

    private function validate($from, $args, $types = [])
    {
        $this->validateArity($from, count($args), count($types));
        foreach ($args as $index => $value) {
            if (!isset($types[$index]) || !$types[$index]) {
                continue;
            }
            $this->validateType("{$from}:{$index}", $value, $types[$index]);
        }
    }

    private function validateType($from, $value, array $types)
    {
        if ($types[0] == 'any'
            || in_array(Utils::type($value), $types)
            || ($value === [] && in_array('object', $types))
        ) {
            return;
        }
        $msg = 'must be one of the following types: ' . implode(', ', $types)
            . '. ' . Utils::type($value) . ' found';
        $this->typeError($from, $msg);
    }

    /**
     * Validates value A and B, ensures they both are correctly typed, and of
     * the same type.
     *
     * @param string   $from   String of function:argument_position
     * @param array    $types  Array of valid value types.
     * @param mixed    $a      Value A
     * @param mixed    $b      Value B
     */
    private function validateSeq($from, array $types, $a, $b)
    {
        $ta = Utils::type($a);
        $tb = Utils::type($b);

        if ($ta !== $tb) {
            $msg = "encountered a type mismatch in sequence: {$ta}, {$tb}";
            $this->typeError($from, $msg);
        }

        $typeMatch = ($types && $types[0] == 'any') || in_array($ta, $types);
        if (!$typeMatch) {
            $msg = 'encountered a type error in sequence. The argument must be '
                . 'an array of ' . implode('|', $types) . ' types. '
                . "Found {$ta}, {$tb}.";
            $this->typeError($from, $msg);
        }
    }

    /**
     * Reduces and validates an array of values to a single value using a fn.
     *
     * @param string   $from   String of function:argument_position
     * @param array    $values Values to reduce.
     * @param array    $types  Array of valid value types.
     * @param callable $reduce Reduce function that accepts ($carry, $item).
     *
     * @return mixed
     */
    private function reduce($from, array $values, array $types, callable $reduce)
    {
        $i = -1;
        return array_reduce(
            $values,
            function ($carry, $item) use ($from, $types, $reduce, &$i) {
                if (++$i > 0) {
                    $this->validateSeq($from, $types, $carry, $item);
                }
                return $reduce($carry, $item, $i);
            }
        );
    }

    /**
     * Validates the return values of expressions as they are applied.
     *
     * @param string   $from  Function name : position
     * @param callable $expr  Expression function to validate.
     * @param array    $types Array of acceptable return type values.
     *
     * @return callable Returns a wrapped function
     */
    private function wrapExpression($from, callable $expr, array $types)
    {
        list($fn, $pos) = explode(':', $from);
        $from = "The expression return value of argument {$pos} of {$fn}";
        return function ($value) use ($from, $expr, $types) {
            $value = $expr($value);
            $this->validateType($from, $value, $types);
            return $value;
        };
    }

    /** @internal Pass function name validation off to runtime */
    public function __call($name, $args)
    {
        $name = str_replace('fn_', '', $name);
        throw new \RuntimeException("Call to undefined function {$name}");
    }
}
<?php
namespace JmesPath;

/**
 * Returns data from the input array that matches a JMESPath expression.
 *
 * @param string $expression Expression to search.
 * @param mixed $data Data to search.
 *
 * @return mixed|null
 */
if (!function_exists(__NAMESPACE__ . '\search')) {
    function search($expression, $data)
    {
        return Env::search($expression, $data);
    }
}
<?php
namespace JmesPath;

/**
 * Tokenizes JMESPath expressions
 */
class Lexer
{
    const T_DOT = 'dot';
    const T_STAR = 'star';
    const T_COMMA = 'comma';
    const T_COLON = 'colon';
    const T_CURRENT = 'current';
    const T_EXPREF = 'expref';
    const T_LPAREN = 'lparen';
    const T_RPAREN = 'rparen';
    const T_LBRACE = 'lbrace';
    const T_RBRACE = 'rbrace';
    const T_LBRACKET = 'lbracket';
    const T_RBRACKET = 'rbracket';
    const T_FLATTEN = 'flatten';
    const T_IDENTIFIER = 'identifier';
    const T_NUMBER = 'number';
    const T_QUOTED_IDENTIFIER = 'quoted_identifier';
    const T_UNKNOWN = 'unknown';
    const T_PIPE = 'pipe';
    const T_OR = 'or';
    const T_AND = 'and';
    const T_NOT = 'not';
    const T_FILTER = 'filter';
    const T_LITERAL = 'literal';
    const T_EOF = 'eof';
    const T_COMPARATOR = 'comparator';

    const STATE_IDENTIFIER = 0;
    const STATE_NUMBER = 1;
    const STATE_SINGLE_CHAR = 2;
    const STATE_WHITESPACE = 3;
    const STATE_STRING_LITERAL = 4;
    const STATE_QUOTED_STRING = 5;
    const STATE_JSON_LITERAL = 6;
    const STATE_LBRACKET = 7;
    const STATE_PIPE = 8;
    const STATE_LT = 9;
    const STATE_GT = 10;
    const STATE_EQ = 11;
    const STATE_NOT = 12;
    const STATE_AND = 13;

    /** @var array We know what token we are consuming based on each char */
    private static $transitionTable = [
        '<'  => self::STATE_LT,
        '>'  => self::STATE_GT,
        '='  => self::STATE_EQ,
        '!'  => self::STATE_NOT,
        '['  => self::STATE_LBRACKET,
        '|'  => self::STATE_PIPE,
        '&'  => self::STATE_AND,
        '`'  => self::STATE_JSON_LITERAL,
        '"'  => self::STATE_QUOTED_STRING,
        "'"  => self::STATE_STRING_LITERAL,
        '-'  => self::STATE_NUMBER,
        '0'  => self::STATE_NUMBER,
        '1'  => self::STATE_NUMBER,
        '2'  => self::STATE_NUMBER,
        '3'  => self::STATE_NUMBER,
        '4'  => self::STATE_NUMBER,
        '5'  => self::STATE_NUMBER,
        '6'  => self::STATE_NUMBER,
        '7'  => self::STATE_NUMBER,
        '8'  => self::STATE_NUMBER,
        '9'  => self::STATE_NUMBER,
        ' '  => self::STATE_WHITESPACE,
        "\t" => self::STATE_WHITESPACE,
        "\n" => self::STATE_WHITESPACE,
        "\r" => self::STATE_WHITESPACE,
        '.'  => self::STATE_SINGLE_CHAR,
        '*'  => self::STATE_SINGLE_CHAR,
        ']'  => self::STATE_SINGLE_CHAR,
        ','  => self::STATE_SINGLE_CHAR,
        ':'  => self::STATE_SINGLE_CHAR,
        '@'  => self::STATE_SINGLE_CHAR,
        '('  => self::STATE_SINGLE_CHAR,
        ')'  => self::STATE_SINGLE_CHAR,
        '{'  => self::STATE_SINGLE_CHAR,
        '}'  => self::STATE_SINGLE_CHAR,
        '_'  => self::STATE_IDENTIFIER,
        'A'  => self::STATE_IDENTIFIER,
        'B'  => self::STATE_IDENTIFIER,
        'C'  => self::STATE_IDENTIFIER,
        'D'  => self::STATE_IDENTIFIER,
        'E'  => self::STATE_IDENTIFIER,
        'F'  => self::STATE_IDENTIFIER,
        'G'  => self::STATE_IDENTIFIER,
        'H'  => self::STATE_IDENTIFIER,
        'I'  => self::STATE_IDENTIFIER,
        'J'  => self::STATE_IDENTIFIER,
        'K'  => self::STATE_IDENTIFIER,
        'L'  => self::STATE_IDENTIFIER,
        'M'  => self::STATE_IDENTIFIER,
        'N'  => self::STATE_IDENTIFIER,
        'O'  => self::STATE_IDENTIFIER,
        'P'  => self::STATE_IDENTIFIER,
        'Q'  => self::STATE_IDENTIFIER,
        'R'  => self::STATE_IDENTIFIER,
        'S'  => self::STATE_IDENTIFIER,
        'T'  => self::STATE_IDENTIFIER,
        'U'  => self::STATE_IDENTIFIER,
        'V'  => self::STATE_IDENTIFIER,
        'W'  => self::STATE_IDENTIFIER,
        'X'  => self::STATE_IDENTIFIER,
        'Y'  => self::STATE_IDENTIFIER,
        'Z'  => self::STATE_IDENTIFIER,
        'a'  => self::STATE_IDENTIFIER,
        'b'  => self::STATE_IDENTIFIER,
        'c'  => self::STATE_IDENTIFIER,
        'd'  => self::STATE_IDENTIFIER,
        'e'  => self::STATE_IDENTIFIER,
        'f'  => self::STATE_IDENTIFIER,
        'g'  => self::STATE_IDENTIFIER,
        'h'  => self::STATE_IDENTIFIER,
        'i'  => self::STATE_IDENTIFIER,
        'j'  => self::STATE_IDENTIFIER,
        'k'  => self::STATE_IDENTIFIER,
        'l'  => self::STATE_IDENTIFIER,
        'm'  => self::STATE_IDENTIFIER,
        'n'  => self::STATE_IDENTIFIER,
        'o'  => self::STATE_IDENTIFIER,
        'p'  => self::STATE_IDENTIFIER,
        'q'  => self::STATE_IDENTIFIER,
        'r'  => self::STATE_IDENTIFIER,
        's'  => self::STATE_IDENTIFIER,
        't'  => self::STATE_IDENTIFIER,
        'u'  => self::STATE_IDENTIFIER,
        'v'  => self::STATE_IDENTIFIER,
        'w'  => self::STATE_IDENTIFIER,
        'x'  => self::STATE_IDENTIFIER,
        'y'  => self::STATE_IDENTIFIER,
        'z'  => self::STATE_IDENTIFIER,
    ];

    /** @var array Valid identifier characters after first character */
    private $validIdentifier = [
        'A' => true, 'B' => true, 'C' => true, 'D' => true, 'E' => true,
        'F' => true, 'G' => true, 'H' => true, 'I' => true, 'J' => true,
        'K' => true, 'L' => true, 'M' => true, 'N' => true, 'O' => true,
        'P' => true, 'Q' => true, 'R' => true, 'S' => true, 'T' => true,
        'U' => true, 'V' => true, 'W' => true, 'X' => true, 'Y' => true,
        'Z' => true, 'a' => true, 'b' => true, 'c' => true, 'd' => true,
        'e' => true, 'f' => true, 'g' => true, 'h' => true, 'i' => true,
        'j' => true, 'k' => true, 'l' => true, 'm' => true, 'n' => true,
        'o' => true, 'p' => true, 'q' => true, 'r' => true, 's' => true,
        't' => true, 'u' => true, 'v' => true, 'w' => true, 'x' => true,
        'y' => true, 'z' => true, '_' => true, '0' => true, '1' => true,
        '2' => true, '3' => true, '4' => true, '5' => true, '6' => true,
        '7' => true, '8' => true, '9' => true,
    ];

    /** @var array Valid number characters after the first character */
    private $numbers = [
        '0' => true, '1' => true, '2' => true, '3' => true, '4' => true,
        '5' => true, '6' => true, '7' => true, '8' => true, '9' => true
    ];

    /** @var array Map of simple single character tokens */
    private $simpleTokens = [
        '.' => self::T_DOT,
        '*' => self::T_STAR,
        ']' => self::T_RBRACKET,
        ',' => self::T_COMMA,
        ':' => self::T_COLON,
        '@' => self::T_CURRENT,
        '(' => self::T_LPAREN,
        ')' => self::T_RPAREN,
        '{' => self::T_LBRACE,
        '}' => self::T_RBRACE,
    ];

    /**
     * Tokenize the JMESPath expression into an array of tokens hashes that
     * contain a 'type', 'value', and 'key'.
     *
     * @param string $input JMESPath input
     *
     * @return array
     * @throws SyntaxErrorException
     */
    public function tokenize($input)
    {
        $tokens = [];

        if ($input === '') {
            goto eof;
        }

        $chars = str_split($input);

        while (false !== ($current = current($chars))) {

            // Every character must be in the transition character table.
            if (!isset(self::$transitionTable[$current])) {
                $tokens[] = [
                    'type'  => self::T_UNKNOWN,
                    'pos'   => key($chars),
                    'value' => $current
                ];
                next($chars);
                continue;
            }

            $state = self::$transitionTable[$current];

            if ($state === self::STATE_SINGLE_CHAR) {

                // Consume simple tokens like ".", ",", "@", etc.
                $tokens[] = [
                    'type'  => $this->simpleTokens[$current],
                    'pos'   => key($chars),
                    'value' => $current
                ];
                next($chars);

            } elseif ($state === self::STATE_IDENTIFIER) {

                // Consume identifiers
                $start = key($chars);
                $buffer = '';
                do {
                    $buffer .= $current;
                    $current = next($chars);
                } while ($current !== false && isset($this->validIdentifier[$current]));
                $tokens[] = [
                    'type'  => self::T_IDENTIFIER,
                    'value' => $buffer,
                    'pos'   => $start
                ];

            } elseif ($state === self::STATE_WHITESPACE) {

                // Skip whitespace
                next($chars);

            } elseif ($state === self::STATE_LBRACKET) {

                // Consume "[", "[?", and "[]"
                $position = key($chars);
                $actual = next($chars);
                if ($actual === ']') {
                    next($chars);
                    $tokens[] = [
                        'type'  => self::T_FLATTEN,
                        'pos'   => $position,
                        'value' => '[]'
                    ];
                } elseif ($actual === '?') {
                    next($chars);
                    $tokens[] = [
                        'type'  => self::T_FILTER,
                        'pos'   => $position,
                        'value' => '[?'
                    ];
                } else {
                    $tokens[] = [
                        'type'  => self::T_LBRACKET,
                        'pos'   => $position,
                        'value' => '['
                    ];
                }

            } elseif ($state === self::STATE_STRING_LITERAL) {

                // Consume raw string literals
                $t = $this->inside($chars, "'", self::T_LITERAL);
                $t['value'] = str_replace("\\'", "'", $t['value']);
                $tokens[] = $t;

            } elseif ($state === self::STATE_PIPE) {

                // Consume pipe and OR
                $tokens[] = $this->matchOr($chars, '|', '|', self::T_OR, self::T_PIPE);

            } elseif ($state == self::STATE_JSON_LITERAL) {

                // Consume JSON literals
                $token = $this->inside($chars, '`', self::T_LITERAL);
                if ($token['type'] === self::T_LITERAL) {
                    $token['value'] = str_replace('\\`', '`', $token['value']);
                    $token = $this->parseJson($token);
                }
                $tokens[] = $token;

            } elseif ($state == self::STATE_NUMBER) {

                // Consume numbers
                $start = key($chars);
                $buffer = '';
                do {
                    $buffer .= $current;
                    $current = next($chars);
                } while ($current !== false && isset($this->numbers[$current]));
                $tokens[] = [
                    'type'  => self::T_NUMBER,
                    'value' => (int)$buffer,
                    'pos'   => $start
                ];

            } elseif ($state === self::STATE_QUOTED_STRING) {

                // Consume quoted identifiers
                $token = $this->inside($chars, '"', self::T_QUOTED_IDENTIFIER);
                if ($token['type'] === self::T_QUOTED_IDENTIFIER) {
                    $token['value'] = '"' . $token['value'] . '"';
                    $token = $this->parseJson($token);
                }
                $tokens[] = $token;

            } elseif ($state === self::STATE_EQ) {

                // Consume equals
                $tokens[] = $this->matchOr($chars, '=', '=', self::T_COMPARATOR, self::T_UNKNOWN);

            } elseif ($state == self::STATE_AND) {

                $tokens[] = $this->matchOr($chars, '&', '&', self::T_AND, self::T_EXPREF);

            } elseif ($state === self::STATE_NOT) {

                // Consume not equal
                $tokens[] = $this->matchOr($chars, '!', '=', self::T_COMPARATOR, self::T_NOT);

            } else {

                // either '<' or '>'
                // Consume less than and greater than
                $tokens[] = $this->matchOr($chars, $current, '=', self::T_COMPARATOR, self::T_COMPARATOR);

            }
        }

        eof:
        $tokens[] = [
            'type'  => self::T_EOF,
            'pos'   => strlen($input),
            'value' => null
        ];

        return $tokens;
    }

    /**
     * Returns a token based on whether or not the next token matches the
     * expected value. If it does, a token of "$type" is returned. Otherwise,
     * a token of "$orElse" type is returned.
     *
     * @param array  $chars    Array of characters by reference.
     * @param string $current  The current character.
     * @param string $expected Expected character.
     * @param string $type     Expected result type.
     * @param string $orElse   Otherwise return a token of this type.
     *
     * @return array Returns a conditional token.
     */
    private function matchOr(array &$chars, $current, $expected, $type, $orElse)
    {
        if (next($chars) === $expected) {
            next($chars);
            return [
                'type'  => $type,
                'pos'   => key($chars) - 1,
                'value' => $current . $expected
            ];
        }

        return [
            'type'  => $orElse,
            'pos'   => key($chars) - 1,
            'value' => $current
        ];
    }

    /**
     * Returns a token the is the result of consuming inside of delimiter
     * characters. Escaped delimiters will be adjusted before returning a
     * value. If the token is not closed, "unknown" is returned.
     *
     * @param array  $chars Array of characters by reference.
     * @param string $delim The delimiter character.
     * @param string $type  Token type.
     *
     * @return array Returns the consumed token.
     */
    private function inside(array &$chars, $delim, $type)
    {
        $position = key($chars);
        $current = next($chars);
        $buffer = '';

        while ($current !== $delim) {
            if ($current === '\\') {
                $buffer .= '\\';
                $current = next($chars);
            }
            if ($current === false) {
                // Unclosed delimiter
                return [
                    'type'  => self::T_UNKNOWN,
                    'value' => $buffer,
                    'pos'   => $position
                ];
            }
            $buffer .= $current;
            $current = next($chars);
        }

        next($chars);

        return ['type' => $type, 'value' => $buffer, 'pos' => $position];
    }

    /**
     * Parses a JSON token or sets the token type to "unknown" on error.
     *
     * @param array $token Token that needs parsing.
     *
     * @return array Returns a token with a parsed value.
     */
    private function parseJson(array $token)
    {
        $value = json_decode($token['value'], true);

        if ($error = json_last_error()) {
            // Legacy support for elided quotes. Try to parse again by adding
            // quotes around the bad input value.
            $value = json_decode('"' . $token['value'] . '"', true);
            if ($error = json_last_error()) {
                $token['type'] = self::T_UNKNOWN;
                return $token;
            }
        }

        $token['value'] = $value;
        return $token;
    }
}
<?php
namespace JmesPath;

use JmesPath\Lexer as T;

/**
 * JMESPath Pratt parser
 * @link http://hall.org.ua/halls/wizzard/pdf/Vaughan.Pratt.TDOP.pdf
 */
class Parser
{
    /** @var Lexer */
    private $lexer;
    private $tokens;
    private $token;
    private $tpos;
    private $expression;
    private static $nullToken = ['type' => T::T_EOF];
    private static $currentNode = ['type' => T::T_CURRENT];

    private static $bp = [
        T::T_EOF               => 0,
        T::T_QUOTED_IDENTIFIER => 0,
        T::T_IDENTIFIER        => 0,
        T::T_RBRACKET          => 0,
        T::T_RPAREN            => 0,
        T::T_COMMA             => 0,
        T::T_RBRACE            => 0,
        T::T_NUMBER            => 0,
        T::T_CURRENT           => 0,
        T::T_EXPREF            => 0,
        T::T_COLON             => 0,
        T::T_PIPE              => 1,
        T::T_OR                => 2,
        T::T_AND               => 3,
        T::T_COMPARATOR        => 5,
        T::T_FLATTEN           => 9,
        T::T_STAR              => 20,
        T::T_FILTER            => 21,
        T::T_DOT               => 40,
        T::T_NOT               => 45,
        T::T_LBRACE            => 50,
        T::T_LBRACKET          => 55,
        T::T_LPAREN            => 60,
    ];

    /** @var array Acceptable tokens after a dot token */
    private static $afterDot = [
        T::T_IDENTIFIER        => true, // foo.bar
        T::T_QUOTED_IDENTIFIER => true, // foo."bar"
        T::T_STAR              => true, // foo.*
        T::T_LBRACE            => true, // foo[1]
        T::T_LBRACKET          => true, // foo{a: 0}
        T::T_FILTER            => true, // foo.[?bar==10]
    ];

    /**
     * @param Lexer $lexer Lexer used to tokenize expressions
     */
    public function __construct(Lexer $lexer = null)
    {
        $this->lexer = $lexer ?: new Lexer();
    }

    /**
     * Parses a JMESPath expression into an AST
     *
     * @param string $expression JMESPath expression to compile
     *
     * @return array Returns an array based AST
     * @throws SyntaxErrorException
     */
    public function parse($expression)
    {
        $this->expression = $expression;
        $this->tokens = $this->lexer->tokenize($expression);
        $this->tpos = -1;
        $this->next();
        $result = $this->expr();

        if ($this->token['type'] === T::T_EOF) {
            return $result;
        }

        throw $this->syntax('Did not reach the end of the token stream');
    }

    /**
     * Parses an expression while rbp < lbp.
     *
     * @param int   $rbp  Right bound precedence
     *
     * @return array
     */
    private function expr($rbp = 0)
    {
        $left = $this->{"nud_{$this->token['type']}"}();
        while ($rbp < self::$bp[$this->token['type']]) {
            $left = $this->{"led_{$this->token['type']}"}($left);
        }

        return $left;
    }

    private function nud_identifier()
    {
        $token = $this->token;
        $this->next();
        return ['type' => 'field', 'value' => $token['value']];
    }

    private function nud_quoted_identifier()
    {
        $token = $this->token;
        $this->next();
        $this->assertNotToken(T::T_LPAREN);
        return ['type' => 'field', 'value' => $token['value']];
    }

    private function nud_current()
    {
        $this->next();
        return self::$currentNode;
    }

    private function nud_literal()
    {
        $token = $this->token;
        $this->next();
        return ['type' => 'literal', 'value' => $token['value']];
    }

    private function nud_expref()
    {
        $this->next();
        return ['type' => T::T_EXPREF, 'children' => [$this->expr(self::$bp[T::T_EXPREF])]];
    }

    private function nud_not()
    {
        $this->next();
        return ['type' => T::T_NOT, 'children' => [$this->expr(self::$bp[T::T_NOT])]];
    }

    private function nud_lparen() {
        $this->next();
        $result = $this->expr(0);
        if ($this->token['type'] !== T::T_RPAREN) {
            throw $this->syntax('Unclosed `(`');
        }
        $this->next();
        return $result;
    }

    private function nud_lbrace()
    {
        static $validKeys = [T::T_QUOTED_IDENTIFIER => true, T::T_IDENTIFIER => true];
        $this->next($validKeys);
        $pairs = [];

        do {
            $pairs[] = $this->parseKeyValuePair();
            if ($this->token['type'] == T::T_COMMA) {
                $this->next($validKeys);
            }
        } while ($this->token['type'] !== T::T_RBRACE);

        $this->next();

        return['type' => 'multi_select_hash', 'children' => $pairs];
    }

    private function nud_flatten()
    {
        return $this->led_flatten(self::$currentNode);
    }

    private function nud_filter()
    {
        return $this->led_filter(self::$currentNode);
    }

    private function nud_star()
    {
        return $this->parseWildcardObject(self::$currentNode);
    }

    private function nud_lbracket()
    {
        $this->next();
        $type = $this->token['type'];
        if ($type == T::T_NUMBER || $type == T::T_COLON) {
            return $this->parseArrayIndexExpression();
        } elseif ($type == T::T_STAR && $this->lookahead() == T::T_RBRACKET) {
            return $this->parseWildcardArray();
        } else {
            return $this->parseMultiSelectList();
        }
    }

    private function led_lbracket(array $left)
    {
        static $nextTypes = [T::T_NUMBER => true, T::T_COLON => true, T::T_STAR => true];
        $this->next($nextTypes);
        switch ($this->token['type']) {
            case T::T_NUMBER:
            case T::T_COLON:
                return [
                    'type' => 'subexpression',
                    'children' => [$left, $this->parseArrayIndexExpression()]
                ];
            default:
                return $this->parseWildcardArray($left);
        }
    }

    private function led_flatten(array $left)
    {
        $this->next();

        return [
            'type'     => 'projection',
            'from'     => 'array',
            'children' => [
                ['type' => T::T_FLATTEN, 'children' => [$left]],
                $this->parseProjection(self::$bp[T::T_FLATTEN])
            ]
        ];
    }

    private function led_dot(array $left)
    {
        $this->next(self::$afterDot);

        if ($this->token['type'] == T::T_STAR) {
            return $this->parseWildcardObject($left);
        }

        return [
            'type'     => 'subexpression',
            'children' => [$left, $this->parseDot(self::$bp[T::T_DOT])]
        ];
    }

    private function led_or(array $left)
    {
        $this->next();
        return [
            'type'     => T::T_OR,
            'children' => [$left, $this->expr(self::$bp[T::T_OR])]
        ];
    }

    private function led_and(array $left)
    {
        $this->next();
        return [
            'type'     => T::T_AND,
            'children' => [$left, $this->expr(self::$bp[T::T_AND])]
        ];
    }

    private function led_pipe(array $left)
    {
        $this->next();
        return [
            'type'     => T::T_PIPE,
            'children' => [$left, $this->expr(self::$bp[T::T_PIPE])]
        ];
    }

    private function led_lparen(array $left)
    {
        $args = [];
        $this->next();

        while ($this->token['type'] != T::T_RPAREN) {
            $args[] = $this->expr(0);
            if ($this->token['type'] == T::T_COMMA) {
                $this->next();
            }
        }

        $this->next();

        return [
            'type'     => 'function',
            'value'    => $left['value'],
            'children' => $args
        ];
    }

    private function led_filter(array $left)
    {
        $this->next();
        $expression = $this->expr();
        if ($this->token['type'] != T::T_RBRACKET) {
            throw $this->syntax('Expected a closing rbracket for the filter');
        }

        $this->next();
        $rhs = $this->parseProjection(self::$bp[T::T_FILTER]);

        return [
            'type'       => 'projection',
            'from'       => 'array',
            'children'   => [
                $left ?: self::$currentNode,
                [
                    'type' => 'condition',
                    'children' => [$expression, $rhs]
                ]
            ]
        ];
    }

    private function led_comparator(array $left)
    {
        $token = $this->token;
        $this->next();

        return [
            'type'     => T::T_COMPARATOR,
            'value'    => $token['value'],
            'children' => [$left, $this->expr(self::$bp[T::T_COMPARATOR])]
        ];
    }

    private function parseProjection($bp)
    {
        $type = $this->token['type'];
        if (self::$bp[$type] < 10) {
            return self::$currentNode;
        } elseif ($type == T::T_DOT) {
            $this->next(self::$afterDot);
            return $this->parseDot($bp);
        } elseif ($type == T::T_LBRACKET || $type == T::T_FILTER) {
            return $this->expr($bp);
        }

        throw $this->syntax('Syntax error after projection');
    }

    private function parseDot($bp)
    {
        if ($this->token['type'] == T::T_LBRACKET) {
            $this->next();
            return $this->parseMultiSelectList();
        }

        return $this->expr($bp);
    }

    private function parseKeyValuePair()
    {
        static $validColon = [T::T_COLON => true];
        $key = $this->token['value'];
        $this->next($validColon);
        $this->next();

        return [
            'type'     => 'key_val_pair',
            'value'    => $key,
            'children' => [$this->expr()]
        ];
    }

    private function parseWildcardObject(array $left = null)
    {
        $this->next();

        return [
            'type'     => 'projection',
            'from'     => 'object',
            'children' => [
                $left ?: self::$currentNode,
                $this->parseProjection(self::$bp[T::T_STAR])
            ]
        ];
    }

    private function parseWildcardArray(array $left = null)
    {
        static $getRbracket = [T::T_RBRACKET => true];
        $this->next($getRbracket);
        $this->next();

        return [
            'type'     => 'projection',
            'from'     => 'array',
            'children' => [
                $left ?: self::$currentNode,
                $this->parseProjection(self::$bp[T::T_STAR])
            ]
        ];
    }

    /**
     * Parses an array index expression (e.g., [0], [1:2:3]
     */
    private function parseArrayIndexExpression()
    {
        static $matchNext = [
            T::T_NUMBER   => true,
            T::T_COLON    => true,
            T::T_RBRACKET => true
        ];

        $pos = 0;
        $parts = [null, null, null];
        $expected = $matchNext;

        do {
            if ($this->token['type'] == T::T_COLON) {
                $pos++;
                $expected = $matchNext;
            } elseif ($this->token['type'] == T::T_NUMBER) {
                $parts[$pos] = $this->token['value'];
                $expected = [T::T_COLON => true, T::T_RBRACKET => true];
            }
            $this->next($expected);
        } while ($this->token['type'] != T::T_RBRACKET);

        // Consume the closing bracket
        $this->next();

        if ($pos === 0) {
            // No colons were found so this is a simple index extraction
            return ['type' => 'index', 'value' => $parts[0]];
        }

        if ($pos > 2) {
            throw $this->syntax('Invalid array slice syntax: too many colons');
        }

        // Sliced array from start (e.g., [2:])
        return [
            'type'     => 'projection',
            'from'     => 'array',
            'children' => [
                ['type' => 'slice', 'value' => $parts],
                $this->parseProjection(self::$bp[T::T_STAR])
            ]
        ];
    }

    private function parseMultiSelectList()
    {
        $nodes = [];

        do {
            $nodes[] = $this->expr();
            if ($this->token['type'] == T::T_COMMA) {
                $this->next();
                $this->assertNotToken(T::T_RBRACKET);
            }
        } while ($this->token['type'] !== T::T_RBRACKET);
        $this->next();

        return ['type' => 'multi_select_list', 'children' => $nodes];
    }

    private function syntax($msg)
    {
        return new SyntaxErrorException($msg, $this->token, $this->expression);
    }

    private function lookahead()
    {
        return (!isset($this->tokens[$this->tpos + 1]))
            ? T::T_EOF
            : $this->tokens[$this->tpos + 1]['type'];
    }

    private function next(array $match = null)
    {
        if (!isset($this->tokens[$this->tpos + 1])) {
            $this->token = self::$nullToken;
        } else {
            $this->token = $this->tokens[++$this->tpos];
        }

        if ($match && !isset($match[$this->token['type']])) {
            throw $this->syntax($match);
        }
    }

    private function assertNotToken($type)
    {
        if ($this->token['type'] == $type) {
            throw $this->syntax("Token {$this->tpos} not allowed to be $type");
        }
    }

    /**
     * @internal Handles undefined tokens without paying the cost of validation
     */
    public function __call($method, $args)
    {
        $prefix = substr($method, 0, 4);
        if ($prefix == 'nud_' || $prefix == 'led_') {
            $token = substr($method, 4);
            $message = "Unexpected \"$token\" token ($method). Expected one of"
                . " the following tokens: "
                . implode(', ', array_map(function ($i) {
                    return '"' . substr($i, 4) . '"';
                }, array_filter(
                    get_class_methods($this),
                    function ($i) use ($prefix) {
                        return strpos($i, $prefix) === 0;
                    }
                )));
            throw $this->syntax($message);
        }

        throw new \BadMethodCallException("Call to undefined method $method");
    }
}
<?php
namespace JmesPath;

/**
 * Syntax errors raise this exception that gives context
 */
class SyntaxErrorException extends \InvalidArgumentException
{
    /**
     * @param string $expectedTypesOrMessage Expected array of tokens or message
     * @param array  $token                  Current token
     * @param string $expression             Expression input
     */
    public function __construct(
        $expectedTypesOrMessage,
        array $token,
        $expression
    ) {
        $message = "Syntax error at character {$token['pos']}\n"
            . $expression . "\n" . str_repeat(' ', $token['pos']) . "^\n";
        $message .= !is_array($expectedTypesOrMessage)
            ? $expectedTypesOrMessage
            : $this->createTokenMessage($token, $expectedTypesOrMessage);
        parent::__construct($message);
    }

    private function createTokenMessage(array $token, array $valid)
    {
        return sprintf(
            'Expected one of the following: %s; found %s "%s"',
            implode(', ', array_keys($valid)),
            $token['type'],
            $token['value']
        );
    }
}
<?php
namespace JmesPath;

/**
 * Tree visitor used to compile JMESPath expressions into native PHP code.
 */
class TreeCompiler
{
    private $indentation;
    private $source;
    private $vars;

    /**
     * @param array  $ast    AST to compile.
     * @param string $fnName The name of the function to generate.
     * @param string $expr   Expression being compiled.
     *
     * @return string
     */
    public function visit(array $ast, $fnName, $expr)
    {
        $this->vars = [];
        $this->source = $this->indentation = '';
        $this->write("<?php\n")
            ->write('use JmesPath\\TreeInterpreter as Ti;')
            ->write('use JmesPath\\FnDispatcher as Fn;')
            ->write('use JmesPath\\Utils;')
            ->write('')
            ->write('function %s(Ti $interpreter, $value) {', $fnName)
            ->indent()
                ->dispatch($ast)
                ->write('')
                ->write('return $value;')
            ->outdent()
        ->write('}');

        return $this->source;
    }

    /**
     * @param array $node
     * @return mixed
     */
    private function dispatch(array $node)
    {
        return $this->{"visit_{$node['type']}"}($node);
    }

    /**
     * Creates a monotonically incrementing unique variable name by prefix.
     *
     * @param string $prefix Variable name prefix
     *
     * @return string
     */
    private function makeVar($prefix)
    {
        if (!isset($this->vars[$prefix])) {
            $this->vars[$prefix] = 0;
            return '$' . $prefix;
        }

        return '$' . $prefix . ++$this->vars[$prefix];
    }

    /**
     * Writes the given line of source code. Pass positional arguments to write
     * that match the format of sprintf.
     *
     * @param string $str String to write
     * @return $this
     */
    private function write($str)
    {
        $this->source .= $this->indentation;
        if (func_num_args() == 1) {
            $this->source .= $str . "\n";
            return $this;
        }
        $this->source .= vsprintf($str, array_slice(func_get_args(), 1)) . "\n";
        return $this;
    }

    /**
     * Decreases the indentation level of code being written
     * @return $this
     */
    private function outdent()
    {
        $this->indentation = substr($this->indentation, 0, -4);
        return $this;
    }

    /**
     * Increases the indentation level of code being written
     * @return $this
     */
    private function indent()
    {
        $this->indentation .= '    ';
        return $this;
    }

    private function visit_or(array $node)
    {
        $a = $this->makeVar('beforeOr');
        return $this
            ->write('%s = $value;', $a)
            ->dispatch($node['children'][0])
            ->write('if (!$value && $value !== "0" && $value !== 0) {')
                ->indent()
                ->write('$value = %s;', $a)
                ->dispatch($node['children'][1])
                ->outdent()
            ->write('}');
    }

    private function visit_and(array $node)
    {
        $a = $this->makeVar('beforeAnd');
        return $this
            ->write('%s = $value;', $a)
            ->dispatch($node['children'][0])
            ->write('if ($value || $value === "0" || $value === 0) {')
                ->indent()
                ->write('$value = %s;', $a)
                ->dispatch($node['children'][1])
                ->outdent()
            ->write('}');
    }

    private function visit_not(array $node)
    {
        return $this
            ->write('// Visiting not node')
            ->dispatch($node['children'][0])
            ->write('// Applying boolean not to result of not node')
            ->write('$value = !Utils::isTruthy($value);');
    }

    private function visit_subexpression(array $node)
    {
        return $this
            ->dispatch($node['children'][0])
            ->write('if ($value !== null) {')
                ->indent()
                ->dispatch($node['children'][1])
                ->outdent()
            ->write('}');
    }

    private function visit_field(array $node)
    {
        $arr = '$value[' . var_export($node['value'], true) . ']';
        $obj = '$value->{' . var_export($node['value'], true) . '}';
        $this->write('if (is_array($value) || $value instanceof \\ArrayAccess) {')
                ->indent()
                ->write('$value = isset(%s) ? %s : null;', $arr, $arr)
                ->outdent()
            ->write('} elseif ($value instanceof \\stdClass) {')
                ->indent()
                ->write('$value = isset(%s) ? %s : null;', $obj, $obj)
                ->outdent()
            ->write("} else {")
                ->indent()
                ->write('$value = null;')
                ->outdent()
            ->write("}");

        return $this;
    }

    private function visit_index(array $node)
    {
        if ($node['value'] >= 0) {
            $check = '$value[' . $node['value'] . ']';
            return $this->write(
                '$value = (is_array($value) || $value instanceof \\ArrayAccess)'
                    . ' && isset(%s) ? %s : null;',
                $check, $check
            );
        }

        $a = $this->makeVar('count');
        return $this
            ->write('if (is_array($value) || ($value instanceof \\ArrayAccess && $value instanceof \\Countable)) {')
                ->indent()
                ->write('%s = count($value) + %s;', $a, $node['value'])
                ->write('$value = isset($value[%s]) ? $value[%s] : null;', $a, $a)
                ->outdent()
            ->write('} else {')
                ->indent()
                ->write('$value = null;')
                ->outdent()
            ->write('}');
    }

    private function visit_literal(array $node)
    {
        return $this->write('$value = %s;', var_export($node['value'], true));
    }

    private function visit_pipe(array $node)
    {
        return $this
            ->dispatch($node['children'][0])
            ->dispatch($node['children'][1]);
    }

    private function visit_multi_select_list(array $node)
    {
        return $this->visit_multi_select_hash($node);
    }

    private function visit_multi_select_hash(array $node)
    {
        $listVal = $this->makeVar('list');
        $value = $this->makeVar('prev');
        $this->write('if ($value !== null) {')
            ->indent()
            ->write('%s = [];', $listVal)
            ->write('%s = $value;', $value);

        $first = true;
        foreach ($node['children'] as $child) {
            if (!$first) {
                $this->write('$value = %s;', $value);
            }
            $first = false;
            if ($node['type'] == 'multi_select_hash') {
                $this->dispatch($child['children'][0]);
                $key = var_export($child['value'], true);
                $this->write('%s[%s] = $value;', $listVal, $key);
            } else {
                $this->dispatch($child);
                $this->write('%s[] = $value;', $listVal);
            }
        }

        return $this
            ->write('$value = %s;', $listVal)
            ->outdent()
            ->write('}');
    }

    private function visit_function(array $node)
    {
        $value = $this->makeVar('val');
        $args = $this->makeVar('args');
        $this->write('%s = $value;', $value)
            ->write('%s = [];', $args);

        foreach ($node['children'] as $arg) {
            $this->dispatch($arg);
            $this->write('%s[] = $value;', $args)
                ->write('$value = %s;', $value);
        }

        return $this->write(
            '$value = Fn::getInstance()->__invoke("%s", %s);',
            $node['value'], $args
        );
    }

    private function visit_slice(array $node)
    {
        return $this
            ->write('$value = !is_string($value) && !Utils::isArray($value)')
            ->write('    ? null : Utils::slice($value, %s, %s, %s);',
                var_export($node['value'][0], true),
                var_export($node['value'][1], true),
                var_export($node['value'][2], true)
            );
    }

    private function visit_current(array $node)
    {
        return $this->write('// Visiting current node (no-op)');
    }

    private function visit_expref(array $node)
    {
        $child = var_export($node['children'][0], true);
        return $this->write('$value = function ($value) use ($interpreter) {')
            ->indent()
            ->write('return $interpreter->visit(%s, $value);', $child)
            ->outdent()
        ->write('};');
    }

    private function visit_flatten(array $node)
    {
        $this->dispatch($node['children'][0]);
        $merged = $this->makeVar('merged');
        $val = $this->makeVar('val');

        $this
            ->write('// Visiting merge node')
            ->write('if (!Utils::isArray($value)) {')
                ->indent()
                ->write('$value = null;')
                ->outdent()
            ->write('} else {')
                ->indent()
                ->write('%s = [];', $merged)
                ->write('foreach ($value as %s) {', $val)
                    ->indent()
                    ->write('if (is_array(%s) && isset(%s[0])) {', $val, $val)
                        ->indent()
                        ->write('%s = array_merge(%s, %s);', $merged, $merged, $val)
                        ->outdent()
                    ->write('} elseif (%s !== []) {', $val)
                        ->indent()
                        ->write('%s[] = %s;', $merged, $val)
                        ->outdent()
                    ->write('}')
                    ->outdent()
                ->write('}')
                ->write('$value = %s;', $merged)
                ->outdent()
            ->write('}');

        return $this;
    }

    private function visit_projection(array $node)
    {
        $val = $this->makeVar('val');
        $collected = $this->makeVar('collected');
        $this->write('// Visiting projection node')
            ->dispatch($node['children'][0])
            ->write('');

        if (!isset($node['from'])) {
            $this->write('if (!is_array($value) || !($value instanceof \stdClass)) { $value = null; }');
        } elseif ($node['from'] == 'object') {
            $this->write('if (!Utils::isObject($value)) { $value = null; }');
        } elseif ($node['from'] == 'array') {
            $this->write('if (!Utils::isArray($value)) { $value = null; }');
        }

        $this->write('if ($value !== null) {')
            ->indent()
            ->write('%s = [];', $collected)
            ->write('foreach ((array) $value as %s) {', $val)
                ->indent()
                ->write('$value = %s;', $val)
                ->dispatch($node['children'][1])
                ->write('if ($value !== null) {')
                    ->indent()
                    ->write('%s[] = $value;', $collected)
                    ->outdent()
                ->write('}')
                ->outdent()
            ->write('}')
            ->write('$value = %s;', $collected)
            ->outdent()
        ->write('}');

        return $this;
    }

    private function visit_condition(array $node)
    {
        $value = $this->makeVar('beforeCondition');
        return $this
            ->write('%s = $value;', $value)
            ->write('// Visiting condition node')
            ->dispatch($node['children'][0])
            ->write('// Checking result of condition node')
            ->write('if (Utils::isTruthy($value)) {')
                ->indent()
                ->write('$value = %s;', $value)
                ->dispatch($node['children'][1])
                ->outdent()
            ->write('} else {')
                ->indent()
                ->write('$value = null;')
                ->outdent()
            ->write('}');
    }

    private function visit_comparator(array $node)
    {
        $value = $this->makeVar('val');
        $a = $this->makeVar('left');
        $b = $this->makeVar('right');

        $this
            ->write('// Visiting comparator node')
            ->write('%s = $value;', $value)
            ->dispatch($node['children'][0])
            ->write('%s = $value;', $a)
            ->write('$value = %s;', $value)
            ->dispatch($node['children'][1])
            ->write('%s = $value;', $b);

        if ($node['value'] == '==') {
            $this->write('$value = Utils::isEqual(%s, %s);', $a, $b);
        } elseif ($node['value'] == '!=') {
            $this->write('$value = !Utils::isEqual(%s, %s);', $a, $b);
        } else {
            $this->write(
                '$value = (is_int(%s) || is_float(%s)) && (is_int(%s) || is_float(%s)) && %s %s %s;',
                $a, $a, $b, $b, $a, $node['value'], $b
            );
        }

        return $this;
    }

    /** @internal */
    public function __call($method, $args)
    {
        throw new \RuntimeException(
            sprintf('Invalid node encountered: %s', json_encode($args[0]))
        );
    }
}
<?php
namespace JmesPath;

/**
 * Tree visitor used to evaluates JMESPath AST expressions.
 */
class TreeInterpreter
{
    /** @var callable */
    private $fnDispatcher;

    /**
     * @param callable $fnDispatcher Function dispatching function that accepts
     *                               a function name argument and an array of
     *                               function arguments and returns the result.
     */
    public function __construct(callable $fnDispatcher = null)
    {
        $this->fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance();
    }

    /**
     * Visits each node in a JMESPath AST and returns the evaluated result.
     *
     * @param array $node JMESPath AST node
     * @param mixed $data Data to evaluate
     *
     * @return mixed
     */
    public function visit(array $node, $data)
    {
        return $this->dispatch($node, $data);
    }

    /**
     * Recursively traverses an AST using depth-first, pre-order traversal.
     * The evaluation logic for each node type is embedded into a large switch
     * statement to avoid the cost of "double dispatch".
     * @return mixed
     */
    private function dispatch(array $node, $value)
    {
        $dispatcher = $this->fnDispatcher;

        switch ($node['type']) {

            case 'field':
                if (is_array($value) || $value instanceof \ArrayAccess) {
                    return isset($value[$node['value']]) ? $value[$node['value']] : null;
                } elseif ($value instanceof \stdClass) {
                    return isset($value->{$node['value']}) ? $value->{$node['value']} : null;
                }
                return null;

            case 'subexpression':
                return $this->dispatch(
                    $node['children'][1],
                    $this->dispatch($node['children'][0], $value)
                );

            case 'index':
                if (!Utils::isArray($value)) {
                    return null;
                }
                $idx = $node['value'] >= 0
                    ? $node['value']
                    : $node['value'] + count($value);
                return isset($value[$idx]) ? $value[$idx] : null;

            case 'projection':
                $left = $this->dispatch($node['children'][0], $value);
                switch ($node['from']) {
                    case 'object':
                        if (!Utils::isObject($left)) {
                            return null;
                        }
                        break;
                    case 'array':
                        if (!Utils::isArray($left)) {
                            return null;
                        }
                        break;
                    default:
                        if (!is_array($left) || !($left instanceof \stdClass)) {
                            return null;
                        }
                }

                $collected = [];
                foreach ((array) $left as $val) {
                    $result = $this->dispatch($node['children'][1], $val);
                    if ($result !== null) {
                        $collected[] = $result;
                    }
                }

                return $collected;

            case 'flatten':
                static $skipElement = [];
                $value = $this->dispatch($node['children'][0], $value);

                if (!Utils::isArray($value)) {
                    return null;
                }

                $merged = [];
                foreach ($value as $values) {
                    // Only merge up arrays lists and not hashes
                    if (is_array($values) && isset($values[0])) {
                        $merged = array_merge($merged, $values);
                    } elseif ($values !== $skipElement) {
                        $merged[] = $values;
                    }
                }

                return $merged;

            case 'literal':
                return $node['value'];

            case 'current':
                return $value;

            case 'or':
                $result = $this->dispatch($node['children'][0], $value);
                return Utils::isTruthy($result)
                    ? $result
                    : $this->dispatch($node['children'][1], $value);

            case 'and':
                $result = $this->dispatch($node['children'][0], $value);
                return Utils::isTruthy($result)
                    ? $this->dispatch($node['children'][1], $value)
                    : $result;

            case 'not':
                return !Utils::isTruthy(
                    $this->dispatch($node['children'][0], $value)
                );

            case 'pipe':
                return $this->dispatch(
                    $node['children'][1],
                    $this->dispatch($node['children'][0], $value)
                );

            case 'multi_select_list':
                if ($value === null) {
                    return null;
                }

                $collected = [];
                foreach ($node['children'] as $node) {
                    $collected[] = $this->dispatch($node, $value);
                }

                return $collected;

            case 'multi_select_hash':
                if ($value === null) {
                    return null;
                }

                $collected = [];
                foreach ($node['children'] as $node) {
                    $collected[$node['value']] = $this->dispatch(
                        $node['children'][0],
                        $value
                    );
                }

                return $collected;

            case 'comparator':
                $left = $this->dispatch($node['children'][0], $value);
                $right = $this->dispatch($node['children'][1], $value);
                if ($node['value'] == '==') {
                    return Utils::isEqual($left, $right);
                } elseif ($node['value'] == '!=') {
                    return !Utils::isEqual($left, $right);
                } else {
                    return self::relativeCmp($left, $right, $node['value']);
                }

            case 'condition':
                return Utils::isTruthy($this->dispatch($node['children'][0], $value))
                    ? $this->dispatch($node['children'][1], $value)
                    : null;

            case 'function':
                $args = [];
                foreach ($node['children'] as $arg) {
                    $args[] = $this->dispatch($arg, $value);
                }
                return $dispatcher($node['value'], $args);

            case 'slice':
                return is_string($value) || Utils::isArray($value)
                    ? Utils::slice(
                        $value,
                        $node['value'][0],
                        $node['value'][1],
                        $node['value'][2]
                    ) : null;

            case 'expref':
                $apply = $node['children'][0];
                return function ($value) use ($apply) {
                    return $this->visit($apply, $value);
                };

            default:
                throw new \RuntimeException("Unknown node type: {$node['type']}");
        }
    }

    /**
     * @return bool
     */
    private static function relativeCmp($left, $right, $cmp)
    {
        if (!(is_int($left) || is_float($left)) || !(is_int($right) || is_float($right))) {
            return false;
        }

        switch ($cmp) {
            case '>': return $left > $right;
            case '>=': return $left >= $right;
            case '<': return $left < $right;
            case '<=': return $left <= $right;
            default: throw new \RuntimeException("Invalid comparison: $cmp");
        }
    }
}
<?php
namespace JmesPath;

class Utils
{
    static $typeMap = [
        'boolean' => 'boolean',
        'string'  => 'string',
        'NULL'    => 'null',
        'double'  => 'number',
        'float'   => 'number',
        'integer' => 'number'
    ];

    /**
     * Returns true if the value is truthy
     *
     * @param mixed $value Value to check
     *
     * @return bool
     */
    public static function isTruthy($value)
    {
        if (!$value) {
            return $value === 0 || $value === '0';
        } elseif ($value instanceof \stdClass) {
            return (bool) get_object_vars($value);
        } else {
            return true;
        }
    }

    /**
     * Gets the JMESPath type equivalent of a PHP variable.
     *
     * @param mixed $arg PHP variable
     * @return string Returns the JSON data type
     * @throws \InvalidArgumentException when an unknown type is given.
     */
    public static function type($arg)
    {
        $type = gettype($arg);
        if (isset(self::$typeMap[$type])) {
            return self::$typeMap[$type];
        } elseif ($type === 'array') {
            if (empty($arg)) {
                return 'array';
            }
            reset($arg);
            return key($arg) === 0 ? 'array' : 'object';
        } elseif ($arg instanceof \stdClass) {
            return 'object';
        } elseif ($arg instanceof \Closure) {
            return 'expression';
        } elseif ($arg instanceof \ArrayAccess
            && $arg instanceof \Countable
        ) {
            return count($arg) == 0 || $arg->offsetExists(0)
                ? 'array'
                : 'object';
        } elseif (method_exists($arg, '__toString')) {
            return 'string';
        }

        throw new \InvalidArgumentException(
            'Unable to determine JMESPath type from ' . get_class($arg)
        );
    }

    /**
     * Determine if the provided value is a JMESPath compatible object.
     *
     * @param mixed $value
     *
     * @return bool
     */
    public static function isObject($value)
    {
        if (is_array($value)) {
            return !$value || array_keys($value)[0] !== 0;
        }

        // Handle array-like values. Must be empty or offset 0 does not exist
        return $value instanceof \Countable && $value instanceof \ArrayAccess
            ? count($value) == 0 || !$value->offsetExists(0)
            : $value instanceof \stdClass;
    }

    /**
     * Determine if the provided value is a JMESPath compatible array.
     *
     * @param mixed $value
     *
     * @return bool
     */
    public static function isArray($value)
    {
        if (is_array($value)) {
            return !$value || array_keys($value)[0] === 0;
        }

        // Handle array-like values. Must be empty or offset 0 exists.
        return $value instanceof \Countable && $value instanceof \ArrayAccess
            ? count($value) == 0 || $value->offsetExists(0)
            : false;
    }

    /**
     * JSON aware value comparison function.
     *
     * @param mixed $a First value to compare
     * @param mixed $b Second value to compare
     *
     * @return bool
     */
    public static function isEqual($a, $b)
    {
        if ($a === $b) {
            return true;
        } elseif ($a instanceof \stdClass) {
            return self::isEqual((array) $a, $b);
        } elseif ($b instanceof \stdClass) {
            return self::isEqual($a, (array) $b);
        } else {
            return false;
        }
    }

    /**
     * JMESPath requires a stable sorting algorithm, so here we'll implement
     * a simple Schwartzian transform that uses array index positions as tie
     * breakers.
     *
     * @param array    $data   List or map of data to sort
     * @param callable $sortFn Callable used to sort values
     *
     * @return array Returns the sorted array
     * @link http://en.wikipedia.org/wiki/Schwartzian_transform
     */
    public static function stableSort(array $data, callable $sortFn)
    {
        // Decorate each item by creating an array of [value, index]
        array_walk($data, function (&$v, $k) { $v = [$v, $k]; });
        // Sort by the sort function and use the index as a tie-breaker
        uasort($data, function ($a, $b) use ($sortFn) {
            return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1);
        });

        // Undecorate each item and return the resulting sorted array
        return array_map(function ($v) { return $v[0]; }, array_values($data));
    }

    /**
     * Creates a Python-style slice of a string or array.
     *
     * @param array|string $value Value to slice
     * @param int|null     $start Starting position
     * @param int|null     $stop  Stop position
     * @param int          $step  Step (1, 2, -1, -2, etc.)
     *
     * @return array|string
     * @throws \InvalidArgumentException
     */
    public static function slice($value, $start = null, $stop = null, $step = 1)
    {
        if (!is_array($value) && !is_string($value)) {
            throw new \InvalidArgumentException('Expects string or array');
        }

        return self::sliceIndices($value, $start, $stop, $step);
    }

    private static function adjustEndpoint($length, $endpoint, $step)
    {
        if ($endpoint < 0) {
            $endpoint += $length;
            if ($endpoint < 0) {
                $endpoint = $step < 0 ? -1 : 0;
            }
        } elseif ($endpoint >= $length) {
            $endpoint = $step < 0 ? $length - 1 : $length;
        }

        return $endpoint;
    }

    private static function adjustSlice($length, $start, $stop, $step)
    {
        if ($step === null) {
            $step = 1;
        } elseif ($step === 0) {
            throw new \RuntimeException('step cannot be 0');
        }

        if ($start === null) {
            $start = $step < 0 ? $length - 1 : 0;
        } else {
            $start = self::adjustEndpoint($length, $start, $step);
        }

        if ($stop === null) {
            $stop = $step < 0 ? -1 : $length;
        } else {
            $stop = self::adjustEndpoint($length, $stop, $step);
        }

        return [$start, $stop, $step];
    }

    private static function sliceIndices($subject, $start, $stop, $step)
    {
        $type = gettype($subject);
        $len = $type == 'string' ? strlen($subject) : count($subject);
        list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step);

        $result = [];
        if ($step > 0) {
            for ($i = $start; $i < $stop; $i += $step) {
                $result[] = $subject[$i];
            }
        } else {
            for ($i = $start; $i > $stop; $i += $step) {
                $result[] = $subject[$i];
            }
        }

        return $type == 'string' ? implode($result, '') : $result;
    }
}
phpseclib Lead Developer:  TerraFrost (Jim Wigginton)

phpseclib Developers:      monnerat (Patrick Monnerat)
                           bantu (Andreas Fischer)
                           petrich (Hans-Jürgen Petrich)
                           GrahamCampbell (Graham Campbell)
{
    "name": "phpseclib/phpseclib",
    "type": "library",
    "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
    "keywords": [
        "security",
        "crypto",
        "cryptography",
        "encryption",
        "signature",
        "signing",
        "rsa",
        "aes",
        "blowfish",
        "twofish",
        "ssh",
        "sftp",
        "x509",
        "x.509",
        "asn1",
        "asn.1",
        "BigInteger"
        ],
    "homepage": "http://phpseclib.sourceforge.net",
    "license": "MIT",
    "authors": [
        {
            "name": "Jim Wigginton",
            "email": "terrafrost@php.net",
            "role": "Lead Developer"
        },
        {
            "name": "Patrick Monnerat",
            "email": "pm@datasphere.ch",
            "role": "Developer"
        },
        {
            "name": "Andreas Fischer",
            "email": "bantu@phpbb.com",
            "role": "Developer"
        },
        {
            "name": "Hans-Jürgen Petrich",
            "email": "petrich@tronic-media.com",
            "role": "Developer"
        },
        {
            "name": "Graham Campbell",
            "email": "graham@alt-three.com",
            "role": "Developer"
        }
    ],
    "require": {
        "php": ">=5.3.3"
    },
    "require-dev": {
        "phing/phing": "~2.7",
        "phpunit/phpunit": "~4.0",
        "sami/sami": "~2.0",
        "squizlabs/php_codesniffer": "~2.0"
    },
    "suggest": {
        "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
        "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
        "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
        "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations."
    },
    "autoload": {
        "files": [
            "phpseclib/bootstrap.php"
        ],
        "psr-4": {
            "phpseclib\\": "phpseclib/"
        }
    }
}
{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
        "This file is @generated automatically"
    ],
    "hash": "8599992bf6058a9da82372eb8bcae2c2",
    "content-hash": "fde47c84178c55c06de858a2128e3d07",
    "packages": [],
    "packages-dev": [
        {
            "name": "doctrine/instantiator",
            "version": "1.0.5",
            "source": {
                "type": "git",
                "url": "https://github.com/doctrine/instantiator.git",
                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3,<8.0-DEV"
            },
            "require-dev": {
                "athletic/athletic": "~0.1.8",
                "ext-pdo": "*",
                "ext-phar": "*",
                "phpunit/phpunit": "~4.0",
                "squizlabs/php_codesniffer": "~2.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Marco Pivetta",
                    "email": "ocramius@gmail.com",
                    "homepage": "http://ocramius.github.com/"
                }
            ],
            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
            "homepage": "https://github.com/doctrine/instantiator",
            "keywords": [
                "constructor",
                "instantiate"
            ],
            "time": "2015-06-14 21:17:01"
        },
        {
            "name": "michelf/php-markdown",
            "version": "1.6.0",
            "source": {
                "type": "git",
                "url": "https://github.com/michelf/php-markdown.git",
                "reference": "156e56ee036505ec637d761ee62dc425d807183c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/michelf/php-markdown/zipball/156e56ee036505ec637d761ee62dc425d807183c",
                "reference": "156e56ee036505ec637d761ee62dc425d807183c",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-lib": "1.4.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Michelf": ""
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Michel Fortin",
                    "email": "michel.fortin@michelf.ca",
                    "homepage": "https://michelf.ca/",
                    "role": "Developer"
                },
                {
                    "name": "John Gruber",
                    "homepage": "https://daringfireball.net/"
                }
            ],
            "description": "PHP Markdown",
            "homepage": "https://michelf.ca/projects/php-markdown/",
            "keywords": [
                "markdown"
            ],
            "time": "2015-12-24 01:37:31"
        },
        {
            "name": "nikic/php-parser",
            "version": "v0.9.5",
            "source": {
                "type": "git",
                "url": "https://github.com/nikic/PHP-Parser.git",
                "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ef70767475434bdb3615b43c327e2cae17ef12eb",
                "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "php": ">=5.2"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "0.9-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "PHPParser": "lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Nikita Popov"
                }
            ],
            "description": "A PHP parser written in PHP",
            "keywords": [
                "parser",
                "php"
            ],
            "time": "2014-07-23 18:24:17"
        },
        {
            "name": "phing/phing",
            "version": "2.14.0",
            "source": {
                "type": "git",
                "url": "https://github.com/phingofficial/phing.git",
                "reference": "7dd73c83c377623def54b58121f46b4dcb35dd61"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phingofficial/phing/zipball/7dd73c83c377623def54b58121f46b4dcb35dd61",
                "reference": "7dd73c83c377623def54b58121f46b4dcb35dd61",
                "shasum": ""
            },
            "require": {
                "php": ">=5.2.0"
            },
            "require-dev": {
                "ext-pdo_sqlite": "*",
                "lastcraft/simpletest": "@dev",
                "mikey179/vfsstream": "^1.6",
                "pdepend/pdepend": "2.x",
                "pear/archive_tar": "1.4.x",
                "pear/http_request2": "dev-trunk",
                "pear/net_growl": "dev-trunk",
                "pear/pear-core-minimal": "1.10.1",
                "pear/versioncontrol_git": "@dev",
                "pear/versioncontrol_svn": "~0.5",
                "phpdocumentor/phpdocumentor": "2.x",
                "phploc/phploc": "~2.0.6",
                "phpmd/phpmd": "~2.2",
                "phpunit/phpunit": ">=3.7",
                "sebastian/git": "~1.0",
                "sebastian/phpcpd": "2.x",
                "squizlabs/php_codesniffer": "~2.2",
                "symfony/yaml": "~2.7"
            },
            "suggest": {
                "pdepend/pdepend": "PHP version of JDepend",
                "pear/archive_tar": "Tar file management class",
                "pear/versioncontrol_git": "A library that provides OO interface to handle Git repository",
                "pear/versioncontrol_svn": "A simple OO-style interface for Subversion, the free/open-source version control system",
                "phpdocumentor/phpdocumentor": "Documentation Generator for PHP",
                "phploc/phploc": "A tool for quickly measuring the size of a PHP project",
                "phpmd/phpmd": "PHP version of PMD tool",
                "phpunit/php-code-coverage": "Library that provides collection, processing, and rendering functionality for PHP code coverage information",
                "phpunit/phpunit": "The PHP Unit Testing Framework",
                "sebastian/phpcpd": "Copy/Paste Detector (CPD) for PHP code",
                "tedivm/jshrink": "Javascript Minifier built in PHP"
            },
            "bin": [
                "bin/phing"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.14.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "classes/phing/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "include-path": [
                "classes"
            ],
            "license": [
                "LGPL-3.0"
            ],
            "authors": [
                {
                    "name": "Michiel Rook",
                    "email": "mrook@php.net"
                },
                {
                    "name": "Phing Community",
                    "homepage": "https://www.phing.info/trac/wiki/Development/Contributors"
                }
            ],
            "description": "PHing Is Not GNU make; it's a PHP project build system or build tool based on Apache Ant.",
            "homepage": "https://www.phing.info/",
            "keywords": [
                "build",
                "phing",
                "task",
                "tool"
            ],
            "time": "2016-03-10 21:39:23"
        },
        {
            "name": "phpdocumentor/reflection-common",
            "version": "1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
                "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
                "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
                "shasum": ""
            },
            "require": {
                "php": ">=5.5"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.6"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "phpDocumentor\\Reflection\\": [
                        "src"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Jaap van Otterdijk",
                    "email": "opensource@ijaap.nl"
                }
            ],
            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
            "homepage": "http://www.phpdoc.org",
            "keywords": [
                "FQSEN",
                "phpDocumentor",
                "phpdoc",
                "reflection",
                "static analysis"
            ],
            "time": "2015-12-27 11:43:31"
        },
        {
            "name": "phpdocumentor/reflection-docblock",
            "version": "3.1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
                "reference": "9270140b940ff02e58ec577c237274e92cd40cdd"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd",
                "reference": "9270140b940ff02e58ec577c237274e92cd40cdd",
                "shasum": ""
            },
            "require": {
                "php": ">=5.5",
                "phpdocumentor/reflection-common": "^1.0@dev",
                "phpdocumentor/type-resolver": "^0.2.0",
                "webmozart/assert": "^1.0"
            },
            "require-dev": {
                "mockery/mockery": "^0.9.4",
                "phpunit/phpunit": "^4.4"
            },
            "type": "library",
            "autoload": {
                "psr-4": {
                    "phpDocumentor\\Reflection\\": [
                        "src/"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Mike van Riel",
                    "email": "me@mikevanriel.com"
                }
            ],
            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
            "time": "2016-06-10 09:48:41"
        },
        {
            "name": "phpdocumentor/type-resolver",
            "version": "0.2",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/TypeResolver.git",
                "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443",
                "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443",
                "shasum": ""
            },
            "require": {
                "php": ">=5.5",
                "phpdocumentor/reflection-common": "^1.0"
            },
            "require-dev": {
                "mockery/mockery": "^0.9.4",
                "phpunit/phpunit": "^5.2||^4.8.24"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "phpDocumentor\\Reflection\\": [
                        "src/"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Mike van Riel",
                    "email": "me@mikevanriel.com"
                }
            ],
            "time": "2016-06-10 07:14:17"
        },
        {
            "name": "phpspec/prophecy",
            "version": "v1.6.1",
            "source": {
                "type": "git",
                "url": "https://github.com/phpspec/prophecy.git",
                "reference": "58a8137754bc24b25740d4281399a4a3596058e0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0",
                "reference": "58a8137754bc24b25740d4281399a4a3596058e0",
                "shasum": ""
            },
            "require": {
                "doctrine/instantiator": "^1.0.2",
                "php": "^5.3|^7.0",
                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
                "sebastian/comparator": "^1.1",
                "sebastian/recursion-context": "^1.0"
            },
            "require-dev": {
                "phpspec/phpspec": "^2.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.6.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Prophecy\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Konstantin Kudryashov",
                    "email": "ever.zet@gmail.com",
                    "homepage": "http://everzet.com"
                },
                {
                    "name": "Marcello Duarte",
                    "email": "marcello.duarte@gmail.com"
                }
            ],
            "description": "Highly opinionated mocking framework for PHP 5.3+",
            "homepage": "https://github.com/phpspec/prophecy",
            "keywords": [
                "Double",
                "Dummy",
                "fake",
                "mock",
                "spy",
                "stub"
            ],
            "time": "2016-06-07 08:13:47"
        },
        {
            "name": "phpunit/php-code-coverage",
            "version": "2.2.4",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "phpunit/php-file-iterator": "~1.3",
                "phpunit/php-text-template": "~1.2",
                "phpunit/php-token-stream": "~1.3",
                "sebastian/environment": "^1.3.2",
                "sebastian/version": "~1.0"
            },
            "require-dev": {
                "ext-xdebug": ">=2.1.4",
                "phpunit/phpunit": "~4"
            },
            "suggest": {
                "ext-dom": "*",
                "ext-xdebug": ">=2.2.1",
                "ext-xmlwriter": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.2.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
            "keywords": [
                "coverage",
                "testing",
                "xunit"
            ],
            "time": "2015-10-06 15:47:00"
        },
        {
            "name": "phpunit/php-file-iterator",
            "version": "1.4.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
            "keywords": [
                "filesystem",
                "iterator"
            ],
            "time": "2015-06-21 13:08:43"
        },
        {
            "name": "phpunit/php-text-template",
            "version": "1.2.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-text-template.git",
                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "Simple template engine.",
            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
            "keywords": [
                "template"
            ],
            "time": "2015-06-21 13:50:34"
        },
        {
            "name": "phpunit/php-timer",
            "version": "1.0.8",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-timer.git",
                "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
                "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4|~5"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Utility class for timing",
            "homepage": "https://github.com/sebastianbergmann/php-timer/",
            "keywords": [
                "timer"
            ],
            "time": "2016-05-12 18:03:57"
        },
        {
            "name": "phpunit/php-token-stream",
            "version": "1.4.8",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.2"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Wrapper around PHP's tokenizer extension.",
            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
            "keywords": [
                "tokenizer"
            ],
            "time": "2015-09-15 10:49:45"
        },
        {
            "name": "phpunit/phpunit",
            "version": "4.8.26",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/phpunit.git",
                "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74",
                "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74",
                "shasum": ""
            },
            "require": {
                "ext-dom": "*",
                "ext-json": "*",
                "ext-pcre": "*",
                "ext-reflection": "*",
                "ext-spl": "*",
                "php": ">=5.3.3",
                "phpspec/prophecy": "^1.3.1",
                "phpunit/php-code-coverage": "~2.1",
                "phpunit/php-file-iterator": "~1.4",
                "phpunit/php-text-template": "~1.2",
                "phpunit/php-timer": "^1.0.6",
                "phpunit/phpunit-mock-objects": "~2.3",
                "sebastian/comparator": "~1.1",
                "sebastian/diff": "~1.2",
                "sebastian/environment": "~1.3",
                "sebastian/exporter": "~1.2",
                "sebastian/global-state": "~1.0",
                "sebastian/version": "~1.0",
                "symfony/yaml": "~2.1|~3.0"
            },
            "suggest": {
                "phpunit/php-invoker": "~1.1"
            },
            "bin": [
                "phpunit"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "4.8.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "The PHP Unit Testing framework.",
            "homepage": "https://phpunit.de/",
            "keywords": [
                "phpunit",
                "testing",
                "xunit"
            ],
            "time": "2016-05-17 03:09:28"
        },
        {
            "name": "phpunit/phpunit-mock-objects",
            "version": "2.3.8",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
                "shasum": ""
            },
            "require": {
                "doctrine/instantiator": "^1.0.2",
                "php": ">=5.3.3",
                "phpunit/php-text-template": "~1.2",
                "sebastian/exporter": "~1.2"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "suggest": {
                "ext-soap": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.3.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Mock Object library for PHPUnit",
            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
            "keywords": [
                "mock",
                "xunit"
            ],
            "time": "2015-10-02 06:51:40"
        },
        {
            "name": "pimple/pimple",
            "version": "v2.1.1",
            "source": {
                "type": "git",
                "url": "https://github.com/silexphp/Pimple.git",
                "reference": "ea22fb2880faf7b7b0e17c9809c6fe25b071fd76"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/ea22fb2880faf7b7b0e17c9809c6fe25b071fd76",
                "reference": "ea22fb2880faf7b7b0e17c9809c6fe25b071fd76",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.1.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Pimple": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                }
            ],
            "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
            "homepage": "http://pimple.sensiolabs.org",
            "keywords": [
                "container",
                "dependency injection"
            ],
            "time": "2014-07-24 07:10:08"
        },
        {
            "name": "sami/sami",
            "version": "v2.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/FriendsOfPHP/Sami.git",
                "reference": "fa58b324f41aa2aefe21dac4f22d8c98965fc012"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/FriendsOfPHP/Sami/zipball/fa58b324f41aa2aefe21dac4f22d8c98965fc012",
                "reference": "fa58b324f41aa2aefe21dac4f22d8c98965fc012",
                "shasum": ""
            },
            "require": {
                "michelf/php-markdown": "~1.3",
                "nikic/php-parser": "0.9.*",
                "php": ">=5.3.0",
                "pimple/pimple": "2.*",
                "symfony/console": "~2.1",
                "symfony/filesystem": "~2.1",
                "symfony/finder": "~2.1",
                "symfony/process": "~2.1",
                "symfony/yaml": "~2.1",
                "twig/twig": "1.*"
            },
            "bin": [
                "sami.php"
            ],
            "type": "application",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.0-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Sami": "."
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                }
            ],
            "description": "Sami, an API documentation generator",
            "homepage": "http://sami.sensiolabs.org",
            "keywords": [
                "phpdoc"
            ],
            "time": "2014-06-25 12:05:18"
        },
        {
            "name": "sebastian/comparator",
            "version": "1.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/comparator.git",
                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "sebastian/diff": "~1.2",
                "sebastian/exporter": "~1.2"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.2.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Volker Dusch",
                    "email": "github@wallbash.com"
                },
                {
                    "name": "Bernhard Schussek",
                    "email": "bschussek@2bepublished.at"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Provides the functionality to compare PHP values for equality",
            "homepage": "http://www.github.com/sebastianbergmann/comparator",
            "keywords": [
                "comparator",
                "compare",
                "equality"
            ],
            "time": "2015-07-26 15:48:44"
        },
        {
            "name": "sebastian/diff",
            "version": "1.4.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/diff.git",
                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.8"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Kore Nordmann",
                    "email": "mail@kore-nordmann.de"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Diff implementation",
            "homepage": "https://github.com/sebastianbergmann/diff",
            "keywords": [
                "diff"
            ],
            "time": "2015-12-08 07:14:41"
        },
        {
            "name": "sebastian/environment",
            "version": "1.3.7",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/environment.git",
                "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716",
                "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Provides functionality to handle HHVM/PHP environments",
            "homepage": "http://www.github.com/sebastianbergmann/environment",
            "keywords": [
                "Xdebug",
                "environment",
                "hhvm"
            ],
            "time": "2016-05-17 03:18:57"
        },
        {
            "name": "sebastian/exporter",
            "version": "1.2.2",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/exporter.git",
                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "sebastian/recursion-context": "~1.0"
            },
            "require-dev": {
                "ext-mbstring": "*",
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Volker Dusch",
                    "email": "github@wallbash.com"
                },
                {
                    "name": "Bernhard Schussek",
                    "email": "bschussek@2bepublished.at"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                },
                {
                    "name": "Adam Harvey",
                    "email": "aharvey@php.net"
                }
            ],
            "description": "Provides the functionality to export PHP variables for visualization",
            "homepage": "http://www.github.com/sebastianbergmann/exporter",
            "keywords": [
                "export",
                "exporter"
            ],
            "time": "2016-06-17 09:04:28"
        },
        {
            "name": "sebastian/global-state",
            "version": "1.1.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/global-state.git",
                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.2"
            },
            "suggest": {
                "ext-uopz": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Snapshotting of global state",
            "homepage": "http://www.github.com/sebastianbergmann/global-state",
            "keywords": [
                "global state"
            ],
            "time": "2015-10-12 03:26:01"
        },
        {
            "name": "sebastian/recursion-context",
            "version": "1.0.2",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/recursion-context.git",
                "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
                "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                },
                {
                    "name": "Adam Harvey",
                    "email": "aharvey@php.net"
                }
            ],
            "description": "Provides functionality to recursively process PHP variables",
            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
            "time": "2015-11-11 19:50:13"
        },
        {
            "name": "sebastian/version",
            "version": "1.0.6",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/version.git",
                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
                "shasum": ""
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
            "homepage": "https://github.com/sebastianbergmann/version",
            "time": "2015-06-21 13:59:46"
        },
        {
            "name": "squizlabs/php_codesniffer",
            "version": "2.6.1",
            "source": {
                "type": "git",
                "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
                "reference": "fb72ed32f8418db5e7770be1653e62e0d6f5dd3d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/fb72ed32f8418db5e7770be1653e62e0d6f5dd3d",
                "reference": "fb72ed32f8418db5e7770be1653e62e0d6f5dd3d",
                "shasum": ""
            },
            "require": {
                "ext-simplexml": "*",
                "ext-tokenizer": "*",
                "ext-xmlwriter": "*",
                "php": ">=5.1.2"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.0"
            },
            "bin": [
                "scripts/phpcs",
                "scripts/phpcbf"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "CodeSniffer.php",
                    "CodeSniffer/CLI.php",
                    "CodeSniffer/Exception.php",
                    "CodeSniffer/File.php",
                    "CodeSniffer/Fixer.php",
                    "CodeSniffer/Report.php",
                    "CodeSniffer/Reporting.php",
                    "CodeSniffer/Sniff.php",
                    "CodeSniffer/Tokens.php",
                    "CodeSniffer/Reports/",
                    "CodeSniffer/Tokenizers/",
                    "CodeSniffer/DocGenerators/",
                    "CodeSniffer/Standards/AbstractPatternSniff.php",
                    "CodeSniffer/Standards/AbstractScopeSniff.php",
                    "CodeSniffer/Standards/AbstractVariableSniff.php",
                    "CodeSniffer/Standards/IncorrectPatternException.php",
                    "CodeSniffer/Standards/Generic/Sniffs/",
                    "CodeSniffer/Standards/MySource/Sniffs/",
                    "CodeSniffer/Standards/PEAR/Sniffs/",
                    "CodeSniffer/Standards/PSR1/Sniffs/",
                    "CodeSniffer/Standards/PSR2/Sniffs/",
                    "CodeSniffer/Standards/Squiz/Sniffs/",
                    "CodeSniffer/Standards/Zend/Sniffs/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Greg Sherwood",
                    "role": "lead"
                }
            ],
            "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
            "homepage": "http://www.squizlabs.com/php-codesniffer",
            "keywords": [
                "phpcs",
                "standards"
            ],
            "time": "2016-05-30 22:24:32"
        },
        {
            "name": "symfony/console",
            "version": "v2.8.7",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/console.git",
                "reference": "5ac8bc9aa77bb2edf06af3a1bb6bc1020d23acd3"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/console/zipball/5ac8bc9aa77bb2edf06af3a1bb6bc1020d23acd3",
                "reference": "5ac8bc9aa77bb2edf06af3a1bb6bc1020d23acd3",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9",
                "symfony/polyfill-mbstring": "~1.0"
            },
            "require-dev": {
                "psr/log": "~1.0",
                "symfony/event-dispatcher": "~2.1|~3.0.0",
                "symfony/process": "~2.1|~3.0.0"
            },
            "suggest": {
                "psr/log": "For using the console logger",
                "symfony/event-dispatcher": "",
                "symfony/process": ""
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Console\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Console Component",
            "homepage": "https://symfony.com",
            "time": "2016-06-06 15:06:25"
        },
        {
            "name": "symfony/filesystem",
            "version": "v2.8.7",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/filesystem.git",
                "reference": "dee379131dceed90a429e951546b33edfe7dccbb"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/filesystem/zipball/dee379131dceed90a429e951546b33edfe7dccbb",
                "reference": "dee379131dceed90a429e951546b33edfe7dccbb",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Filesystem\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Filesystem Component",
            "homepage": "https://symfony.com",
            "time": "2016-04-12 18:01:21"
        },
        {
            "name": "symfony/finder",
            "version": "v2.8.7",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/finder.git",
                "reference": "3ec095fab1800222732ca522a95dce8fa124007b"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/finder/zipball/3ec095fab1800222732ca522a95dce8fa124007b",
                "reference": "3ec095fab1800222732ca522a95dce8fa124007b",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Finder\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Finder Component",
            "homepage": "https://symfony.com",
            "time": "2016-06-06 11:11:27"
        },
        {
            "name": "symfony/polyfill-mbstring",
            "version": "v1.2.0",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/polyfill-mbstring.git",
                "reference": "dff51f72b0706335131b00a7f49606168c582594"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594",
                "reference": "dff51f72b0706335131b00a7f49606168c582594",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "suggest": {
                "ext-mbstring": "For best performance"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.2-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Polyfill\\Mbstring\\": ""
                },
                "files": [
                    "bootstrap.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Nicolas Grekas",
                    "email": "p@tchwork.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony polyfill for the Mbstring extension",
            "homepage": "https://symfony.com",
            "keywords": [
                "compatibility",
                "mbstring",
                "polyfill",
                "portable",
                "shim"
            ],
            "time": "2016-05-18 14:26:46"
        },
        {
            "name": "symfony/process",
            "version": "v2.8.7",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/process.git",
                "reference": "115347d00c342198cdc52a7bd8bc15b5ab43500c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/process/zipball/115347d00c342198cdc52a7bd8bc15b5ab43500c",
                "reference": "115347d00c342198cdc52a7bd8bc15b5ab43500c",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Process\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Process Component",
            "homepage": "https://symfony.com",
            "time": "2016-06-06 11:11:27"
        },
        {
            "name": "symfony/yaml",
            "version": "v2.8.7",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/yaml.git",
                "reference": "815fabf3f48c7d1df345a69d1ad1a88f59757b34"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/yaml/zipball/815fabf3f48c7d1df345a69d1ad1a88f59757b34",
                "reference": "815fabf3f48c7d1df345a69d1ad1a88f59757b34",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Yaml\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Yaml Component",
            "homepage": "https://symfony.com",
            "time": "2016-06-06 11:11:27"
        },
        {
            "name": "twig/twig",
            "version": "v1.24.1",
            "source": {
                "type": "git",
                "url": "https://github.com/twigphp/Twig.git",
                "reference": "3566d311a92aae4deec6e48682dc5a4528c4a512"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/twigphp/Twig/zipball/3566d311a92aae4deec6e48682dc5a4528c4a512",
                "reference": "3566d311a92aae4deec6e48682dc5a4528c4a512",
                "shasum": ""
            },
            "require": {
                "php": ">=5.2.7"
            },
            "require-dev": {
                "symfony/debug": "~2.7",
                "symfony/phpunit-bridge": "~2.7"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.24-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Twig_": "lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com",
                    "homepage": "http://fabien.potencier.org",
                    "role": "Lead Developer"
                },
                {
                    "name": "Armin Ronacher",
                    "email": "armin.ronacher@active-4.com",
                    "role": "Project Founder"
                },
                {
                    "name": "Twig Team",
                    "homepage": "http://twig.sensiolabs.org/contributors",
                    "role": "Contributors"
                }
            ],
            "description": "Twig, the flexible, fast, and secure template language for PHP",
            "homepage": "http://twig.sensiolabs.org",
            "keywords": [
                "templating"
            ],
            "time": "2016-05-30 09:11:59"
        },
        {
            "name": "webmozart/assert",
            "version": "1.0.2",
            "source": {
                "type": "git",
                "url": "https://github.com/webmozart/assert.git",
                "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde",
                "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.6"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Webmozart\\Assert\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Bernhard Schussek",
                    "email": "bschussek@gmail.com"
                }
            ],
            "description": "Assertions to validate method input/output with nice error messages.",
            "keywords": [
                "assert",
                "check",
                "validate"
            ],
            "time": "2015-08-24 13:29:44"
        }
    ],
    "aliases": [],
    "minimum-stability": "stable",
    "stability-flags": [],
    "prefer-stable": false,
    "prefer-lowest": false,
    "platform": {
        "php": ">=5.3.3"
    },
    "platform-dev": []
}
Copyright 2007-2016 TerraFrost and other contributors
http://phpseclib.sourceforge.net/

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.
<?php
/**
 * Bootstrapping File for phpseclib
 *
 * @license http://www.opensource.org/licenses/mit-license.html MIT License
 */

if (extension_loaded('mbstring')) {
    // 2 - MB_OVERLOAD_STRING
    if (ini_get('mbstring.func_overload') & 2) {
        throw new \UnexpectedValueException(
            'Overloading of string functions using mbstring.func_overload ' .
            'is not supported by phpseclib.'
        );
    }
}
<?php

/**
 * Pure-PHP implementation of AES.
 *
 * Uses mcrypt, if available/possible, and an internal implementation, otherwise.
 *
 * PHP version 5
 *
 * NOTE: Since AES.php is (for compatibility and phpseclib-historical reasons) virtually
 * just a wrapper to Rijndael.php you may consider using Rijndael.php instead of
 * to save one include_once().
 *
 * If {@link self::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
 * {@link self::setKey() setKey()}.  ie. if the key is 128-bits, the key length will be 128-bits.  If it's 136-bits
 * it'll be null-padded to 192-bits and 192 bits will be the key length until {@link self::setKey() setKey()}
 * is called, again, at which point, it'll be recalculated.
 *
 * Since \phpseclib\Crypt\AES extends \phpseclib\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't
 * make a whole lot of sense.  {@link self::setBlockLength() setBlockLength()}, for instance.  Calling that function,
 * however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one).
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $aes = new \phpseclib\Crypt\AES();
 *
 *    $aes->setKey('abcdefghijklmnop');
 *
 *    $size = 10 * 1024;
 *    $plaintext = '';
 *    for ($i = 0; $i < $size; $i++) {
 *        $plaintext.= 'a';
 *    }
 *
 *    echo $aes->decrypt($aes->encrypt($plaintext));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   AES
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2008 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP implementation of AES.
 *
 * @package AES
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class AES extends Rijndael
{
    /**
     * Dummy function
     *
     * Since \phpseclib\Crypt\AES extends \phpseclib\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything.
     *
     * @see \phpseclib\Crypt\Rijndael::setBlockLength()
     * @access public
     * @param int $length
     */
    function setBlockLength($length)
    {
        return;
    }

    /**
     * Sets the key length
     *
     * Valid key lengths are 128, 192, and 256.  If the length is less than 128, it will be rounded up to
     * 128.  If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount.
     *
     * @see \phpseclib\Crypt\Rijndael:setKeyLength()
     * @access public
     * @param int $length
     */
    function setKeyLength($length)
    {
        switch ($length) {
            case 160:
                $length = 192;
                break;
            case 224:
                $length = 256;
        }
        parent::setKeyLength($length);
    }

    /**
     * Sets the key.
     *
     * Rijndael supports five different key lengths, AES only supports three.
     *
     * @see \phpseclib\Crypt\Rijndael:setKey()
     * @see setKeyLength()
     * @access public
     * @param string $key
     */
    function setKey($key)
    {
        parent::setKey($key);

        if (!$this->explicit_key_length) {
            $length = strlen($key);
            switch (true) {
                case $length <= 16:
                    $this->key_length = 16;
                    break;
                case $length <= 24:
                    $this->key_length = 24;
                    break;
                default:
                    $this->key_length = 32;
            }
            $this->_setEngine();
        }
    }
}
<?php

/**
 * Base Class for all \phpseclib\Crypt\* cipher classes
 *
 * PHP version 5
 *
 * Internally for phpseclib developers:
 *  If you plan to add a new cipher class, please note following rules:
 *
 *  - The new \phpseclib\Crypt\* cipher class should extend \phpseclib\Crypt\Base
 *
 *  - Following methods are then required to be overridden/overloaded:
 *
 *    - _encryptBlock()
 *
 *    - _decryptBlock()
 *
 *    - _setupKey()
 *
 *  - All other methods are optional to be overridden/overloaded
 *
 *  - Look at the source code of the current ciphers how they extend \phpseclib\Crypt\Base
 *    and take one of them as a start up for the new cipher class.
 *
 *  - Please read all the other comments/notes/hints here also for each class var/method
 *
 * @category  Crypt
 * @package   Base
 * @author    Jim Wigginton <terrafrost@php.net>
 * @author    Hans-Juergen Petrich <petrich@tronic-media.com>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Base Class for all \phpseclib\Crypt\* cipher classes
 *
 * @package Base
 * @author  Jim Wigginton <terrafrost@php.net>
 * @author  Hans-Juergen Petrich <petrich@tronic-media.com>
 */
abstract class Base
{
    /**#@+
     * @access public
     * @see \phpseclib\Crypt\Base::encrypt()
     * @see \phpseclib\Crypt\Base::decrypt()
     */
    /**
     * Encrypt / decrypt using the Counter mode.
     *
     * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
     *
     * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
     */
    const MODE_CTR = -1;
    /**
     * Encrypt / decrypt using the Electronic Code Book mode.
     *
     * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
     */
    const MODE_ECB = 1;
    /**
     * Encrypt / decrypt using the Code Book Chaining mode.
     *
     * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
     */
    const MODE_CBC = 2;
    /**
     * Encrypt / decrypt using the Cipher Feedback mode.
     *
     * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
     */
    const MODE_CFB = 3;
    /**
     * Encrypt / decrypt using the Output Feedback mode.
     *
     * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
     */
    const MODE_OFB = 4;
    /**
     * Encrypt / decrypt using streaming mode.
     */
    const MODE_STREAM = 5;
    /**#@-*/

    /**
     * Whirlpool available flag
     *
     * @see \phpseclib\Crypt\Base::_hashInlineCryptFunction()
     * @var bool
     * @access private
     */
    static $WHIRLPOOL_AVAILABLE;

    /**#@+
     * @access private
     * @see \phpseclib\Crypt\Base::__construct()
     */
    /**
     * Base value for the internal implementation $engine switch
     */
    const ENGINE_INTERNAL = 1;
    /**
     * Base value for the mcrypt implementation $engine switch
     */
    const ENGINE_MCRYPT = 2;
    /**
     * Base value for the mcrypt implementation $engine switch
     */
    const ENGINE_OPENSSL = 3;
    /**#@-*/

    /**
     * The Encryption Mode
     *
     * @see self::__construct()
     * @var int
     * @access private
     */
    var $mode;

    /**
     * The Block Length of the block cipher
     *
     * @var int
     * @access private
     */
    var $block_size = 16;

    /**
     * The Key
     *
     * @see self::setKey()
     * @var string
     * @access private
     */
    var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

    /**
     * The Initialization Vector
     *
     * @see self::setIV()
     * @var string
     * @access private
     */
    var $iv;

    /**
     * A "sliding" Initialization Vector
     *
     * @see self::enableContinuousBuffer()
     * @see self::_clearBuffers()
     * @var string
     * @access private
     */
    var $encryptIV;

    /**
     * A "sliding" Initialization Vector
     *
     * @see self::enableContinuousBuffer()
     * @see self::_clearBuffers()
     * @var string
     * @access private
     */
    var $decryptIV;

    /**
     * Continuous Buffer status
     *
     * @see self::enableContinuousBuffer()
     * @var bool
     * @access private
     */
    var $continuousBuffer = false;

    /**
     * Encryption buffer for CTR, OFB and CFB modes
     *
     * @see self::encrypt()
     * @see self::_clearBuffers()
     * @var array
     * @access private
     */
    var $enbuffer;

    /**
     * Decryption buffer for CTR, OFB and CFB modes
     *
     * @see self::decrypt()
     * @see self::_clearBuffers()
     * @var array
     * @access private
     */
    var $debuffer;

    /**
     * mcrypt resource for encryption
     *
     * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
     * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
     *
     * @see self::encrypt()
     * @var resource
     * @access private
     */
    var $enmcrypt;

    /**
     * mcrypt resource for decryption
     *
     * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
     * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
     *
     * @see self::decrypt()
     * @var resource
     * @access private
     */
    var $demcrypt;

    /**
     * Does the enmcrypt resource need to be (re)initialized?
     *
     * @see \phpseclib\Crypt\Twofish::setKey()
     * @see \phpseclib\Crypt\Twofish::setIV()
     * @var bool
     * @access private
     */
    var $enchanged = true;

    /**
     * Does the demcrypt resource need to be (re)initialized?
     *
     * @see \phpseclib\Crypt\Twofish::setKey()
     * @see \phpseclib\Crypt\Twofish::setIV()
     * @var bool
     * @access private
     */
    var $dechanged = true;

    /**
     * mcrypt resource for CFB mode
     *
     * mcrypt's CFB mode, in (and only in) buffered context,
     * is broken, so phpseclib implements the CFB mode by it self,
     * even when the mcrypt php extension is available.
     *
     * In order to do the CFB-mode work (fast) phpseclib
     * use a separate ECB-mode mcrypt resource.
     *
     * @link http://phpseclib.sourceforge.net/cfb-demo.phps
     * @see self::encrypt()
     * @see self::decrypt()
     * @see self::_setupMcrypt()
     * @var resource
     * @access private
     */
    var $ecb;

    /**
     * Optimizing value while CFB-encrypting
     *
     * Only relevant if $continuousBuffer enabled
     * and $engine == self::ENGINE_MCRYPT
     *
     * It's faster to re-init $enmcrypt if
     * $buffer bytes > $cfb_init_len than
     * using the $ecb resource furthermore.
     *
     * This value depends of the chosen cipher
     * and the time it would be needed for it's
     * initialization [by mcrypt_generic_init()]
     * which, typically, depends on the complexity
     * on its internaly Key-expanding algorithm.
     *
     * @see self::encrypt()
     * @var int
     * @access private
     */
    var $cfb_init_len = 600;

    /**
     * Does internal cipher state need to be (re)initialized?
     *
     * @see self::setKey()
     * @see self::setIV()
     * @see self::disableContinuousBuffer()
     * @var bool
     * @access private
     */
    var $changed = true;

    /**
     * Padding status
     *
     * @see self::enablePadding()
     * @var bool
     * @access private
     */
    var $padding = true;

    /**
     * Is the mode one that is paddable?
     *
     * @see self::__construct()
     * @var bool
     * @access private
     */
    var $paddable = false;

    /**
     * Holds which crypt engine internaly should be use,
     * which will be determined automatically on __construct()
     *
     * Currently available $engines are:
     * - self::ENGINE_OPENSSL  (very fast, php-extension: openssl, extension_loaded('openssl') required)
     * - self::ENGINE_MCRYPT   (fast, php-extension: mcrypt, extension_loaded('mcrypt') required)
     * - self::ENGINE_INTERNAL (slower, pure php-engine, no php-extension required)
     *
     * @see self::_setEngine()
     * @see self::encrypt()
     * @see self::decrypt()
     * @var int
     * @access private
     */
    var $engine;

    /**
     * Holds the preferred crypt engine
     *
     * @see self::_setEngine()
     * @see self::setPreferredEngine()
     * @var int
     * @access private
     */
    var $preferredEngine;

    /**
     * The mcrypt specific name of the cipher
     *
     * Only used if $engine == self::ENGINE_MCRYPT
     *
     * @link http://www.php.net/mcrypt_module_open
     * @link http://www.php.net/mcrypt_list_algorithms
     * @see self::_setupMcrypt()
     * @var string
     * @access private
     */
    var $cipher_name_mcrypt;

    /**
     * The openssl specific name of the cipher
     *
     * Only used if $engine == self::ENGINE_OPENSSL
     *
     * @link http://www.php.net/openssl-get-cipher-methods
     * @var string
     * @access private
     */
    var $cipher_name_openssl;

    /**
     * The openssl specific name of the cipher in ECB mode
     *
     * If OpenSSL does not support the mode we're trying to use (CTR)
     * it can still be emulated with ECB mode.
     *
     * @link http://www.php.net/openssl-get-cipher-methods
     * @var string
     * @access private
     */
    var $cipher_name_openssl_ecb;

    /**
     * The default salt used by setPassword()
     *
     * @see self::setPassword()
     * @var string
     * @access private
     */
    var $password_default_salt = 'phpseclib/salt';

    /**
     * The name of the performance-optimized callback function
     *
     * Used by encrypt() / decrypt()
     * only if $engine == self::ENGINE_INTERNAL
     *
     * @see self::encrypt()
     * @see self::decrypt()
     * @see self::_setupInlineCrypt()
     * @see self::$use_inline_crypt
     * @var Callback
     * @access private
     */
    var $inline_crypt;

    /**
     * Holds whether performance-optimized $inline_crypt() can/should be used.
     *
     * @see self::encrypt()
     * @see self::decrypt()
     * @see self::inline_crypt
     * @var mixed
     * @access private
     */
    var $use_inline_crypt;

    /**
     * If OpenSSL can be used in ECB but not in CTR we can emulate CTR
     *
     * @see self::_openssl_ctr_process()
     * @var bool
     * @access private
     */
    var $openssl_emulate_ctr = false;

    /**
     * Determines what options are passed to openssl_encrypt/decrypt
     *
     * @see self::isValidEngine()
     * @var mixed
     * @access private
     */
    var $openssl_options;

    /**
     * Has the key length explicitly been set or should it be derived from the key, itself?
     *
     * @see self::setKeyLength()
     * @var bool
     * @access private
     */
    var $explicit_key_length = false;

    /**
     * Don't truncate / null pad key
     *
     * @see self::_clearBuffers()
     * @var bool
     * @access private
     */
    var $skip_key_adjustment = false;

    /**
     * Default Constructor.
     *
     * Determines whether or not the mcrypt extension should be used.
     *
     * $mode could be:
     *
     * - self::MODE_ECB
     *
     * - self::MODE_CBC
     *
     * - self::MODE_CTR
     *
     * - self::MODE_CFB
     *
     * - self::MODE_OFB
     *
     * If not explicitly set, self::MODE_CBC will be used.
     *
     * @param int $mode
     * @access public
     */
    function __construct($mode = self::MODE_CBC)
    {
        // $mode dependent settings
        switch ($mode) {
            case self::MODE_ECB:
                $this->paddable = true;
                $this->mode = self::MODE_ECB;
                break;
            case self::MODE_CTR:
            case self::MODE_CFB:
            case self::MODE_OFB:
            case self::MODE_STREAM:
                $this->mode = $mode;
                break;
            case self::MODE_CBC:
            default:
                $this->paddable = true;
                $this->mode = self::MODE_CBC;
        }

        $this->_setEngine();

        // Determining whether inline crypting can be used by the cipher
        if ($this->use_inline_crypt !== false && function_exists('create_function')) {
            $this->use_inline_crypt = true;
        }
    }

    /**
     * Sets the initialization vector. (optional)
     *
     * SetIV is not required when self::MODE_ECB (or ie for AES: \phpseclib\Crypt\AES::MODE_ECB) is being used.  If not explicitly set, it'll be assumed
     * to be all zero's.
     *
     * @access public
     * @param string $iv
     * @internal Can be overwritten by a sub class, but does not have to be
     */
    function setIV($iv)
    {
        if ($this->mode == self::MODE_ECB) {
            return;
        }

        $this->iv = $iv;
        $this->changed = true;
    }

    /**
     * Sets the key length.
     *
     * Keys with explicitly set lengths need to be treated accordingly
     *
     * @access public
     * @param int $length
     */
    function setKeyLength($length)
    {
        $this->explicit_key_length = true;
        $this->changed = true;
        $this->_setEngine();
    }

    /**
     * Returns the current key length in bits
     *
     * @access public
     * @return int
     */
    function getKeyLength()
    {
        return $this->key_length << 3;
    }

    /**
     * Returns the current block length in bits
     *
     * @access public
     * @return int
     */
    function getBlockLength()
    {
        return $this->block_size << 3;
    }

    /**
     * Sets the key.
     *
     * The min/max length(s) of the key depends on the cipher which is used.
     * If the key not fits the length(s) of the cipher it will paded with null bytes
     * up to the closest valid key length.  If the key is more than max length,
     * we trim the excess bits.
     *
     * If the key is not explicitly set, it'll be assumed to be all null bytes.
     *
     * @access public
     * @param string $key
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function setKey($key)
    {
        if (!$this->explicit_key_length) {
            $this->setKeyLength(strlen($key) << 3);
            $this->explicit_key_length = false;
        }

        $this->key = $key;
        $this->changed = true;
        $this->_setEngine();
    }

    /**
     * Sets the password.
     *
     * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows:
     *     {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1:
     *         $hash, $salt, $count, $dkLen
     *
     *         Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php
     *
     * @see Crypt/Hash.php
     * @param string $password
     * @param string $method
     * @return bool
     * @access public
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function setPassword($password, $method = 'pbkdf2')
    {
        $key = '';

        switch ($method) {
            default: // 'pbkdf2' or 'pbkdf1'
                $func_args = func_get_args();

                // Hash function
                $hash = isset($func_args[2]) ? $func_args[2] : 'sha1';

                // WPA and WPA2 use the SSID as the salt
                $salt = isset($func_args[3]) ? $func_args[3] : $this->password_default_salt;

                // RFC2898#section-4.2 uses 1,000 iterations by default
                // WPA and WPA2 use 4,096.
                $count = isset($func_args[4]) ? $func_args[4] : 1000;

                // Keylength
                if (isset($func_args[5])) {
                    $dkLen = $func_args[5];
                } else {
                    $dkLen = $method == 'pbkdf1' ? 2 * $this->key_length : $this->key_length;
                }

                switch (true) {
                    case $method == 'pbkdf1':
                        $hashObj = new Hash();
                        $hashObj->setHash($hash);
                        if ($dkLen > $hashObj->getLength()) {
                            user_error('Derived key too long');
                            return false;
                        }
                        $t = $password . $salt;
                        for ($i = 0; $i < $count; ++$i) {
                            $t = $hashObj->hash($t);
                        }
                        $key = substr($t, 0, $dkLen);

                        $this->setKey(substr($key, 0, $dkLen >> 1));
                        $this->setIV(substr($key, $dkLen >> 1));

                        return true;
                    // Determining if php[>=5.5.0]'s hash_pbkdf2() function avail- and useable
                    case !function_exists('hash_pbkdf2'):
                    case !function_exists('hash_algos'):
                    case !in_array($hash, hash_algos()):
                        $i = 1;
                        while (strlen($key) < $dkLen) {
                            $hmac = new Hash();
                            $hmac->setHash($hash);
                            $hmac->setKey($password);
                            $f = $u = $hmac->hash($salt . pack('N', $i++));
                            for ($j = 2; $j <= $count; ++$j) {
                                $u = $hmac->hash($u);
                                $f^= $u;
                            }
                            $key.= $f;
                        }
                        $key = substr($key, 0, $dkLen);
                        break;
                    default:
                        $key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true);
                }
        }

        $this->setKey($key);

        return true;
    }

    /**
     * Encrypts a message.
     *
     * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other cipher
     * implementations may or may not pad in the same manner.  Other common approaches to padding and the reasons why it's
     * necessary are discussed in the following
     * URL:
     *
     * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
     *
     * An alternative to padding is to, separately, send the length of the file.  This is what SSH, in fact, does.
     * strlen($plaintext) will still need to be a multiple of the block size, however, arbitrary values can be added to make it that
     * length.
     *
     * @see self::decrypt()
     * @access public
     * @param string $plaintext
     * @return string $ciphertext
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function encrypt($plaintext)
    {
        if ($this->paddable) {
            $plaintext = $this->_pad($plaintext);
        }

        if ($this->engine === self::ENGINE_OPENSSL) {
            if ($this->changed) {
                $this->_clearBuffers();
                $this->changed = false;
            }
            switch ($this->mode) {
                case self::MODE_STREAM:
                    return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options);
                case self::MODE_ECB:
                    $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options);
                    return !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result;
                case self::MODE_CBC:
                    $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV);
                    if (!defined('OPENSSL_RAW_DATA')) {
                        $result = substr($result, 0, -$this->block_size);
                    }
                    if ($this->continuousBuffer) {
                        $this->encryptIV = substr($result, -$this->block_size);
                    }
                    return $result;
                case self::MODE_CTR:
                    return $this->_openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer);
                case self::MODE_CFB:
                    // cfb loosely routines inspired by openssl's:
                    // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
                    $ciphertext = '';
                    if ($this->continuousBuffer) {
                        $iv = &$this->encryptIV;
                        $pos = &$this->enbuffer['pos'];
                    } else {
                        $iv = $this->encryptIV;
                        $pos = 0;
                    }
                    $len = strlen($plaintext);
                    $i = 0;
                    if ($pos) {
                        $orig_pos = $pos;
                        $max = $this->block_size - $pos;
                        if ($len >= $max) {
                            $i = $max;
                            $len-= $max;
                            $pos = 0;
                        } else {
                            $i = $len;
                            $pos+= $len;
                            $len = 0;
                        }
                        // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
                        $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
                        $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
                        $plaintext = substr($plaintext, $i);
                    }

                    $overflow = $len % $this->block_size;

                    if ($overflow) {
                        $ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
                        $iv = $this->_string_pop($ciphertext, $this->block_size);

                        $size = $len - $overflow;
                        $block = $iv ^ substr($plaintext, -$overflow);
                        $iv = substr_replace($iv, $block, 0, $overflow);
                        $ciphertext.= $block;
                        $pos = $overflow;
                    } elseif ($len) {
                        $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
                        $iv = substr($ciphertext, -$this->block_size);
                    }

                    return $ciphertext;
                case self::MODE_OFB:
                    return $this->_openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer);
            }
        }

        if ($this->engine === self::ENGINE_MCRYPT) {
            if ($this->changed) {
                $this->_setupMcrypt();
                $this->changed = false;
            }
            if ($this->enchanged) {
                @mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV);
                $this->enchanged = false;
            }

            // re: {@link http://phpseclib.sourceforge.net/cfb-demo.phps}
            // using mcrypt's default handing of CFB the above would output two different things.  using phpseclib's
            // rewritten CFB implementation the above outputs the same thing twice.
            if ($this->mode == self::MODE_CFB && $this->continuousBuffer) {
                $block_size = $this->block_size;
                $iv = &$this->encryptIV;
                $pos = &$this->enbuffer['pos'];
                $len = strlen($plaintext);
                $ciphertext = '';
                $i = 0;
                if ($pos) {
                    $orig_pos = $pos;
                    $max = $block_size - $pos;
                    if ($len >= $max) {
                        $i = $max;
                        $len-= $max;
                        $pos = 0;
                    } else {
                        $i = $len;
                        $pos+= $len;
                        $len = 0;
                    }
                    $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
                    $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
                    $this->enbuffer['enmcrypt_init'] = true;
                }
                if ($len >= $block_size) {
                    if ($this->enbuffer['enmcrypt_init'] === false || $len > $this->cfb_init_len) {
                        if ($this->enbuffer['enmcrypt_init'] === true) {
                            @mcrypt_generic_init($this->enmcrypt, $this->key, $iv);
                            $this->enbuffer['enmcrypt_init'] = false;
                        }
                        $ciphertext.= @mcrypt_generic($this->enmcrypt, substr($plaintext, $i, $len - $len % $block_size));
                        $iv = substr($ciphertext, -$block_size);
                        $len%= $block_size;
                    } else {
                        while ($len >= $block_size) {
                            $iv = @mcrypt_generic($this->ecb, $iv) ^ substr($plaintext, $i, $block_size);
                            $ciphertext.= $iv;
                            $len-= $block_size;
                            $i+= $block_size;
                        }
                    }
                }

                if ($len) {
                    $iv = @mcrypt_generic($this->ecb, $iv);
                    $block = $iv ^ substr($plaintext, -$len);
                    $iv = substr_replace($iv, $block, 0, $len);
                    $ciphertext.= $block;
                    $pos = $len;
                }

                return $ciphertext;
            }

            $ciphertext = @mcrypt_generic($this->enmcrypt, $plaintext);

            if (!$this->continuousBuffer) {
                @mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV);
            }

            return $ciphertext;
        }

        if ($this->changed) {
            $this->_setup();
            $this->changed = false;
        }
        if ($this->use_inline_crypt) {
            $inline = $this->inline_crypt;
            return $inline('encrypt', $this, $plaintext);
        }

        $buffer = &$this->enbuffer;
        $block_size = $this->block_size;
        $ciphertext = '';
        switch ($this->mode) {
            case self::MODE_ECB:
                for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
                    $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size));
                }
                break;
            case self::MODE_CBC:
                $xor = $this->encryptIV;
                for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
                    $block = substr($plaintext, $i, $block_size);
                    $block = $this->_encryptBlock($block ^ $xor);
                    $xor = $block;
                    $ciphertext.= $block;
                }
                if ($this->continuousBuffer) {
                    $this->encryptIV = $xor;
                }
                break;
            case self::MODE_CTR:
                $xor = $this->encryptIV;
                if (strlen($buffer['ciphertext'])) {
                    for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
                        $block = substr($plaintext, $i, $block_size);
                        if (strlen($block) > strlen($buffer['ciphertext'])) {
                            $buffer['ciphertext'].= $this->_encryptBlock($xor);
                        }
                        $this->_increment_str($xor);
                        $key = $this->_string_shift($buffer['ciphertext'], $block_size);
                        $ciphertext.= $block ^ $key;
                    }
                } else {
                    for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
                        $block = substr($plaintext, $i, $block_size);
                        $key = $this->_encryptBlock($xor);
                        $this->_increment_str($xor);
                        $ciphertext.= $block ^ $key;
                    }
                }
                if ($this->continuousBuffer) {
                    $this->encryptIV = $xor;
                    if ($start = strlen($plaintext) % $block_size) {
                        $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
                    }
                }
                break;
            case self::MODE_CFB:
                // cfb loosely routines inspired by openssl's:
                // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
                if ($this->continuousBuffer) {
                    $iv = &$this->encryptIV;
                    $pos = &$buffer['pos'];
                } else {
                    $iv = $this->encryptIV;
                    $pos = 0;
                }
                $len = strlen($plaintext);
                $i = 0;
                if ($pos) {
                    $orig_pos = $pos;
                    $max = $block_size - $pos;
                    if ($len >= $max) {
                        $i = $max;
                        $len-= $max;
                        $pos = 0;
                    } else {
                        $i = $len;
                        $pos+= $len;
                        $len = 0;
                    }
                    // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
                    $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
                    $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
                }
                while ($len >= $block_size) {
                    $iv = $this->_encryptBlock($iv) ^ substr($plaintext, $i, $block_size);
                    $ciphertext.= $iv;
                    $len-= $block_size;
                    $i+= $block_size;
                }
                if ($len) {
                    $iv = $this->_encryptBlock($iv);
                    $block = $iv ^ substr($plaintext, $i);
                    $iv = substr_replace($iv, $block, 0, $len);
                    $ciphertext.= $block;
                    $pos = $len;
                }
                break;
            case self::MODE_OFB:
                $xor = $this->encryptIV;
                if (strlen($buffer['xor'])) {
                    for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
                        $block = substr($plaintext, $i, $block_size);
                        if (strlen($block) > strlen($buffer['xor'])) {
                            $xor = $this->_encryptBlock($xor);
                            $buffer['xor'].= $xor;
                        }
                        $key = $this->_string_shift($buffer['xor'], $block_size);
                        $ciphertext.= $block ^ $key;
                    }
                } else {
                    for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
                        $xor = $this->_encryptBlock($xor);
                        $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor;
                    }
                    $key = $xor;
                }
                if ($this->continuousBuffer) {
                    $this->encryptIV = $xor;
                    if ($start = strlen($plaintext) % $block_size) {
                        $buffer['xor'] = substr($key, $start) . $buffer['xor'];
                    }
                }
                break;
            case self::MODE_STREAM:
                $ciphertext = $this->_encryptBlock($plaintext);
                break;
        }

        return $ciphertext;
    }

    /**
     * Decrypts a message.
     *
     * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until
     * it is.
     *
     * @see self::encrypt()
     * @access public
     * @param string $ciphertext
     * @return string $plaintext
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function decrypt($ciphertext)
    {
        if ($this->paddable) {
            // we pad with chr(0) since that's what mcrypt_generic does.  to quote from {@link http://www.php.net/function.mcrypt-generic}:
            // "The data is padded with "\0" to make sure the length of the data is n * blocksize."
            $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0));
        }

        if ($this->engine === self::ENGINE_OPENSSL) {
            if ($this->changed) {
                $this->_clearBuffers();
                $this->changed = false;
            }
            switch ($this->mode) {
                case self::MODE_STREAM:
                    $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options);
                    break;
                case self::MODE_ECB:
                    if (!defined('OPENSSL_RAW_DATA')) {
                        $ciphertext.= openssl_encrypt('', $this->cipher_name_openssl_ecb, $this->key, true);
                    }
                    $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options);
                    break;
                case self::MODE_CBC:
                    if (!defined('OPENSSL_RAW_DATA')) {
                        $padding = str_repeat(chr($this->block_size), $this->block_size) ^ substr($ciphertext, -$this->block_size);
                        $ciphertext.= substr(openssl_encrypt($padding, $this->cipher_name_openssl_ecb, $this->key, true), 0, $this->block_size);
                        $offset = 2 * $this->block_size;
                    } else {
                        $offset = $this->block_size;
                    }
                    $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->decryptIV);
                    if ($this->continuousBuffer) {
                        $this->decryptIV = substr($ciphertext, -$offset, $this->block_size);
                    }
                    break;
                case self::MODE_CTR:
                    $plaintext = $this->_openssl_ctr_process($ciphertext, $this->decryptIV, $this->debuffer);
                    break;
                case self::MODE_CFB:
                    // cfb loosely routines inspired by openssl's:
                    // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
                    $plaintext = '';
                    if ($this->continuousBuffer) {
                        $iv = &$this->decryptIV;
                        $pos = &$this->buffer['pos'];
                    } else {
                        $iv = $this->decryptIV;
                        $pos = 0;
                    }
                    $len = strlen($ciphertext);
                    $i = 0;
                    if ($pos) {
                        $orig_pos = $pos;
                        $max = $this->block_size - $pos;
                        if ($len >= $max) {
                            $i = $max;
                            $len-= $max;
                            $pos = 0;
                        } else {
                            $i = $len;
                            $pos+= $len;
                            $len = 0;
                        }
                        // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $this->blocksize
                        $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
                        $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
                        $ciphertext = substr($ciphertext, $i);
                    }
                    $overflow = $len % $this->block_size;
                    if ($overflow) {
                        $plaintext.= openssl_decrypt(substr($ciphertext, 0, -$overflow), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
                        if ($len - $overflow) {
                            $iv = substr($ciphertext, -$overflow - $this->block_size, -$overflow);
                        }
                        $iv = openssl_encrypt(str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
                        $plaintext.= $iv ^ substr($ciphertext, -$overflow);
                        $iv = substr_replace($iv, substr($ciphertext, -$overflow), 0, $overflow);
                        $pos = $overflow;
                    } elseif ($len) {
                        $plaintext.= openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
                        $iv = substr($ciphertext, -$this->block_size);
                    }
                    break;
                case self::MODE_OFB:
                    $plaintext = $this->_openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer);
            }

            return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
        }

        if ($this->engine === self::ENGINE_MCRYPT) {
            $block_size = $this->block_size;
            if ($this->changed) {
                $this->_setupMcrypt();
                $this->changed = false;
            }
            if ($this->dechanged) {
                @mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV);
                $this->dechanged = false;
            }

            if ($this->mode == self::MODE_CFB && $this->continuousBuffer) {
                $iv = &$this->decryptIV;
                $pos = &$this->debuffer['pos'];
                $len = strlen($ciphertext);
                $plaintext = '';
                $i = 0;
                if ($pos) {
                    $orig_pos = $pos;
                    $max = $block_size - $pos;
                    if ($len >= $max) {
                        $i = $max;
                        $len-= $max;
                        $pos = 0;
                    } else {
                        $i = $len;
                        $pos+= $len;
                        $len = 0;
                    }
                    // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
                    $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
                    $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
                }
                if ($len >= $block_size) {
                    $cb = substr($ciphertext, $i, $len - $len % $block_size);
                    $plaintext.= @mcrypt_generic($this->ecb, $iv . $cb) ^ $cb;
                    $iv = substr($cb, -$block_size);
                    $len%= $block_size;
                }
                if ($len) {
                    $iv = @mcrypt_generic($this->ecb, $iv);
                    $plaintext.= $iv ^ substr($ciphertext, -$len);
                    $iv = substr_replace($iv, substr($ciphertext, -$len), 0, $len);
                    $pos = $len;
                }

                return $plaintext;
            }

            $plaintext = @mdecrypt_generic($this->demcrypt, $ciphertext);

            if (!$this->continuousBuffer) {
                @mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV);
            }

            return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
        }

        if ($this->changed) {
            $this->_setup();
            $this->changed = false;
        }
        if ($this->use_inline_crypt) {
            $inline = $this->inline_crypt;
            return $inline('decrypt', $this, $ciphertext);
        }

        $block_size = $this->block_size;

        $buffer = &$this->debuffer;
        $plaintext = '';
        switch ($this->mode) {
            case self::MODE_ECB:
                for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
                    $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size));
                }
                break;
            case self::MODE_CBC:
                $xor = $this->decryptIV;
                for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
                    $block = substr($ciphertext, $i, $block_size);
                    $plaintext.= $this->_decryptBlock($block) ^ $xor;
                    $xor = $block;
                }
                if ($this->continuousBuffer) {
                    $this->decryptIV = $xor;
                }
                break;
            case self::MODE_CTR:
                $xor = $this->decryptIV;
                if (strlen($buffer['ciphertext'])) {
                    for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
                        $block = substr($ciphertext, $i, $block_size);
                        if (strlen($block) > strlen($buffer['ciphertext'])) {
                            $buffer['ciphertext'].= $this->_encryptBlock($xor);
                            $this->_increment_str($xor);
                        }
                        $key = $this->_string_shift($buffer['ciphertext'], $block_size);
                        $plaintext.= $block ^ $key;
                    }
                } else {
                    for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
                        $block = substr($ciphertext, $i, $block_size);
                        $key = $this->_encryptBlock($xor);
                        $this->_increment_str($xor);
                        $plaintext.= $block ^ $key;
                    }
                }
                if ($this->continuousBuffer) {
                    $this->decryptIV = $xor;
                    if ($start = strlen($ciphertext) % $block_size) {
                        $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
                    }
                }
                break;
            case self::MODE_CFB:
                if ($this->continuousBuffer) {
                    $iv = &$this->decryptIV;
                    $pos = &$buffer['pos'];
                } else {
                    $iv = $this->decryptIV;
                    $pos = 0;
                }
                $len = strlen($ciphertext);
                $i = 0;
                if ($pos) {
                    $orig_pos = $pos;
                    $max = $block_size - $pos;
                    if ($len >= $max) {
                        $i = $max;
                        $len-= $max;
                        $pos = 0;
                    } else {
                        $i = $len;
                        $pos+= $len;
                        $len = 0;
                    }
                    // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
                    $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
                    $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
                }
                while ($len >= $block_size) {
                    $iv = $this->_encryptBlock($iv);
                    $cb = substr($ciphertext, $i, $block_size);
                    $plaintext.= $iv ^ $cb;
                    $iv = $cb;
                    $len-= $block_size;
                    $i+= $block_size;
                }
                if ($len) {
                    $iv = $this->_encryptBlock($iv);
                    $plaintext.= $iv ^ substr($ciphertext, $i);
                    $iv = substr_replace($iv, substr($ciphertext, $i), 0, $len);
                    $pos = $len;
                }
                break;
            case self::MODE_OFB:
                $xor = $this->decryptIV;
                if (strlen($buffer['xor'])) {
                    for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
                        $block = substr($ciphertext, $i, $block_size);
                        if (strlen($block) > strlen($buffer['xor'])) {
                            $xor = $this->_encryptBlock($xor);
                            $buffer['xor'].= $xor;
                        }
                        $key = $this->_string_shift($buffer['xor'], $block_size);
                        $plaintext.= $block ^ $key;
                    }
                } else {
                    for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
                        $xor = $this->_encryptBlock($xor);
                        $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor;
                    }
                    $key = $xor;
                }
                if ($this->continuousBuffer) {
                    $this->decryptIV = $xor;
                    if ($start = strlen($ciphertext) % $block_size) {
                        $buffer['xor'] = substr($key, $start) . $buffer['xor'];
                    }
                }
                break;
            case self::MODE_STREAM:
                $plaintext = $this->_decryptBlock($ciphertext);
                break;
        }
        return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
    }

    /**
     * OpenSSL CTR Processor
     *
     * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
     * for CTR is the same for both encrypting and decrypting this function is re-used by both Base::encrypt()
     * and Base::decrypt(). Also, OpenSSL doesn't implement CTR for all of it's symmetric ciphers so this
     * function will emulate CTR with ECB when necessary.
     *
     * @see self::encrypt()
     * @see self::decrypt()
     * @param string $plaintext
     * @param string $encryptIV
     * @param array $buffer
     * @return string
     * @access private
     */
    function _openssl_ctr_process($plaintext, &$encryptIV, &$buffer)
    {
        $ciphertext = '';

        $block_size = $this->block_size;
        $key = $this->key;

        if ($this->openssl_emulate_ctr) {
            $xor = $encryptIV;
            if (strlen($buffer['ciphertext'])) {
                for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
                    $block = substr($plaintext, $i, $block_size);
                    if (strlen($block) > strlen($buffer['ciphertext'])) {
                        $result = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options);
                        $result = !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result;
                        $buffer['ciphertext'].= $result;
                    }
                    $this->_increment_str($xor);
                    $otp = $this->_string_shift($buffer['ciphertext'], $block_size);
                    $ciphertext.= $block ^ $otp;
                }
            } else {
                for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
                    $block = substr($plaintext, $i, $block_size);
                    $otp = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options);
                    $otp = !defined('OPENSSL_RAW_DATA') ? substr($otp, 0, -$this->block_size) : $otp;
                    $this->_increment_str($xor);
                    $ciphertext.= $block ^ $otp;
                }
            }
            if ($this->continuousBuffer) {
                $encryptIV = $xor;
                if ($start = strlen($plaintext) % $block_size) {
                    $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
                }
            }

            return $ciphertext;
        }

        if (strlen($buffer['ciphertext'])) {
            $ciphertext = $plaintext ^ $this->_string_shift($buffer['ciphertext'], strlen($plaintext));
            $plaintext = substr($plaintext, strlen($ciphertext));

            if (!strlen($plaintext)) {
                return $ciphertext;
            }
        }

        $overflow = strlen($plaintext) % $block_size;
        if ($overflow) {
            $plaintext2 = $this->_string_pop($plaintext, $overflow); // ie. trim $plaintext to a multiple of $block_size and put rest of $plaintext in $plaintext2
            $encrypted = openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV);
            $temp = $this->_string_pop($encrypted, $block_size);
            $ciphertext.= $encrypted . ($plaintext2 ^ $temp);
            if ($this->continuousBuffer) {
                $buffer['ciphertext'] = substr($temp, $overflow);
                $encryptIV = $temp;
            }
        } elseif (!strlen($buffer['ciphertext'])) {
            $ciphertext.= openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV);
            $temp = $this->_string_pop($ciphertext, $block_size);
            if ($this->continuousBuffer) {
                $encryptIV = $temp;
            }
        }
        if ($this->continuousBuffer) {
            if (!defined('OPENSSL_RAW_DATA')) {
                $encryptIV.= openssl_encrypt('', $this->cipher_name_openssl_ecb, $key, $this->openssl_options);
            }
            $encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, $this->openssl_options);
            if ($overflow) {
                $this->_increment_str($encryptIV);
            }
        }

        return $ciphertext;
    }

    /**
     * OpenSSL OFB Processor
     *
     * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
     * for OFB is the same for both encrypting and decrypting this function is re-used by both Base::encrypt()
     * and Base::decrypt().
     *
     * @see self::encrypt()
     * @see self::decrypt()
     * @param string $plaintext
     * @param string $encryptIV
     * @param array $buffer
     * @return string
     * @access private
     */
    function _openssl_ofb_process($plaintext, &$encryptIV, &$buffer)
    {
        if (strlen($buffer['xor'])) {
            $ciphertext = $plaintext ^ $buffer['xor'];
            $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext));
            $plaintext = substr($plaintext, strlen($ciphertext));
        } else {
            $ciphertext = '';
        }

        $block_size = $this->block_size;

        $len = strlen($plaintext);
        $key = $this->key;
        $overflow = $len % $block_size;

        if (strlen($plaintext)) {
            if ($overflow) {
                $ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV);
                $xor = $this->_string_pop($ciphertext, $block_size);
                if ($this->continuousBuffer) {
                    $encryptIV = $xor;
                }
                $ciphertext.= $this->_string_shift($xor, $overflow) ^ substr($plaintext, -$overflow);
                if ($this->continuousBuffer) {
                    $buffer['xor'] = $xor;
                }
            } else {
                $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV);
                if ($this->continuousBuffer) {
                    $encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size);
                }
            }
        }

        return $ciphertext;
    }

    /**
     * phpseclib <-> OpenSSL Mode Mapper
     *
     * May need to be overwritten by classes extending this one in some cases
     *
     * @return int
     * @access private
     */
    function _openssl_translate_mode()
    {
        switch ($this->mode) {
            case self::MODE_ECB:
                return 'ecb';
            case self::MODE_CBC:
                return 'cbc';
            case self::MODE_CTR:
                return 'ctr';
            case self::MODE_CFB:
                return 'cfb';
            case self::MODE_OFB:
                return 'ofb';
        }
    }

    /**
     * Pad "packets".
     *
     * Block ciphers working by encrypting between their specified [$this->]block_size at a time
     * If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to
     * pad the input so that it is of the proper length.
     *
     * Padding is enabled by default.  Sometimes, however, it is undesirable to pad strings.  Such is the case in SSH,
     * where "packets" are padded with random bytes before being encrypted.  Unpad these packets and you risk stripping
     * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is
     * transmitted separately)
     *
     * @see self::disablePadding()
     * @access public
     */
    function enablePadding()
    {
        $this->padding = true;
    }

    /**
     * Do not pad packets.
     *
     * @see self::enablePadding()
     * @access public
     */
    function disablePadding()
    {
        $this->padding = false;
    }

    /**
     * Treat consecutive "packets" as if they are a continuous buffer.
     *
     * Say you have a 32-byte plaintext $plaintext.  Using the default behavior, the two following code snippets
     * will yield different outputs:
     *
     * <code>
     *    echo $rijndael->encrypt(substr($plaintext,  0, 16));
     *    echo $rijndael->encrypt(substr($plaintext, 16, 16));
     * </code>
     * <code>
     *    echo $rijndael->encrypt($plaintext);
     * </code>
     *
     * The solution is to enable the continuous buffer.  Although this will resolve the above discrepancy, it creates
     * another, as demonstrated with the following:
     *
     * <code>
     *    $rijndael->encrypt(substr($plaintext, 0, 16));
     *    echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
     * </code>
     * <code>
     *    echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
     * </code>
     *
     * With the continuous buffer disabled, these would yield the same output.  With it enabled, they yield different
     * outputs.  The reason is due to the fact that the initialization vector's change after every encryption /
     * decryption round when the continuous buffer is enabled.  When it's disabled, they remain constant.
     *
     * Put another way, when the continuous buffer is enabled, the state of the \phpseclib\Crypt\*() object changes after each
     * encryption / decryption round, whereas otherwise, it'd remain constant.  For this reason, it's recommended that
     * continuous buffers not be used.  They do offer better security and are, in fact, sometimes required (SSH uses them),
     * however, they are also less intuitive and more likely to cause you problems.
     *
     * @see self::disableContinuousBuffer()
     * @access public
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function enableContinuousBuffer()
    {
        if ($this->mode == self::MODE_ECB) {
            return;
        }

        $this->continuousBuffer = true;

        $this->_setEngine();
    }

    /**
     * Treat consecutive packets as if they are a discontinuous buffer.
     *
     * The default behavior.
     *
     * @see self::enableContinuousBuffer()
     * @access public
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function disableContinuousBuffer()
    {
        if ($this->mode == self::MODE_ECB) {
            return;
        }
        if (!$this->continuousBuffer) {
            return;
        }

        $this->continuousBuffer = false;
        $this->changed = true;

        $this->_setEngine();
    }

    /**
     * Test for engine validity
     *
     * @see self::__construct()
     * @param int $engine
     * @access public
     * @return bool
     */
    function isValidEngine($engine)
    {
        switch ($engine) {
            case self::ENGINE_OPENSSL:
                if ($this->mode == self::MODE_STREAM && $this->continuousBuffer) {
                    return false;
                }
                $this->openssl_emulate_ctr = false;
                $result = $this->cipher_name_openssl &&
                          extension_loaded('openssl') &&
                          // PHP 5.3.0 - 5.3.2 did not let you set IV's
                          version_compare(PHP_VERSION, '5.3.3', '>=');
                if (!$result) {
                    return false;
                }

                // prior to PHP 5.4.0 OPENSSL_RAW_DATA and OPENSSL_ZERO_PADDING were not defined. instead of expecting an integer
                // $options openssl_encrypt expected a boolean $raw_data.
                if (!defined('OPENSSL_RAW_DATA')) {
                    $this->openssl_options = true;
                } else {
                    $this->openssl_options = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
                }

                $methods = openssl_get_cipher_methods();
                if (in_array($this->cipher_name_openssl, $methods)) {
                    return true;
                }
                // not all of openssl's symmetric cipher's support ctr. for those
                // that don't we'll emulate it
                switch ($this->mode) {
                    case self::MODE_CTR:
                        if (in_array($this->cipher_name_openssl_ecb, $methods)) {
                            $this->openssl_emulate_ctr = true;
                            return true;
                        }
                }
                return false;
            case self::ENGINE_MCRYPT:
                return $this->cipher_name_mcrypt &&
                       extension_loaded('mcrypt') &&
                       in_array($this->cipher_name_mcrypt, @mcrypt_list_algorithms());
            case self::ENGINE_INTERNAL:
                return true;
        }

        return false;
    }

    /**
     * Sets the preferred crypt engine
     *
     * Currently, $engine could be:
     *
     * - \phpseclib\Crypt\Base::ENGINE_OPENSSL  [very fast]
     *
     * - \phpseclib\Crypt\Base::ENGINE_MCRYPT   [fast]
     *
     * - \phpseclib\Crypt\Base::ENGINE_INTERNAL [slow]
     *
     * If the preferred crypt engine is not available the fastest available one will be used
     *
     * @see self::__construct()
     * @param int $engine
     * @access public
     */
    function setPreferredEngine($engine)
    {
        switch ($engine) {
            //case self::ENGINE_OPENSSL;
            case self::ENGINE_MCRYPT:
            case self::ENGINE_INTERNAL:
                $this->preferredEngine = $engine;
                break;
            default:
                $this->preferredEngine = self::ENGINE_OPENSSL;
        }

        $this->_setEngine();
    }

    /**
     * Returns the engine currently being utilized
     *
     * @see self::_setEngine()
     * @access public
     */
    function getEngine()
    {
        return $this->engine;
    }

    /**
     * Sets the engine as appropriate
     *
     * @see self::__construct()
     * @access private
     */
    function _setEngine()
    {
        $this->engine = null;

        $candidateEngines = array(
            $this->preferredEngine,
            self::ENGINE_OPENSSL,
            self::ENGINE_MCRYPT
        );
        foreach ($candidateEngines as $engine) {
            if ($this->isValidEngine($engine)) {
                $this->engine = $engine;
                break;
            }
        }
        if (!$this->engine) {
            $this->engine = self::ENGINE_INTERNAL;
        }

        if ($this->engine != self::ENGINE_MCRYPT && $this->enmcrypt) {
            // Closing the current mcrypt resource(s). _mcryptSetup() will, if needed,
            // (re)open them with the module named in $this->cipher_name_mcrypt
            @mcrypt_module_close($this->enmcrypt);
            @mcrypt_module_close($this->demcrypt);
            $this->enmcrypt = null;
            $this->demcrypt = null;

            if ($this->ecb) {
                @mcrypt_module_close($this->ecb);
                $this->ecb = null;
            }
        }

        $this->changed = true;
    }

    /**
     * Encrypts a block
     *
     * Note: Must be extended by the child \phpseclib\Crypt\* class
     *
     * @access private
     * @param string $in
     * @return string
     */
    abstract function _encryptBlock($in);

    /**
     * Decrypts a block
     *
     * Note: Must be extended by the child \phpseclib\Crypt\* class
     *
     * @access private
     * @param string $in
     * @return string
     */
    abstract function _decryptBlock($in);

    /**
     * Setup the key (expansion)
     *
     * Only used if $engine == self::ENGINE_INTERNAL
     *
     * Note: Must extend by the child \phpseclib\Crypt\* class
     *
     * @see self::_setup()
     * @access private
     */
    abstract function _setupKey();

    /**
     * Setup the self::ENGINE_INTERNAL $engine
     *
     * (re)init, if necessary, the internal cipher $engine and flush all $buffers
     * Used (only) if $engine == self::ENGINE_INTERNAL
     *
     * _setup() will be called each time if $changed === true
     * typically this happens when using one or more of following public methods:
     *
     * - setKey()
     *
     * - setIV()
     *
     * - disableContinuousBuffer()
     *
     * - First run of encrypt() / decrypt() with no init-settings
     *
     * @see self::setKey()
     * @see self::setIV()
     * @see self::disableContinuousBuffer()
     * @access private
     * @internal _setup() is always called before en/decryption.
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function _setup()
    {
        $this->_clearBuffers();
        $this->_setupKey();

        if ($this->use_inline_crypt) {
            $this->_setupInlineCrypt();
        }
    }

    /**
     * Setup the self::ENGINE_MCRYPT $engine
     *
     * (re)init, if necessary, the (ext)mcrypt resources and flush all $buffers
     * Used (only) if $engine = self::ENGINE_MCRYPT
     *
     * _setupMcrypt() will be called each time if $changed === true
     * typically this happens when using one or more of following public methods:
     *
     * - setKey()
     *
     * - setIV()
     *
     * - disableContinuousBuffer()
     *
     * - First run of encrypt() / decrypt()
     *
     * @see self::setKey()
     * @see self::setIV()
     * @see self::disableContinuousBuffer()
     * @access private
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function _setupMcrypt()
    {
        $this->_clearBuffers();
        $this->enchanged = $this->dechanged = true;

        if (!isset($this->enmcrypt)) {
            static $mcrypt_modes = array(
                self::MODE_CTR    => 'ctr',
                self::MODE_ECB    => MCRYPT_MODE_ECB,
                self::MODE_CBC    => MCRYPT_MODE_CBC,
                self::MODE_CFB    => 'ncfb',
                self::MODE_OFB    => MCRYPT_MODE_NOFB,
                self::MODE_STREAM => MCRYPT_MODE_STREAM,
            );

            $this->demcrypt = @mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], '');
            $this->enmcrypt = @mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], '');

            // we need the $ecb mcrypt resource (only) in MODE_CFB with enableContinuousBuffer()
            // to workaround mcrypt's broken ncfb implementation in buffered mode
            // see: {@link http://phpseclib.sourceforge.net/cfb-demo.phps}
            if ($this->mode == self::MODE_CFB) {
                $this->ecb = @mcrypt_module_open($this->cipher_name_mcrypt, '', MCRYPT_MODE_ECB, '');
            }
        } // else should mcrypt_generic_deinit be called?

        if ($this->mode == self::MODE_CFB) {
            @mcrypt_generic_init($this->ecb, $this->key, str_repeat("\0", $this->block_size));
        }
    }

    /**
     * Pads a string
     *
     * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize.
     * $this->block_size - (strlen($text) % $this->block_size) bytes are added, each of which is equal to
     * chr($this->block_size - (strlen($text) % $this->block_size)
     *
     * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless
     * and padding will, hence forth, be enabled.
     *
     * @see self::_unpad()
     * @param string $text
     * @access private
     * @return string
     */
    function _pad($text)
    {
        $length = strlen($text);

        if (!$this->padding) {
            if ($length % $this->block_size == 0) {
                return $text;
            } else {
                user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})");
                $this->padding = true;
            }
        }

        $pad = $this->block_size - ($length % $this->block_size);

        return str_pad($text, $length + $pad, chr($pad));
    }

    /**
     * Unpads a string.
     *
     * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong
     * and false will be returned.
     *
     * @see self::_pad()
     * @param string $text
     * @access private
     * @return string
     */
    function _unpad($text)
    {
        if (!$this->padding) {
            return $text;
        }

        $length = ord($text[strlen($text) - 1]);

        if (!$length || $length > $this->block_size) {
            return false;
        }

        return substr($text, 0, -$length);
    }

    /**
     * Clears internal buffers
     *
     * Clearing/resetting the internal buffers is done everytime
     * after disableContinuousBuffer() or on cipher $engine (re)init
     * ie after setKey() or setIV()
     *
     * @access public
     * @internal Could, but not must, extend by the child Crypt_* class
     */
    function _clearBuffers()
    {
        $this->enbuffer = $this->debuffer = array('ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true);

        // mcrypt's handling of invalid's $iv:
        // $this->encryptIV = $this->decryptIV = strlen($this->iv) == $this->block_size ? $this->iv : str_repeat("\0", $this->block_size);
        $this->encryptIV = $this->decryptIV = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, "\0");

        if (!$this->skip_key_adjustment) {
            $this->key = str_pad(substr($this->key, 0, $this->key_length), $this->key_length, "\0");
        }
    }

    /**
     * String Shift
     *
     * Inspired by array_shift
     *
     * @param string $string
     * @param int $index
     * @access private
     * @return string
     */
    function _string_shift(&$string, $index = 1)
    {
        $substr = substr($string, 0, $index);
        $string = substr($string, $index);
        return $substr;
    }

    /**
     * String Pop
     *
     * Inspired by array_pop
     *
     * @param string $string
     * @param int $index
     * @access private
     * @return string
     */
    function _string_pop(&$string, $index = 1)
    {
        $substr = substr($string, -$index);
        $string = substr($string, 0, -$index);
        return $substr;
    }

    /**
     * Increment the current string
     *
     * @see self::decrypt()
     * @see self::encrypt()
     * @param string $var
     * @access private
     */
    function _increment_str(&$var)
    {
        for ($i = 4; $i <= strlen($var); $i+= 4) {
            $temp = substr($var, -$i, 4);
            switch ($temp) {
                case "\xFF\xFF\xFF\xFF":
                    $var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
                    break;
                case "\x7F\xFF\xFF\xFF":
                    $var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
                    return;
                default:
                    $temp = unpack('Nnum', $temp);
                    $var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
                    return;
            }
        }

        $remainder = strlen($var) % 4;

        if ($remainder == 0) {
            return;
        }

        $temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
        $temp = substr(pack('N', $temp['num'] + 1), -$remainder);
        $var = substr_replace($var, $temp, 0, $remainder);
    }

    /**
     * Setup the performance-optimized function for de/encrypt()
     *
     * Stores the created (or existing) callback function-name
     * in $this->inline_crypt
     *
     * Internally for phpseclib developers:
     *
     *     _setupInlineCrypt() would be called only if:
     *
     *     - $engine == self::ENGINE_INTERNAL and
     *
     *     - $use_inline_crypt === true
     *
     *     - each time on _setup(), after(!) _setupKey()
     *
     *
     *     This ensures that _setupInlineCrypt() has always a
     *     full ready2go initializated internal cipher $engine state
     *     where, for example, the keys allready expanded,
     *     keys/block_size calculated and such.
     *
     *     It is, each time if called, the responsibility of _setupInlineCrypt():
     *
     *     - to set $this->inline_crypt to a valid and fully working callback function
     *       as a (faster) replacement for encrypt() / decrypt()
     *
     *     - NOT to create unlimited callback functions (for memory reasons!)
     *       no matter how often _setupInlineCrypt() would be called. At some
     *       point of amount they must be generic re-useable.
     *
     *     - the code of _setupInlineCrypt() it self,
     *       and the generated callback code,
     *       must be, in following order:
     *       - 100% safe
     *       - 100% compatible to encrypt()/decrypt()
     *       - using only php5+ features/lang-constructs/php-extensions if
     *         compatibility (down to php4) or fallback is provided
     *       - readable/maintainable/understandable/commented and... not-cryptic-styled-code :-)
     *       - >= 10% faster than encrypt()/decrypt() [which is, by the way,
     *         the reason for the existence of _setupInlineCrypt() :-)]
     *       - memory-nice
     *       - short (as good as possible)
     *
     * Note: - _setupInlineCrypt() is using _createInlineCryptFunction() to create the full callback function code.
     *       - In case of using inline crypting, _setupInlineCrypt() must extend by the child \phpseclib\Crypt\* class.
     *       - The following variable names are reserved:
     *         - $_*  (all variable names prefixed with an underscore)
     *         - $self (object reference to it self. Do not use $this, but $self instead)
     *         - $in (the content of $in has to en/decrypt by the generated code)
     *       - The callback function should not use the 'return' statement, but en/decrypt'ing the content of $in only
     *
     *
     * @see self::_setup()
     * @see self::_createInlineCryptFunction()
     * @see self::encrypt()
     * @see self::decrypt()
     * @access private
     * @internal If a Crypt_* class providing inline crypting it must extend _setupInlineCrypt()
     */
    function _setupInlineCrypt()
    {
        // If, for any reason, an extending \phpseclib\Crypt\Base() \phpseclib\Crypt\* class
        // not using inline crypting then it must be ensured that: $this->use_inline_crypt = false
        // ie in the class var declaration of $use_inline_crypt in general for the \phpseclib\Crypt\* class,
        // in the constructor at object instance-time
        // or, if it's runtime-specific, at runtime

        $this->use_inline_crypt = false;
    }

    /**
     * Creates the performance-optimized function for en/decrypt()
     *
     * Internally for phpseclib developers:
     *
     *    _createInlineCryptFunction():
     *
     *    - merge the $cipher_code [setup'ed by _setupInlineCrypt()]
     *      with the current [$this->]mode of operation code
     *
     *    - create the $inline function, which called by encrypt() / decrypt()
     *      as its replacement to speed up the en/decryption operations.
     *
     *    - return the name of the created $inline callback function
     *
     *    - used to speed up en/decryption
     *
     *
     *
     *    The main reason why can speed up things [up to 50%] this way are:
     *
     *    - using variables more effective then regular.
     *      (ie no use of expensive arrays but integers $k_0, $k_1 ...
     *      or even, for example, the pure $key[] values hardcoded)
     *
     *    - avoiding 1000's of function calls of ie _encryptBlock()
     *      but inlining the crypt operations.
     *      in the mode of operation for() loop.
     *
     *    - full loop unroll the (sometimes key-dependent) rounds
     *      avoiding this way ++$i counters and runtime-if's etc...
     *
     *    The basic code architectur of the generated $inline en/decrypt()
     *    lambda function, in pseudo php, is:
     *
     *    <code>
     *    +----------------------------------------------------------------------------------------------+
     *    | callback $inline = create_function:                                                          |
     *    | lambda_function_0001_crypt_ECB($action, $text)                                               |
     *    | {                                                                                            |
     *    |     INSERT PHP CODE OF:                                                                      |
     *    |     $cipher_code['init_crypt'];                  // general init code.                       |
     *    |                                                  // ie: $sbox'es declarations used for       |
     *    |                                                  //     encrypt and decrypt'ing.             |
     *    |                                                                                              |
     *    |     switch ($action) {                                                                       |
     *    |         case 'encrypt':                                                                      |
     *    |             INSERT PHP CODE OF:                                                              |
     *    |             $cipher_code['init_encrypt'];       // encrypt sepcific init code.               |
     *    |                                                    ie: specified $key or $box                |
     *    |                                                        declarations for encrypt'ing.         |
     *    |                                                                                              |
     *    |             foreach ($ciphertext) {                                                          |
     *    |                 $in = $block_size of $ciphertext;                                            |
     *    |                                                                                              |
     *    |                 INSERT PHP CODE OF:                                                          |
     *    |                 $cipher_code['encrypt_block'];  // encrypt's (string) $in, which is always:  |
     *    |                                                 // strlen($in) == $this->block_size          |
     *    |                                                 // here comes the cipher algorithm in action |
     *    |                                                 // for encryption.                           |
     *    |                                                 // $cipher_code['encrypt_block'] has to      |
     *    |                                                 // encrypt the content of the $in variable   |
     *    |                                                                                              |
     *    |                 $plaintext .= $in;                                                           |
     *    |             }                                                                                |
     *    |             return $plaintext;                                                               |
     *    |                                                                                              |
     *    |         case 'decrypt':                                                                      |
     *    |             INSERT PHP CODE OF:                                                              |
     *    |             $cipher_code['init_decrypt'];       // decrypt sepcific init code                |
     *    |                                                    ie: specified $key or $box                |
     *    |                                                        declarations for decrypt'ing.         |
     *    |             foreach ($plaintext) {                                                           |
     *    |                 $in = $block_size of $plaintext;                                             |
     *    |                                                                                              |
     *    |                 INSERT PHP CODE OF:                                                          |
     *    |                 $cipher_code['decrypt_block'];  // decrypt's (string) $in, which is always   |
     *    |                                                 // strlen($in) == $this->block_size          |
     *    |                                                 // here comes the cipher algorithm in action |
     *    |                                                 // for decryption.                           |
     *    |                                                 // $cipher_code['decrypt_block'] has to      |
     *    |                                                 // decrypt the content of the $in variable   |
     *    |                 $ciphertext .= $in;                                                          |
     *    |             }                                                                                |
     *    |             return $ciphertext;                                                              |
     *    |     }                                                                                        |
     *    | }                                                                                            |
     *    +----------------------------------------------------------------------------------------------+
     *    </code>
     *
     *    See also the \phpseclib\Crypt\*::_setupInlineCrypt()'s for
     *    productive inline $cipher_code's how they works.
     *
     *    Structure of:
     *    <code>
     *    $cipher_code = array(
     *        'init_crypt'    => (string) '', // optional
     *        'init_encrypt'  => (string) '', // optional
     *        'init_decrypt'  => (string) '', // optional
     *        'encrypt_block' => (string) '', // required
     *        'decrypt_block' => (string) ''  // required
     *    );
     *    </code>
     *
     * @see self::_setupInlineCrypt()
     * @see self::encrypt()
     * @see self::decrypt()
     * @param array $cipher_code
     * @access private
     * @return string (the name of the created callback function)
     */
    function _createInlineCryptFunction($cipher_code)
    {
        $block_size = $this->block_size;

        // optional
        $init_crypt    = isset($cipher_code['init_crypt'])    ? $cipher_code['init_crypt']    : '';
        $init_encrypt  = isset($cipher_code['init_encrypt'])  ? $cipher_code['init_encrypt']  : '';
        $init_decrypt  = isset($cipher_code['init_decrypt'])  ? $cipher_code['init_decrypt']  : '';
        // required
        $encrypt_block = $cipher_code['encrypt_block'];
        $decrypt_block = $cipher_code['decrypt_block'];

        // Generating mode of operation inline code,
        // merged with the $cipher_code algorithm
        // for encrypt- and decryption.
        switch ($this->mode) {
            case self::MODE_ECB:
                $encrypt = $init_encrypt . '
                    $_ciphertext = "";
                    $_plaintext_len = strlen($_text);

                    for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
                        $in = substr($_text, $_i, '.$block_size.');
                        '.$encrypt_block.'
                        $_ciphertext.= $in;
                    }

                    return $_ciphertext;
                    ';

                $decrypt = $init_decrypt . '
                    $_plaintext = "";
                    $_text = str_pad($_text, strlen($_text) + ('.$block_size.' - strlen($_text) % '.$block_size.') % '.$block_size.', chr(0));
                    $_ciphertext_len = strlen($_text);

                    for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
                        $in = substr($_text, $_i, '.$block_size.');
                        '.$decrypt_block.'
                        $_plaintext.= $in;
                    }

                    return $self->_unpad($_plaintext);
                    ';
                break;
            case self::MODE_CTR:
                $encrypt = $init_encrypt . '
                    $_ciphertext = "";
                    $_plaintext_len = strlen($_text);
                    $_xor = $self->encryptIV;
                    $_buffer = &$self->enbuffer;
                    if (strlen($_buffer["ciphertext"])) {
                        for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
                            $_block = substr($_text, $_i, '.$block_size.');
                            if (strlen($_block) > strlen($_buffer["ciphertext"])) {
                                $in = $_xor;
                                '.$encrypt_block.'
                                $self->_increment_str($_xor);
                                $_buffer["ciphertext"].= $in;
                            }
                            $_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.');
                            $_ciphertext.= $_block ^ $_key;
                        }
                    } else {
                        for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
                            $_block = substr($_text, $_i, '.$block_size.');
                            $in = $_xor;
                            '.$encrypt_block.'
                            $self->_increment_str($_xor);
                            $_key = $in;
                            $_ciphertext.= $_block ^ $_key;
                        }
                    }
                    if ($self->continuousBuffer) {
                        $self->encryptIV = $_xor;
                        if ($_start = $_plaintext_len % '.$block_size.') {
                            $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"];
                        }
                    }

                    return $_ciphertext;
                ';

                $decrypt = $init_encrypt . '
                    $_plaintext = "";
                    $_ciphertext_len = strlen($_text);
                    $_xor = $self->decryptIV;
                    $_buffer = &$self->debuffer;

                    if (strlen($_buffer["ciphertext"])) {
                        for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
                            $_block = substr($_text, $_i, '.$block_size.');
                            if (strlen($_block) > strlen($_buffer["ciphertext"])) {
                                $in = $_xor;
                                '.$encrypt_block.'
                                $self->_increment_str($_xor);
                                $_buffer["ciphertext"].= $in;
                            }
                            $_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.');
                            $_plaintext.= $_block ^ $_key;
                        }
                    } else {
                        for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
                            $_block = substr($_text, $_i, '.$block_size.');
                            $in = $_xor;
                            '.$encrypt_block.'
                            $self->_increment_str($_xor);
                            $_key = $in;
                            $_plaintext.= $_block ^ $_key;
                        }
                    }
                    if ($self->continuousBuffer) {
                        $self->decryptIV = $_xor;
                        if ($_start = $_ciphertext_len % '.$block_size.') {
                            $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"];
                        }
                    }

                    return $_plaintext;
                    ';
                break;
            case self::MODE_CFB:
                $encrypt = $init_encrypt . '
                    $_ciphertext = "";
                    $_buffer = &$self->enbuffer;

                    if ($self->continuousBuffer) {
                        $_iv = &$self->encryptIV;
                        $_pos = &$_buffer["pos"];
                    } else {
                        $_iv = $self->encryptIV;
                        $_pos = 0;
                    }
                    $_len = strlen($_text);
                    $_i = 0;
                    if ($_pos) {
                        $_orig_pos = $_pos;
                        $_max = '.$block_size.' - $_pos;
                        if ($_len >= $_max) {
                            $_i = $_max;
                            $_len-= $_max;
                            $_pos = 0;
                        } else {
                            $_i = $_len;
                            $_pos+= $_len;
                            $_len = 0;
                        }
                        $_ciphertext = substr($_iv, $_orig_pos) ^ $_text;
                        $_iv = substr_replace($_iv, $_ciphertext, $_orig_pos, $_i);
                    }
                    while ($_len >= '.$block_size.') {
                        $in = $_iv;
                        '.$encrypt_block.';
                        $_iv = $in ^ substr($_text, $_i, '.$block_size.');
                        $_ciphertext.= $_iv;
                        $_len-= '.$block_size.';
                        $_i+= '.$block_size.';
                    }
                    if ($_len) {
                        $in = $_iv;
                        '.$encrypt_block.'
                        $_iv = $in;
                        $_block = $_iv ^ substr($_text, $_i);
                        $_iv = substr_replace($_iv, $_block, 0, $_len);
                        $_ciphertext.= $_block;
                        $_pos = $_len;
                    }
                    return $_ciphertext;
                ';

                $decrypt = $init_encrypt . '
                    $_plaintext = "";
                    $_buffer = &$self->debuffer;

                    if ($self->continuousBuffer) {
                        $_iv = &$self->decryptIV;
                        $_pos = &$_buffer["pos"];
                    } else {
                        $_iv = $self->decryptIV;
                        $_pos = 0;
                    }
                    $_len = strlen($_text);
                    $_i = 0;
                    if ($_pos) {
                        $_orig_pos = $_pos;
                        $_max = '.$block_size.' - $_pos;
                        if ($_len >= $_max) {
                            $_i = $_max;
                            $_len-= $_max;
                            $_pos = 0;
                        } else {
                            $_i = $_len;
                            $_pos+= $_len;
                            $_len = 0;
                        }
                        $_plaintext = substr($_iv, $_orig_pos) ^ $_text;
                        $_iv = substr_replace($_iv, substr($_text, 0, $_i), $_orig_pos, $_i);
                    }
                    while ($_len >= '.$block_size.') {
                        $in = $_iv;
                        '.$encrypt_block.'
                        $_iv = $in;
                        $cb = substr($_text, $_i, '.$block_size.');
                        $_plaintext.= $_iv ^ $cb;
                        $_iv = $cb;
                        $_len-= '.$block_size.';
                        $_i+= '.$block_size.';
                    }
                    if ($_len) {
                        $in = $_iv;
                        '.$encrypt_block.'
                        $_iv = $in;
                        $_plaintext.= $_iv ^ substr($_text, $_i);
                        $_iv = substr_replace($_iv, substr($_text, $_i), 0, $_len);
                        $_pos = $_len;
                    }

                    return $_plaintext;
                    ';
                break;
            case self::MODE_OFB:
                $encrypt = $init_encrypt . '
                    $_ciphertext = "";
                    $_plaintext_len = strlen($_text);
                    $_xor = $self->encryptIV;
                    $_buffer = &$self->enbuffer;

                    if (strlen($_buffer["xor"])) {
                        for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
                            $_block = substr($_text, $_i, '.$block_size.');
                            if (strlen($_block) > strlen($_buffer["xor"])) {
                                $in = $_xor;
                                '.$encrypt_block.'
                                $_xor = $in;
                                $_buffer["xor"].= $_xor;
                            }
                            $_key = $self->_string_shift($_buffer["xor"], '.$block_size.');
                            $_ciphertext.= $_block ^ $_key;
                        }
                    } else {
                        for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
                            $in = $_xor;
                            '.$encrypt_block.'
                            $_xor = $in;
                            $_ciphertext.= substr($_text, $_i, '.$block_size.') ^ $_xor;
                        }
                        $_key = $_xor;
                    }
                    if ($self->continuousBuffer) {
                        $self->encryptIV = $_xor;
                        if ($_start = $_plaintext_len % '.$block_size.') {
                             $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"];
                        }
                    }
                    return $_ciphertext;
                    ';

                $decrypt = $init_encrypt . '
                    $_plaintext = "";
                    $_ciphertext_len = strlen($_text);
                    $_xor = $self->decryptIV;
                    $_buffer = &$self->debuffer;

                    if (strlen($_buffer["xor"])) {
                        for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
                            $_block = substr($_text, $_i, '.$block_size.');
                            if (strlen($_block) > strlen($_buffer["xor"])) {
                                $in = $_xor;
                                '.$encrypt_block.'
                                $_xor = $in;
                                $_buffer["xor"].= $_xor;
                            }
                            $_key = $self->_string_shift($_buffer["xor"], '.$block_size.');
                            $_plaintext.= $_block ^ $_key;
                        }
                    } else {
                        for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
                            $in = $_xor;
                            '.$encrypt_block.'
                            $_xor = $in;
                            $_plaintext.= substr($_text, $_i, '.$block_size.') ^ $_xor;
                        }
                        $_key = $_xor;
                    }
                    if ($self->continuousBuffer) {
                        $self->decryptIV = $_xor;
                        if ($_start = $_ciphertext_len % '.$block_size.') {
                             $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"];
                        }
                    }
                    return $_plaintext;
                    ';
                break;
            case self::MODE_STREAM:
                $encrypt = $init_encrypt . '
                    $_ciphertext = "";
                    '.$encrypt_block.'
                    return $_ciphertext;
                    ';
                $decrypt = $init_decrypt . '
                    $_plaintext = "";
                    '.$decrypt_block.'
                    return $_plaintext;
                    ';
                break;
            // case self::MODE_CBC:
            default:
                $encrypt = $init_encrypt . '
                    $_ciphertext = "";
                    $_plaintext_len = strlen($_text);

                    $in = $self->encryptIV;

                    for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
                        $in = substr($_text, $_i, '.$block_size.') ^ $in;
                        '.$encrypt_block.'
                        $_ciphertext.= $in;
                    }

                    if ($self->continuousBuffer) {
                        $self->encryptIV = $in;
                    }

                    return $_ciphertext;
                    ';

                $decrypt = $init_decrypt . '
                    $_plaintext = "";
                    $_text = str_pad($_text, strlen($_text) + ('.$block_size.' - strlen($_text) % '.$block_size.') % '.$block_size.', chr(0));
                    $_ciphertext_len = strlen($_text);

                    $_iv = $self->decryptIV;

                    for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
                        $in = $_block = substr($_text, $_i, '.$block_size.');
                        '.$decrypt_block.'
                        $_plaintext.= $in ^ $_iv;
                        $_iv = $_block;
                    }

                    if ($self->continuousBuffer) {
                        $self->decryptIV = $_iv;
                    }

                    return $self->_unpad($_plaintext);
                    ';
                break;
        }

        // Create the $inline function and return its name as string. Ready to run!
        return create_function('$_action, &$self, $_text', $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' }');
    }

    /**
     * Holds the lambda_functions table (classwide)
     *
     * Each name of the lambda function, created from
     * _setupInlineCrypt() && _createInlineCryptFunction()
     * is stored, classwide (!), here for reusing.
     *
     * The string-based index of $function is a classwide
     * unique value representing, at least, the $mode of
     * operation (or more... depends of the optimizing level)
     * for which $mode the lambda function was created.
     *
     * @access private
     * @return array &$functions
     */
    function &_getLambdaFunctions()
    {
        static $functions = array();
        return $functions;
    }

    /**
     * Generates a digest from $bytes
     *
     * @see self::_setupInlineCrypt()
     * @access private
     * @param $bytes
     * @return string
     */
    function _hashInlineCryptFunction($bytes)
    {
        if (!isset(self::$WHIRLPOOL_AVAILABLE)) {
            self::$WHIRLPOOL_AVAILABLE = extension_loaded('hash') && in_array('whirlpool', hash_algos());
        }

        $result = '';
        $hash = $bytes;

        switch (true) {
            case self::$WHIRLPOOL_AVAILABLE:
                foreach (str_split($bytes, 64) as $t) {
                    $hash = hash('whirlpool', $hash, true);
                    $result .= $t ^ $hash;
                }
                return $result . hash('whirlpool', $hash, true);
            default:
                $len = strlen($bytes);
                for ($i = 0; $i < $len; $i+=20) {
                    $t = substr($bytes, $i, 20);
                    $hash = pack('H*', sha1($hash));
                    $result .= $t ^ $hash;
                }
                return $result . pack('H*', sha1($hash));
        }
    }
}
<?php

/**
 * Pure-PHP implementation of Blowfish.
 *
 * Uses mcrypt, if available, and an internal implementation, otherwise.
 *
 * PHP version 5
 *
 * Useful resources are as follows:
 *
 *  - {@link http://en.wikipedia.org/wiki/Blowfish_(cipher) Wikipedia description of Blowfish}
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $blowfish = new \phpseclib\Crypt\Blowfish();
 *
 *    $blowfish->setKey('12345678901234567890123456789012');
 *
 *    $plaintext = str_repeat('a', 1024);
 *
 *    echo $blowfish->decrypt($blowfish->encrypt($plaintext));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   Blowfish
 * @author    Jim Wigginton <terrafrost@php.net>
 * @author    Hans-Juergen Petrich <petrich@tronic-media.com>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP implementation of Blowfish.
 *
 * @package Blowfish
 * @author  Jim Wigginton <terrafrost@php.net>
 * @author  Hans-Juergen Petrich <petrich@tronic-media.com>
 * @access  public
 */
class Blowfish extends Base
{
    /**
     * Block Length of the cipher
     *
     * @see \phpseclib\Crypt\Base::block_size
     * @var int
     * @access private
     */
    var $block_size = 8;

    /**
     * The mcrypt specific name of the cipher
     *
     * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
     * @var string
     * @access private
     */
    var $cipher_name_mcrypt = 'blowfish';

    /**
     * Optimizing value while CFB-encrypting
     *
     * @see \phpseclib\Crypt\Base::cfb_init_len
     * @var int
     * @access private
     */
    var $cfb_init_len = 500;

    /**
     * The fixed subkeys boxes ($sbox0 - $sbox3) with 256 entries each
     *
     * S-Box 0
     *
     * @access private
     * @var    array
     */
    var $sbox0 = array(
        0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
        0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
        0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
        0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
        0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
        0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
        0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
        0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
        0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
        0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
        0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
        0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
        0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
        0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
        0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
        0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
        0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
        0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
        0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
        0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
        0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
        0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
        0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
        0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
        0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
        0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
        0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
        0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
        0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
        0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
        0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
        0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
    );

    /**
     * S-Box 1
     *
     * @access private
     * @var    array
     */
    var $sbox1 = array(
        0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
        0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
        0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
        0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
        0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
        0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
        0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
        0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
        0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
        0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
        0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
        0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
        0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
        0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
        0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
        0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
        0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
        0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
        0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
        0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
        0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
        0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
        0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
        0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
        0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
        0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
        0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
        0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
        0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
        0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
        0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
        0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
    );

    /**
     * S-Box 2
     *
     * @access private
     * @var    array
     */
    var $sbox2 = array(
        0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
        0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
        0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
        0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
        0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
        0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
        0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
        0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
        0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
        0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
        0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
        0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
        0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
        0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
        0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
        0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
        0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
        0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
        0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
        0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
        0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
        0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
        0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
        0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
        0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
        0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
        0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
        0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
        0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
        0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
        0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
        0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
    );

    /**
     * S-Box 3
     *
     * @access private
     * @var    array
     */
    var $sbox3 = array(
        0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
        0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
        0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
        0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
        0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
        0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
        0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
        0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
        0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
        0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
        0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
        0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
        0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
        0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
        0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
        0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
        0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
        0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
        0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
        0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
        0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
        0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
        0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
        0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
        0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
        0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
        0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
        0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
        0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
        0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
        0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
        0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
    );

    /**
     * P-Array consists of 18 32-bit subkeys
     *
     * @var array
     * @access private
     */
    var $parray = array(
        0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
        0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
        0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b
    );

    /**
     * The BCTX-working Array
     *
     * Holds the expanded key [p] and the key-depended s-boxes [sb]
     *
     * @var array
     * @access private
     */
    var $bctx;

    /**
     * Holds the last used key
     *
     * @var array
     * @access private
     */
    var $kl;

    /**
     * The Key Length (in bytes)
     *
     * @see \phpseclib\Crypt\Base::setKeyLength()
     * @var int
     * @access private
     * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16.  Exists in conjunction with $Nk
     *    because the encryption / decryption / key schedule creation requires this number and not $key_length.  We could
     *    derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
     *    of that, we'll just precompute it once.
     */
    var $key_length = 16;

    /**
     * Sets the key length.
     *
     * Key lengths can be between 32 and 448 bits.
     *
     * @access public
     * @param int $length
     */
    function setKeyLength($length)
    {
        if ($length < 32) {
            $this->key_length = 7;
        } elseif ($length > 448) {
            $this->key_length = 56;
        } else {
            $this->key_length = $length >> 3;
        }

        parent::setKeyLength($length);
    }

    /**
     * Test for engine validity
     *
     * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
     *
     * @see \phpseclib\Crypt\Base::isValidEngine()
     * @param int $engine
     * @access public
     * @return bool
     */
    function isValidEngine($engine)
    {
        if ($engine == self::ENGINE_OPENSSL) {
            if ($this->key_length != 16) {
                return false;
            }
            $this->cipher_name_openssl_ecb = 'bf-ecb';
            $this->cipher_name_openssl = 'bf-' . $this->_openssl_translate_mode();
        }

        return parent::isValidEngine($engine);
    }

    /**
     * Setup the key (expansion)
     *
     * @see \phpseclib\Crypt\Base::_setupKey()
     * @access private
     */
    function _setupKey()
    {
        if (isset($this->kl['key']) && $this->key === $this->kl['key']) {
            // already expanded
            return;
        }
        $this->kl = array('key' => $this->key);

        /* key-expanding p[] and S-Box building sb[] */
        $this->bctx = array(
            'p'  => array(),
            'sb' => array(
                $this->sbox0,
                $this->sbox1,
                $this->sbox2,
                $this->sbox3
            )
        );

        // unpack binary string in unsigned chars
        $key  = array_values(unpack('C*', $this->key));
        $keyl = count($key);
        for ($j = 0, $i = 0; $i < 18; ++$i) {
            // xor P1 with the first 32-bits of the key, xor P2 with the second 32-bits ...
            for ($data = 0, $k = 0; $k < 4; ++$k) {
                $data = ($data << 8) | $key[$j];
                if (++$j >= $keyl) {
                    $j = 0;
                }
            }
            $this->bctx['p'][] = $this->parray[$i] ^ $data;
        }

        // encrypt the zero-string, replace P1 and P2 with the encrypted data,
        // encrypt P3 and P4 with the new P1 and P2, do it with all P-array and subkeys
        $data = "\0\0\0\0\0\0\0\0";
        for ($i = 0; $i < 18; $i += 2) {
            list($l, $r) = array_values(unpack('N*', $data = $this->_encryptBlock($data)));
            $this->bctx['p'][$i    ] = $l;
            $this->bctx['p'][$i + 1] = $r;
        }
        for ($i = 0; $i < 4; ++$i) {
            for ($j = 0; $j < 256; $j += 2) {
                list($l, $r) = array_values(unpack('N*', $data = $this->_encryptBlock($data)));
                $this->bctx['sb'][$i][$j    ] = $l;
                $this->bctx['sb'][$i][$j + 1] = $r;
            }
        }
    }

    /**
     * Encrypts a block
     *
     * @access private
     * @param string $in
     * @return string
     */
    function _encryptBlock($in)
    {
        $p = $this->bctx["p"];
        // extract($this->bctx["sb"], EXTR_PREFIX_ALL, "sb"); // slower
        $sb_0 = $this->bctx["sb"][0];
        $sb_1 = $this->bctx["sb"][1];
        $sb_2 = $this->bctx["sb"][2];
        $sb_3 = $this->bctx["sb"][3];

        $in = unpack("N*", $in);
        $l = $in[1];
        $r = $in[2];

        for ($i = 0; $i < 16; $i+= 2) {
            $l^= $p[$i];
            $r^= ($sb_0[$l >> 24 & 0xff]  +
                  $sb_1[$l >> 16 & 0xff]  ^
                  $sb_2[$l >>  8 & 0xff]) +
                  $sb_3[$l       & 0xff];

            $r^= $p[$i + 1];
            $l^= ($sb_0[$r >> 24 & 0xff]  +
                  $sb_1[$r >> 16 & 0xff]  ^
                  $sb_2[$r >>  8 & 0xff]) +
                  $sb_3[$r       & 0xff];
        }
        return pack("N*", $r ^ $p[17], $l ^ $p[16]);
    }

    /**
     * Decrypts a block
     *
     * @access private
     * @param string $in
     * @return string
     */
    function _decryptBlock($in)
    {
        $p = $this->bctx["p"];
        $sb_0 = $this->bctx["sb"][0];
        $sb_1 = $this->bctx["sb"][1];
        $sb_2 = $this->bctx["sb"][2];
        $sb_3 = $this->bctx["sb"][3];

        $in = unpack("N*", $in);
        $l = $in[1];
        $r = $in[2];

        for ($i = 17; $i > 2; $i-= 2) {
            $l^= $p[$i];
            $r^= ($sb_0[$l >> 24 & 0xff]  +
                  $sb_1[$l >> 16 & 0xff]  ^
                  $sb_2[$l >>  8 & 0xff]) +
                  $sb_3[$l       & 0xff];

            $r^= $p[$i - 1];
            $l^= ($sb_0[$r >> 24 & 0xff]  +
                  $sb_1[$r >> 16 & 0xff]  ^
                  $sb_2[$r >>  8 & 0xff]) +
                  $sb_3[$r       & 0xff];
        }
        return pack("N*", $r ^ $p[0], $l ^ $p[1]);
    }

    /**
     * Setup the performance-optimized function for de/encrypt()
     *
     * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
     * @access private
     */
    function _setupInlineCrypt()
    {
        $lambda_functions =& self::_getLambdaFunctions();

        // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function.
        // (Currently, for Blowfish, one generated $lambda_function cost on php5.5@32bit ~100kb unfreeable mem and ~180kb on php5.5@64bit)
        // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one.
        $gen_hi_opt_code = (bool)(count($lambda_functions) < 10);

        // Generation of a unique hash for our generated code
        $code_hash = "Crypt_Blowfish, {$this->mode}";
        if ($gen_hi_opt_code) {
            $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
        }

        if (!isset($lambda_functions[$code_hash])) {
            switch (true) {
                case $gen_hi_opt_code:
                    $p = $this->bctx['p'];
                    $init_crypt = '
                        static $sb_0, $sb_1, $sb_2, $sb_3;
                        if (!$sb_0) {
                            $sb_0 = $self->bctx["sb"][0];
                            $sb_1 = $self->bctx["sb"][1];
                            $sb_2 = $self->bctx["sb"][2];
                            $sb_3 = $self->bctx["sb"][3];
                        }
                    ';
                    break;
                default:
                    $p   = array();
                    for ($i = 0; $i < 18; ++$i) {
                        $p[] = '$p_' . $i;
                    }
                    $init_crypt = '
                        list($sb_0, $sb_1, $sb_2, $sb_3) = $self->bctx["sb"];
                        list(' . implode(',', $p) . ') = $self->bctx["p"];

                    ';
            }

            // Generating encrypt code:
            $encrypt_block = '
                $in = unpack("N*", $in);
                $l = $in[1];
                $r = $in[2];
            ';
            for ($i = 0; $i < 16; $i+= 2) {
                $encrypt_block.= '
                    $l^= ' . $p[$i] . ';
                    $r^= ($sb_0[$l >> 24 & 0xff]  +
                          $sb_1[$l >> 16 & 0xff]  ^
                          $sb_2[$l >>  8 & 0xff]) +
                          $sb_3[$l       & 0xff];

                    $r^= ' . $p[$i + 1] . ';
                    $l^= ($sb_0[$r >> 24 & 0xff]  +
                          $sb_1[$r >> 16 & 0xff]  ^
                          $sb_2[$r >>  8 & 0xff]) +
                          $sb_3[$r       & 0xff];
                ';
            }
            $encrypt_block.= '
                $in = pack("N*",
                    $r ^ ' . $p[17] . ',
                    $l ^ ' . $p[16] . '
                );
            ';

            // Generating decrypt code:
            $decrypt_block = '
                $in = unpack("N*", $in);
                $l = $in[1];
                $r = $in[2];
            ';

            for ($i = 17; $i > 2; $i-= 2) {
                $decrypt_block.= '
                    $l^= ' . $p[$i] . ';
                    $r^= ($sb_0[$l >> 24 & 0xff]  +
                          $sb_1[$l >> 16 & 0xff]  ^
                          $sb_2[$l >>  8 & 0xff]) +
                          $sb_3[$l       & 0xff];

                    $r^= ' . $p[$i - 1] . ';
                    $l^= ($sb_0[$r >> 24 & 0xff]  +
                          $sb_1[$r >> 16 & 0xff]  ^
                          $sb_2[$r >>  8 & 0xff]) +
                          $sb_3[$r       & 0xff];
                ';
            }

            $decrypt_block.= '
                $in = pack("N*",
                    $r ^ ' . $p[0] . ',
                    $l ^ ' . $p[1] . '
                );
            ';

            $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
                array(
                   'init_crypt'    => $init_crypt,
                   'init_encrypt'  => '',
                   'init_decrypt'  => '',
                   'encrypt_block' => $encrypt_block,
                   'decrypt_block' => $decrypt_block
                )
            );
        }
        $this->inline_crypt = $lambda_functions[$code_hash];
    }
}
<?php

/**
 * Pure-PHP implementation of DES.
 *
 * Uses mcrypt, if available, and an internal implementation, otherwise.
 *
 * PHP version 5
 *
 * Useful resources are as follows:
 *
 *  - {@link http://en.wikipedia.org/wiki/DES_supplementary_material Wikipedia: DES supplementary material}
 *  - {@link http://www.itl.nist.gov/fipspubs/fip46-2.htm FIPS 46-2 - (DES), Data Encryption Standard}
 *  - {@link http://www.cs.eku.edu/faculty/styer/460/Encrypt/JS-DES.html JavaScript DES Example}
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $des = new \phpseclib\Crypt\DES();
 *
 *    $des->setKey('abcdefgh');
 *
 *    $size = 10 * 1024;
 *    $plaintext = '';
 *    for ($i = 0; $i < $size; $i++) {
 *        $plaintext.= 'a';
 *    }
 *
 *    echo $des->decrypt($des->encrypt($plaintext));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   DES
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP implementation of DES.
 *
 * @package DES
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class DES extends Base
{
    /**#@+
     * @access private
     * @see \phpseclib\Crypt\DES::_setupKey()
     * @see \phpseclib\Crypt\DES::_processBlock()
     */
    /**
     * Contains $keys[self::ENCRYPT]
     */
    const ENCRYPT = 0;
    /**
     * Contains $keys[self::DECRYPT]
     */
    const DECRYPT = 1;
    /**#@-*/

    /**
     * Block Length of the cipher
     *
     * @see \phpseclib\Crypt\Base::block_size
     * @var int
     * @access private
     */
    var $block_size = 8;

    /**
     * Key Length (in bytes)
     *
     * @see \phpseclib\Crypt\Base::setKeyLength()
     * @var int
     * @access private
     */
    var $key_length = 8;

    /**
     * The mcrypt specific name of the cipher
     *
     * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
     * @var string
     * @access private
     */
    var $cipher_name_mcrypt = 'des';

    /**
     * The OpenSSL names of the cipher / modes
     *
     * @see \phpseclib\Crypt\Base::openssl_mode_names
     * @var array
     * @access private
     */
    var $openssl_mode_names = array(
        self::MODE_ECB => 'des-ecb',
        self::MODE_CBC => 'des-cbc',
        self::MODE_CFB => 'des-cfb',
        self::MODE_OFB => 'des-ofb'
        // self::MODE_CTR is undefined for DES
    );

    /**
     * Optimizing value while CFB-encrypting
     *
     * @see \phpseclib\Crypt\Base::cfb_init_len
     * @var int
     * @access private
     */
    var $cfb_init_len = 500;

    /**
     * Switch for DES/3DES encryption
     *
     * Used only if $engine == self::ENGINE_INTERNAL
     *
     * @see self::_setupKey()
     * @see self::_processBlock()
     * @var int
     * @access private
     */
    var $des_rounds = 1;

    /**
     * max possible size of $key
     *
     * @see self::setKey()
     * @var string
     * @access private
     */
    var $key_length_max = 8;

    /**
     * The Key Schedule
     *
     * @see self::_setupKey()
     * @var array
     * @access private
     */
    var $keys;

    /**
     * Shuffle table.
     *
     * For each byte value index, the entry holds an 8-byte string
     * with each byte containing all bits in the same state as the
     * corresponding bit in the index value.
     *
     * @see self::_processBlock()
     * @see self::_setupKey()
     * @var array
     * @access private
     */
    var $shuffle = array(
        "\x00\x00\x00\x00\x00\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\xFF",
        "\x00\x00\x00\x00\x00\x00\xFF\x00", "\x00\x00\x00\x00\x00\x00\xFF\xFF",
        "\x00\x00\x00\x00\x00\xFF\x00\x00", "\x00\x00\x00\x00\x00\xFF\x00\xFF",
        "\x00\x00\x00\x00\x00\xFF\xFF\x00", "\x00\x00\x00\x00\x00\xFF\xFF\xFF",
        "\x00\x00\x00\x00\xFF\x00\x00\x00", "\x00\x00\x00\x00\xFF\x00\x00\xFF",
        "\x00\x00\x00\x00\xFF\x00\xFF\x00", "\x00\x00\x00\x00\xFF\x00\xFF\xFF",
        "\x00\x00\x00\x00\xFF\xFF\x00\x00", "\x00\x00\x00\x00\xFF\xFF\x00\xFF",
        "\x00\x00\x00\x00\xFF\xFF\xFF\x00", "\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
        "\x00\x00\x00\xFF\x00\x00\x00\x00", "\x00\x00\x00\xFF\x00\x00\x00\xFF",
        "\x00\x00\x00\xFF\x00\x00\xFF\x00", "\x00\x00\x00\xFF\x00\x00\xFF\xFF",
        "\x00\x00\x00\xFF\x00\xFF\x00\x00", "\x00\x00\x00\xFF\x00\xFF\x00\xFF",
        "\x00\x00\x00\xFF\x00\xFF\xFF\x00", "\x00\x00\x00\xFF\x00\xFF\xFF\xFF",
        "\x00\x00\x00\xFF\xFF\x00\x00\x00", "\x00\x00\x00\xFF\xFF\x00\x00\xFF",
        "\x00\x00\x00\xFF\xFF\x00\xFF\x00", "\x00\x00\x00\xFF\xFF\x00\xFF\xFF",
        "\x00\x00\x00\xFF\xFF\xFF\x00\x00", "\x00\x00\x00\xFF\xFF\xFF\x00\xFF",
        "\x00\x00\x00\xFF\xFF\xFF\xFF\x00", "\x00\x00\x00\xFF\xFF\xFF\xFF\xFF",
        "\x00\x00\xFF\x00\x00\x00\x00\x00", "\x00\x00\xFF\x00\x00\x00\x00\xFF",
        "\x00\x00\xFF\x00\x00\x00\xFF\x00", "\x00\x00\xFF\x00\x00\x00\xFF\xFF",
        "\x00\x00\xFF\x00\x00\xFF\x00\x00", "\x00\x00\xFF\x00\x00\xFF\x00\xFF",
        "\x00\x00\xFF\x00\x00\xFF\xFF\x00", "\x00\x00\xFF\x00\x00\xFF\xFF\xFF",
        "\x00\x00\xFF\x00\xFF\x00\x00\x00", "\x00\x00\xFF\x00\xFF\x00\x00\xFF",
        "\x00\x00\xFF\x00\xFF\x00\xFF\x00", "\x00\x00\xFF\x00\xFF\x00\xFF\xFF",
        "\x00\x00\xFF\x00\xFF\xFF\x00\x00", "\x00\x00\xFF\x00\xFF\xFF\x00\xFF",
        "\x00\x00\xFF\x00\xFF\xFF\xFF\x00", "\x00\x00\xFF\x00\xFF\xFF\xFF\xFF",
        "\x00\x00\xFF\xFF\x00\x00\x00\x00", "\x00\x00\xFF\xFF\x00\x00\x00\xFF",
        "\x00\x00\xFF\xFF\x00\x00\xFF\x00", "\x00\x00\xFF\xFF\x00\x00\xFF\xFF",
        "\x00\x00\xFF\xFF\x00\xFF\x00\x00", "\x00\x00\xFF\xFF\x00\xFF\x00\xFF",
        "\x00\x00\xFF\xFF\x00\xFF\xFF\x00", "\x00\x00\xFF\xFF\x00\xFF\xFF\xFF",
        "\x00\x00\xFF\xFF\xFF\x00\x00\x00", "\x00\x00\xFF\xFF\xFF\x00\x00\xFF",
        "\x00\x00\xFF\xFF\xFF\x00\xFF\x00", "\x00\x00\xFF\xFF\xFF\x00\xFF\xFF",
        "\x00\x00\xFF\xFF\xFF\xFF\x00\x00", "\x00\x00\xFF\xFF\xFF\xFF\x00\xFF",
        "\x00\x00\xFF\xFF\xFF\xFF\xFF\x00", "\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF",
        "\x00\xFF\x00\x00\x00\x00\x00\x00", "\x00\xFF\x00\x00\x00\x00\x00\xFF",
        "\x00\xFF\x00\x00\x00\x00\xFF\x00", "\x00\xFF\x00\x00\x00\x00\xFF\xFF",
        "\x00\xFF\x00\x00\x00\xFF\x00\x00", "\x00\xFF\x00\x00\x00\xFF\x00\xFF",
        "\x00\xFF\x00\x00\x00\xFF\xFF\x00", "\x00\xFF\x00\x00\x00\xFF\xFF\xFF",
        "\x00\xFF\x00\x00\xFF\x00\x00\x00", "\x00\xFF\x00\x00\xFF\x00\x00\xFF",
        "\x00\xFF\x00\x00\xFF\x00\xFF\x00", "\x00\xFF\x00\x00\xFF\x00\xFF\xFF",
        "\x00\xFF\x00\x00\xFF\xFF\x00\x00", "\x00\xFF\x00\x00\xFF\xFF\x00\xFF",
        "\x00\xFF\x00\x00\xFF\xFF\xFF\x00", "\x00\xFF\x00\x00\xFF\xFF\xFF\xFF",
        "\x00\xFF\x00\xFF\x00\x00\x00\x00", "\x00\xFF\x00\xFF\x00\x00\x00\xFF",
        "\x00\xFF\x00\xFF\x00\x00\xFF\x00", "\x00\xFF\x00\xFF\x00\x00\xFF\xFF",
        "\x00\xFF\x00\xFF\x00\xFF\x00\x00", "\x00\xFF\x00\xFF\x00\xFF\x00\xFF",
        "\x00\xFF\x00\xFF\x00\xFF\xFF\x00", "\x00\xFF\x00\xFF\x00\xFF\xFF\xFF",
        "\x00\xFF\x00\xFF\xFF\x00\x00\x00", "\x00\xFF\x00\xFF\xFF\x00\x00\xFF",
        "\x00\xFF\x00\xFF\xFF\x00\xFF\x00", "\x00\xFF\x00\xFF\xFF\x00\xFF\xFF",
        "\x00\xFF\x00\xFF\xFF\xFF\x00\x00", "\x00\xFF\x00\xFF\xFF\xFF\x00\xFF",
        "\x00\xFF\x00\xFF\xFF\xFF\xFF\x00", "\x00\xFF\x00\xFF\xFF\xFF\xFF\xFF",
        "\x00\xFF\xFF\x00\x00\x00\x00\x00", "\x00\xFF\xFF\x00\x00\x00\x00\xFF",
        "\x00\xFF\xFF\x00\x00\x00\xFF\x00", "\x00\xFF\xFF\x00\x00\x00\xFF\xFF",
        "\x00\xFF\xFF\x00\x00\xFF\x00\x00", "\x00\xFF\xFF\x00\x00\xFF\x00\xFF",
        "\x00\xFF\xFF\x00\x00\xFF\xFF\x00", "\x00\xFF\xFF\x00\x00\xFF\xFF\xFF",
        "\x00\xFF\xFF\x00\xFF\x00\x00\x00", "\x00\xFF\xFF\x00\xFF\x00\x00\xFF",
        "\x00\xFF\xFF\x00\xFF\x00\xFF\x00", "\x00\xFF\xFF\x00\xFF\x00\xFF\xFF",
        "\x00\xFF\xFF\x00\xFF\xFF\x00\x00", "\x00\xFF\xFF\x00\xFF\xFF\x00\xFF",
        "\x00\xFF\xFF\x00\xFF\xFF\xFF\x00", "\x00\xFF\xFF\x00\xFF\xFF\xFF\xFF",
        "\x00\xFF\xFF\xFF\x00\x00\x00\x00", "\x00\xFF\xFF\xFF\x00\x00\x00\xFF",
        "\x00\xFF\xFF\xFF\x00\x00\xFF\x00", "\x00\xFF\xFF\xFF\x00\x00\xFF\xFF",
        "\x00\xFF\xFF\xFF\x00\xFF\x00\x00", "\x00\xFF\xFF\xFF\x00\xFF\x00\xFF",
        "\x00\xFF\xFF\xFF\x00\xFF\xFF\x00", "\x00\xFF\xFF\xFF\x00\xFF\xFF\xFF",
        "\x00\xFF\xFF\xFF\xFF\x00\x00\x00", "\x00\xFF\xFF\xFF\xFF\x00\x00\xFF",
        "\x00\xFF\xFF\xFF\xFF\x00\xFF\x00", "\x00\xFF\xFF\xFF\xFF\x00\xFF\xFF",
        "\x00\xFF\xFF\xFF\xFF\xFF\x00\x00", "\x00\xFF\xFF\xFF\xFF\xFF\x00\xFF",
        "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
        "\xFF\x00\x00\x00\x00\x00\x00\x00", "\xFF\x00\x00\x00\x00\x00\x00\xFF",
        "\xFF\x00\x00\x00\x00\x00\xFF\x00", "\xFF\x00\x00\x00\x00\x00\xFF\xFF",
        "\xFF\x00\x00\x00\x00\xFF\x00\x00", "\xFF\x00\x00\x00\x00\xFF\x00\xFF",
        "\xFF\x00\x00\x00\x00\xFF\xFF\x00", "\xFF\x00\x00\x00\x00\xFF\xFF\xFF",
        "\xFF\x00\x00\x00\xFF\x00\x00\x00", "\xFF\x00\x00\x00\xFF\x00\x00\xFF",
        "\xFF\x00\x00\x00\xFF\x00\xFF\x00", "\xFF\x00\x00\x00\xFF\x00\xFF\xFF",
        "\xFF\x00\x00\x00\xFF\xFF\x00\x00", "\xFF\x00\x00\x00\xFF\xFF\x00\xFF",
        "\xFF\x00\x00\x00\xFF\xFF\xFF\x00", "\xFF\x00\x00\x00\xFF\xFF\xFF\xFF",
        "\xFF\x00\x00\xFF\x00\x00\x00\x00", "\xFF\x00\x00\xFF\x00\x00\x00\xFF",
        "\xFF\x00\x00\xFF\x00\x00\xFF\x00", "\xFF\x00\x00\xFF\x00\x00\xFF\xFF",
        "\xFF\x00\x00\xFF\x00\xFF\x00\x00", "\xFF\x00\x00\xFF\x00\xFF\x00\xFF",
        "\xFF\x00\x00\xFF\x00\xFF\xFF\x00", "\xFF\x00\x00\xFF\x00\xFF\xFF\xFF",
        "\xFF\x00\x00\xFF\xFF\x00\x00\x00", "\xFF\x00\x00\xFF\xFF\x00\x00\xFF",
        "\xFF\x00\x00\xFF\xFF\x00\xFF\x00", "\xFF\x00\x00\xFF\xFF\x00\xFF\xFF",
        "\xFF\x00\x00\xFF\xFF\xFF\x00\x00", "\xFF\x00\x00\xFF\xFF\xFF\x00\xFF",
        "\xFF\x00\x00\xFF\xFF\xFF\xFF\x00", "\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF",
        "\xFF\x00\xFF\x00\x00\x00\x00\x00", "\xFF\x00\xFF\x00\x00\x00\x00\xFF",
        "\xFF\x00\xFF\x00\x00\x00\xFF\x00", "\xFF\x00\xFF\x00\x00\x00\xFF\xFF",
        "\xFF\x00\xFF\x00\x00\xFF\x00\x00", "\xFF\x00\xFF\x00\x00\xFF\x00\xFF",
        "\xFF\x00\xFF\x00\x00\xFF\xFF\x00", "\xFF\x00\xFF\x00\x00\xFF\xFF\xFF",
        "\xFF\x00\xFF\x00\xFF\x00\x00\x00", "\xFF\x00\xFF\x00\xFF\x00\x00\xFF",
        "\xFF\x00\xFF\x00\xFF\x00\xFF\x00", "\xFF\x00\xFF\x00\xFF\x00\xFF\xFF",
        "\xFF\x00\xFF\x00\xFF\xFF\x00\x00", "\xFF\x00\xFF\x00\xFF\xFF\x00\xFF",
        "\xFF\x00\xFF\x00\xFF\xFF\xFF\x00", "\xFF\x00\xFF\x00\xFF\xFF\xFF\xFF",
        "\xFF\x00\xFF\xFF\x00\x00\x00\x00", "\xFF\x00\xFF\xFF\x00\x00\x00\xFF",
        "\xFF\x00\xFF\xFF\x00\x00\xFF\x00", "\xFF\x00\xFF\xFF\x00\x00\xFF\xFF",
        "\xFF\x00\xFF\xFF\x00\xFF\x00\x00", "\xFF\x00\xFF\xFF\x00\xFF\x00\xFF",
        "\xFF\x00\xFF\xFF\x00\xFF\xFF\x00", "\xFF\x00\xFF\xFF\x00\xFF\xFF\xFF",
        "\xFF\x00\xFF\xFF\xFF\x00\x00\x00", "\xFF\x00\xFF\xFF\xFF\x00\x00\xFF",
        "\xFF\x00\xFF\xFF\xFF\x00\xFF\x00", "\xFF\x00\xFF\xFF\xFF\x00\xFF\xFF",
        "\xFF\x00\xFF\xFF\xFF\xFF\x00\x00", "\xFF\x00\xFF\xFF\xFF\xFF\x00\xFF",
        "\xFF\x00\xFF\xFF\xFF\xFF\xFF\x00", "\xFF\x00\xFF\xFF\xFF\xFF\xFF\xFF",
        "\xFF\xFF\x00\x00\x00\x00\x00\x00", "\xFF\xFF\x00\x00\x00\x00\x00\xFF",
        "\xFF\xFF\x00\x00\x00\x00\xFF\x00", "\xFF\xFF\x00\x00\x00\x00\xFF\xFF",
        "\xFF\xFF\x00\x00\x00\xFF\x00\x00", "\xFF\xFF\x00\x00\x00\xFF\x00\xFF",
        "\xFF\xFF\x00\x00\x00\xFF\xFF\x00", "\xFF\xFF\x00\x00\x00\xFF\xFF\xFF",
        "\xFF\xFF\x00\x00\xFF\x00\x00\x00", "\xFF\xFF\x00\x00\xFF\x00\x00\xFF",
        "\xFF\xFF\x00\x00\xFF\x00\xFF\x00", "\xFF\xFF\x00\x00\xFF\x00\xFF\xFF",
        "\xFF\xFF\x00\x00\xFF\xFF\x00\x00", "\xFF\xFF\x00\x00\xFF\xFF\x00\xFF",
        "\xFF\xFF\x00\x00\xFF\xFF\xFF\x00", "\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF",
        "\xFF\xFF\x00\xFF\x00\x00\x00\x00", "\xFF\xFF\x00\xFF\x00\x00\x00\xFF",
        "\xFF\xFF\x00\xFF\x00\x00\xFF\x00", "\xFF\xFF\x00\xFF\x00\x00\xFF\xFF",
        "\xFF\xFF\x00\xFF\x00\xFF\x00\x00", "\xFF\xFF\x00\xFF\x00\xFF\x00\xFF",
        "\xFF\xFF\x00\xFF\x00\xFF\xFF\x00", "\xFF\xFF\x00\xFF\x00\xFF\xFF\xFF",
        "\xFF\xFF\x00\xFF\xFF\x00\x00\x00", "\xFF\xFF\x00\xFF\xFF\x00\x00\xFF",
        "\xFF\xFF\x00\xFF\xFF\x00\xFF\x00", "\xFF\xFF\x00\xFF\xFF\x00\xFF\xFF",
        "\xFF\xFF\x00\xFF\xFF\xFF\x00\x00", "\xFF\xFF\x00\xFF\xFF\xFF\x00\xFF",
        "\xFF\xFF\x00\xFF\xFF\xFF\xFF\x00", "\xFF\xFF\x00\xFF\xFF\xFF\xFF\xFF",
        "\xFF\xFF\xFF\x00\x00\x00\x00\x00", "\xFF\xFF\xFF\x00\x00\x00\x00\xFF",
        "\xFF\xFF\xFF\x00\x00\x00\xFF\x00", "\xFF\xFF\xFF\x00\x00\x00\xFF\xFF",
        "\xFF\xFF\xFF\x00\x00\xFF\x00\x00", "\xFF\xFF\xFF\x00\x00\xFF\x00\xFF",
        "\xFF\xFF\xFF\x00\x00\xFF\xFF\x00", "\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF",
        "\xFF\xFF\xFF\x00\xFF\x00\x00\x00", "\xFF\xFF\xFF\x00\xFF\x00\x00\xFF",
        "\xFF\xFF\xFF\x00\xFF\x00\xFF\x00", "\xFF\xFF\xFF\x00\xFF\x00\xFF\xFF",
        "\xFF\xFF\xFF\x00\xFF\xFF\x00\x00", "\xFF\xFF\xFF\x00\xFF\xFF\x00\xFF",
        "\xFF\xFF\xFF\x00\xFF\xFF\xFF\x00", "\xFF\xFF\xFF\x00\xFF\xFF\xFF\xFF",
        "\xFF\xFF\xFF\xFF\x00\x00\x00\x00", "\xFF\xFF\xFF\xFF\x00\x00\x00\xFF",
        "\xFF\xFF\xFF\xFF\x00\x00\xFF\x00", "\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF",
        "\xFF\xFF\xFF\xFF\x00\xFF\x00\x00", "\xFF\xFF\xFF\xFF\x00\xFF\x00\xFF",
        "\xFF\xFF\xFF\xFF\x00\xFF\xFF\x00", "\xFF\xFF\xFF\xFF\x00\xFF\xFF\xFF",
        "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00", "\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF",
        "\xFF\xFF\xFF\xFF\xFF\x00\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF",
        "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF",
        "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
    );

    /**
     * IP mapping helper table.
     *
     * Indexing this table with each source byte performs the initial bit permutation.
     *
     * @var array
     * @access private
     */
    var $ipmap = array(
        0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x21, 0x31,
        0x02, 0x12, 0x03, 0x13, 0x22, 0x32, 0x23, 0x33,
        0x40, 0x50, 0x41, 0x51, 0x60, 0x70, 0x61, 0x71,
        0x42, 0x52, 0x43, 0x53, 0x62, 0x72, 0x63, 0x73,
        0x04, 0x14, 0x05, 0x15, 0x24, 0x34, 0x25, 0x35,
        0x06, 0x16, 0x07, 0x17, 0x26, 0x36, 0x27, 0x37,
        0x44, 0x54, 0x45, 0x55, 0x64, 0x74, 0x65, 0x75,
        0x46, 0x56, 0x47, 0x57, 0x66, 0x76, 0x67, 0x77,
        0x80, 0x90, 0x81, 0x91, 0xA0, 0xB0, 0xA1, 0xB1,
        0x82, 0x92, 0x83, 0x93, 0xA2, 0xB2, 0xA3, 0xB3,
        0xC0, 0xD0, 0xC1, 0xD1, 0xE0, 0xF0, 0xE1, 0xF1,
        0xC2, 0xD2, 0xC3, 0xD3, 0xE2, 0xF2, 0xE3, 0xF3,
        0x84, 0x94, 0x85, 0x95, 0xA4, 0xB4, 0xA5, 0xB5,
        0x86, 0x96, 0x87, 0x97, 0xA6, 0xB6, 0xA7, 0xB7,
        0xC4, 0xD4, 0xC5, 0xD5, 0xE4, 0xF4, 0xE5, 0xF5,
        0xC6, 0xD6, 0xC7, 0xD7, 0xE6, 0xF6, 0xE7, 0xF7,
        0x08, 0x18, 0x09, 0x19, 0x28, 0x38, 0x29, 0x39,
        0x0A, 0x1A, 0x0B, 0x1B, 0x2A, 0x3A, 0x2B, 0x3B,
        0x48, 0x58, 0x49, 0x59, 0x68, 0x78, 0x69, 0x79,
        0x4A, 0x5A, 0x4B, 0x5B, 0x6A, 0x7A, 0x6B, 0x7B,
        0x0C, 0x1C, 0x0D, 0x1D, 0x2C, 0x3C, 0x2D, 0x3D,
        0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F,
        0x4C, 0x5C, 0x4D, 0x5D, 0x6C, 0x7C, 0x6D, 0x7D,
        0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F,
        0x88, 0x98, 0x89, 0x99, 0xA8, 0xB8, 0xA9, 0xB9,
        0x8A, 0x9A, 0x8B, 0x9B, 0xAA, 0xBA, 0xAB, 0xBB,
        0xC8, 0xD8, 0xC9, 0xD9, 0xE8, 0xF8, 0xE9, 0xF9,
        0xCA, 0xDA, 0xCB, 0xDB, 0xEA, 0xFA, 0xEB, 0xFB,
        0x8C, 0x9C, 0x8D, 0x9D, 0xAC, 0xBC, 0xAD, 0xBD,
        0x8E, 0x9E, 0x8F, 0x9F, 0xAE, 0xBE, 0xAF, 0xBF,
        0xCC, 0xDC, 0xCD, 0xDD, 0xEC, 0xFC, 0xED, 0xFD,
        0xCE, 0xDE, 0xCF, 0xDF, 0xEE, 0xFE, 0xEF, 0xFF
    );

    /**
     * Inverse IP mapping helper table.
     * Indexing this table with a byte value reverses the bit order.
     *
     * @var array
     * @access private
     */
    var $invipmap = array(
        0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
        0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
        0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
        0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
        0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
        0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
        0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
        0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
        0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
        0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
        0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
        0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
        0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
        0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
        0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
        0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
        0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
        0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
        0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
        0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
        0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
        0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
        0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
        0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
        0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
        0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
        0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
        0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
        0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
        0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
        0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
        0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
    );

    /**
     * Pre-permuted S-box1
     *
     * Each box ($sbox1-$sbox8) has been vectorized, then each value pre-permuted using the
     * P table: concatenation can then be replaced by exclusive ORs.
     *
     * @var array
     * @access private
     */
    var $sbox1 = array(
        0x00808200, 0x00000000, 0x00008000, 0x00808202,
        0x00808002, 0x00008202, 0x00000002, 0x00008000,
        0x00000200, 0x00808200, 0x00808202, 0x00000200,
        0x00800202, 0x00808002, 0x00800000, 0x00000002,
        0x00000202, 0x00800200, 0x00800200, 0x00008200,
        0x00008200, 0x00808000, 0x00808000, 0x00800202,
        0x00008002, 0x00800002, 0x00800002, 0x00008002,
        0x00000000, 0x00000202, 0x00008202, 0x00800000,
        0x00008000, 0x00808202, 0x00000002, 0x00808000,
        0x00808200, 0x00800000, 0x00800000, 0x00000200,
        0x00808002, 0x00008000, 0x00008200, 0x00800002,
        0x00000200, 0x00000002, 0x00800202, 0x00008202,
        0x00808202, 0x00008002, 0x00808000, 0x00800202,
        0x00800002, 0x00000202, 0x00008202, 0x00808200,
        0x00000202, 0x00800200, 0x00800200, 0x00000000,
        0x00008002, 0x00008200, 0x00000000, 0x00808002
    );

    /**
     * Pre-permuted S-box2
     *
     * @var array
     * @access private
     */
    var $sbox2 = array(
        0x40084010, 0x40004000, 0x00004000, 0x00084010,
        0x00080000, 0x00000010, 0x40080010, 0x40004010,
        0x40000010, 0x40084010, 0x40084000, 0x40000000,
        0x40004000, 0x00080000, 0x00000010, 0x40080010,
        0x00084000, 0x00080010, 0x40004010, 0x00000000,
        0x40000000, 0x00004000, 0x00084010, 0x40080000,
        0x00080010, 0x40000010, 0x00000000, 0x00084000,
        0x00004010, 0x40084000, 0x40080000, 0x00004010,
        0x00000000, 0x00084010, 0x40080010, 0x00080000,
        0x40004010, 0x40080000, 0x40084000, 0x00004000,
        0x40080000, 0x40004000, 0x00000010, 0x40084010,
        0x00084010, 0x00000010, 0x00004000, 0x40000000,
        0x00004010, 0x40084000, 0x00080000, 0x40000010,
        0x00080010, 0x40004010, 0x40000010, 0x00080010,
        0x00084000, 0x00000000, 0x40004000, 0x00004010,
        0x40000000, 0x40080010, 0x40084010, 0x00084000
    );

    /**
     * Pre-permuted S-box3
     *
     * @var array
     * @access private
     */
    var $sbox3 = array(
        0x00000104, 0x04010100, 0x00000000, 0x04010004,
        0x04000100, 0x00000000, 0x00010104, 0x04000100,
        0x00010004, 0x04000004, 0x04000004, 0x00010000,
        0x04010104, 0x00010004, 0x04010000, 0x00000104,
        0x04000000, 0x00000004, 0x04010100, 0x00000100,
        0x00010100, 0x04010000, 0x04010004, 0x00010104,
        0x04000104, 0x00010100, 0x00010000, 0x04000104,
        0x00000004, 0x04010104, 0x00000100, 0x04000000,
        0x04010100, 0x04000000, 0x00010004, 0x00000104,
        0x00010000, 0x04010100, 0x04000100, 0x00000000,
        0x00000100, 0x00010004, 0x04010104, 0x04000100,
        0x04000004, 0x00000100, 0x00000000, 0x04010004,
        0x04000104, 0x00010000, 0x04000000, 0x04010104,
        0x00000004, 0x00010104, 0x00010100, 0x04000004,
        0x04010000, 0x04000104, 0x00000104, 0x04010000,
        0x00010104, 0x00000004, 0x04010004, 0x00010100
    );

    /**
     * Pre-permuted S-box4
     *
     * @var array
     * @access private
     */
    var $sbox4 = array(
        0x80401000, 0x80001040, 0x80001040, 0x00000040,
        0x00401040, 0x80400040, 0x80400000, 0x80001000,
        0x00000000, 0x00401000, 0x00401000, 0x80401040,
        0x80000040, 0x00000000, 0x00400040, 0x80400000,
        0x80000000, 0x00001000, 0x00400000, 0x80401000,
        0x00000040, 0x00400000, 0x80001000, 0x00001040,
        0x80400040, 0x80000000, 0x00001040, 0x00400040,
        0x00001000, 0x00401040, 0x80401040, 0x80000040,
        0x00400040, 0x80400000, 0x00401000, 0x80401040,
        0x80000040, 0x00000000, 0x00000000, 0x00401000,
        0x00001040, 0x00400040, 0x80400040, 0x80000000,
        0x80401000, 0x80001040, 0x80001040, 0x00000040,
        0x80401040, 0x80000040, 0x80000000, 0x00001000,
        0x80400000, 0x80001000, 0x00401040, 0x80400040,
        0x80001000, 0x00001040, 0x00400000, 0x80401000,
        0x00000040, 0x00400000, 0x00001000, 0x00401040
    );

    /**
     * Pre-permuted S-box5
     *
     * @var array
     * @access private
     */
    var $sbox5 = array(
        0x00000080, 0x01040080, 0x01040000, 0x21000080,
        0x00040000, 0x00000080, 0x20000000, 0x01040000,
        0x20040080, 0x00040000, 0x01000080, 0x20040080,
        0x21000080, 0x21040000, 0x00040080, 0x20000000,
        0x01000000, 0x20040000, 0x20040000, 0x00000000,
        0x20000080, 0x21040080, 0x21040080, 0x01000080,
        0x21040000, 0x20000080, 0x00000000, 0x21000000,
        0x01040080, 0x01000000, 0x21000000, 0x00040080,
        0x00040000, 0x21000080, 0x00000080, 0x01000000,
        0x20000000, 0x01040000, 0x21000080, 0x20040080,
        0x01000080, 0x20000000, 0x21040000, 0x01040080,
        0x20040080, 0x00000080, 0x01000000, 0x21040000,
        0x21040080, 0x00040080, 0x21000000, 0x21040080,
        0x01040000, 0x00000000, 0x20040000, 0x21000000,
        0x00040080, 0x01000080, 0x20000080, 0x00040000,
        0x00000000, 0x20040000, 0x01040080, 0x20000080
    );

    /**
     * Pre-permuted S-box6
     *
     * @var array
     * @access private
     */
    var $sbox6 = array(
        0x10000008, 0x10200000, 0x00002000, 0x10202008,
        0x10200000, 0x00000008, 0x10202008, 0x00200000,
        0x10002000, 0x00202008, 0x00200000, 0x10000008,
        0x00200008, 0x10002000, 0x10000000, 0x00002008,
        0x00000000, 0x00200008, 0x10002008, 0x00002000,
        0x00202000, 0x10002008, 0x00000008, 0x10200008,
        0x10200008, 0x00000000, 0x00202008, 0x10202000,
        0x00002008, 0x00202000, 0x10202000, 0x10000000,
        0x10002000, 0x00000008, 0x10200008, 0x00202000,
        0x10202008, 0x00200000, 0x00002008, 0x10000008,
        0x00200000, 0x10002000, 0x10000000, 0x00002008,
        0x10000008, 0x10202008, 0x00202000, 0x10200000,
        0x00202008, 0x10202000, 0x00000000, 0x10200008,
        0x00000008, 0x00002000, 0x10200000, 0x00202008,
        0x00002000, 0x00200008, 0x10002008, 0x00000000,
        0x10202000, 0x10000000, 0x00200008, 0x10002008
    );

    /**
     * Pre-permuted S-box7
     *
     * @var array
     * @access private
     */
    var $sbox7 = array(
        0x00100000, 0x02100001, 0x02000401, 0x00000000,
        0x00000400, 0x02000401, 0x00100401, 0x02100400,
        0x02100401, 0x00100000, 0x00000000, 0x02000001,
        0x00000001, 0x02000000, 0x02100001, 0x00000401,
        0x02000400, 0x00100401, 0x00100001, 0x02000400,
        0x02000001, 0x02100000, 0x02100400, 0x00100001,
        0x02100000, 0x00000400, 0x00000401, 0x02100401,
        0x00100400, 0x00000001, 0x02000000, 0x00100400,
        0x02000000, 0x00100400, 0x00100000, 0x02000401,
        0x02000401, 0x02100001, 0x02100001, 0x00000001,
        0x00100001, 0x02000000, 0x02000400, 0x00100000,
        0x02100400, 0x00000401, 0x00100401, 0x02100400,
        0x00000401, 0x02000001, 0x02100401, 0x02100000,
        0x00100400, 0x00000000, 0x00000001, 0x02100401,
        0x00000000, 0x00100401, 0x02100000, 0x00000400,
        0x02000001, 0x02000400, 0x00000400, 0x00100001
    );

    /**
     * Pre-permuted S-box8
     *
     * @var array
     * @access private
     */
    var $sbox8 = array(
        0x08000820, 0x00000800, 0x00020000, 0x08020820,
        0x08000000, 0x08000820, 0x00000020, 0x08000000,
        0x00020020, 0x08020000, 0x08020820, 0x00020800,
        0x08020800, 0x00020820, 0x00000800, 0x00000020,
        0x08020000, 0x08000020, 0x08000800, 0x00000820,
        0x00020800, 0x00020020, 0x08020020, 0x08020800,
        0x00000820, 0x00000000, 0x00000000, 0x08020020,
        0x08000020, 0x08000800, 0x00020820, 0x00020000,
        0x00020820, 0x00020000, 0x08020800, 0x00000800,
        0x00000020, 0x08020020, 0x00000800, 0x00020820,
        0x08000800, 0x00000020, 0x08000020, 0x08020000,
        0x08020020, 0x08000000, 0x00020000, 0x08000820,
        0x00000000, 0x08020820, 0x00020020, 0x08000020,
        0x08020000, 0x08000800, 0x08000820, 0x00000000,
        0x08020820, 0x00020800, 0x00020800, 0x00000820,
        0x00000820, 0x00020020, 0x08000000, 0x08020800
    );

    /**
     * Test for engine validity
     *
     * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
     *
     * @see \phpseclib\Crypt\Base::isValidEngine()
     * @param int $engine
     * @access public
     * @return bool
     */
    function isValidEngine($engine)
    {
        if ($this->key_length_max == 8) {
            if ($engine == self::ENGINE_OPENSSL) {
                $this->cipher_name_openssl_ecb = 'des-ecb';
                $this->cipher_name_openssl = 'des-' . $this->_openssl_translate_mode();
            }
        }

        return parent::isValidEngine($engine);
    }

    /**
     * Sets the key.
     *
     * Keys can be of any length.  DES, itself, uses 64-bit keys (eg. strlen($key) == 8), however, we
     * only use the first eight, if $key has more then eight characters in it, and pad $key with the
     * null byte if it is less then eight characters long.
     *
     * DES also requires that every eighth bit be a parity bit, however, we'll ignore that.
     *
     * If the key is not explicitly set, it'll be assumed to be all zero's.
     *
     * @see \phpseclib\Crypt\Base::setKey()
     * @access public
     * @param string $key
     */
    function setKey($key)
    {
        // We check/cut here only up to max length of the key.
        // Key padding to the proper length will be done in _setupKey()
        if (strlen($key) > $this->key_length_max) {
            $key = substr($key, 0, $this->key_length_max);
        }

        // Sets the key
        parent::setKey($key);
    }

    /**
     * Encrypts a block
     *
     * @see \phpseclib\Crypt\Base::_encryptBlock()
     * @see \phpseclib\Crypt\Base::encrypt()
     * @see self::encrypt()
     * @access private
     * @param string $in
     * @return string
     */
    function _encryptBlock($in)
    {
        return $this->_processBlock($in, self::ENCRYPT);
    }

    /**
     * Decrypts a block
     *
     * @see \phpseclib\Crypt\Base::_decryptBlock()
     * @see \phpseclib\Crypt\Base::decrypt()
     * @see self::decrypt()
     * @access private
     * @param string $in
     * @return string
     */
    function _decryptBlock($in)
    {
        return $this->_processBlock($in, self::DECRYPT);
    }

    /**
     * Encrypts or decrypts a 64-bit block
     *
     * $mode should be either self::ENCRYPT or self::DECRYPT.  See
     * {@link http://en.wikipedia.org/wiki/Image:Feistel.png Feistel.png} to get a general
     * idea of what this function does.
     *
     * @see self::_encryptBlock()
     * @see self::_decryptBlock()
     * @access private
     * @param string $block
     * @param int $mode
     * @return string
     */
    function _processBlock($block, $mode)
    {
        static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip;
        if (!$sbox1) {
            $sbox1 = array_map("intval", $this->sbox1);
            $sbox2 = array_map("intval", $this->sbox2);
            $sbox3 = array_map("intval", $this->sbox3);
            $sbox4 = array_map("intval", $this->sbox4);
            $sbox5 = array_map("intval", $this->sbox5);
            $sbox6 = array_map("intval", $this->sbox6);
            $sbox7 = array_map("intval", $this->sbox7);
            $sbox8 = array_map("intval", $this->sbox8);
            /* Merge $shuffle with $[inv]ipmap */
            for ($i = 0; $i < 256; ++$i) {
                $shuffleip[]    =  $this->shuffle[$this->ipmap[$i]];
                $shuffleinvip[] =  $this->shuffle[$this->invipmap[$i]];
            }
        }

        $keys  = $this->keys[$mode];
        $ki    = -1;

        // Do the initial IP permutation.
        $t = unpack('Nl/Nr', $block);
        list($l, $r) = array($t['l'], $t['r']);
        $block = ($shuffleip[ $r        & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
                 ($shuffleip[($r >>  8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
                 ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
                 ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
                 ($shuffleip[ $l        & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
                 ($shuffleip[($l >>  8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
                 ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
                 ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01");

        // Extract L0 and R0.
        $t = unpack('Nl/Nr', $block);
        list($l, $r) = array($t['l'], $t['r']);

        for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) {
            // Perform the 16 steps.
            for ($i = 0; $i < 16; $i++) {
                // start of "the Feistel (F) function" - see the following URL:
                // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png
                // Merge key schedule.
                $b1 = (($r >>  3) & 0x1FFFFFFF) ^ ($r << 29) ^ $keys[++$ki];
                $b2 = (($r >> 31) & 0x00000001) ^ ($r <<  1) ^ $keys[++$ki];

                // S-box indexing.
                $t = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^
                     $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^
                     $sbox5[($b1 >>  8) & 0x3F] ^ $sbox6[($b2 >>  8) & 0x3F] ^
                     $sbox7[ $b1        & 0x3F] ^ $sbox8[ $b2        & 0x3F] ^ $l;
                // end of "the Feistel (F) function"

                $l = $r;
                $r = $t;
            }

            // Last step should not permute L & R.
            $t = $l;
            $l = $r;
            $r = $t;
        }

        // Perform the inverse IP permutation.
        return ($shuffleinvip[($r >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
               ($shuffleinvip[($l >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
               ($shuffleinvip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
               ($shuffleinvip[($l >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
               ($shuffleinvip[($r >>  8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
               ($shuffleinvip[($l >>  8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
               ($shuffleinvip[ $r        & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
               ($shuffleinvip[ $l        & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01");
    }

    /**
     * Creates the key schedule
     *
     * @see \phpseclib\Crypt\Base::_setupKey()
     * @access private
     */
    function _setupKey()
    {
        if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->des_rounds === $this->kl['des_rounds']) {
            // already expanded
            return;
        }
        $this->kl = array('key' => $this->key, 'des_rounds' => $this->des_rounds);

        static $shifts = array( // number of key bits shifted per round
            1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
        );

        static $pc1map = array(
            0x00, 0x00, 0x08, 0x08, 0x04, 0x04, 0x0C, 0x0C,
            0x02, 0x02, 0x0A, 0x0A, 0x06, 0x06, 0x0E, 0x0E,
            0x10, 0x10, 0x18, 0x18, 0x14, 0x14, 0x1C, 0x1C,
            0x12, 0x12, 0x1A, 0x1A, 0x16, 0x16, 0x1E, 0x1E,
            0x20, 0x20, 0x28, 0x28, 0x24, 0x24, 0x2C, 0x2C,
            0x22, 0x22, 0x2A, 0x2A, 0x26, 0x26, 0x2E, 0x2E,
            0x30, 0x30, 0x38, 0x38, 0x34, 0x34, 0x3C, 0x3C,
            0x32, 0x32, 0x3A, 0x3A, 0x36, 0x36, 0x3E, 0x3E,
            0x40, 0x40, 0x48, 0x48, 0x44, 0x44, 0x4C, 0x4C,
            0x42, 0x42, 0x4A, 0x4A, 0x46, 0x46, 0x4E, 0x4E,
            0x50, 0x50, 0x58, 0x58, 0x54, 0x54, 0x5C, 0x5C,
            0x52, 0x52, 0x5A, 0x5A, 0x56, 0x56, 0x5E, 0x5E,
            0x60, 0x60, 0x68, 0x68, 0x64, 0x64, 0x6C, 0x6C,
            0x62, 0x62, 0x6A, 0x6A, 0x66, 0x66, 0x6E, 0x6E,
            0x70, 0x70, 0x78, 0x78, 0x74, 0x74, 0x7C, 0x7C,
            0x72, 0x72, 0x7A, 0x7A, 0x76, 0x76, 0x7E, 0x7E,
            0x80, 0x80, 0x88, 0x88, 0x84, 0x84, 0x8C, 0x8C,
            0x82, 0x82, 0x8A, 0x8A, 0x86, 0x86, 0x8E, 0x8E,
            0x90, 0x90, 0x98, 0x98, 0x94, 0x94, 0x9C, 0x9C,
            0x92, 0x92, 0x9A, 0x9A, 0x96, 0x96, 0x9E, 0x9E,
            0xA0, 0xA0, 0xA8, 0xA8, 0xA4, 0xA4, 0xAC, 0xAC,
            0xA2, 0xA2, 0xAA, 0xAA, 0xA6, 0xA6, 0xAE, 0xAE,
            0xB0, 0xB0, 0xB8, 0xB8, 0xB4, 0xB4, 0xBC, 0xBC,
            0xB2, 0xB2, 0xBA, 0xBA, 0xB6, 0xB6, 0xBE, 0xBE,
            0xC0, 0xC0, 0xC8, 0xC8, 0xC4, 0xC4, 0xCC, 0xCC,
            0xC2, 0xC2, 0xCA, 0xCA, 0xC6, 0xC6, 0xCE, 0xCE,
            0xD0, 0xD0, 0xD8, 0xD8, 0xD4, 0xD4, 0xDC, 0xDC,
            0xD2, 0xD2, 0xDA, 0xDA, 0xD6, 0xD6, 0xDE, 0xDE,
            0xE0, 0xE0, 0xE8, 0xE8, 0xE4, 0xE4, 0xEC, 0xEC,
            0xE2, 0xE2, 0xEA, 0xEA, 0xE6, 0xE6, 0xEE, 0xEE,
            0xF0, 0xF0, 0xF8, 0xF8, 0xF4, 0xF4, 0xFC, 0xFC,
            0xF2, 0xF2, 0xFA, 0xFA, 0xF6, 0xF6, 0xFE, 0xFE
        );

        // Mapping tables for the PC-2 transformation.
        static $pc2mapc1 = array(
            0x00000000, 0x00000400, 0x00200000, 0x00200400,
            0x00000001, 0x00000401, 0x00200001, 0x00200401,
            0x02000000, 0x02000400, 0x02200000, 0x02200400,
            0x02000001, 0x02000401, 0x02200001, 0x02200401
        );
        static $pc2mapc2 = array(
            0x00000000, 0x00000800, 0x08000000, 0x08000800,
            0x00010000, 0x00010800, 0x08010000, 0x08010800,
            0x00000000, 0x00000800, 0x08000000, 0x08000800,
            0x00010000, 0x00010800, 0x08010000, 0x08010800,
            0x00000100, 0x00000900, 0x08000100, 0x08000900,
            0x00010100, 0x00010900, 0x08010100, 0x08010900,
            0x00000100, 0x00000900, 0x08000100, 0x08000900,
            0x00010100, 0x00010900, 0x08010100, 0x08010900,
            0x00000010, 0x00000810, 0x08000010, 0x08000810,
            0x00010010, 0x00010810, 0x08010010, 0x08010810,
            0x00000010, 0x00000810, 0x08000010, 0x08000810,
            0x00010010, 0x00010810, 0x08010010, 0x08010810,
            0x00000110, 0x00000910, 0x08000110, 0x08000910,
            0x00010110, 0x00010910, 0x08010110, 0x08010910,
            0x00000110, 0x00000910, 0x08000110, 0x08000910,
            0x00010110, 0x00010910, 0x08010110, 0x08010910,
            0x00040000, 0x00040800, 0x08040000, 0x08040800,
            0x00050000, 0x00050800, 0x08050000, 0x08050800,
            0x00040000, 0x00040800, 0x08040000, 0x08040800,
            0x00050000, 0x00050800, 0x08050000, 0x08050800,
            0x00040100, 0x00040900, 0x08040100, 0x08040900,
            0x00050100, 0x00050900, 0x08050100, 0x08050900,
            0x00040100, 0x00040900, 0x08040100, 0x08040900,
            0x00050100, 0x00050900, 0x08050100, 0x08050900,
            0x00040010, 0x00040810, 0x08040010, 0x08040810,
            0x00050010, 0x00050810, 0x08050010, 0x08050810,
            0x00040010, 0x00040810, 0x08040010, 0x08040810,
            0x00050010, 0x00050810, 0x08050010, 0x08050810,
            0x00040110, 0x00040910, 0x08040110, 0x08040910,
            0x00050110, 0x00050910, 0x08050110, 0x08050910,
            0x00040110, 0x00040910, 0x08040110, 0x08040910,
            0x00050110, 0x00050910, 0x08050110, 0x08050910,
            0x01000000, 0x01000800, 0x09000000, 0x09000800,
            0x01010000, 0x01010800, 0x09010000, 0x09010800,
            0x01000000, 0x01000800, 0x09000000, 0x09000800,
            0x01010000, 0x01010800, 0x09010000, 0x09010800,
            0x01000100, 0x01000900, 0x09000100, 0x09000900,
            0x01010100, 0x01010900, 0x09010100, 0x09010900,
            0x01000100, 0x01000900, 0x09000100, 0x09000900,
            0x01010100, 0x01010900, 0x09010100, 0x09010900,
            0x01000010, 0x01000810, 0x09000010, 0x09000810,
            0x01010010, 0x01010810, 0x09010010, 0x09010810,
            0x01000010, 0x01000810, 0x09000010, 0x09000810,
            0x01010010, 0x01010810, 0x09010010, 0x09010810,
            0x01000110, 0x01000910, 0x09000110, 0x09000910,
            0x01010110, 0x01010910, 0x09010110, 0x09010910,
            0x01000110, 0x01000910, 0x09000110, 0x09000910,
            0x01010110, 0x01010910, 0x09010110, 0x09010910,
            0x01040000, 0x01040800, 0x09040000, 0x09040800,
            0x01050000, 0x01050800, 0x09050000, 0x09050800,
            0x01040000, 0x01040800, 0x09040000, 0x09040800,
            0x01050000, 0x01050800, 0x09050000, 0x09050800,
            0x01040100, 0x01040900, 0x09040100, 0x09040900,
            0x01050100, 0x01050900, 0x09050100, 0x09050900,
            0x01040100, 0x01040900, 0x09040100, 0x09040900,
            0x01050100, 0x01050900, 0x09050100, 0x09050900,
            0x01040010, 0x01040810, 0x09040010, 0x09040810,
            0x01050010, 0x01050810, 0x09050010, 0x09050810,
            0x01040010, 0x01040810, 0x09040010, 0x09040810,
            0x01050010, 0x01050810, 0x09050010, 0x09050810,
            0x01040110, 0x01040910, 0x09040110, 0x09040910,
            0x01050110, 0x01050910, 0x09050110, 0x09050910,
            0x01040110, 0x01040910, 0x09040110, 0x09040910,
            0x01050110, 0x01050910, 0x09050110, 0x09050910
        );
        static $pc2mapc3 = array(
            0x00000000, 0x00000004, 0x00001000, 0x00001004,
            0x00000000, 0x00000004, 0x00001000, 0x00001004,
            0x10000000, 0x10000004, 0x10001000, 0x10001004,
            0x10000000, 0x10000004, 0x10001000, 0x10001004,
            0x00000020, 0x00000024, 0x00001020, 0x00001024,
            0x00000020, 0x00000024, 0x00001020, 0x00001024,
            0x10000020, 0x10000024, 0x10001020, 0x10001024,
            0x10000020, 0x10000024, 0x10001020, 0x10001024,
            0x00080000, 0x00080004, 0x00081000, 0x00081004,
            0x00080000, 0x00080004, 0x00081000, 0x00081004,
            0x10080000, 0x10080004, 0x10081000, 0x10081004,
            0x10080000, 0x10080004, 0x10081000, 0x10081004,
            0x00080020, 0x00080024, 0x00081020, 0x00081024,
            0x00080020, 0x00080024, 0x00081020, 0x00081024,
            0x10080020, 0x10080024, 0x10081020, 0x10081024,
            0x10080020, 0x10080024, 0x10081020, 0x10081024,
            0x20000000, 0x20000004, 0x20001000, 0x20001004,
            0x20000000, 0x20000004, 0x20001000, 0x20001004,
            0x30000000, 0x30000004, 0x30001000, 0x30001004,
            0x30000000, 0x30000004, 0x30001000, 0x30001004,
            0x20000020, 0x20000024, 0x20001020, 0x20001024,
            0x20000020, 0x20000024, 0x20001020, 0x20001024,
            0x30000020, 0x30000024, 0x30001020, 0x30001024,
            0x30000020, 0x30000024, 0x30001020, 0x30001024,
            0x20080000, 0x20080004, 0x20081000, 0x20081004,
            0x20080000, 0x20080004, 0x20081000, 0x20081004,
            0x30080000, 0x30080004, 0x30081000, 0x30081004,
            0x30080000, 0x30080004, 0x30081000, 0x30081004,
            0x20080020, 0x20080024, 0x20081020, 0x20081024,
            0x20080020, 0x20080024, 0x20081020, 0x20081024,
            0x30080020, 0x30080024, 0x30081020, 0x30081024,
            0x30080020, 0x30080024, 0x30081020, 0x30081024,
            0x00000002, 0x00000006, 0x00001002, 0x00001006,
            0x00000002, 0x00000006, 0x00001002, 0x00001006,
            0x10000002, 0x10000006, 0x10001002, 0x10001006,
            0x10000002, 0x10000006, 0x10001002, 0x10001006,
            0x00000022, 0x00000026, 0x00001022, 0x00001026,
            0x00000022, 0x00000026, 0x00001022, 0x00001026,
            0x10000022, 0x10000026, 0x10001022, 0x10001026,
            0x10000022, 0x10000026, 0x10001022, 0x10001026,
            0x00080002, 0x00080006, 0x00081002, 0x00081006,
            0x00080002, 0x00080006, 0x00081002, 0x00081006,
            0x10080002, 0x10080006, 0x10081002, 0x10081006,
            0x10080002, 0x10080006, 0x10081002, 0x10081006,
            0x00080022, 0x00080026, 0x00081022, 0x00081026,
            0x00080022, 0x00080026, 0x00081022, 0x00081026,
            0x10080022, 0x10080026, 0x10081022, 0x10081026,
            0x10080022, 0x10080026, 0x10081022, 0x10081026,
            0x20000002, 0x20000006, 0x20001002, 0x20001006,
            0x20000002, 0x20000006, 0x20001002, 0x20001006,
            0x30000002, 0x30000006, 0x30001002, 0x30001006,
            0x30000002, 0x30000006, 0x30001002, 0x30001006,
            0x20000022, 0x20000026, 0x20001022, 0x20001026,
            0x20000022, 0x20000026, 0x20001022, 0x20001026,
            0x30000022, 0x30000026, 0x30001022, 0x30001026,
            0x30000022, 0x30000026, 0x30001022, 0x30001026,
            0x20080002, 0x20080006, 0x20081002, 0x20081006,
            0x20080002, 0x20080006, 0x20081002, 0x20081006,
            0x30080002, 0x30080006, 0x30081002, 0x30081006,
            0x30080002, 0x30080006, 0x30081002, 0x30081006,
            0x20080022, 0x20080026, 0x20081022, 0x20081026,
            0x20080022, 0x20080026, 0x20081022, 0x20081026,
            0x30080022, 0x30080026, 0x30081022, 0x30081026,
            0x30080022, 0x30080026, 0x30081022, 0x30081026
        );
        static $pc2mapc4 = array(
            0x00000000, 0x00100000, 0x00000008, 0x00100008,
            0x00000200, 0x00100200, 0x00000208, 0x00100208,
            0x00000000, 0x00100000, 0x00000008, 0x00100008,
            0x00000200, 0x00100200, 0x00000208, 0x00100208,
            0x04000000, 0x04100000, 0x04000008, 0x04100008,
            0x04000200, 0x04100200, 0x04000208, 0x04100208,
            0x04000000, 0x04100000, 0x04000008, 0x04100008,
            0x04000200, 0x04100200, 0x04000208, 0x04100208,
            0x00002000, 0x00102000, 0x00002008, 0x00102008,
            0x00002200, 0x00102200, 0x00002208, 0x00102208,
            0x00002000, 0x00102000, 0x00002008, 0x00102008,
            0x00002200, 0x00102200, 0x00002208, 0x00102208,
            0x04002000, 0x04102000, 0x04002008, 0x04102008,
            0x04002200, 0x04102200, 0x04002208, 0x04102208,
            0x04002000, 0x04102000, 0x04002008, 0x04102008,
            0x04002200, 0x04102200, 0x04002208, 0x04102208,
            0x00000000, 0x00100000, 0x00000008, 0x00100008,
            0x00000200, 0x00100200, 0x00000208, 0x00100208,
            0x00000000, 0x00100000, 0x00000008, 0x00100008,
            0x00000200, 0x00100200, 0x00000208, 0x00100208,
            0x04000000, 0x04100000, 0x04000008, 0x04100008,
            0x04000200, 0x04100200, 0x04000208, 0x04100208,
            0x04000000, 0x04100000, 0x04000008, 0x04100008,
            0x04000200, 0x04100200, 0x04000208, 0x04100208,
            0x00002000, 0x00102000, 0x00002008, 0x00102008,
            0x00002200, 0x00102200, 0x00002208, 0x00102208,
            0x00002000, 0x00102000, 0x00002008, 0x00102008,
            0x00002200, 0x00102200, 0x00002208, 0x00102208,
            0x04002000, 0x04102000, 0x04002008, 0x04102008,
            0x04002200, 0x04102200, 0x04002208, 0x04102208,
            0x04002000, 0x04102000, 0x04002008, 0x04102008,
            0x04002200, 0x04102200, 0x04002208, 0x04102208,
            0x00020000, 0x00120000, 0x00020008, 0x00120008,
            0x00020200, 0x00120200, 0x00020208, 0x00120208,
            0x00020000, 0x00120000, 0x00020008, 0x00120008,
            0x00020200, 0x00120200, 0x00020208, 0x00120208,
            0x04020000, 0x04120000, 0x04020008, 0x04120008,
            0x04020200, 0x04120200, 0x04020208, 0x04120208,
            0x04020000, 0x04120000, 0x04020008, 0x04120008,
            0x04020200, 0x04120200, 0x04020208, 0x04120208,
            0x00022000, 0x00122000, 0x00022008, 0x00122008,
            0x00022200, 0x00122200, 0x00022208, 0x00122208,
            0x00022000, 0x00122000, 0x00022008, 0x00122008,
            0x00022200, 0x00122200, 0x00022208, 0x00122208,
            0x04022000, 0x04122000, 0x04022008, 0x04122008,
            0x04022200, 0x04122200, 0x04022208, 0x04122208,
            0x04022000, 0x04122000, 0x04022008, 0x04122008,
            0x04022200, 0x04122200, 0x04022208, 0x04122208,
            0x00020000, 0x00120000, 0x00020008, 0x00120008,
            0x00020200, 0x00120200, 0x00020208, 0x00120208,
            0x00020000, 0x00120000, 0x00020008, 0x00120008,
            0x00020200, 0x00120200, 0x00020208, 0x00120208,
            0x04020000, 0x04120000, 0x04020008, 0x04120008,
            0x04020200, 0x04120200, 0x04020208, 0x04120208,
            0x04020000, 0x04120000, 0x04020008, 0x04120008,
            0x04020200, 0x04120200, 0x04020208, 0x04120208,
            0x00022000, 0x00122000, 0x00022008, 0x00122008,
            0x00022200, 0x00122200, 0x00022208, 0x00122208,
            0x00022000, 0x00122000, 0x00022008, 0x00122008,
            0x00022200, 0x00122200, 0x00022208, 0x00122208,
            0x04022000, 0x04122000, 0x04022008, 0x04122008,
            0x04022200, 0x04122200, 0x04022208, 0x04122208,
            0x04022000, 0x04122000, 0x04022008, 0x04122008,
            0x04022200, 0x04122200, 0x04022208, 0x04122208
        );
        static $pc2mapd1 = array(
            0x00000000, 0x00000001, 0x08000000, 0x08000001,
            0x00200000, 0x00200001, 0x08200000, 0x08200001,
            0x00000002, 0x00000003, 0x08000002, 0x08000003,
            0x00200002, 0x00200003, 0x08200002, 0x08200003
        );
        static $pc2mapd2 = array(
            0x00000000, 0x00100000, 0x00000800, 0x00100800,
            0x00000000, 0x00100000, 0x00000800, 0x00100800,
            0x04000000, 0x04100000, 0x04000800, 0x04100800,
            0x04000000, 0x04100000, 0x04000800, 0x04100800,
            0x00000004, 0x00100004, 0x00000804, 0x00100804,
            0x00000004, 0x00100004, 0x00000804, 0x00100804,
            0x04000004, 0x04100004, 0x04000804, 0x04100804,
            0x04000004, 0x04100004, 0x04000804, 0x04100804,
            0x00000000, 0x00100000, 0x00000800, 0x00100800,
            0x00000000, 0x00100000, 0x00000800, 0x00100800,
            0x04000000, 0x04100000, 0x04000800, 0x04100800,
            0x04000000, 0x04100000, 0x04000800, 0x04100800,
            0x00000004, 0x00100004, 0x00000804, 0x00100804,
            0x00000004, 0x00100004, 0x00000804, 0x00100804,
            0x04000004, 0x04100004, 0x04000804, 0x04100804,
            0x04000004, 0x04100004, 0x04000804, 0x04100804,
            0x00000200, 0x00100200, 0x00000A00, 0x00100A00,
            0x00000200, 0x00100200, 0x00000A00, 0x00100A00,
            0x04000200, 0x04100200, 0x04000A00, 0x04100A00,
            0x04000200, 0x04100200, 0x04000A00, 0x04100A00,
            0x00000204, 0x00100204, 0x00000A04, 0x00100A04,
            0x00000204, 0x00100204, 0x00000A04, 0x00100A04,
            0x04000204, 0x04100204, 0x04000A04, 0x04100A04,
            0x04000204, 0x04100204, 0x04000A04, 0x04100A04,
            0x00000200, 0x00100200, 0x00000A00, 0x00100A00,
            0x00000200, 0x00100200, 0x00000A00, 0x00100A00,
            0x04000200, 0x04100200, 0x04000A00, 0x04100A00,
            0x04000200, 0x04100200, 0x04000A00, 0x04100A00,
            0x00000204, 0x00100204, 0x00000A04, 0x00100A04,
            0x00000204, 0x00100204, 0x00000A04, 0x00100A04,
            0x04000204, 0x04100204, 0x04000A04, 0x04100A04,
            0x04000204, 0x04100204, 0x04000A04, 0x04100A04,
            0x00020000, 0x00120000, 0x00020800, 0x00120800,
            0x00020000, 0x00120000, 0x00020800, 0x00120800,
            0x04020000, 0x04120000, 0x04020800, 0x04120800,
            0x04020000, 0x04120000, 0x04020800, 0x04120800,
            0x00020004, 0x00120004, 0x00020804, 0x00120804,
            0x00020004, 0x00120004, 0x00020804, 0x00120804,
            0x04020004, 0x04120004, 0x04020804, 0x04120804,
            0x04020004, 0x04120004, 0x04020804, 0x04120804,
            0x00020000, 0x00120000, 0x00020800, 0x00120800,
            0x00020000, 0x00120000, 0x00020800, 0x00120800,
            0x04020000, 0x04120000, 0x04020800, 0x04120800,
            0x04020000, 0x04120000, 0x04020800, 0x04120800,
            0x00020004, 0x00120004, 0x00020804, 0x00120804,
            0x00020004, 0x00120004, 0x00020804, 0x00120804,
            0x04020004, 0x04120004, 0x04020804, 0x04120804,
            0x04020004, 0x04120004, 0x04020804, 0x04120804,
            0x00020200, 0x00120200, 0x00020A00, 0x00120A00,
            0x00020200, 0x00120200, 0x00020A00, 0x00120A00,
            0x04020200, 0x04120200, 0x04020A00, 0x04120A00,
            0x04020200, 0x04120200, 0x04020A00, 0x04120A00,
            0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
            0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
            0x04020204, 0x04120204, 0x04020A04, 0x04120A04,
            0x04020204, 0x04120204, 0x04020A04, 0x04120A04,
            0x00020200, 0x00120200, 0x00020A00, 0x00120A00,
            0x00020200, 0x00120200, 0x00020A00, 0x00120A00,
            0x04020200, 0x04120200, 0x04020A00, 0x04120A00,
            0x04020200, 0x04120200, 0x04020A00, 0x04120A00,
            0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
            0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
            0x04020204, 0x04120204, 0x04020A04, 0x04120A04,
            0x04020204, 0x04120204, 0x04020A04, 0x04120A04
        );
        static $pc2mapd3 = array(
            0x00000000, 0x00010000, 0x02000000, 0x02010000,
            0x00000020, 0x00010020, 0x02000020, 0x02010020,
            0x00040000, 0x00050000, 0x02040000, 0x02050000,
            0x00040020, 0x00050020, 0x02040020, 0x02050020,
            0x00002000, 0x00012000, 0x02002000, 0x02012000,
            0x00002020, 0x00012020, 0x02002020, 0x02012020,
            0x00042000, 0x00052000, 0x02042000, 0x02052000,
            0x00042020, 0x00052020, 0x02042020, 0x02052020,
            0x00000000, 0x00010000, 0x02000000, 0x02010000,
            0x00000020, 0x00010020, 0x02000020, 0x02010020,
            0x00040000, 0x00050000, 0x02040000, 0x02050000,
            0x00040020, 0x00050020, 0x02040020, 0x02050020,
            0x00002000, 0x00012000, 0x02002000, 0x02012000,
            0x00002020, 0x00012020, 0x02002020, 0x02012020,
            0x00042000, 0x00052000, 0x02042000, 0x02052000,
            0x00042020, 0x00052020, 0x02042020, 0x02052020,
            0x00000010, 0x00010010, 0x02000010, 0x02010010,
            0x00000030, 0x00010030, 0x02000030, 0x02010030,
            0x00040010, 0x00050010, 0x02040010, 0x02050010,
            0x00040030, 0x00050030, 0x02040030, 0x02050030,
            0x00002010, 0x00012010, 0x02002010, 0x02012010,
            0x00002030, 0x00012030, 0x02002030, 0x02012030,
            0x00042010, 0x00052010, 0x02042010, 0x02052010,
            0x00042030, 0x00052030, 0x02042030, 0x02052030,
            0x00000010, 0x00010010, 0x02000010, 0x02010010,
            0x00000030, 0x00010030, 0x02000030, 0x02010030,
            0x00040010, 0x00050010, 0x02040010, 0x02050010,
            0x00040030, 0x00050030, 0x02040030, 0x02050030,
            0x00002010, 0x00012010, 0x02002010, 0x02012010,
            0x00002030, 0x00012030, 0x02002030, 0x02012030,
            0x00042010, 0x00052010, 0x02042010, 0x02052010,
            0x00042030, 0x00052030, 0x02042030, 0x02052030,
            0x20000000, 0x20010000, 0x22000000, 0x22010000,
            0x20000020, 0x20010020, 0x22000020, 0x22010020,
            0x20040000, 0x20050000, 0x22040000, 0x22050000,
            0x20040020, 0x20050020, 0x22040020, 0x22050020,
            0x20002000, 0x20012000, 0x22002000, 0x22012000,
            0x20002020, 0x20012020, 0x22002020, 0x22012020,
            0x20042000, 0x20052000, 0x22042000, 0x22052000,
            0x20042020, 0x20052020, 0x22042020, 0x22052020,
            0x20000000, 0x20010000, 0x22000000, 0x22010000,
            0x20000020, 0x20010020, 0x22000020, 0x22010020,
            0x20040000, 0x20050000, 0x22040000, 0x22050000,
            0x20040020, 0x20050020, 0x22040020, 0x22050020,
            0x20002000, 0x20012000, 0x22002000, 0x22012000,
            0x20002020, 0x20012020, 0x22002020, 0x22012020,
            0x20042000, 0x20052000, 0x22042000, 0x22052000,
            0x20042020, 0x20052020, 0x22042020, 0x22052020,
            0x20000010, 0x20010010, 0x22000010, 0x22010010,
            0x20000030, 0x20010030, 0x22000030, 0x22010030,
            0x20040010, 0x20050010, 0x22040010, 0x22050010,
            0x20040030, 0x20050030, 0x22040030, 0x22050030,
            0x20002010, 0x20012010, 0x22002010, 0x22012010,
            0x20002030, 0x20012030, 0x22002030, 0x22012030,
            0x20042010, 0x20052010, 0x22042010, 0x22052010,
            0x20042030, 0x20052030, 0x22042030, 0x22052030,
            0x20000010, 0x20010010, 0x22000010, 0x22010010,
            0x20000030, 0x20010030, 0x22000030, 0x22010030,
            0x20040010, 0x20050010, 0x22040010, 0x22050010,
            0x20040030, 0x20050030, 0x22040030, 0x22050030,
            0x20002010, 0x20012010, 0x22002010, 0x22012010,
            0x20002030, 0x20012030, 0x22002030, 0x22012030,
            0x20042010, 0x20052010, 0x22042010, 0x22052010,
            0x20042030, 0x20052030, 0x22042030, 0x22052030
        );
        static $pc2mapd4 = array(
            0x00000000, 0x00000400, 0x01000000, 0x01000400,
            0x00000000, 0x00000400, 0x01000000, 0x01000400,
            0x00000100, 0x00000500, 0x01000100, 0x01000500,
            0x00000100, 0x00000500, 0x01000100, 0x01000500,
            0x10000000, 0x10000400, 0x11000000, 0x11000400,
            0x10000000, 0x10000400, 0x11000000, 0x11000400,
            0x10000100, 0x10000500, 0x11000100, 0x11000500,
            0x10000100, 0x10000500, 0x11000100, 0x11000500,
            0x00080000, 0x00080400, 0x01080000, 0x01080400,
            0x00080000, 0x00080400, 0x01080000, 0x01080400,
            0x00080100, 0x00080500, 0x01080100, 0x01080500,
            0x00080100, 0x00080500, 0x01080100, 0x01080500,
            0x10080000, 0x10080400, 0x11080000, 0x11080400,
            0x10080000, 0x10080400, 0x11080000, 0x11080400,
            0x10080100, 0x10080500, 0x11080100, 0x11080500,
            0x10080100, 0x10080500, 0x11080100, 0x11080500,
            0x00000008, 0x00000408, 0x01000008, 0x01000408,
            0x00000008, 0x00000408, 0x01000008, 0x01000408,
            0x00000108, 0x00000508, 0x01000108, 0x01000508,
            0x00000108, 0x00000508, 0x01000108, 0x01000508,
            0x10000008, 0x10000408, 0x11000008, 0x11000408,
            0x10000008, 0x10000408, 0x11000008, 0x11000408,
            0x10000108, 0x10000508, 0x11000108, 0x11000508,
            0x10000108, 0x10000508, 0x11000108, 0x11000508,
            0x00080008, 0x00080408, 0x01080008, 0x01080408,
            0x00080008, 0x00080408, 0x01080008, 0x01080408,
            0x00080108, 0x00080508, 0x01080108, 0x01080508,
            0x00080108, 0x00080508, 0x01080108, 0x01080508,
            0x10080008, 0x10080408, 0x11080008, 0x11080408,
            0x10080008, 0x10080408, 0x11080008, 0x11080408,
            0x10080108, 0x10080508, 0x11080108, 0x11080508,
            0x10080108, 0x10080508, 0x11080108, 0x11080508,
            0x00001000, 0x00001400, 0x01001000, 0x01001400,
            0x00001000, 0x00001400, 0x01001000, 0x01001400,
            0x00001100, 0x00001500, 0x01001100, 0x01001500,
            0x00001100, 0x00001500, 0x01001100, 0x01001500,
            0x10001000, 0x10001400, 0x11001000, 0x11001400,
            0x10001000, 0x10001400, 0x11001000, 0x11001400,
            0x10001100, 0x10001500, 0x11001100, 0x11001500,
            0x10001100, 0x10001500, 0x11001100, 0x11001500,
            0x00081000, 0x00081400, 0x01081000, 0x01081400,
            0x00081000, 0x00081400, 0x01081000, 0x01081400,
            0x00081100, 0x00081500, 0x01081100, 0x01081500,
            0x00081100, 0x00081500, 0x01081100, 0x01081500,
            0x10081000, 0x10081400, 0x11081000, 0x11081400,
            0x10081000, 0x10081400, 0x11081000, 0x11081400,
            0x10081100, 0x10081500, 0x11081100, 0x11081500,
            0x10081100, 0x10081500, 0x11081100, 0x11081500,
            0x00001008, 0x00001408, 0x01001008, 0x01001408,
            0x00001008, 0x00001408, 0x01001008, 0x01001408,
            0x00001108, 0x00001508, 0x01001108, 0x01001508,
            0x00001108, 0x00001508, 0x01001108, 0x01001508,
            0x10001008, 0x10001408, 0x11001008, 0x11001408,
            0x10001008, 0x10001408, 0x11001008, 0x11001408,
            0x10001108, 0x10001508, 0x11001108, 0x11001508,
            0x10001108, 0x10001508, 0x11001108, 0x11001508,
            0x00081008, 0x00081408, 0x01081008, 0x01081408,
            0x00081008, 0x00081408, 0x01081008, 0x01081408,
            0x00081108, 0x00081508, 0x01081108, 0x01081508,
            0x00081108, 0x00081508, 0x01081108, 0x01081508,
            0x10081008, 0x10081408, 0x11081008, 0x11081408,
            0x10081008, 0x10081408, 0x11081008, 0x11081408,
            0x10081108, 0x10081508, 0x11081108, 0x11081508,
            0x10081108, 0x10081508, 0x11081108, 0x11081508
        );

        $keys = array();
        for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) {
            // pad the key and remove extra characters as appropriate.
            $key = str_pad(substr($this->key, $des_round * 8, 8), 8, "\0");

            // Perform the PC/1 transformation and compute C and D.
            $t = unpack('Nl/Nr', $key);
            list($l, $r) = array($t['l'], $t['r']);
            $key = ($this->shuffle[$pc1map[ $r        & 0xFF]] & "\x80\x80\x80\x80\x80\x80\x80\x00") |
                   ($this->shuffle[$pc1map[($r >>  8) & 0xFF]] & "\x40\x40\x40\x40\x40\x40\x40\x00") |
                   ($this->shuffle[$pc1map[($r >> 16) & 0xFF]] & "\x20\x20\x20\x20\x20\x20\x20\x00") |
                   ($this->shuffle[$pc1map[($r >> 24) & 0xFF]] & "\x10\x10\x10\x10\x10\x10\x10\x00") |
                   ($this->shuffle[$pc1map[ $l        & 0xFF]] & "\x08\x08\x08\x08\x08\x08\x08\x00") |
                   ($this->shuffle[$pc1map[($l >>  8) & 0xFF]] & "\x04\x04\x04\x04\x04\x04\x04\x00") |
                   ($this->shuffle[$pc1map[($l >> 16) & 0xFF]] & "\x02\x02\x02\x02\x02\x02\x02\x00") |
                   ($this->shuffle[$pc1map[($l >> 24) & 0xFF]] & "\x01\x01\x01\x01\x01\x01\x01\x00");
            $key = unpack('Nc/Nd', $key);
            $c = ( $key['c'] >> 4) & 0x0FFFFFFF;
            $d = (($key['d'] >> 4) & 0x0FFFFFF0) | ($key['c'] & 0x0F);

            $keys[$des_round] = array(
                self::ENCRYPT => array(),
                self::DECRYPT => array_fill(0, 32, 0)
            );
            for ($i = 0, $ki = 31; $i < 16; ++$i, $ki-= 2) {
                $c <<= $shifts[$i];
                $c = ($c | ($c >> 28)) & 0x0FFFFFFF;
                $d <<= $shifts[$i];
                $d = ($d | ($d >> 28)) & 0x0FFFFFFF;

                // Perform the PC-2 transformation.
                $cp = $pc2mapc1[ $c >> 24        ] | $pc2mapc2[($c >> 16) & 0xFF] |
                      $pc2mapc3[($c >>  8) & 0xFF] | $pc2mapc4[ $c        & 0xFF];
                $dp = $pc2mapd1[ $d >> 24        ] | $pc2mapd2[($d >> 16) & 0xFF] |
                      $pc2mapd3[($d >>  8) & 0xFF] | $pc2mapd4[ $d        & 0xFF];

                // Reorder: odd bytes/even bytes. Push the result in key schedule.
                $val1 = ( $cp        & 0xFF000000) | (($cp <<  8) & 0x00FF0000) |
                        (($dp >> 16) & 0x0000FF00) | (($dp >>  8) & 0x000000FF);
                $val2 = (($cp <<  8) & 0xFF000000) | (($cp << 16) & 0x00FF0000) |
                        (($dp >>  8) & 0x0000FF00) | ( $dp        & 0x000000FF);
                $keys[$des_round][self::ENCRYPT][       ] = $val1;
                $keys[$des_round][self::DECRYPT][$ki - 1] = $val1;
                $keys[$des_round][self::ENCRYPT][       ] = $val2;
                $keys[$des_round][self::DECRYPT][$ki    ] = $val2;
            }
        }

        switch ($this->des_rounds) {
            case 3: // 3DES keys
                $this->keys = array(
                    self::ENCRYPT => array_merge(
                        $keys[0][self::ENCRYPT],
                        $keys[1][self::DECRYPT],
                        $keys[2][self::ENCRYPT]
                    ),
                    self::DECRYPT => array_merge(
                        $keys[2][self::DECRYPT],
                        $keys[1][self::ENCRYPT],
                        $keys[0][self::DECRYPT]
                    )
                );
                break;
            // case 1: // DES keys
            default:
                $this->keys = array(
                    self::ENCRYPT => $keys[0][self::ENCRYPT],
                    self::DECRYPT => $keys[0][self::DECRYPT]
                );
        }
    }

    /**
     * Setup the performance-optimized function for de/encrypt()
     *
     * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
     * @access private
     */
    function _setupInlineCrypt()
    {
        $lambda_functions =& self::_getLambdaFunctions();

        // Engine configuration for:
        // -  DES ($des_rounds == 1) or
        // - 3DES ($des_rounds == 3)
        $des_rounds = $this->des_rounds;

        // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function.
        // (Currently, for DES, one generated $lambda_function cost on php5.5@32bit ~135kb unfreeable mem and ~230kb on php5.5@64bit)
        // (Currently, for TripleDES, one generated $lambda_function cost on php5.5@32bit ~240kb unfreeable mem and ~340kb on php5.5@64bit)
        // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one
        $gen_hi_opt_code = (bool)( count($lambda_functions) < 10 );

        // Generation of a unique hash for our generated code
        $code_hash = "Crypt_DES, $des_rounds, {$this->mode}";
        if ($gen_hi_opt_code) {
            // For hi-optimized code, we create for each combination of
            // $mode, $des_rounds and $this->key its own encrypt/decrypt function.
            // After max 10 hi-optimized functions, we create generic
            // (still very fast.. but not ultra) functions for each $mode/$des_rounds
            // Currently 2 * 5 generic functions will be then max. possible.
            $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
        }

        // Is there a re-usable $lambda_functions in there? If not, we have to create it.
        if (!isset($lambda_functions[$code_hash])) {
            // Init code for both, encrypt and decrypt.
            $init_crypt = 'static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip;
                if (!$sbox1) {
                    $sbox1 = array_map("intval", $self->sbox1);
                    $sbox2 = array_map("intval", $self->sbox2);
                    $sbox3 = array_map("intval", $self->sbox3);
                    $sbox4 = array_map("intval", $self->sbox4);
                    $sbox5 = array_map("intval", $self->sbox5);
                    $sbox6 = array_map("intval", $self->sbox6);
                    $sbox7 = array_map("intval", $self->sbox7);
                    $sbox8 = array_map("intval", $self->sbox8);'
                    /* Merge $shuffle with $[inv]ipmap */ . '
                    for ($i = 0; $i < 256; ++$i) {
                        $shuffleip[]    =  $self->shuffle[$self->ipmap[$i]];
                        $shuffleinvip[] =  $self->shuffle[$self->invipmap[$i]];
                    }
                }
            ';

            switch (true) {
                case $gen_hi_opt_code:
                    // In Hi-optimized code mode, we use our [3]DES key schedule as hardcoded integers.
                    // No futher initialisation of the $keys schedule is necessary.
                    // That is the extra performance boost.
                    $k = array(
                        self::ENCRYPT => $this->keys[self::ENCRYPT],
                        self::DECRYPT => $this->keys[self::DECRYPT]
                    );
                    $init_encrypt = '';
                    $init_decrypt = '';
                    break;
                default:
                    // In generic optimized code mode, we have to use, as the best compromise [currently],
                    // our key schedule as $ke/$kd arrays. (with hardcoded indexes...)
                    $k = array(
                        self::ENCRYPT => array(),
                        self::DECRYPT => array()
                    );
                    for ($i = 0, $c = count($this->keys[self::ENCRYPT]); $i < $c; ++$i) {
                        $k[self::ENCRYPT][$i] = '$ke[' . $i . ']';
                        $k[self::DECRYPT][$i] = '$kd[' . $i . ']';
                    }
                    $init_encrypt = '$ke = $self->keys[self::ENCRYPT];';
                    $init_decrypt = '$kd = $self->keys[self::DECRYPT];';
                    break;
            }

            // Creating code for en- and decryption.
            $crypt_block = array();
            foreach (array(self::ENCRYPT, self::DECRYPT) as $c) {
                /* Do the initial IP permutation. */
                $crypt_block[$c] = '
                    $in = unpack("N*", $in);
                    $l  = $in[1];
                    $r  = $in[2];
                    $in = unpack("N*",
                        ($shuffleip[ $r        & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
                        ($shuffleip[($r >>  8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
                        ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
                        ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
                        ($shuffleip[ $l        & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
                        ($shuffleip[($l >>  8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
                        ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
                        ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01")
                    );
                    ' . /* Extract L0 and R0 */ '
                    $l = $in[1];
                    $r = $in[2];
                ';

                $l = '$l';
                $r = '$r';

                // Perform DES or 3DES.
                for ($ki = -1, $des_round = 0; $des_round < $des_rounds; ++$des_round) {
                    // Perform the 16 steps.
                    for ($i = 0; $i < 16; ++$i) {
                        // start of "the Feistel (F) function" - see the following URL:
                        // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png
                        // Merge key schedule.
                        $crypt_block[$c].= '
                            $b1 = ((' . $r . ' >>  3) & 0x1FFFFFFF)  ^ (' . $r . ' << 29) ^ ' . $k[$c][++$ki] . ';
                            $b2 = ((' . $r . ' >> 31) & 0x00000001)  ^ (' . $r . ' <<  1) ^ ' . $k[$c][++$ki] . ';' .
                            /* S-box indexing. */
                            $l . ' = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^
                                     $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^
                                     $sbox5[($b1 >>  8) & 0x3F] ^ $sbox6[($b2 >>  8) & 0x3F] ^
                                     $sbox7[ $b1        & 0x3F] ^ $sbox8[ $b2        & 0x3F] ^ ' . $l . ';
                        ';
                        // end of "the Feistel (F) function"

                        // swap L & R
                        list($l, $r) = array($r, $l);
                    }
                    list($l, $r) = array($r, $l);
                }

                // Perform the inverse IP permutation.
                $crypt_block[$c].= '$in =
                    ($shuffleinvip[($l >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
                    ($shuffleinvip[($r >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
                    ($shuffleinvip[($l >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
                    ($shuffleinvip[($r >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
                    ($shuffleinvip[($l >>  8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
                    ($shuffleinvip[($r >>  8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
                    ($shuffleinvip[ $l        & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
                    ($shuffleinvip[ $r        & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01");
                ';
            }

            // Creates the inline-crypt function
            $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
                array(
                   'init_crypt'    => $init_crypt,
                   'init_encrypt'  => $init_encrypt,
                   'init_decrypt'  => $init_decrypt,
                   'encrypt_block' => $crypt_block[self::ENCRYPT],
                   'decrypt_block' => $crypt_block[self::DECRYPT]
                )
            );
        }

        // Set the inline-crypt function as callback in: $this->inline_crypt
        $this->inline_crypt = $lambda_functions[$code_hash];
    }
}
<?php

/**
 * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions.
 *
 * Uses hash() or mhash() if available and an internal implementation, otherwise.  Currently supports the following:
 *
 * md2, md5, md5-96, sha1, sha1-96, sha256, sha256-96, sha384, and sha512, sha512-96
 *
 * If {@link self::setKey() setKey()} is called, {@link self::hash() hash()} will return the HMAC as opposed to
 * the hash.  If no valid algorithm is provided, sha1 will be used.
 *
 * PHP version 5
 *
 * {@internal The variable names are the same as those in
 * {@link http://tools.ietf.org/html/rfc2104#section-2 RFC2104}.}}
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $hash = new \phpseclib\Crypt\Hash('sha1');
 *
 *    $hash->setKey('abcdefg');
 *
 *    echo base64_encode($hash->hash('abcdefg'));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   Hash
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

use phpseclib\Math\BigInteger;

/**
 * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions.
 *
 * @package Hash
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class Hash
{
    /**#@+
     * @access private
     * @see \phpseclib\Crypt\Hash::__construct()
     */
    /**
     * Toggles the internal implementation
     */
    const MODE_INTERNAL = 1;
    /**
     * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+.
     */
    const MODE_MHASH = 2;
    /**
     * Toggles the hash() implementation, which works on PHP 5.1.2+.
     */
    const MODE_HASH = 3;
    /**#@-*/

    /**
     * Hash Parameter
     *
     * @see self::setHash()
     * @var int
     * @access private
     */
    var $hashParam;

    /**
     * Byte-length of compression blocks / key (Internal HMAC)
     *
     * @see self::setAlgorithm()
     * @var int
     * @access private
     */
    var $b;

    /**
     * Byte-length of hash output (Internal HMAC)
     *
     * @see self::setHash()
     * @var int
     * @access private
     */
    var $l = false;

    /**
     * Hash Algorithm
     *
     * @see self::setHash()
     * @var string
     * @access private
     */
    var $hash;

    /**
     * Key
     *
     * @see self::setKey()
     * @var string
     * @access private
     */
    var $key = false;

    /**
     * Outer XOR (Internal HMAC)
     *
     * @see self::setKey()
     * @var string
     * @access private
     */
    var $opad;

    /**
     * Inner XOR (Internal HMAC)
     *
     * @see self::setKey()
     * @var string
     * @access private
     */
    var $ipad;

    /**
     * Default Constructor.
     *
     * @param string $hash
     * @return \phpseclib\Crypt\Hash
     * @access public
     */
    function __construct($hash = 'sha1')
    {
        if (!defined('CRYPT_HASH_MODE')) {
            switch (true) {
                case extension_loaded('hash'):
                    define('CRYPT_HASH_MODE', self::MODE_HASH);
                    break;
                case extension_loaded('mhash'):
                    define('CRYPT_HASH_MODE', self::MODE_MHASH);
                    break;
                default:
                    define('CRYPT_HASH_MODE', self::MODE_INTERNAL);
            }
        }

        $this->setHash($hash);
    }

    /**
     * Sets the key for HMACs
     *
     * Keys can be of any length.
     *
     * @access public
     * @param string $key
     */
    function setKey($key = false)
    {
        $this->key = $key;
    }

    /**
     * Gets the hash function.
     *
     * As set by the constructor or by the setHash() method.
     *
     * @access public
     * @return string
     */
    function getHash()
    {
        return $this->hashParam;
    }

    /**
     * Sets the hash function.
     *
     * @access public
     * @param string $hash
     */
    function setHash($hash)
    {
        $this->hashParam = $hash = strtolower($hash);
        switch ($hash) {
            case 'md5-96':
            case 'sha1-96':
            case 'sha256-96':
            case 'sha512-96':
                $hash = substr($hash, 0, -3);
                $this->l = 12; // 96 / 8 = 12
                break;
            case 'md2':
            case 'md5':
                $this->l = 16;
                break;
            case 'sha1':
                $this->l = 20;
                break;
            case 'sha256':
                $this->l = 32;
                break;
            case 'sha384':
                $this->l = 48;
                break;
            case 'sha512':
                $this->l = 64;
        }

        switch ($hash) {
            case 'md2':
                $mode = CRYPT_HASH_MODE == self::MODE_HASH && in_array('md2', hash_algos()) ?
                    self::MODE_HASH : self::MODE_INTERNAL;
                break;
            case 'sha384':
            case 'sha512':
                $mode = CRYPT_HASH_MODE == self::MODE_MHASH ? self::MODE_INTERNAL : CRYPT_HASH_MODE;
                break;
            default:
                $mode = CRYPT_HASH_MODE;
        }

        switch ($mode) {
            case self::MODE_MHASH:
                switch ($hash) {
                    case 'md5':
                        $this->hash = MHASH_MD5;
                        break;
                    case 'sha256':
                        $this->hash = MHASH_SHA256;
                        break;
                    case 'sha1':
                    default:
                        $this->hash = MHASH_SHA1;
                }
                return;
            case self::MODE_HASH:
                switch ($hash) {
                    case 'md5':
                        $this->hash = 'md5';
                        return;
                    case 'md2':
                    case 'sha256':
                    case 'sha384':
                    case 'sha512':
                        $this->hash = $hash;
                        return;
                    case 'sha1':
                    default:
                        $this->hash = 'sha1';
                }
                return;
        }

        switch ($hash) {
            case 'md2':
                $this->b = 16;
                $this->hash = array($this, '_md2');
                break;
            case 'md5':
                $this->b = 64;
                $this->hash = array($this, '_md5');
                break;
            case 'sha256':
                $this->b = 64;
                $this->hash = array($this, '_sha256');
                break;
            case 'sha384':
            case 'sha512':
                $this->b = 128;
                $this->hash = array($this, '_sha512');
                break;
            case 'sha1':
            default:
                $this->b = 64;
                $this->hash = array($this, '_sha1');
        }

        $this->ipad = str_repeat(chr(0x36), $this->b);
        $this->opad = str_repeat(chr(0x5C), $this->b);
    }

    /**
     * Compute the HMAC.
     *
     * @access public
     * @param string $text
     * @return string
     */
    function hash($text)
    {
        $mode = is_array($this->hash) ? self::MODE_INTERNAL : CRYPT_HASH_MODE;

        if (!empty($this->key) || is_string($this->key)) {
            switch ($mode) {
                case self::MODE_MHASH:
                    $output = mhash($this->hash, $text, $this->key);
                    break;
                case self::MODE_HASH:
                    $output = hash_hmac($this->hash, $text, $this->key, true);
                    break;
                case self::MODE_INTERNAL:
                    /* "Applications that use keys longer than B bytes will first hash the key using H and then use the
                        resultant L byte string as the actual key to HMAC."

                        -- http://tools.ietf.org/html/rfc2104#section-2 */
                    $key = strlen($this->key) > $this->b ? call_user_func($this->hash, $this->key) : $this->key;

                    $key    = str_pad($key, $this->b, chr(0));      // step 1
                    $temp   = $this->ipad ^ $key;                   // step 2
                    $temp  .= $text;                                // step 3
                    $temp   = call_user_func($this->hash, $temp);   // step 4
                    $output = $this->opad ^ $key;                   // step 5
                    $output.= $temp;                                // step 6
                    $output = call_user_func($this->hash, $output); // step 7
            }
        } else {
            switch ($mode) {
                case self::MODE_MHASH:
                    $output = mhash($this->hash, $text);
                    break;
                case self::MODE_HASH:
                    $output = hash($this->hash, $text, true);
                    break;
                case self::MODE_INTERNAL:
                    $output = call_user_func($this->hash, $text);
            }
        }

        return substr($output, 0, $this->l);
    }

    /**
     * Returns the hash length (in bytes)
     *
     * @access public
     * @return int
     */
    function getLength()
    {
        return $this->l;
    }

    /**
     * Wrapper for MD5
     *
     * @access private
     * @param string $m
     */
    function _md5($m)
    {
        return pack('H*', md5($m));
    }

    /**
     * Wrapper for SHA1
     *
     * @access private
     * @param string $m
     */
    function _sha1($m)
    {
        return pack('H*', sha1($m));
    }

    /**
     * Pure-PHP implementation of MD2
     *
     * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}.
     *
     * @access private
     * @param string $m
     */
    function _md2($m)
    {
        static $s = array(
             41,  46,  67, 201, 162, 216, 124,   1,  61,  54,  84, 161, 236, 240, 6,
             19,  98, 167,   5, 243, 192, 199, 115, 140, 152, 147,  43, 217, 188,
             76, 130, 202,  30, 155,  87,  60, 253, 212, 224,  22, 103,  66, 111, 24,
            138,  23, 229,  18, 190,  78, 196, 214, 218, 158, 222,  73, 160, 251,
            245, 142, 187,  47, 238, 122, 169, 104, 121, 145,  21, 178,   7,  63,
            148, 194,  16, 137,  11,  34,  95,  33, 128, 127,  93, 154,  90, 144, 50,
             39,  53,  62, 204, 231, 191, 247, 151,   3, 255,  25,  48, 179,  72, 165,
            181, 209, 215,  94, 146,  42, 172,  86, 170, 198,  79, 184,  56, 210,
            150, 164, 125, 182, 118, 252, 107, 226, 156, 116,   4, 241,  69, 157,
            112,  89, 100, 113, 135,  32, 134,  91, 207, 101, 230,  45, 168,   2, 27,
             96,  37, 173, 174, 176, 185, 246,  28,  70,  97, 105,  52,  64, 126, 15,
             85,  71, 163,  35, 221,  81, 175,  58, 195,  92, 249, 206, 186, 197,
            234,  38,  44,  83,  13, 110, 133,  40, 132,   9, 211, 223, 205, 244, 65,
            129,  77,  82, 106, 220,  55, 200, 108, 193, 171, 250,  36, 225, 123,
              8,  12, 189, 177,  74, 120, 136, 149, 139, 227,  99, 232, 109, 233,
            203, 213, 254,  59,   0,  29,  57, 242, 239, 183,  14, 102,  88, 208, 228,
            166, 119, 114, 248, 235, 117,  75,  10,  49,  68,  80, 180, 143, 237,
             31,  26, 219, 153, 141,  51, 159,  17, 131, 20
        );

        // Step 1. Append Padding Bytes
        $pad = 16 - (strlen($m) & 0xF);
        $m.= str_repeat(chr($pad), $pad);

        $length = strlen($m);

        // Step 2. Append Checksum
        $c = str_repeat(chr(0), 16);
        $l = chr(0);
        for ($i = 0; $i < $length; $i+= 16) {
            for ($j = 0; $j < 16; $j++) {
                // RFC1319 incorrectly states that C[j] should be set to S[c xor L]
                //$c[$j] = chr($s[ord($m[$i + $j] ^ $l)]);
                // per <http://www.rfc-editor.org/errata_search.php?rfc=1319>, however, C[j] should be set to S[c xor L] xor C[j]
                $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j]));
                $l = $c[$j];
            }
        }
        $m.= $c;

        $length+= 16;

        // Step 3. Initialize MD Buffer
        $x = str_repeat(chr(0), 48);

        // Step 4. Process Message in 16-Byte Blocks
        for ($i = 0; $i < $length; $i+= 16) {
            for ($j = 0; $j < 16; $j++) {
                $x[$j + 16] = $m[$i + $j];
                $x[$j + 32] = $x[$j + 16] ^ $x[$j];
            }
            $t = chr(0);
            for ($j = 0; $j < 18; $j++) {
                for ($k = 0; $k < 48; $k++) {
                    $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]);
                    //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]);
                }
                $t = chr(ord($t) + $j);
            }
        }

        // Step 5. Output
        return substr($x, 0, 16);
    }

    /**
     * Pure-PHP implementation of SHA256
     *
     * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}.
     *
     * @access private
     * @param string $m
     */
    function _sha256($m)
    {
        if (extension_loaded('suhosin')) {
            return pack('H*', sha256($m));
        }

        // Initialize variables
        $hash = array(
            0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
        );
        // Initialize table of round constants
        // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
        static $k = array(
            0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
            0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
            0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
            0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
            0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
            0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
            0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
            0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
        );

        // Pre-processing
        $length = strlen($m);
        // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64
        $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F));
        $m[$length] = chr(0x80);
        // we don't support hashing strings 512MB long
        $m.= pack('N2', 0, $length << 3);

        // Process the message in successive 512-bit chunks
        $chunks = str_split($m, 64);
        foreach ($chunks as $chunk) {
            $w = array();
            for ($i = 0; $i < 16; $i++) {
                extract(unpack('Ntemp', $this->_string_shift($chunk, 4)));
                $w[] = $temp;
            }

            // Extend the sixteen 32-bit words into sixty-four 32-bit words
            for ($i = 16; $i < 64; $i++) {
                // @codingStandardsIgnoreStart
                $s0 = $this->_rightRotate($w[$i - 15],  7) ^
                      $this->_rightRotate($w[$i - 15], 18) ^
                      $this->_rightShift( $w[$i - 15],  3);
                $s1 = $this->_rightRotate($w[$i - 2], 17) ^
                      $this->_rightRotate($w[$i - 2], 19) ^
                      $this->_rightShift( $w[$i - 2], 10);
                // @codingStandardsIgnoreEnd
                $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1);
            }

            // Initialize hash value for this chunk
            list($a, $b, $c, $d, $e, $f, $g, $h) = $hash;

            // Main loop
            for ($i = 0; $i < 64; $i++) {
                $s0 = $this->_rightRotate($a,  2) ^
                      $this->_rightRotate($a, 13) ^
                      $this->_rightRotate($a, 22);
                $maj = ($a & $b) ^
                       ($a & $c) ^
                       ($b & $c);
                $t2 = $this->_add($s0, $maj);

                $s1 = $this->_rightRotate($e,  6) ^
                      $this->_rightRotate($e, 11) ^
                      $this->_rightRotate($e, 25);
                $ch = ($e & $f) ^
                      ($this->_not($e) & $g);
                $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]);

                $h = $g;
                $g = $f;
                $f = $e;
                $e = $this->_add($d, $t1);
                $d = $c;
                $c = $b;
                $b = $a;
                $a = $this->_add($t1, $t2);
            }

            // Add this chunk's hash to result so far
            $hash = array(
                $this->_add($hash[0], $a),
                $this->_add($hash[1], $b),
                $this->_add($hash[2], $c),
                $this->_add($hash[3], $d),
                $this->_add($hash[4], $e),
                $this->_add($hash[5], $f),
                $this->_add($hash[6], $g),
                $this->_add($hash[7], $h)
            );
        }

        // Produce the final hash value (big-endian)
        return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]);
    }

    /**
     * Pure-PHP implementation of SHA384 and SHA512
     *
     * @access private
     * @param string $m
     */
    function _sha512($m)
    {
        static $init384, $init512, $k;

        if (!isset($k)) {
            // Initialize variables
            $init384 = array( // initial values for SHA384
                'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939',
                '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4'
            );
            $init512 = array( // initial values for SHA512
                '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1',
                '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179'
            );

            for ($i = 0; $i < 8; $i++) {
                $init384[$i] = new BigInteger($init384[$i], 16);
                $init384[$i]->setPrecision(64);
                $init512[$i] = new BigInteger($init512[$i], 16);
                $init512[$i]->setPrecision(64);
            }

            // Initialize table of round constants
            // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409)
            $k = array(
                '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc',
                '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118',
                'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2',
                '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694',
                'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65',
                '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5',
                '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4',
                'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70',
                '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df',
                '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b',
                'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30',
                'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8',
                '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8',
                '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3',
                '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec',
                '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b',
                'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178',
                '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b',
                '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c',
                '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817'
            );

            for ($i = 0; $i < 80; $i++) {
                $k[$i] = new BigInteger($k[$i], 16);
            }
        }

        $hash = $this->l == 48 ? $init384 : $init512;

        // Pre-processing
        $length = strlen($m);
        // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128
        $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F));
        $m[$length] = chr(0x80);
        // we don't support hashing strings 512MB long
        $m.= pack('N4', 0, 0, 0, $length << 3);

        // Process the message in successive 1024-bit chunks
        $chunks = str_split($m, 128);
        foreach ($chunks as $chunk) {
            $w = array();
            for ($i = 0; $i < 16; $i++) {
                $temp = new BigInteger($this->_string_shift($chunk, 8), 256);
                $temp->setPrecision(64);
                $w[] = $temp;
            }

            // Extend the sixteen 32-bit words into eighty 32-bit words
            for ($i = 16; $i < 80; $i++) {
                $temp = array(
                          $w[$i - 15]->bitwise_rightRotate(1),
                          $w[$i - 15]->bitwise_rightRotate(8),
                          $w[$i - 15]->bitwise_rightShift(7)
                );
                $s0 = $temp[0]->bitwise_xor($temp[1]);
                $s0 = $s0->bitwise_xor($temp[2]);
                $temp = array(
                          $w[$i - 2]->bitwise_rightRotate(19),
                          $w[$i - 2]->bitwise_rightRotate(61),
                          $w[$i - 2]->bitwise_rightShift(6)
                );
                $s1 = $temp[0]->bitwise_xor($temp[1]);
                $s1 = $s1->bitwise_xor($temp[2]);
                $w[$i] = $w[$i - 16]->copy();
                $w[$i] = $w[$i]->add($s0);
                $w[$i] = $w[$i]->add($w[$i - 7]);
                $w[$i] = $w[$i]->add($s1);
            }

            // Initialize hash value for this chunk
            $a = $hash[0]->copy();
            $b = $hash[1]->copy();
            $c = $hash[2]->copy();
            $d = $hash[3]->copy();
            $e = $hash[4]->copy();
            $f = $hash[5]->copy();
            $g = $hash[6]->copy();
            $h = $hash[7]->copy();

            // Main loop
            for ($i = 0; $i < 80; $i++) {
                $temp = array(
                    $a->bitwise_rightRotate(28),
                    $a->bitwise_rightRotate(34),
                    $a->bitwise_rightRotate(39)
                );
                $s0 = $temp[0]->bitwise_xor($temp[1]);
                $s0 = $s0->bitwise_xor($temp[2]);
                $temp = array(
                    $a->bitwise_and($b),
                    $a->bitwise_and($c),
                    $b->bitwise_and($c)
                );
                $maj = $temp[0]->bitwise_xor($temp[1]);
                $maj = $maj->bitwise_xor($temp[2]);
                $t2 = $s0->add($maj);

                $temp = array(
                    $e->bitwise_rightRotate(14),
                    $e->bitwise_rightRotate(18),
                    $e->bitwise_rightRotate(41)
                );
                $s1 = $temp[0]->bitwise_xor($temp[1]);
                $s1 = $s1->bitwise_xor($temp[2]);
                $temp = array(
                    $e->bitwise_and($f),
                    $g->bitwise_and($e->bitwise_not())
                );
                $ch = $temp[0]->bitwise_xor($temp[1]);
                $t1 = $h->add($s1);
                $t1 = $t1->add($ch);
                $t1 = $t1->add($k[$i]);
                $t1 = $t1->add($w[$i]);

                $h = $g->copy();
                $g = $f->copy();
                $f = $e->copy();
                $e = $d->add($t1);
                $d = $c->copy();
                $c = $b->copy();
                $b = $a->copy();
                $a = $t1->add($t2);
            }

            // Add this chunk's hash to result so far
            $hash = array(
                $hash[0]->add($a),
                $hash[1]->add($b),
                $hash[2]->add($c),
                $hash[3]->add($d),
                $hash[4]->add($e),
                $hash[5]->add($f),
                $hash[6]->add($g),
                $hash[7]->add($h)
            );
        }

        // Produce the final hash value (big-endian)
        // (\phpseclib\Crypt\Hash::hash() trims the output for hashes but not for HMACs.  as such, we trim the output here)
        $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() .
                $hash[4]->toBytes() . $hash[5]->toBytes();
        if ($this->l != 48) {
            $temp.= $hash[6]->toBytes() . $hash[7]->toBytes();
        }

        return $temp;
    }

    /**
     * Right Rotate
     *
     * @access private
     * @param int $int
     * @param int $amt
     * @see self::_sha256()
     * @return int
     */
    function _rightRotate($int, $amt)
    {
        $invamt = 32 - $amt;
        $mask = (1 << $invamt) - 1;
        return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask);
    }

    /**
     * Right Shift
     *
     * @access private
     * @param int $int
     * @param int $amt
     * @see self::_sha256()
     * @return int
     */
    function _rightShift($int, $amt)
    {
        $mask = (1 << (32 - $amt)) - 1;
        return ($int >> $amt) & $mask;
    }

    /**
     * Not
     *
     * @access private
     * @param int $int
     * @see self::_sha256()
     * @return int
     */
    function _not($int)
    {
        return ~$int & 0xFFFFFFFF;
    }

    /**
     * Add
     *
     * _sha256() adds multiple unsigned 32-bit integers.  Since PHP doesn't support unsigned integers and since the
     * possibility of overflow exists, care has to be taken.  BigInteger could be used but this should be faster.
     *
     * @param int $...
     * @return int
     * @see self::_sha256()
     * @access private
     */
    function _add()
    {
        static $mod;
        if (!isset($mod)) {
            $mod = pow(2, 32);
        }

        $result = 0;
        $arguments = func_get_args();
        foreach ($arguments as $argument) {
            $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument;
        }

        return fmod($result, $mod);
    }

    /**
     * String Shift
     *
     * Inspired by array_shift
     *
     * @param string $string
     * @param int $index
     * @return string
     * @access private
     */
    function _string_shift(&$string, $index = 1)
    {
        $substr = substr($string, 0, $index);
        $string = substr($string, $index);
        return $substr;
    }
}
<?php

/**
 * Random Number Generator
 *
 * PHP version 5
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    echo bin2hex(\phpseclib\Crypt\Random::string(8));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   Random
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP Random Number Generator
 *
 * @package Random
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class Random
{
    /**
     * Generate a random string.
     *
     * Although microoptimizations are generally discouraged as they impair readability this function is ripe with
     * microoptimizations because this function has the potential of being called a huge number of times.
     * eg. for RSA key generation.
     *
     * @param int $length
     * @return string
     */
    static function string($length)
    {
        if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
            try {
                return \random_bytes($length);
            } catch (\Throwable $e) {
                // If a sufficient source of randomness is unavailable, random_bytes() will throw an
                // object that implements the Throwable interface (Exception, TypeError, Error).
                // We don't actually need to do anything here. The string() method should just continue
                // as normal. Note, however, that if we don't have a sufficient source of randomness for
                // random_bytes(), most of the other calls here will fail too, so we'll end up using
                // the PHP implementation.
            }
        }

        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            // method 1. prior to PHP 5.3 this would call rand() on windows hence the function_exists('class_alias') call.
            // ie. class_alias is a function that was introduced in PHP 5.3
            if (extension_loaded('mcrypt') && function_exists('class_alias')) {
                return mcrypt_create_iv($length);
            }
            // method 2. openssl_random_pseudo_bytes was introduced in PHP 5.3.0 but prior to PHP 5.3.4 there was,
            // to quote <http://php.net/ChangeLog-5.php#5.3.4>, "possible blocking behavior". as of 5.3.4
            // openssl_random_pseudo_bytes and mcrypt_create_iv do the exact same thing on Windows. ie. they both
            // call php_win32_get_random_bytes():
            //
            // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/openssl/openssl.c#L5008
            // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1392
            //
            // php_win32_get_random_bytes() is defined thusly:
            //
            // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/win32/winutil.c#L80
            //
            // we're calling it, all the same, in the off chance that the mcrypt extension is not available
            if (extension_loaded('openssl') && version_compare(PHP_VERSION, '5.3.4', '>=')) {
                return openssl_random_pseudo_bytes($length);
            }
        } else {
            // method 1. the fastest
            if (extension_loaded('openssl')) {
                return openssl_random_pseudo_bytes($length);
            }
            // method 2
            static $fp = true;
            if ($fp === true) {
                // warning's will be output unles the error suppression operator is used. errors such as
                // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
                $fp = @fopen('/dev/urandom', 'rb');
            }
            if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource()
                return fread($fp, $length);
            }
            // method 3. pretty much does the same thing as method 2 per the following url:
            // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391
            // surprisingly slower than method 2. maybe that's because mcrypt_create_iv does a bunch of error checking that we're
            // not doing. regardless, this'll only be called if this PHP script couldn't open /dev/urandom due to open_basedir
            // restrictions or some such
            if (extension_loaded('mcrypt')) {
                return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
            }
        }
        // at this point we have no choice but to use a pure-PHP CSPRNG

        // cascade entropy across multiple PHP instances by fixing the session and collecting all
        // environmental variables, including the previous session data and the current session
        // data.
        //
        // mt_rand seeds itself by looking at the PID and the time, both of which are (relatively)
        // easy to guess at. linux uses mouse clicks, keyboard timings, etc, as entropy sources, but
        // PHP isn't low level to be able to use those as sources and on a web server there's not likely
        // going to be a ton of keyboard or mouse action. web servers do have one thing that we can use
        // however, a ton of people visiting the website. obviously you don't want to base your seeding
        // soley on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled
        // by the user and (2) this isn't just looking at the data sent by the current user - it's based
        // on the data sent by all users. one user requests the page and a hash of their info is saved.
        // another user visits the page and the serialization of their data is utilized along with the
        // server envirnment stuff and a hash of the previous http request data (which itself utilizes
        // a hash of the session data before that). certainly an attacker should be assumed to have
        // full control over his own http requests. he, however, is not going to have control over
        // everyone's http requests.
        static $crypto = false, $v;
        if ($crypto === false) {
            // save old session data
            $old_session_id = session_id();
            $old_use_cookies = ini_get('session.use_cookies');
            $old_session_cache_limiter = session_cache_limiter();
            $_OLD_SESSION = isset($_SESSION) ? $_SESSION : false;
            if ($old_session_id != '') {
                session_write_close();
            }

            session_id(1);
            ini_set('session.use_cookies', 0);
            session_cache_limiter('');
            session_start();

            $v = $seed = $_SESSION['seed'] = pack('H*', sha1(
                (isset($_SERVER) ? phpseclib_safe_serialize($_SERVER) : '') .
                (isset($_POST) ? phpseclib_safe_serialize($_POST) : '') .
                (isset($_GET) ? phpseclib_safe_serialize($_GET) : '') .
                (isset($_COOKIE) ? phpseclib_safe_serialize($_COOKIE) : '') .
                phpseclib_safe_serialize($GLOBALS) .
                phpseclib_safe_serialize($_SESSION) .
                phpseclib_safe_serialize($_OLD_SESSION)
            ));
            if (!isset($_SESSION['count'])) {
                $_SESSION['count'] = 0;
            }
            $_SESSION['count']++;

            session_write_close();

            // restore old session data
            if ($old_session_id != '') {
                session_id($old_session_id);
                session_start();
                ini_set('session.use_cookies', $old_use_cookies);
                session_cache_limiter($old_session_cache_limiter);
            } else {
                if ($_OLD_SESSION !== false) {
                    $_SESSION = $_OLD_SESSION;
                    unset($_OLD_SESSION);
                } else {
                    unset($_SESSION);
                }
            }

            // in SSH2 a shared secret and an exchange hash are generated through the key exchange process.
            // the IV client to server is the hash of that "nonce" with the letter A and for the encryption key it's the letter C.
            // if the hash doesn't produce enough a key or an IV that's long enough concat successive hashes of the
            // original hash and the current hash. we'll be emulating that. for more info see the following URL:
            //
            // http://tools.ietf.org/html/rfc4253#section-7.2
            //
            // see the is_string($crypto) part for an example of how to expand the keys
            $key = pack('H*', sha1($seed . 'A'));
            $iv = pack('H*', sha1($seed . 'C'));

            // ciphers are used as per the nist.gov link below. also, see this link:
            //
            // http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Designs_based_on_cryptographic_primitives
            switch (true) {
                case class_exists('\phpseclib\Crypt\AES'):
                    $crypto = new AES(Base::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\Twofish'):
                    $crypto = new Twofish(Base::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\Blowfish'):
                    $crypto = new Blowfish(Base::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\TripleDES'):
                    $crypto = new TripleDES(Base::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\DES'):
                    $crypto = new DES(Base::MODE_CTR);
                    break;
                case class_exists('\phpseclib\Crypt\RC4'):
                    $crypto = new RC4();
                    break;
                default:
                    user_error(__CLASS__ . ' requires at least one symmetric cipher be loaded');
                    return false;
            }

            $crypto->setKey($key);
            $crypto->setIV($iv);
            $crypto->enableContinuousBuffer();
        }

        //return $crypto->encrypt(str_repeat("\0", $length));

        // the following is based off of ANSI X9.31:
        //
        // http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf
        //
        // OpenSSL uses that same standard for it's random numbers:
        //
        // http://www.opensource.apple.com/source/OpenSSL/OpenSSL-38/openssl/fips-1.0/rand/fips_rand.c
        // (do a search for "ANS X9.31 A.2.4")
        $result = '';
        while (strlen($result) < $length) {
            $i = $crypto->encrypt(microtime()); // strlen(microtime()) == 21
            $r = $crypto->encrypt($i ^ $v); // strlen($v) == 20
            $v = $crypto->encrypt($r ^ $i); // strlen($r) == 20
            $result.= $r;
        }
        return substr($result, 0, $length);
    }
}

if (!function_exists('phpseclib_safe_serialize')) {
    /**
     * Safely serialize variables
     *
     * If a class has a private __sleep() method it'll give a fatal error on PHP 5.2 and earlier.
     * PHP 5.3 will emit a warning.
     *
     * @param mixed $arr
     * @access public
     */
    function phpseclib_safe_serialize(&$arr)
    {
        if (is_object($arr)) {
            return '';
        }
        if (!is_array($arr)) {
            return serialize($arr);
        }
        // prevent circular array recursion
        if (isset($arr['__phpseclib_marker'])) {
            return '';
        }
        $safearr = array();
        $arr['__phpseclib_marker'] = true;
        foreach (array_keys($arr) as $key) {
            // do not recurse on the '__phpseclib_marker' key itself, for smaller memory usage
            if ($key !== '__phpseclib_marker') {
                $safearr[$key] = phpseclib_safe_serialize($arr[$key]);
            }
        }
        unset($arr['__phpseclib_marker']);
        return serialize($safearr);
    }
}
<?php

/**
 * Pure-PHP implementation of RC2.
 *
 * Uses mcrypt, if available, and an internal implementation, otherwise.
 *
 * PHP version 5
 *
 * Useful resources are as follows:
 *
 *  - {@link http://tools.ietf.org/html/rfc2268}
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $rc2 = new \phpseclib\Crypt\RC2();
 *
 *    $rc2->setKey('abcdefgh');
 *
 *    $plaintext = str_repeat('a', 1024);
 *
 *    echo $rc2->decrypt($rc2->encrypt($plaintext));
 * ?>
 * </code>
 *
 * @category Crypt
 * @package  RC2
 * @author   Patrick Monnerat <pm@datasphere.ch>
 * @license  http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link     http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP implementation of RC2.
 *
 * @package RC2
 * @access  public
 */
class RC2 extends Base
{
    /**
     * Block Length of the cipher
     *
     * @see \phpseclib\Crypt\Base::block_size
     * @var int
     * @access private
     */
    var $block_size = 8;

    /**
     * The Key
     *
     * @see \phpseclib\Crypt\Base::key
     * @see self::setKey()
     * @var string
     * @access private
     */
    var $key;

    /**
     * The Original (unpadded) Key
     *
     * @see \phpseclib\Crypt\Base::key
     * @see self::setKey()
     * @see self::encrypt()
     * @see self::decrypt()
     * @var string
     * @access private
     */
    var $orig_key;

    /**
     * Don't truncate / null pad key
     *
     * @see \phpseclib\Crypt\Base::_clearBuffers()
     * @var bool
     * @access private
     */
    var $skip_key_adjustment = true;

    /**
     * Key Length (in bytes)
     *
     * @see \phpseclib\Crypt\RC2::setKeyLength()
     * @var int
     * @access private
     */
    var $key_length = 16; // = 128 bits

    /**
     * The mcrypt specific name of the cipher
     *
     * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
     * @var string
     * @access private
     */
    var $cipher_name_mcrypt = 'rc2';

    /**
     * Optimizing value while CFB-encrypting
     *
     * @see \phpseclib\Crypt\Base::cfb_init_len
     * @var int
     * @access private
     */
    var $cfb_init_len = 500;

    /**
     * The key length in bits.
     *
     * @see self::setKeyLength()
     * @see self::setKey()
     * @var int
     * @access private
     * @internal Should be in range [1..1024].
     * @internal Changing this value after setting the key has no effect.
     */
    var $default_key_length = 1024;

    /**
     * The key length in bits.
     *
     * @see self::isValidEnine()
     * @see self::setKey()
     * @var int
     * @access private
     * @internal Should be in range [1..1024].
     */
    var $current_key_length;

    /**
     * The Key Schedule
     *
     * @see self::_setupKey()
     * @var array
     * @access private
     */
    var $keys;

    /**
     * Key expansion randomization table.
     * Twice the same 256-value sequence to save a modulus in key expansion.
     *
     * @see self::setKey()
     * @var array
     * @access private
     */
    var $pitable = array(
        0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED,
        0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D,
        0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E,
        0x62, 0x4C, 0x64, 0x88, 0x44, 0x8B, 0xFB, 0xA2,
        0x17, 0x9A, 0x59, 0xF5, 0x87, 0xB3, 0x4F, 0x13,
        0x61, 0x45, 0x6D, 0x8D, 0x09, 0x81, 0x7D, 0x32,
        0xBD, 0x8F, 0x40, 0xEB, 0x86, 0xB7, 0x7B, 0x0B,
        0xF0, 0x95, 0x21, 0x22, 0x5C, 0x6B, 0x4E, 0x82,
        0x54, 0xD6, 0x65, 0x93, 0xCE, 0x60, 0xB2, 0x1C,
        0x73, 0x56, 0xC0, 0x14, 0xA7, 0x8C, 0xF1, 0xDC,
        0x12, 0x75, 0xCA, 0x1F, 0x3B, 0xBE, 0xE4, 0xD1,
        0x42, 0x3D, 0xD4, 0x30, 0xA3, 0x3C, 0xB6, 0x26,
        0x6F, 0xBF, 0x0E, 0xDA, 0x46, 0x69, 0x07, 0x57,
        0x27, 0xF2, 0x1D, 0x9B, 0xBC, 0x94, 0x43, 0x03,
        0xF8, 0x11, 0xC7, 0xF6, 0x90, 0xEF, 0x3E, 0xE7,
        0x06, 0xC3, 0xD5, 0x2F, 0xC8, 0x66, 0x1E, 0xD7,
        0x08, 0xE8, 0xEA, 0xDE, 0x80, 0x52, 0xEE, 0xF7,
        0x84, 0xAA, 0x72, 0xAC, 0x35, 0x4D, 0x6A, 0x2A,
        0x96, 0x1A, 0xD2, 0x71, 0x5A, 0x15, 0x49, 0x74,
        0x4B, 0x9F, 0xD0, 0x5E, 0x04, 0x18, 0xA4, 0xEC,
        0xC2, 0xE0, 0x41, 0x6E, 0x0F, 0x51, 0xCB, 0xCC,
        0x24, 0x91, 0xAF, 0x50, 0xA1, 0xF4, 0x70, 0x39,
        0x99, 0x7C, 0x3A, 0x85, 0x23, 0xB8, 0xB4, 0x7A,
        0xFC, 0x02, 0x36, 0x5B, 0x25, 0x55, 0x97, 0x31,
        0x2D, 0x5D, 0xFA, 0x98, 0xE3, 0x8A, 0x92, 0xAE,
        0x05, 0xDF, 0x29, 0x10, 0x67, 0x6C, 0xBA, 0xC9,
        0xD3, 0x00, 0xE6, 0xCF, 0xE1, 0x9E, 0xA8, 0x2C,
        0x63, 0x16, 0x01, 0x3F, 0x58, 0xE2, 0x89, 0xA9,
        0x0D, 0x38, 0x34, 0x1B, 0xAB, 0x33, 0xFF, 0xB0,
        0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E,
        0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77,
        0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD,
        0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED,
        0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D,
        0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E,
        0x62, 0x4C, 0x64, 0x88, 0x44, 0x8B, 0xFB, 0xA2,
        0x17, 0x9A, 0x59, 0xF5, 0x87, 0xB3, 0x4F, 0x13,
        0x61, 0x45, 0x6D, 0x8D, 0x09, 0x81, 0x7D, 0x32,
        0xBD, 0x8F, 0x40, 0xEB, 0x86, 0xB7, 0x7B, 0x0B,
        0xF0, 0x95, 0x21, 0x22, 0x5C, 0x6B, 0x4E, 0x82,
        0x54, 0xD6, 0x65, 0x93, 0xCE, 0x60, 0xB2, 0x1C,
        0x73, 0x56, 0xC0, 0x14, 0xA7, 0x8C, 0xF1, 0xDC,
        0x12, 0x75, 0xCA, 0x1F, 0x3B, 0xBE, 0xE4, 0xD1,
        0x42, 0x3D, 0xD4, 0x30, 0xA3, 0x3C, 0xB6, 0x26,
        0x6F, 0xBF, 0x0E, 0xDA, 0x46, 0x69, 0x07, 0x57,
        0x27, 0xF2, 0x1D, 0x9B, 0xBC, 0x94, 0x43, 0x03,
        0xF8, 0x11, 0xC7, 0xF6, 0x90, 0xEF, 0x3E, 0xE7,
        0x06, 0xC3, 0xD5, 0x2F, 0xC8, 0x66, 0x1E, 0xD7,
        0x08, 0xE8, 0xEA, 0xDE, 0x80, 0x52, 0xEE, 0xF7,
        0x84, 0xAA, 0x72, 0xAC, 0x35, 0x4D, 0x6A, 0x2A,
        0x96, 0x1A, 0xD2, 0x71, 0x5A, 0x15, 0x49, 0x74,
        0x4B, 0x9F, 0xD0, 0x5E, 0x04, 0x18, 0xA4, 0xEC,
        0xC2, 0xE0, 0x41, 0x6E, 0x0F, 0x51, 0xCB, 0xCC,
        0x24, 0x91, 0xAF, 0x50, 0xA1, 0xF4, 0x70, 0x39,
        0x99, 0x7C, 0x3A, 0x85, 0x23, 0xB8, 0xB4, 0x7A,
        0xFC, 0x02, 0x36, 0x5B, 0x25, 0x55, 0x97, 0x31,
        0x2D, 0x5D, 0xFA, 0x98, 0xE3, 0x8A, 0x92, 0xAE,
        0x05, 0xDF, 0x29, 0x10, 0x67, 0x6C, 0xBA, 0xC9,
        0xD3, 0x00, 0xE6, 0xCF, 0xE1, 0x9E, 0xA8, 0x2C,
        0x63, 0x16, 0x01, 0x3F, 0x58, 0xE2, 0x89, 0xA9,
        0x0D, 0x38, 0x34, 0x1B, 0xAB, 0x33, 0xFF, 0xB0,
        0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E,
        0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77,
        0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD
    );

    /**
     * Inverse key expansion randomization table.
     *
     * @see self::setKey()
     * @var array
     * @access private
     */
    var $invpitable = array(
        0xD1, 0xDA, 0xB9, 0x6F, 0x9C, 0xC8, 0x78, 0x66,
        0x80, 0x2C, 0xF8, 0x37, 0xEA, 0xE0, 0x62, 0xA4,
        0xCB, 0x71, 0x50, 0x27, 0x4B, 0x95, 0xD9, 0x20,
        0x9D, 0x04, 0x91, 0xE3, 0x47, 0x6A, 0x7E, 0x53,
        0xFA, 0x3A, 0x3B, 0xB4, 0xA8, 0xBC, 0x5F, 0x68,
        0x08, 0xCA, 0x8F, 0x14, 0xD7, 0xC0, 0xEF, 0x7B,
        0x5B, 0xBF, 0x2F, 0xE5, 0xE2, 0x8C, 0xBA, 0x12,
        0xE1, 0xAF, 0xB2, 0x54, 0x5D, 0x59, 0x76, 0xDB,
        0x32, 0xA2, 0x58, 0x6E, 0x1C, 0x29, 0x64, 0xF3,
        0xE9, 0x96, 0x0C, 0x98, 0x19, 0x8D, 0x3E, 0x26,
        0xAB, 0xA5, 0x85, 0x16, 0x40, 0xBD, 0x49, 0x67,
        0xDC, 0x22, 0x94, 0xBB, 0x3C, 0xC1, 0x9B, 0xEB,
        0x45, 0x28, 0x18, 0xD8, 0x1A, 0x42, 0x7D, 0xCC,
        0xFB, 0x65, 0x8E, 0x3D, 0xCD, 0x2A, 0xA3, 0x60,
        0xAE, 0x93, 0x8A, 0x48, 0x97, 0x51, 0x15, 0xF7,
        0x01, 0x0B, 0xB7, 0x36, 0xB1, 0x2E, 0x11, 0xFD,
        0x84, 0x2D, 0x3F, 0x13, 0x88, 0xB3, 0x34, 0x24,
        0x1B, 0xDE, 0xC5, 0x1D, 0x4D, 0x2B, 0x17, 0x31,
        0x74, 0xA9, 0xC6, 0x43, 0x6D, 0x39, 0x90, 0xBE,
        0xC3, 0xB0, 0x21, 0x6B, 0xF6, 0x0F, 0xD5, 0x99,
        0x0D, 0xAC, 0x1F, 0x5C, 0x9E, 0xF5, 0xF9, 0x4C,
        0xD6, 0xDF, 0x89, 0xE4, 0x8B, 0xFF, 0xC7, 0xAA,
        0xE7, 0xED, 0x46, 0x25, 0xB6, 0x06, 0x5E, 0x35,
        0xB5, 0xEC, 0xCE, 0xE8, 0x6C, 0x30, 0x55, 0x61,
        0x4A, 0xFE, 0xA0, 0x79, 0x03, 0xF0, 0x10, 0x72,
        0x7C, 0xCF, 0x52, 0xA6, 0xA7, 0xEE, 0x44, 0xD3,
        0x9A, 0x57, 0x92, 0xD0, 0x5A, 0x7A, 0x41, 0x7F,
        0x0E, 0x00, 0x63, 0xF2, 0x4F, 0x05, 0x83, 0xC9,
        0xA1, 0xD4, 0xDD, 0xC4, 0x56, 0xF4, 0xD2, 0x77,
        0x81, 0x09, 0x82, 0x33, 0x9F, 0x07, 0x86, 0x75,
        0x38, 0x4E, 0x69, 0xF1, 0xAD, 0x23, 0x73, 0x87,
        0x70, 0x02, 0xC2, 0x1E, 0xB8, 0x0A, 0xFC, 0xE6
    );

    /**
     * Test for engine validity
     *
     * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
     *
     * @see \phpseclib\Crypt\Base::__construct()
     * @param int $engine
     * @access public
     * @return bool
     */
    function isValidEngine($engine)
    {
        switch ($engine) {
            case self::ENGINE_OPENSSL:
                if ($this->current_key_length != 128 || strlen($this->orig_key) < 16) {
                    return false;
                }
                $this->cipher_name_openssl_ecb = 'rc2-ecb';
                $this->cipher_name_openssl = 'rc2-' . $this->_openssl_translate_mode();
        }

        return parent::isValidEngine($engine);
    }

    /**
     * Sets the key length.
     *
     * Valid key lengths are 8 to 1024.
     * Calling this function after setting the key has no effect until the next
     *  \phpseclib\Crypt\RC2::setKey() call.
     *
     * @access public
     * @param int $length in bits
     */
    function setKeyLength($length)
    {
        if ($length < 8) {
            $this->default_key_length = 8;
        } elseif ($length > 1024) {
            $this->default_key_length = 128;
        } else {
            $this->default_key_length = $length;
        }
        $this->current_key_length = $this->default_key_length;

        parent::setKeyLength($length);
    }

    /**
     * Returns the current key length
     *
     * @access public
     * @return int
     */
    function getKeyLength()
    {
        return $this->current_key_length;
    }

    /**
     * Sets the key.
     *
     * Keys can be of any length. RC2, itself, uses 8 to 1024 bit keys (eg.
     * strlen($key) <= 128), however, we only use the first 128 bytes if $key
     * has more then 128 bytes in it, and set $key to a single null byte if
     * it is empty.
     *
     * If the key is not explicitly set, it'll be assumed to be a single
     * null byte.
     *
     * @see \phpseclib\Crypt\Base::setKey()
     * @access public
     * @param string $key
     * @param int $t1 optional Effective key length in bits.
     */
    function setKey($key, $t1 = 0)
    {
        $this->orig_key = $key;

        if ($t1 <= 0) {
            $t1 = $this->default_key_length;
        } elseif ($t1 > 1024) {
            $t1 = 1024;
        }
        $this->current_key_length = $t1;
        // Key byte count should be 1..128.
        $key = strlen($key) ? substr($key, 0, 128) : "\x00";
        $t = strlen($key);

        // The mcrypt RC2 implementation only supports effective key length
        // of 1024 bits. It is however possible to handle effective key
        // lengths in range 1..1024 by expanding the key and applying
        // inverse pitable mapping to the first byte before submitting it
        // to mcrypt.

        // Key expansion.
        $l = array_values(unpack('C*', $key));
        $t8 = ($t1 + 7) >> 3;
        $tm = 0xFF >> (8 * $t8 - $t1);

        // Expand key.
        $pitable = $this->pitable;
        for ($i = $t; $i < 128; $i++) {
            $l[$i] = $pitable[$l[$i - 1] + $l[$i - $t]];
        }
        $i = 128 - $t8;
        $l[$i] = $pitable[$l[$i] & $tm];
        while ($i--) {
            $l[$i] = $pitable[$l[$i + 1] ^ $l[$i + $t8]];
        }

        // Prepare the key for mcrypt.
        $l[0] = $this->invpitable[$l[0]];
        array_unshift($l, 'C*');

        parent::setKey(call_user_func_array('pack', $l));
    }

    /**
     * Encrypts a message.
     *
     * Mostly a wrapper for \phpseclib\Crypt\Base::encrypt, with some additional OpenSSL handling code
     *
     * @see self::decrypt()
     * @access public
     * @param string $plaintext
     * @return string $ciphertext
     */
    function encrypt($plaintext)
    {
        if ($this->engine == self::ENGINE_OPENSSL) {
            $temp = $this->key;
            $this->key = $this->orig_key;
            $result = parent::encrypt($plaintext);
            $this->key = $temp;
            return $result;
        }

        return parent::encrypt($plaintext);
    }

    /**
     * Decrypts a message.
     *
     * Mostly a wrapper for \phpseclib\Crypt\Base::decrypt, with some additional OpenSSL handling code
     *
     * @see self::encrypt()
     * @access public
     * @param string $ciphertext
     * @return string $plaintext
     */
    function decrypt($ciphertext)
    {
        if ($this->engine == self::ENGINE_OPENSSL) {
            $temp = $this->key;
            $this->key = $this->orig_key;
            $result = parent::decrypt($ciphertext);
            $this->key = $temp;
            return $result;
        }

        return parent::decrypt($ciphertext);
    }

    /**
     * Encrypts a block
     *
     * @see \phpseclib\Crypt\Base::_encryptBlock()
     * @see \phpseclib\Crypt\Base::encrypt()
     * @access private
     * @param string $in
     * @return string
     */
    function _encryptBlock($in)
    {
        list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in));
        $keys = $this->keys;
        $limit = 20;
        $actions = array($limit => 44, 44 => 64);
        $j = 0;

        for (;;) {
            // Mixing round.
            $r0 = (($r0 + $keys[$j++] + ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1;
            $r0 |= $r0 >> 16;
            $r1 = (($r1 + $keys[$j++] + ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2;
            $r1 |= $r1 >> 16;
            $r2 = (($r2 + $keys[$j++] + ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3;
            $r2 |= $r2 >> 16;
            $r3 = (($r3 + $keys[$j++] + ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5;
            $r3 |= $r3 >> 16;

            if ($j === $limit) {
                if ($limit === 64) {
                    break;
                }

                // Mashing round.
                $r0 += $keys[$r3 & 0x3F];
                $r1 += $keys[$r0 & 0x3F];
                $r2 += $keys[$r1 & 0x3F];
                $r3 += $keys[$r2 & 0x3F];
                $limit = $actions[$limit];
            }
        }

        return pack('vvvv', $r0, $r1, $r2, $r3);
    }

    /**
     * Decrypts a block
     *
     * @see \phpseclib\Crypt\Base::_decryptBlock()
     * @see \phpseclib\Crypt\Base::decrypt()
     * @access private
     * @param string $in
     * @return string
     */
    function _decryptBlock($in)
    {
        list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in));
        $keys = $this->keys;
        $limit = 44;
        $actions = array($limit => 20, 20 => 0);
        $j = 64;

        for (;;) {
            // R-mixing round.
            $r3 = ($r3 | ($r3 << 16)) >> 5;
            $r3 = ($r3 - $keys[--$j] - ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF;
            $r2 = ($r2 | ($r2 << 16)) >> 3;
            $r2 = ($r2 - $keys[--$j] - ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF;
            $r1 = ($r1 | ($r1 << 16)) >> 2;
            $r1 = ($r1 - $keys[--$j] - ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF;
            $r0 = ($r0 | ($r0 << 16)) >> 1;
            $r0 = ($r0 - $keys[--$j] - ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;

            if ($j === $limit) {
                if ($limit === 0) {
                    break;
                }

                // R-mashing round.
                $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF;
                $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF;
                $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF;
                $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;
                $limit = $actions[$limit];
            }
        }

        return pack('vvvv', $r0, $r1, $r2, $r3);
    }

    /**
     * Setup the \phpseclib\Crypt\Base::ENGINE_MCRYPT $engine
     *
     * @see \phpseclib\Crypt\Base::_setupMcrypt()
     * @access private
     */
    function _setupMcrypt()
    {
        if (!isset($this->key)) {
            $this->setKey('');
        }

        parent::_setupMcrypt();
    }

    /**
     * Creates the key schedule
     *
     * @see \phpseclib\Crypt\Base::_setupKey()
     * @access private
     */
    function _setupKey()
    {
        if (!isset($this->key)) {
            $this->setKey('');
        }

        // Key has already been expanded in \phpseclib\Crypt\RC2::setKey():
        // Only the first value must be altered.
        $l = unpack('Ca/Cb/v*', $this->key);
        array_unshift($l, $this->pitable[$l['a']] | ($l['b'] << 8));
        unset($l['a']);
        unset($l['b']);
        $this->keys = $l;
    }

    /**
     * Setup the performance-optimized function for de/encrypt()
     *
     * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
     * @access private
     */
    function _setupInlineCrypt()
    {
        $lambda_functions =& self::_getLambdaFunctions();

        // The first 10 generated $lambda_functions will use the $keys hardcoded as integers
        // for the mixing rounds, for better inline crypt performance [~20% faster].
        // But for memory reason we have to limit those ultra-optimized $lambda_functions to an amount of 10.
        // (Currently, for Crypt_RC2, one generated $lambda_function cost on php5.5@32bit ~60kb unfreeable mem and ~100kb on php5.5@64bit)
        $gen_hi_opt_code = (bool)(count($lambda_functions) < 10);

        // Generation of a unique hash for our generated code
        $code_hash = "Crypt_RC2, {$this->mode}";
        if ($gen_hi_opt_code) {
            $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
        }

        // Is there a re-usable $lambda_functions in there?
        // If not, we have to create it.
        if (!isset($lambda_functions[$code_hash])) {
            // Init code for both, encrypt and decrypt.
            $init_crypt = '$keys = $self->keys;';

            switch (true) {
                case $gen_hi_opt_code:
                    $keys = $this->keys;
                default:
                    $keys = array();
                    foreach ($this->keys as $k => $v) {
                        $keys[$k] = '$keys[' . $k . ']';
                    }
            }

            // $in is the current 8 bytes block which has to be en/decrypt
            $encrypt_block = $decrypt_block = '
                $in = unpack("v4", $in);
                $r0 = $in[1];
                $r1 = $in[2];
                $r2 = $in[3];
                $r3 = $in[4];
            ';

            // Create code for encryption.
            $limit = 20;
            $actions = array($limit => 44, 44 => 64);
            $j = 0;

            for (;;) {
                // Mixing round.
                $encrypt_block .= '
                    $r0 = (($r0 + ' . $keys[$j++] . ' +
                           ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1;
                    $r0 |= $r0 >> 16;
                    $r1 = (($r1 + ' . $keys[$j++] . ' +
                           ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2;
                    $r1 |= $r1 >> 16;
                    $r2 = (($r2 + ' . $keys[$j++] . ' +
                           ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3;
                    $r2 |= $r2 >> 16;
                    $r3 = (($r3 + ' . $keys[$j++] . ' +
                           ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5;
                    $r3 |= $r3 >> 16;';

                if ($j === $limit) {
                    if ($limit === 64) {
                        break;
                    }

                    // Mashing round.
                    $encrypt_block .= '
                        $r0 += $keys[$r3 & 0x3F];
                        $r1 += $keys[$r0 & 0x3F];
                        $r2 += $keys[$r1 & 0x3F];
                        $r3 += $keys[$r2 & 0x3F];';
                    $limit = $actions[$limit];
                }
            }

            $encrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);';

            // Create code for decryption.
            $limit = 44;
            $actions = array($limit => 20, 20 => 0);
            $j = 64;

            for (;;) {
                // R-mixing round.
                $decrypt_block .= '
                    $r3 = ($r3 | ($r3 << 16)) >> 5;
                    $r3 = ($r3 - ' . $keys[--$j] . ' -
                           ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF;
                    $r2 = ($r2 | ($r2 << 16)) >> 3;
                    $r2 = ($r2 - ' . $keys[--$j] . ' -
                           ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF;
                    $r1 = ($r1 | ($r1 << 16)) >> 2;
                    $r1 = ($r1 - ' . $keys[--$j] . ' -
                           ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF;
                    $r0 = ($r0 | ($r0 << 16)) >> 1;
                    $r0 = ($r0 - ' . $keys[--$j] . ' -
                           ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;';

                if ($j === $limit) {
                    if ($limit === 0) {
                        break;
                    }

                    // R-mashing round.
                    $decrypt_block .= '
                        $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF;
                        $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF;
                        $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF;
                        $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;';
                    $limit = $actions[$limit];
                }
            }

            $decrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);';

            // Creates the inline-crypt function
            $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
                array(
                   'init_crypt'    => $init_crypt,
                   'encrypt_block' => $encrypt_block,
                   'decrypt_block' => $decrypt_block
                )
            );
        }

        // Set the inline-crypt function as callback in: $this->inline_crypt
        $this->inline_crypt = $lambda_functions[$code_hash];
    }
}
<?php

/**
 * Pure-PHP implementation of RC4.
 *
 * Uses mcrypt, if available, and an internal implementation, otherwise.
 *
 * PHP version 5
 *
 * Useful resources are as follows:
 *
 *  - {@link http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt ARCFOUR Algorithm}
 *  - {@link http://en.wikipedia.org/wiki/RC4 - Wikipedia: RC4}
 *
 * RC4 is also known as ARCFOUR or ARC4.  The reason is elaborated upon at Wikipedia.  This class is named RC4 and not
 * ARCFOUR or ARC4 because RC4 is how it is referred to in the SSH1 specification.
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $rc4 = new \phpseclib\Crypt\RC4();
 *
 *    $rc4->setKey('abcdefgh');
 *
 *    $size = 10 * 1024;
 *    $plaintext = '';
 *    for ($i = 0; $i < $size; $i++) {
 *        $plaintext.= 'a';
 *    }
 *
 *    echo $rc4->decrypt($rc4->encrypt($plaintext));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   RC4
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP implementation of RC4.
 *
 * @package RC4
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class RC4 extends Base
{
    /**#@+
     * @access private
     * @see \phpseclib\Crypt\RC4::_crypt()
    */
    const ENCRYPT = 0;
    const DECRYPT = 1;
    /**#@-*/

    /**
     * Block Length of the cipher
     *
     * RC4 is a stream cipher
     * so we the block_size to 0
     *
     * @see \phpseclib\Crypt\Base::block_size
     * @var int
     * @access private
     */
    var $block_size = 0;

    /**
     * Key Length (in bytes)
     *
     * @see \phpseclib\Crypt\RC4::setKeyLength()
     * @var int
     * @access private
     */
    var $key_length = 128; // = 1024 bits

    /**
     * The mcrypt specific name of the cipher
     *
     * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
     * @var string
     * @access private
     */
    var $cipher_name_mcrypt = 'arcfour';

    /**
     * Holds whether performance-optimized $inline_crypt() can/should be used.
     *
     * @see \phpseclib\Crypt\Base::inline_crypt
     * @var mixed
     * @access private
     */
    var $use_inline_crypt = false; // currently not available

    /**
     * The Key
     *
     * @see self::setKey()
     * @var string
     * @access private
     */
    var $key = "\0";

    /**
     * The Key Stream for decryption and encryption
     *
     * @see self::setKey()
     * @var array
     * @access private
     */
    var $stream;

    /**
     * Default Constructor.
     *
     * Determines whether or not the mcrypt extension should be used.
     *
     * @see \phpseclib\Crypt\Base::__construct()
     * @return \phpseclib\Crypt\RC4
     * @access public
     */
    function __construct()
    {
        parent::__construct(Base::MODE_STREAM);
    }

    /**
     * Test for engine validity
     *
     * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
     *
     * @see \phpseclib\Crypt\Base::__construct()
     * @param int $engine
     * @access public
     * @return bool
     */
    function isValidEngine($engine)
    {
        switch ($engine) {
            case Base::ENGINE_OPENSSL:
                switch (strlen($this->key)) {
                    case 5:
                        $this->cipher_name_openssl = 'rc4-40';
                        break;
                    case 8:
                        $this->cipher_name_openssl = 'rc4-64';
                        break;
                    case 16:
                        $this->cipher_name_openssl = 'rc4';
                        break;
                    default:
                        return false;
                }
        }

        return parent::isValidEngine($engine);
    }

    /**
     * Dummy function.
     *
     * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1].
     * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before
     * calling setKey().
     *
     * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way.  Since, in that protocol,
     * the IV's are relatively easy to predict, an attack described by
     * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir}
     * can be used to quickly guess at the rest of the key.  The following links elaborate:
     *
     * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009}
     * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack}
     *
     * @param string $iv
     * @see self::setKey()
     * @access public
     */
    function setIV($iv)
    {
    }

    /**
     * Sets the key length
     *
     * Keys can be between 1 and 256 bytes long.
     *
     * @access public
     * @param int $length
     */
    function setKeyLength($length)
    {
        if ($length < 8) {
            $this->key_length = 1;
        } elseif ($length > 2048) {
            $this->key_length = 256;
        } else {
            $this->key_length = $length >> 3;
        }

        parent::setKeyLength($length);
    }

    /**
     * Encrypts a message.
     *
     * @see \phpseclib\Crypt\Base::decrypt()
     * @see self::_crypt()
     * @access public
     * @param string $plaintext
     * @return string $ciphertext
     */
    function encrypt($plaintext)
    {
        if ($this->engine != Base::ENGINE_INTERNAL) {
            return parent::encrypt($plaintext);
        }
        return $this->_crypt($plaintext, self::ENCRYPT);
    }

    /**
     * Decrypts a message.
     *
     * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
     * At least if the continuous buffer is disabled.
     *
     * @see \phpseclib\Crypt\Base::encrypt()
     * @see self::_crypt()
     * @access public
     * @param string $ciphertext
     * @return string $plaintext
     */
    function decrypt($ciphertext)
    {
        if ($this->engine != Base::ENGINE_INTERNAL) {
            return parent::decrypt($ciphertext);
        }
        return $this->_crypt($ciphertext, self::DECRYPT);
    }

    /**
     * Encrypts a block
     *
     * @access private
     * @param string $in
     */
    function _encryptBlock($in)
    {
        // RC4 does not utilize this method
    }

    /**
     * Decrypts a block
     *
     * @access private
     * @param string $in
     */
    function _decryptBlock($in)
    {
        // RC4 does not utilize this method
    }

    /**
     * Setup the key (expansion)
     *
     * @see \phpseclib\Crypt\Base::_setupKey()
     * @access private
     */
    function _setupKey()
    {
        $key = $this->key;
        $keyLength = strlen($key);
        $keyStream = range(0, 255);
        $j = 0;
        for ($i = 0; $i < 256; $i++) {
            $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255;
            $temp = $keyStream[$i];
            $keyStream[$i] = $keyStream[$j];
            $keyStream[$j] = $temp;
        }

        $this->stream = array();
        $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = array(
            0, // index $i
            0, // index $j
            $keyStream
        );
    }

    /**
     * Encrypts or decrypts a message.
     *
     * @see self::encrypt()
     * @see self::decrypt()
     * @access private
     * @param string $text
     * @param int $mode
     * @return string $text
     */
    function _crypt($text, $mode)
    {
        if ($this->changed) {
            $this->_setup();
            $this->changed = false;
        }

        $stream = &$this->stream[$mode];
        if ($this->continuousBuffer) {
            $i = &$stream[0];
            $j = &$stream[1];
            $keyStream = &$stream[2];
        } else {
            $i = $stream[0];
            $j = $stream[1];
            $keyStream = $stream[2];
        }

        $len = strlen($text);
        for ($k = 0; $k < $len; ++$k) {
            $i = ($i + 1) & 255;
            $ksi = $keyStream[$i];
            $j = ($j + $ksi) & 255;
            $ksj = $keyStream[$j];

            $keyStream[$i] = $ksj;
            $keyStream[$j] = $ksi;
            $text[$k] = $text[$k] ^ chr($keyStream[($ksj + $ksi) & 255]);
        }

        return $text;
    }
}
<?php

/**
 * Pure-PHP implementation of Rijndael.
 *
 * Uses mcrypt, if available/possible, and an internal implementation, otherwise.
 *
 * PHP version 5
 *
 * If {@link self::setBlockLength() setBlockLength()} isn't called, it'll be assumed to be 128 bits.  If
 * {@link self::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
 * {@link self::setKey() setKey()}.  ie. if the key is 128-bits, the key length will be 128-bits.  If it's
 * 136-bits it'll be null-padded to 192-bits and 192 bits will be the key length until
 * {@link self::setKey() setKey()} is called, again, at which point, it'll be recalculated.
 *
 * Not all Rijndael implementations may support 160-bits or 224-bits as the block length / key length.  mcrypt, for example,
 * does not.  AES, itself, only supports block lengths of 128 and key lengths of 128, 192, and 256.
 * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=10 Rijndael-ammended.pdf#page=10} defines the
 * algorithm for block lengths of 192 and 256 but not for block lengths / key lengths of 160 and 224.  Indeed, 160 and 224
 * are first defined as valid key / block lengths in
 * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=44 Rijndael-ammended.pdf#page=44}:
 * Extensions: Other block and Cipher Key lengths.
 * Note: Use of 160/224-bit Keys must be explicitly set by setKeyLength(160) respectively setKeyLength(224).
 *
 * {@internal The variable names are the same as those in
 * {@link http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf#page=10 fips-197.pdf#page=10}.}}
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $rijndael = new \phpseclib\Crypt\Rijndael();
 *
 *    $rijndael->setKey('abcdefghijklmnop');
 *
 *    $size = 10 * 1024;
 *    $plaintext = '';
 *    for ($i = 0; $i < $size; $i++) {
 *        $plaintext.= 'a';
 *    }
 *
 *    echo $rijndael->decrypt($rijndael->encrypt($plaintext));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   Rijndael
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2008 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP implementation of Rijndael.
 *
 * @package Rijndael
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class Rijndael extends Base
{
    /**
     * The mcrypt specific name of the cipher
     *
     * Mcrypt is useable for 128/192/256-bit $block_size/$key_length. For 160/224 not.
     * \phpseclib\Crypt\Rijndael determines automatically whether mcrypt is useable
     * or not for the current $block_size/$key_length.
     * In case of, $cipher_name_mcrypt will be set dynamically at run time accordingly.
     *
     * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
     * @see \phpseclib\Crypt\Base::engine
     * @see self::isValidEngine()
     * @var string
     * @access private
     */
    var $cipher_name_mcrypt = 'rijndael-128';

    /**
     * The default salt used by setPassword()
     *
     * @see \phpseclib\Crypt\Base::password_default_salt
     * @see \phpseclib\Crypt\Base::setPassword()
     * @var string
     * @access private
     */
    var $password_default_salt = 'phpseclib';

    /**
     * The Key Schedule
     *
     * @see self::_setup()
     * @var array
     * @access private
     */
    var $w;

    /**
     * The Inverse Key Schedule
     *
     * @see self::_setup()
     * @var array
     * @access private
     */
    var $dw;

    /**
     * The Block Length divided by 32
     *
     * @see self::setBlockLength()
     * @var int
     * @access private
     * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4.  Exists in conjunction with $block_size
     *    because the encryption / decryption / key schedule creation requires this number and not $block_size.  We could
     *    derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
     *    of that, we'll just precompute it once.
     */
    var $Nb = 4;

    /**
     * The Key Length (in bytes)
     *
     * @see self::setKeyLength()
     * @var int
     * @access private
     * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16.  Exists in conjunction with $Nk
     *    because the encryption / decryption / key schedule creation requires this number and not $key_length.  We could
     *    derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
     *    of that, we'll just precompute it once.
     */
    var $key_length = 16;

    /**
     * The Key Length divided by 32
     *
     * @see self::setKeyLength()
     * @var int
     * @access private
     * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4
     */
    var $Nk = 4;

    /**
     * The Number of Rounds
     *
     * @var int
     * @access private
     * @internal The max value is 14, the min value is 10.
     */
    var $Nr;

    /**
     * Shift offsets
     *
     * @var array
     * @access private
     */
    var $c;

    /**
     * Holds the last used key- and block_size information
     *
     * @var array
     * @access private
     */
    var $kl;

    /**
     * Sets the key length.
     *
     * Valid key lengths are 128, 160, 192, 224, and 256.  If the length is less than 128, it will be rounded up to
     * 128.  If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount.
     *
     * Note: phpseclib extends Rijndael (and AES) for using 160- and 224-bit keys but they are officially not defined
     *       and the most (if not all) implementations are not able using 160/224-bit keys but round/pad them up to
     *       192/256 bits as, for example, mcrypt will do.
     *
     *       That said, if you want be compatible with other Rijndael and AES implementations,
     *       you should not setKeyLength(160) or setKeyLength(224).
     *
     * Additional: In case of 160- and 224-bit keys, phpseclib will/can, for that reason, not use
     *             the mcrypt php extension, even if available.
     *             This results then in slower encryption.
     *
     * @access public
     * @param int $length
     */
    function setKeyLength($length)
    {
        switch (true) {
            case $length <= 128:
                $this->key_length = 16;
                break;
            case $length <= 160:
                $this->key_length = 20;
                break;
            case $length <= 192:
                $this->key_length = 24;
                break;
            case $length <= 224:
                $this->key_length = 28;
                break;
            default:
                $this->key_length = 32;
        }

        parent::setKeyLength($length);
    }

    /**
     * Sets the block length
     *
     * Valid block lengths are 128, 160, 192, 224, and 256.  If the length is less than 128, it will be rounded up to
     * 128.  If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount.
     *
     * @access public
     * @param int $length
     */
    function setBlockLength($length)
    {
        $length >>= 5;
        if ($length > 8) {
            $length = 8;
        } elseif ($length < 4) {
            $length = 4;
        }
        $this->Nb = $length;
        $this->block_size = $length << 2;
        $this->changed = true;
        $this->_setEngine();
    }

    /**
     * Test for engine validity
     *
     * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
     *
     * @see \phpseclib\Crypt\Base::__construct()
     * @param int $engine
     * @access public
     * @return bool
     */
    function isValidEngine($engine)
    {
        switch ($engine) {
            case self::ENGINE_OPENSSL:
                if ($this->block_size != 16) {
                    return false;
                }
                $this->cipher_name_openssl_ecb = 'aes-' . ($this->key_length << 3) . '-ecb';
                $this->cipher_name_openssl = 'aes-' . ($this->key_length << 3) . '-' . $this->_openssl_translate_mode();
                break;
            case self::ENGINE_MCRYPT:
                $this->cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3);
                if ($this->key_length % 8) { // is it a 160/224-bit key?
                    // mcrypt is not usable for them, only for 128/192/256-bit keys
                    return false;
                }
        }

        return parent::isValidEngine($engine);
    }

    /**
     * Encrypts a block
     *
     * @access private
     * @param string $in
     * @return string
     */
    function _encryptBlock($in)
    {
        static $tables;
        if (empty($tables)) {
            $tables = &$this->_getTables();
        }
        $t0   = $tables[0];
        $t1   = $tables[1];
        $t2   = $tables[2];
        $t3   = $tables[3];
        $sbox = $tables[4];

        $state = array();
        $words = unpack('N*', $in);

        $c = $this->c;
        $w = $this->w;
        $Nb = $this->Nb;
        $Nr = $this->Nr;

        // addRoundKey
        $wc = $Nb - 1;
        foreach ($words as $word) {
            $state[] = $word ^ $w[++$wc];
        }

        // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components -
        // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding
        // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf.
        // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization.
        // Unfortunately, the description given there is not quite correct.  Per aes.spec.v316.pdf#page=19 [1],
        // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well.

        // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf
        $temp = array();
        for ($round = 1; $round < $Nr; ++$round) {
            $i = 0; // $c[0] == 0
            $j = $c[1];
            $k = $c[2];
            $l = $c[3];

            while ($i < $Nb) {
                $temp[$i] = $t0[$state[$i] >> 24 & 0x000000FF] ^
                            $t1[$state[$j] >> 16 & 0x000000FF] ^
                            $t2[$state[$k] >>  8 & 0x000000FF] ^
                            $t3[$state[$l]       & 0x000000FF] ^
                            $w[++$wc];
                ++$i;
                $j = ($j + 1) % $Nb;
                $k = ($k + 1) % $Nb;
                $l = ($l + 1) % $Nb;
            }
            $state = $temp;
        }

        // subWord
        for ($i = 0; $i < $Nb; ++$i) {
            $state[$i] =   $sbox[$state[$i]       & 0x000000FF]        |
                          ($sbox[$state[$i] >>  8 & 0x000000FF] <<  8) |
                          ($sbox[$state[$i] >> 16 & 0x000000FF] << 16) |
                          ($sbox[$state[$i] >> 24 & 0x000000FF] << 24);
        }

        // shiftRows + addRoundKey
        $i = 0; // $c[0] == 0
        $j = $c[1];
        $k = $c[2];
        $l = $c[3];
        while ($i < $Nb) {
            $temp[$i] = ($state[$i] & 0xFF000000) ^
                        ($state[$j] & 0x00FF0000) ^
                        ($state[$k] & 0x0000FF00) ^
                        ($state[$l] & 0x000000FF) ^
                         $w[$i];
            ++$i;
            $j = ($j + 1) % $Nb;
            $k = ($k + 1) % $Nb;
            $l = ($l + 1) % $Nb;
        }

        switch ($Nb) {
            case 8:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]);
            case 7:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]);
            case 6:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]);
            case 5:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]);
            default:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]);
        }
    }

    /**
     * Decrypts a block
     *
     * @access private
     * @param string $in
     * @return string
     */
    function _decryptBlock($in)
    {
        static $invtables;
        if (empty($invtables)) {
            $invtables = &$this->_getInvTables();
        }
        $dt0   = $invtables[0];
        $dt1   = $invtables[1];
        $dt2   = $invtables[2];
        $dt3   = $invtables[3];
        $isbox = $invtables[4];

        $state = array();
        $words = unpack('N*', $in);

        $c  = $this->c;
        $dw = $this->dw;
        $Nb = $this->Nb;
        $Nr = $this->Nr;

        // addRoundKey
        $wc = $Nb - 1;
        foreach ($words as $word) {
            $state[] = $word ^ $dw[++$wc];
        }

        $temp = array();
        for ($round = $Nr - 1; $round > 0; --$round) {
            $i = 0; // $c[0] == 0
            $j = $Nb - $c[1];
            $k = $Nb - $c[2];
            $l = $Nb - $c[3];

            while ($i < $Nb) {
                $temp[$i] = $dt0[$state[$i] >> 24 & 0x000000FF] ^
                            $dt1[$state[$j] >> 16 & 0x000000FF] ^
                            $dt2[$state[$k] >>  8 & 0x000000FF] ^
                            $dt3[$state[$l]       & 0x000000FF] ^
                            $dw[++$wc];
                ++$i;
                $j = ($j + 1) % $Nb;
                $k = ($k + 1) % $Nb;
                $l = ($l + 1) % $Nb;
            }
            $state = $temp;
        }

        // invShiftRows + invSubWord + addRoundKey
        $i = 0; // $c[0] == 0
        $j = $Nb - $c[1];
        $k = $Nb - $c[2];
        $l = $Nb - $c[3];

        while ($i < $Nb) {
            $word = ($state[$i] & 0xFF000000) |
                    ($state[$j] & 0x00FF0000) |
                    ($state[$k] & 0x0000FF00) |
                    ($state[$l] & 0x000000FF);

            $temp[$i] = $dw[$i] ^ ($isbox[$word       & 0x000000FF]        |
                                  ($isbox[$word >>  8 & 0x000000FF] <<  8) |
                                  ($isbox[$word >> 16 & 0x000000FF] << 16) |
                                  ($isbox[$word >> 24 & 0x000000FF] << 24));
            ++$i;
            $j = ($j + 1) % $Nb;
            $k = ($k + 1) % $Nb;
            $l = ($l + 1) % $Nb;
        }

        switch ($Nb) {
            case 8:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]);
            case 7:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]);
            case 6:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]);
            case 5:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]);
            default:
                return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]);
        }
    }

    /**
     * Setup the key (expansion)
     *
     * @see \phpseclib\Crypt\Base::_setupKey()
     * @access private
     */
    function _setupKey()
    {
        // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field.
        // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse
        static $rcon = array(0,
            0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
            0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000,
            0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000,
            0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000,
            0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000,
            0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000
        );

        if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->key_length === $this->kl['key_length'] && $this->block_size === $this->kl['block_size']) {
            // already expanded
            return;
        }
        $this->kl = array('key' => $this->key, 'key_length' => $this->key_length, 'block_size' => $this->block_size);

        $this->Nk = $this->key_length >> 2;
        // see Rijndael-ammended.pdf#page=44
        $this->Nr = max($this->Nk, $this->Nb) + 6;

        // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44,
        //     "Table 8: Shift offsets in Shiftrow for the alternative block lengths"
        // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14,
        //     "Table 2: Shift offsets for different block lengths"
        switch ($this->Nb) {
            case 4:
            case 5:
            case 6:
                $this->c = array(0, 1, 2, 3);
                break;
            case 7:
                $this->c = array(0, 1, 2, 4);
                break;
            case 8:
                $this->c = array(0, 1, 3, 4);
        }

        $w = array_values(unpack('N*words', $this->key));

        $length = $this->Nb * ($this->Nr + 1);
        for ($i = $this->Nk; $i < $length; $i++) {
            $temp = $w[$i - 1];
            if ($i % $this->Nk == 0) {
                // according to <http://php.net/language.types.integer>, "the size of an integer is platform-dependent".
                // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine,
                // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and'
                // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is.
                $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord
                $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk];
            } elseif ($this->Nk > 6 && $i % $this->Nk == 4) {
                $temp = $this->_subWord($temp);
            }
            $w[$i] = $w[$i - $this->Nk] ^ $temp;
        }

        // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns
        // and generate the inverse key schedule.  more specifically,
        // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=23> (section 5.3.3),
        // "The key expansion for the Inverse Cipher is defined as follows:
        //        1. Apply the Key Expansion.
        //        2. Apply InvMixColumn to all Round Keys except the first and the last one."
        // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher"
        list($dt0, $dt1, $dt2, $dt3) = $this->_getInvTables();
        $temp = $this->w = $this->dw = array();
        for ($i = $row = $col = 0; $i < $length; $i++, $col++) {
            if ($col == $this->Nb) {
                if ($row == 0) {
                    $this->dw[0] = $this->w[0];
                } else {
                    // subWord + invMixColumn + invSubWord = invMixColumn
                    $j = 0;
                    while ($j < $this->Nb) {
                        $dw = $this->_subWord($this->w[$row][$j]);
                        $temp[$j] = $dt0[$dw >> 24 & 0x000000FF] ^
                                    $dt1[$dw >> 16 & 0x000000FF] ^
                                    $dt2[$dw >>  8 & 0x000000FF] ^
                                    $dt3[$dw       & 0x000000FF];
                        $j++;
                    }
                    $this->dw[$row] = $temp;
                }

                $col = 0;
                $row++;
            }
            $this->w[$row][$col] = $w[$i];
        }

        $this->dw[$row] = $this->w[$row];

        // Converting to 1-dim key arrays (both ascending)
        $this->dw = array_reverse($this->dw);
        $w  = array_pop($this->w);
        $dw = array_pop($this->dw);
        foreach ($this->w as $r => $wr) {
            foreach ($wr as $c => $wc) {
                $w[]  = $wc;
                $dw[] = $this->dw[$r][$c];
            }
        }
        $this->w  = $w;
        $this->dw = $dw;
    }

    /**
     * Performs S-Box substitutions
     *
     * @access private
     * @param int $word
     */
    function _subWord($word)
    {
        static $sbox;
        if (empty($sbox)) {
            list(, , , , $sbox) = $this->_getTables();
        }

        return  $sbox[$word       & 0x000000FF]        |
               ($sbox[$word >>  8 & 0x000000FF] <<  8) |
               ($sbox[$word >> 16 & 0x000000FF] << 16) |
               ($sbox[$word >> 24 & 0x000000FF] << 24);
    }

    /**
     * Provides the mixColumns and sboxes tables
     *
     * @see self::_encryptBlock()
     * @see self::_setupInlineCrypt()
     * @see self::_subWord()
     * @access private
     * @return array &$tables
     */
    function &_getTables()
    {
        static $tables;
        if (empty($tables)) {
            // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=19> (section 5.2.1),
            // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so
            // those are the names we'll use.
            $t3 = array_map('intval', array(
                // with array_map('intval', ...) we ensure we have only int's and not
                // some slower floats converted by php automatically on high values
                0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491,
                0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC,
                0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB,
                0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B,
                0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83,
                0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A,
                0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F,
                0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA,
                0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B,
                0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713,
                0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6,
                0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85,
                0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411,
                0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B,
                0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1,
                0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF,
                0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E,
                0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6,
                0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B,
                0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD,
                0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8,
                0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2,
                0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049,
                0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810,
                0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197,
                0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F,
                0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C,
                0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927,
                0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733,
                0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5,
                0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0,
                0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C
            ));

            foreach ($t3 as $t3i) {
                $t0[] = (($t3i << 24) & 0xFF000000) | (($t3i >>  8) & 0x00FFFFFF);
                $t1[] = (($t3i << 16) & 0xFFFF0000) | (($t3i >> 16) & 0x0000FFFF);
                $t2[] = (($t3i <<  8) & 0xFFFFFF00) | (($t3i >> 24) & 0x000000FF);
            }

            $tables = array(
                // The Precomputed mixColumns tables t0 - t3
                $t0,
                $t1,
                $t2,
                $t3,
                // The SubByte S-Box
                array(
                    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
                    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
                    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
                    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
                    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
                    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
                    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
                    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
                    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
                    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
                    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
                    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
                    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
                    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
                    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
                    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
                )
            );
        }
        return $tables;
    }

    /**
     * Provides the inverse mixColumns and inverse sboxes tables
     *
     * @see self::_decryptBlock()
     * @see self::_setupInlineCrypt()
     * @see self::_setupKey()
     * @access private
     * @return array &$tables
     */
    function &_getInvTables()
    {
        static $tables;
        if (empty($tables)) {
            $dt3 = array_map('intval', array(
                0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B,
                0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5,
                0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B,
                0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E,
                0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D,
                0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9,
                0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66,
                0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED,
                0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4,
                0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD,
                0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60,
                0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79,
                0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C,
                0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24,
                0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C,
                0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814,
                0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B,
                0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084,
                0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077,
                0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22,
                0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F,
                0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582,
                0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB,
                0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF,
                0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035,
                0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17,
                0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46,
                0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D,
                0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A,
                0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678,
                0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF,
                0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0
            ));

            foreach ($dt3 as $dt3i) {
                $dt0[] = (($dt3i << 24) & 0xFF000000) | (($dt3i >>  8) & 0x00FFFFFF);
                $dt1[] = (($dt3i << 16) & 0xFFFF0000) | (($dt3i >> 16) & 0x0000FFFF);
                $dt2[] = (($dt3i <<  8) & 0xFFFFFF00) | (($dt3i >> 24) & 0x000000FF);
            };

            $tables = array(
                // The Precomputed inverse mixColumns tables dt0 - dt3
                $dt0,
                $dt1,
                $dt2,
                $dt3,
                // The inverse SubByte S-Box
                array(
                    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
                    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
                    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
                    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
                    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
                    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
                    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
                    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
                    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
                    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
                    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
                    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
                    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
                    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
                    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
                    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
                )
            );
        }
        return $tables;
    }

    /**
     * Setup the performance-optimized function for de/encrypt()
     *
     * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
     * @access private
     */
    function _setupInlineCrypt()
    {
        // Note: _setupInlineCrypt() will be called only if $this->changed === true
        // So here we are'nt under the same heavy timing-stress as we are in _de/encryptBlock() or de/encrypt().
        // However...the here generated function- $code, stored as php callback in $this->inline_crypt, must work as fast as even possible.

        $lambda_functions =& self::_getLambdaFunctions();

        // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function.
        // (Currently, for Crypt_Rijndael/AES, one generated $lambda_function cost on php5.5@32bit ~80kb unfreeable mem and ~130kb on php5.5@64bit)
        // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one.
        $gen_hi_opt_code = (bool)(count($lambda_functions) < 10);

        // Generation of a uniqe hash for our generated code
        $code_hash = "Crypt_Rijndael, {$this->mode}, {$this->Nr}, {$this->Nb}";
        if ($gen_hi_opt_code) {
            $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
        }

        if (!isset($lambda_functions[$code_hash])) {
            switch (true) {
                case $gen_hi_opt_code:
                    // The hi-optimized $lambda_functions will use the key-words hardcoded for better performance.
                    $w  = $this->w;
                    $dw = $this->dw;
                    $init_encrypt = '';
                    $init_decrypt = '';
                    break;
                default:
                    for ($i = 0, $cw = count($this->w); $i < $cw; ++$i) {
                        $w[]  = '$w['  . $i . ']';
                        $dw[] = '$dw[' . $i . ']';
                    }
                    $init_encrypt = '$w  = $self->w;';
                    $init_decrypt = '$dw = $self->dw;';
            }

            $Nr = $this->Nr;
            $Nb = $this->Nb;
            $c  = $this->c;

            // Generating encrypt code:
            $init_encrypt.= '
                static $tables;
                if (empty($tables)) {
                    $tables = &$self->_getTables();
                }
                $t0   = $tables[0];
                $t1   = $tables[1];
                $t2   = $tables[2];
                $t3   = $tables[3];
                $sbox = $tables[4];
            ';

            $s  = 'e';
            $e  = 's';
            $wc = $Nb - 1;

            // Preround: addRoundKey
            $encrypt_block = '$in = unpack("N*", $in);'."\n";
            for ($i = 0; $i < $Nb; ++$i) {
                $encrypt_block .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$w[++$wc].";\n";
            }

            // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
            for ($round = 1; $round < $Nr; ++$round) {
                list($s, $e) = array($e, $s);
                for ($i = 0; $i < $Nb; ++$i) {
                    $encrypt_block.=
                        '$'.$e.$i.' =
                        $t0[($'.$s.$i                  .' >> 24) & 0xff] ^
                        $t1[($'.$s.(($i + $c[1]) % $Nb).' >> 16) & 0xff] ^
                        $t2[($'.$s.(($i + $c[2]) % $Nb).' >>  8) & 0xff] ^
                        $t3[ $'.$s.(($i + $c[3]) % $Nb).'        & 0xff] ^
                        '.$w[++$wc].";\n";
                }
            }

            // Finalround: subWord + shiftRows + addRoundKey
            for ($i = 0; $i < $Nb; ++$i) {
                $encrypt_block.=
                    '$'.$e.$i.' =
                     $sbox[ $'.$e.$i.'        & 0xff]        |
                    ($sbox[($'.$e.$i.' >>  8) & 0xff] <<  8) |
                    ($sbox[($'.$e.$i.' >> 16) & 0xff] << 16) |
                    ($sbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n";
            }
            $encrypt_block .= '$in = pack("N*"'."\n";
            for ($i = 0; $i < $Nb; ++$i) {
                $encrypt_block.= ',
                    ($'.$e.$i                  .' & '.((int)0xFF000000).') ^
                    ($'.$e.(($i + $c[1]) % $Nb).' &         0x00FF0000   ) ^
                    ($'.$e.(($i + $c[2]) % $Nb).' &         0x0000FF00   ) ^
                    ($'.$e.(($i + $c[3]) % $Nb).' &         0x000000FF   ) ^
                    '.$w[$i]."\n";
            }
            $encrypt_block .= ');';

            // Generating decrypt code:
            $init_decrypt.= '
                static $invtables;
                if (empty($invtables)) {
                    $invtables = &$self->_getInvTables();
                }
                $dt0   = $invtables[0];
                $dt1   = $invtables[1];
                $dt2   = $invtables[2];
                $dt3   = $invtables[3];
                $isbox = $invtables[4];
            ';

            $s  = 'e';
            $e  = 's';
            $wc = $Nb - 1;

            // Preround: addRoundKey
            $decrypt_block = '$in = unpack("N*", $in);'."\n";
            for ($i = 0; $i < $Nb; ++$i) {
                $decrypt_block .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$dw[++$wc].';'."\n";
            }

            // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
            for ($round = 1; $round < $Nr; ++$round) {
                list($s, $e) = array($e, $s);
                for ($i = 0; $i < $Nb; ++$i) {
                    $decrypt_block.=
                        '$'.$e.$i.' =
                        $dt0[($'.$s.$i                        .' >> 24) & 0xff] ^
                        $dt1[($'.$s.(($Nb + $i - $c[1]) % $Nb).' >> 16) & 0xff] ^
                        $dt2[($'.$s.(($Nb + $i - $c[2]) % $Nb).' >>  8) & 0xff] ^
                        $dt3[ $'.$s.(($Nb + $i - $c[3]) % $Nb).'        & 0xff] ^
                        '.$dw[++$wc].";\n";
                }
            }

            // Finalround: subWord + shiftRows + addRoundKey
            for ($i = 0; $i < $Nb; ++$i) {
                $decrypt_block.=
                    '$'.$e.$i.' =
                     $isbox[ $'.$e.$i.'        & 0xff]        |
                    ($isbox[($'.$e.$i.' >>  8) & 0xff] <<  8) |
                    ($isbox[($'.$e.$i.' >> 16) & 0xff] << 16) |
                    ($isbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n";
            }
            $decrypt_block .= '$in = pack("N*"'."\n";
            for ($i = 0; $i < $Nb; ++$i) {
                $decrypt_block.= ',
                    ($'.$e.$i.                        ' & '.((int)0xFF000000).') ^
                    ($'.$e.(($Nb + $i - $c[1]) % $Nb).' &         0x00FF0000   ) ^
                    ($'.$e.(($Nb + $i - $c[2]) % $Nb).' &         0x0000FF00   ) ^
                    ($'.$e.(($Nb + $i - $c[3]) % $Nb).' &         0x000000FF   ) ^
                    '.$dw[$i]."\n";
            }
            $decrypt_block .= ');';

            $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
                array(
                   'init_crypt'    => '',
                   'init_encrypt'  => $init_encrypt,
                   'init_decrypt'  => $init_decrypt,
                   'encrypt_block' => $encrypt_block,
                   'decrypt_block' => $decrypt_block
                )
            );
        }
        $this->inline_crypt = $lambda_functions[$code_hash];
    }
}
<?php

/**
 * Pure-PHP PKCS#1 (v2.1) compliant implementation of RSA.
 *
 * PHP version 5
 *
 * Here's an example of how to encrypt and decrypt text with this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $rsa = new \phpseclib\Crypt\RSA();
 *    extract($rsa->createKey());
 *
 *    $plaintext = 'terrafrost';
 *
 *    $rsa->loadKey($privatekey);
 *    $ciphertext = $rsa->encrypt($plaintext);
 *
 *    $rsa->loadKey($publickey);
 *    echo $rsa->decrypt($ciphertext);
 * ?>
 * </code>
 *
 * Here's an example of how to create signatures and verify signatures with this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $rsa = new \phpseclib\Crypt\RSA();
 *    extract($rsa->createKey());
 *
 *    $plaintext = 'terrafrost';
 *
 *    $rsa->loadKey($privatekey);
 *    $signature = $rsa->sign($plaintext);
 *
 *    $rsa->loadKey($publickey);
 *    echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified';
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   RSA
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2009 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

use phpseclib\Math\BigInteger;

/**
 * Pure-PHP PKCS#1 compliant implementation of RSA.
 *
 * @package RSA
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class RSA
{
    /**#@+
     * @access public
     * @see self::encrypt()
     * @see self::decrypt()
     */
    /**
     * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding}
     * (OAEP) for encryption / decryption.
     *
     * Uses sha1 by default.
     *
     * @see self::setHash()
     * @see self::setMGFHash()
     */
    const ENCRYPTION_OAEP = 1;
    /**
     * Use PKCS#1 padding.
     *
     * Although self::ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards
     * compatibility with protocols (like SSH-1) written before OAEP's introduction.
     */
    const ENCRYPTION_PKCS1 = 2;
    /**
     * Do not use any padding
     *
     * Although this method is not recommended it can none-the-less sometimes be useful if you're trying to decrypt some legacy
     * stuff, if you're trying to diagnose why an encrypted message isn't decrypting, etc.
     */
    const ENCRYPTION_NONE = 3;
    /**#@-*/

    /**#@+
     * @access public
     * @see self::sign()
     * @see self::verify()
     * @see self::setHash()
    */
    /**
     * Use the Probabilistic Signature Scheme for signing
     *
     * Uses sha1 by default.
     *
     * @see self::setSaltLength()
     * @see self::setMGFHash()
     */
    const SIGNATURE_PSS = 1;
    /**
     * Use the PKCS#1 scheme by default.
     *
     * Although self::SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards
     * compatibility with protocols (like SSH-2) written before PSS's introduction.
     */
    const SIGNATURE_PKCS1 = 2;
    /**#@-*/

    /**#@+
     * @access private
     * @see \phpseclib\Crypt\RSA::createKey()
    */
    /**
     * ASN1 Integer
     */
    const ASN1_INTEGER = 2;
    /**
     * ASN1 Bit String
     */
    const ASN1_BITSTRING = 3;
    /**
     * ASN1 Octet String
     */
    const ASN1_OCTETSTRING = 4;
    /**
     * ASN1 Object Identifier
     */
    const ASN1_OBJECT = 6;
    /**
     * ASN1 Sequence (with the constucted bit set)
     */
    const ASN1_SEQUENCE = 48;
    /**#@-*/

    /**#@+
     * @access private
     * @see \phpseclib\Crypt\RSA::__construct()
    */
    /**
     * To use the pure-PHP implementation
     */
    const MODE_INTERNAL = 1;
    /**
     * To use the OpenSSL library
     *
     * (if enabled; otherwise, the internal implementation will be used)
     */
    const MODE_OPENSSL = 2;
    /**#@-*/

    /**#@+
     * @access public
     * @see \phpseclib\Crypt\RSA::createKey()
     * @see \phpseclib\Crypt\RSA::setPrivateKeyFormat()
    */
    /**
     * PKCS#1 formatted private key
     *
     * Used by OpenSSH
     */
    const PRIVATE_FORMAT_PKCS1 = 0;
    /**
     * PuTTY formatted private key
     */
    const PRIVATE_FORMAT_PUTTY = 1;
    /**
     * XML formatted private key
     */
    const PRIVATE_FORMAT_XML = 2;
    /**
     * PKCS#8 formatted private key
     */
    const PRIVATE_FORMAT_PKCS8 = 8;
    /**#@-*/

    /**#@+
     * @access public
     * @see \phpseclib\Crypt\RSA::createKey()
     * @see \phpseclib\Crypt\RSA::setPublicKeyFormat()
    */
    /**
     * Raw public key
     *
     * An array containing two \phpseclib\Math\BigInteger objects.
     *
     * The exponent can be indexed with any of the following:
     *
     * 0, e, exponent, publicExponent
     *
     * The modulus can be indexed with any of the following:
     *
     * 1, n, modulo, modulus
     */
    const PUBLIC_FORMAT_RAW = 3;
    /**
     * PKCS#1 formatted public key (raw)
     *
     * Used by File/X509.php
     *
     * Has the following header:
     *
     * -----BEGIN RSA PUBLIC KEY-----
     *
     * Analogous to ssh-keygen's pem format (as specified by -m)
     */
    const PUBLIC_FORMAT_PKCS1 = 4;
    const PUBLIC_FORMAT_PKCS1_RAW = 4;
    /**
     * XML formatted public key
     */
    const PUBLIC_FORMAT_XML = 5;
    /**
     * OpenSSH formatted public key
     *
     * Place in $HOME/.ssh/authorized_keys
     */
    const PUBLIC_FORMAT_OPENSSH = 6;
    /**
     * PKCS#1 formatted public key (encapsulated)
     *
     * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
     *
     * Has the following header:
     *
     * -----BEGIN PUBLIC KEY-----
     *
     * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
     * is specific to private keys it's basically creating a DER-encoded wrapper
     * for keys. This just extends that same concept to public keys (much like ssh-keygen)
     */
    const PUBLIC_FORMAT_PKCS8 = 7;
    /**#@-*/

    /**
     * Precomputed Zero
     *
     * @var \phpseclib\Math\BigInteger
     * @access private
     */
    var $zero;

    /**
     * Precomputed One
     *
     * @var \phpseclib\Math\BigInteger
     * @access private
     */
    var $one;

    /**
     * Private Key Format
     *
     * @var int
     * @access private
     */
    var $privateKeyFormat = self::PRIVATE_FORMAT_PKCS1;

    /**
     * Public Key Format
     *
     * @var int
     * @access public
     */
    var $publicKeyFormat = self::PUBLIC_FORMAT_PKCS8;

    /**
     * Modulus (ie. n)
     *
     * @var \phpseclib\Math\BigInteger
     * @access private
     */
    var $modulus;

    /**
     * Modulus length
     *
     * @var \phpseclib\Math\BigInteger
     * @access private
     */
    var $k;

    /**
     * Exponent (ie. e or d)
     *
     * @var \phpseclib\Math\BigInteger
     * @access private
     */
    var $exponent;

    /**
     * Primes for Chinese Remainder Theorem (ie. p and q)
     *
     * @var array
     * @access private
     */
    var $primes;

    /**
     * Exponents for Chinese Remainder Theorem (ie. dP and dQ)
     *
     * @var array
     * @access private
     */
    var $exponents;

    /**
     * Coefficients for Chinese Remainder Theorem (ie. qInv)
     *
     * @var array
     * @access private
     */
    var $coefficients;

    /**
     * Hash name
     *
     * @var string
     * @access private
     */
    var $hashName;

    /**
     * Hash function
     *
     * @var \phpseclib\Crypt\Hash
     * @access private
     */
    var $hash;

    /**
     * Length of hash function output
     *
     * @var int
     * @access private
     */
    var $hLen;

    /**
     * Length of salt
     *
     * @var int
     * @access private
     */
    var $sLen;

    /**
     * Hash function for the Mask Generation Function
     *
     * @var \phpseclib\Crypt\Hash
     * @access private
     */
    var $mgfHash;

    /**
     * Length of MGF hash function output
     *
     * @var int
     * @access private
     */
    var $mgfHLen;

    /**
     * Encryption mode
     *
     * @var int
     * @access private
     */
    var $encryptionMode = self::ENCRYPTION_OAEP;

    /**
     * Signature mode
     *
     * @var int
     * @access private
     */
    var $signatureMode = self::SIGNATURE_PSS;

    /**
     * Public Exponent
     *
     * @var mixed
     * @access private
     */
    var $publicExponent = false;

    /**
     * Password
     *
     * @var string
     * @access private
     */
    var $password = false;

    /**
     * Components
     *
     * For use with parsing XML formatted keys.  PHP's XML Parser functions use utilized - instead of PHP's DOM functions -
     * because PHP's XML Parser functions work on PHP4 whereas PHP's DOM functions - although surperior - don't.
     *
     * @see self::_start_element_handler()
     * @var array
     * @access private
     */
    var $components = array();

    /**
     * Current String
     *
     * For use with parsing XML formatted keys.
     *
     * @see self::_character_handler()
     * @see self::_stop_element_handler()
     * @var mixed
     * @access private
     */
    var $current;

    /**
     * OpenSSL configuration file name.
     *
     * Set to null to use system configuration file.
     * @see self::createKey()
     * @var mixed
     * @Access public
     */
    var $configFile;

    /**
     * Public key comment field.
     *
     * @var string
     * @access private
     */
    var $comment = 'phpseclib-generated-key';

    /**
     * The constructor
     *
     * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself.  The reason
     * \phpseclib\Crypt\RSA doesn't do it is because OpenSSL doesn't fail gracefully.  openssl_pkey_new(), in particular, requires
     * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late.
     *
     * @return \phpseclib\Crypt\RSA
     * @access public
     */
    function __construct()
    {
        $this->configFile = dirname(__FILE__) . '/../openssl.cnf';

        if (!defined('CRYPT_RSA_MODE')) {
            switch (true) {
                // Math/BigInteger's openssl requirements are a little less stringent than Crypt/RSA's. in particular,
                // Math/BigInteger doesn't require an openssl.cfg file whereas Crypt/RSA does. so if Math/BigInteger
                // can't use OpenSSL it can be pretty trivially assumed, then, that Crypt/RSA can't either.
                case defined('MATH_BIGINTEGER_OPENSSL_DISABLE'):
                    define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
                    break;
                case extension_loaded('openssl') && file_exists($this->configFile):
                    // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work
                    ob_start();
                    @phpinfo();
                    $content = ob_get_contents();
                    ob_end_clean();

                    preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches);

                    $versions = array();
                    if (!empty($matches[1])) {
                        for ($i = 0; $i < count($matches[1]); $i++) {
                            $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i])));

                            // Remove letter part in OpenSSL version
                            if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) {
                                $versions[$matches[1][$i]] = $fullVersion;
                            } else {
                                $versions[$matches[1][$i]] = $m[0];
                            }
                        }
                    }

                    // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+
                    switch (true) {
                        case !isset($versions['Header']):
                        case !isset($versions['Library']):
                        case $versions['Header'] == $versions['Library']:
                        case version_compare($versions['Header'], '1.0.0') >= 0 && version_compare($versions['Library'], '1.0.0') >= 0:
                            define('CRYPT_RSA_MODE', self::MODE_OPENSSL);
                            break;
                        default:
                            define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
                            define('MATH_BIGINTEGER_OPENSSL_DISABLE', true);
                    }
                    break;
                default:
                    define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
            }
        }

        $this->zero = new BigInteger();
        $this->one = new BigInteger(1);

        $this->hash = new Hash('sha1');
        $this->hLen = $this->hash->getLength();
        $this->hashName = 'sha1';
        $this->mgfHash = new Hash('sha1');
        $this->mgfHLen = $this->mgfHash->getLength();
    }

    /**
     * Create public / private key pair
     *
     * Returns an array with the following three elements:
     *  - 'privatekey': The private key.
     *  - 'publickey':  The public key.
     *  - 'partialkey': A partially computed key (if the execution time exceeded $timeout).
     *                  Will need to be passed back to \phpseclib\Crypt\RSA::createKey() as the third parameter for further processing.
     *
     * @access public
     * @param int $bits
     * @param int $timeout
     * @param array $p
     */
    function createKey($bits = 1024, $timeout = false, $partial = array())
    {
        if (!defined('CRYPT_RSA_EXPONENT')) {
            // http://en.wikipedia.org/wiki/65537_%28number%29
            define('CRYPT_RSA_EXPONENT', '65537');
        }
        // per <http://cseweb.ucsd.edu/~hovav/dist/survey.pdf#page=5>, this number ought not result in primes smaller
        // than 256 bits. as a consequence if the key you're trying to create is 1024 bits and you've set CRYPT_RSA_SMALLEST_PRIME
        // to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). at least if
        // CRYPT_RSA_MODE is set to self::MODE_INTERNAL. if CRYPT_RSA_MODE is set to self::MODE_OPENSSL then
        // CRYPT_RSA_SMALLEST_PRIME is ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key
        // generation when there's a chance neither gmp nor OpenSSL are installed)
        if (!defined('CRYPT_RSA_SMALLEST_PRIME')) {
            define('CRYPT_RSA_SMALLEST_PRIME', 4096);
        }

        // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum
        if (CRYPT_RSA_MODE == self::MODE_OPENSSL && $bits >= 384 && CRYPT_RSA_EXPONENT == 65537) {
            $config = array();
            if (isset($this->configFile)) {
                $config['config'] = $this->configFile;
            }
            $rsa = openssl_pkey_new(array('private_key_bits' => $bits) + $config);
            openssl_pkey_export($rsa, $privatekey, null, $config);
            $publickey = openssl_pkey_get_details($rsa);
            $publickey = $publickey['key'];

            $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, self::PRIVATE_FORMAT_PKCS1)));
            $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, self::PUBLIC_FORMAT_PKCS1)));

            // clear the buffer of error strings stemming from a minimalistic openssl.cnf
            while (openssl_error_string() !== false) {
            }

            return array(
                'privatekey' => $privatekey,
                'publickey' => $publickey,
                'partialkey' => false
            );
        }

        static $e;
        if (!isset($e)) {
            $e = new BigInteger(CRYPT_RSA_EXPONENT);
        }

        extract($this->_generateMinMax($bits));
        $absoluteMin = $min;
        $temp = $bits >> 1; // divide by two to see how many bits P and Q would be
        if ($temp > CRYPT_RSA_SMALLEST_PRIME) {
            $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME);
            $temp = CRYPT_RSA_SMALLEST_PRIME;
        } else {
            $num_primes = 2;
        }
        extract($this->_generateMinMax($temp + $bits % $temp));
        $finalMax = $max;
        extract($this->_generateMinMax($temp));

        $generator = new BigInteger();

        $n = $this->one->copy();
        if (!empty($partial)) {
            extract(unserialize($partial));
        } else {
            $exponents = $coefficients = $primes = array();
            $lcm = array(
                'top' => $this->one->copy(),
                'bottom' => false
            );
        }

        $start = time();
        $i0 = count($primes) + 1;

        do {
            for ($i = $i0; $i <= $num_primes; $i++) {
                if ($timeout !== false) {
                    $timeout-= time() - $start;
                    $start = time();
                    if ($timeout <= 0) {
                        return array(
                            'privatekey' => '',
                            'publickey'  => '',
                            'partialkey' => serialize(array(
                                'primes' => $primes,
                                'coefficients' => $coefficients,
                                'lcm' => $lcm,
                                'exponents' => $exponents
                            ))
                        );
                    }
                }

                if ($i == $num_primes) {
                    list($min, $temp) = $absoluteMin->divide($n);
                    if (!$temp->equals($this->zero)) {
                        $min = $min->add($this->one); // ie. ceil()
                    }
                    $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout);
                } else {
                    $primes[$i] = $generator->randomPrime($min, $max, $timeout);
                }

                if ($primes[$i] === false) { // if we've reached the timeout
                    if (count($primes) > 1) {
                        $partialkey = '';
                    } else {
                        array_pop($primes);
                        $partialkey = serialize(array(
                            'primes' => $primes,
                            'coefficients' => $coefficients,
                            'lcm' => $lcm,
                            'exponents' => $exponents
                        ));
                    }

                    return array(
                        'privatekey' => '',
                        'publickey'  => '',
                        'partialkey' => $partialkey
                    );
                }

                // the first coefficient is calculated differently from the rest
                // ie. instead of being $primes[1]->modInverse($primes[2]), it's $primes[2]->modInverse($primes[1])
                if ($i > 2) {
                    $coefficients[$i] = $n->modInverse($primes[$i]);
                }

                $n = $n->multiply($primes[$i]);

                $temp = $primes[$i]->subtract($this->one);

                // textbook RSA implementations use Euler's totient function instead of the least common multiple.
                // see http://en.wikipedia.org/wiki/Euler%27s_totient_function
                $lcm['top'] = $lcm['top']->multiply($temp);
                $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp);

                $exponents[$i] = $e->modInverse($temp);
            }

            list($temp) = $lcm['top']->divide($lcm['bottom']);
            $gcd = $temp->gcd($e);
            $i0 = 1;
        } while (!$gcd->equals($this->one));

        $d = $e->modInverse($temp);

        $coefficients[2] = $primes[2]->modInverse($primes[1]);

        // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.2>:
        // RSAPrivateKey ::= SEQUENCE {
        //     version           Version,
        //     modulus           INTEGER,  -- n
        //     publicExponent    INTEGER,  -- e
        //     privateExponent   INTEGER,  -- d
        //     prime1            INTEGER,  -- p
        //     prime2            INTEGER,  -- q
        //     exponent1         INTEGER,  -- d mod (p-1)
        //     exponent2         INTEGER,  -- d mod (q-1)
        //     coefficient       INTEGER,  -- (inverse of q) mod p
        //     otherPrimeInfos   OtherPrimeInfos OPTIONAL
        // }

        return array(
            'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients),
            'publickey'  => $this->_convertPublicKey($n, $e),
            'partialkey' => false
        );
    }

    /**
     * Convert a private key to the appropriate format.
     *
     * @access private
     * @see self::setPrivateKeyFormat()
     * @param string $RSAPrivateKey
     * @return string
     */
    function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients)
    {
        $signed = $this->privateKeyFormat != self::PRIVATE_FORMAT_XML;
        $num_primes = count($primes);
        $raw = array(
            'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi
            'modulus' => $n->toBytes($signed),
            'publicExponent' => $e->toBytes($signed),
            'privateExponent' => $d->toBytes($signed),
            'prime1' => $primes[1]->toBytes($signed),
            'prime2' => $primes[2]->toBytes($signed),
            'exponent1' => $exponents[1]->toBytes($signed),
            'exponent2' => $exponents[2]->toBytes($signed),
            'coefficient' => $coefficients[2]->toBytes($signed)
        );

        // if the format in question does not support multi-prime rsa and multi-prime rsa was used,
        // call _convertPublicKey() instead.
        switch ($this->privateKeyFormat) {
            case self::PRIVATE_FORMAT_XML:
                if ($num_primes != 2) {
                    return false;
                }
                return "<RSAKeyValue>\r\n" .
                       '  <Modulus>' . base64_encode($raw['modulus']) . "</Modulus>\r\n" .
                       '  <Exponent>' . base64_encode($raw['publicExponent']) . "</Exponent>\r\n" .
                       '  <P>' . base64_encode($raw['prime1']) . "</P>\r\n" .
                       '  <Q>' . base64_encode($raw['prime2']) . "</Q>\r\n" .
                       '  <DP>' . base64_encode($raw['exponent1']) . "</DP>\r\n" .
                       '  <DQ>' . base64_encode($raw['exponent2']) . "</DQ>\r\n" .
                       '  <InverseQ>' . base64_encode($raw['coefficient']) . "</InverseQ>\r\n" .
                       '  <D>' . base64_encode($raw['privateExponent']) . "</D>\r\n" .
                       '</RSAKeyValue>';
                break;
            case self::PRIVATE_FORMAT_PUTTY:
                if ($num_primes != 2) {
                    return false;
                }
                $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: ";
                $encryption = (!empty($this->password) || is_string($this->password)) ? 'aes256-cbc' : 'none';
                $key.= $encryption;
                $key.= "\r\nComment: " . $this->comment . "\r\n";
                $public = pack(
                    'Na*Na*Na*',
                    strlen('ssh-rsa'),
                    'ssh-rsa',
                    strlen($raw['publicExponent']),
                    $raw['publicExponent'],
                    strlen($raw['modulus']),
                    $raw['modulus']
                );
                $source = pack(
                    'Na*Na*Na*Na*',
                    strlen('ssh-rsa'),
                    'ssh-rsa',
                    strlen($encryption),
                    $encryption,
                    strlen($this->comment),
                    $this->comment,
                    strlen($public),
                    $public
                );
                $public = base64_encode($public);
                $key.= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
                $key.= chunk_split($public, 64);
                $private = pack(
                    'Na*Na*Na*Na*',
                    strlen($raw['privateExponent']),
                    $raw['privateExponent'],
                    strlen($raw['prime1']),
                    $raw['prime1'],
                    strlen($raw['prime2']),
                    $raw['prime2'],
                    strlen($raw['coefficient']),
                    $raw['coefficient']
                );
                if (empty($this->password) && !is_string($this->password)) {
                    $source.= pack('Na*', strlen($private), $private);
                    $hashkey = 'putty-private-key-file-mac-key';
                } else {
                    $private.= Random::string(16 - (strlen($private) & 15));
                    $source.= pack('Na*', strlen($private), $private);
                    $sequence = 0;
                    $symkey = '';
                    while (strlen($symkey) < 32) {
                        $temp = pack('Na*', $sequence++, $this->password);
                        $symkey.= pack('H*', sha1($temp));
                    }
                    $symkey = substr($symkey, 0, 32);
                    $crypto = new AES();

                    $crypto->setKey($symkey);
                    $crypto->disablePadding();
                    $private = $crypto->encrypt($private);
                    $hashkey = 'putty-private-key-file-mac-key' . $this->password;
                }

                $private = base64_encode($private);
                $key.= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
                $key.= chunk_split($private, 64);
                $hash = new Hash('sha1');
                $hash->setKey(pack('H*', sha1($hashkey)));
                $key.= 'Private-MAC: ' . bin2hex($hash->hash($source)) . "\r\n";

                return $key;
            default: // eg. self::PRIVATE_FORMAT_PKCS1
                $components = array();
                foreach ($raw as $name => $value) {
                    $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value);
                }

                $RSAPrivateKey = implode('', $components);

                if ($num_primes > 2) {
                    $OtherPrimeInfos = '';
                    for ($i = 3; $i <= $num_primes; $i++) {
                        // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
                        //
                        // OtherPrimeInfo ::= SEQUENCE {
                        //     prime             INTEGER,  -- ri
                        //     exponent          INTEGER,  -- di
                        //     coefficient       INTEGER   -- ti
                        // }
                        $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true));
                        $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true));
                        $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true));
                        $OtherPrimeInfos.= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo);
                    }
                    $RSAPrivateKey.= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos);
                }

                $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);

                if ($this->privateKeyFormat == self::PRIVATE_FORMAT_PKCS8) {
                    $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
                    $RSAPrivateKey = pack(
                        'Ca*a*Ca*a*',
                        self::ASN1_INTEGER,
                        "\01\00",
                        $rsaOID,
                        4,
                        $this->_encodeLength(strlen($RSAPrivateKey)),
                        $RSAPrivateKey
                    );
                    $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
                    if (!empty($this->password) || is_string($this->password)) {
                        $salt = Random::string(8);
                        $iterationCount = 2048;

                        $crypto = new DES();
                        $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount);
                        $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey);

                        $parameters = pack(
                            'Ca*a*Ca*N',
                            self::ASN1_OCTETSTRING,
                            $this->_encodeLength(strlen($salt)),
                            $salt,
                            self::ASN1_INTEGER,
                            $this->_encodeLength(4),
                            $iterationCount
                        );
                        $pbeWithMD5AndDES_CBC = "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03";

                        $encryptionAlgorithm = pack(
                            'Ca*a*Ca*a*',
                            self::ASN1_OBJECT,
                            $this->_encodeLength(strlen($pbeWithMD5AndDES_CBC)),
                            $pbeWithMD5AndDES_CBC,
                            self::ASN1_SEQUENCE,
                            $this->_encodeLength(strlen($parameters)),
                            $parameters
                        );

                        $RSAPrivateKey = pack(
                            'Ca*a*Ca*a*',
                            self::ASN1_SEQUENCE,
                            $this->_encodeLength(strlen($encryptionAlgorithm)),
                            $encryptionAlgorithm,
                            self::ASN1_OCTETSTRING,
                            $this->_encodeLength(strlen($RSAPrivateKey)),
                            $RSAPrivateKey
                        );

                        $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);

                        $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
                                         chunk_split(base64_encode($RSAPrivateKey), 64) .
                                         '-----END ENCRYPTED PRIVATE KEY-----';
                    } else {
                        $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" .
                                         chunk_split(base64_encode($RSAPrivateKey), 64) .
                                         '-----END PRIVATE KEY-----';
                    }
                    return $RSAPrivateKey;
                }

                if (!empty($this->password) || is_string($this->password)) {
                    $iv = Random::string(8);
                    $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key
                    $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8);
                    $des = new TripleDES();
                    $des->setKey($symkey);
                    $des->setIV($iv);
                    $iv = strtoupper(bin2hex($iv));
                    $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
                                     "Proc-Type: 4,ENCRYPTED\r\n" .
                                     "DEK-Info: DES-EDE3-CBC,$iv\r\n" .
                                     "\r\n" .
                                     chunk_split(base64_encode($des->encrypt($RSAPrivateKey)), 64) .
                                     '-----END RSA PRIVATE KEY-----';
                } else {
                    $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
                                     chunk_split(base64_encode($RSAPrivateKey), 64) .
                                     '-----END RSA PRIVATE KEY-----';
                }

                return $RSAPrivateKey;
        }
    }

    /**
     * Convert a public key to the appropriate format
     *
     * @access private
     * @see self::setPublicKeyFormat()
     * @param string $RSAPrivateKey
     * @return string
     */
    function _convertPublicKey($n, $e)
    {
        $signed = $this->publicKeyFormat != self::PUBLIC_FORMAT_XML;

        $modulus = $n->toBytes($signed);
        $publicExponent = $e->toBytes($signed);

        switch ($this->publicKeyFormat) {
            case self::PUBLIC_FORMAT_RAW:
                return array('e' => $e->copy(), 'n' => $n->copy());
            case self::PUBLIC_FORMAT_XML:
                return "<RSAKeyValue>\r\n" .
                       '  <Modulus>' . base64_encode($modulus) . "</Modulus>\r\n" .
                       '  <Exponent>' . base64_encode($publicExponent) . "</Exponent>\r\n" .
                       '</RSAKeyValue>';
                break;
            case self::PUBLIC_FORMAT_OPENSSH:
                // from <http://tools.ietf.org/html/rfc4253#page-15>:
                // string    "ssh-rsa"
                // mpint     e
                // mpint     n
                $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus);
                $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment;

                return $RSAPublicKey;
            default: // eg. self::PUBLIC_FORMAT_PKCS1_RAW or self::PUBLIC_FORMAT_PKCS1
                // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.1>:
                // RSAPublicKey ::= SEQUENCE {
                //     modulus           INTEGER,  -- n
                //     publicExponent    INTEGER   -- e
                // }
                $components = array(
                    'modulus' => pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus),
                    'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent)
                );

                $RSAPublicKey = pack(
                    'Ca*a*a*',
                    self::ASN1_SEQUENCE,
                    $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])),
                    $components['modulus'],
                    $components['publicExponent']
                );

                if ($this->publicKeyFormat == self::PUBLIC_FORMAT_PKCS1_RAW) {
                    $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" .
                                    chunk_split(base64_encode($RSAPublicKey), 64) .
                                    '-----END RSA PUBLIC KEY-----';
                } else {
                    // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
                    $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
                    $RSAPublicKey = chr(0) . $RSAPublicKey;
                    $RSAPublicKey = chr(3) . $this->_encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey;

                    $RSAPublicKey = pack(
                        'Ca*a*',
                        self::ASN1_SEQUENCE,
                        $this->_encodeLength(strlen($rsaOID . $RSAPublicKey)),
                        $rsaOID . $RSAPublicKey
                    );

                    $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
                                     chunk_split(base64_encode($RSAPublicKey), 64) .
                                     '-----END PUBLIC KEY-----';
                }

                return $RSAPublicKey;
        }
    }

    /**
     * Break a public or private key down into its constituant components
     *
     * @access private
     * @see self::_convertPublicKey()
     * @see self::_convertPrivateKey()
     * @param string $key
     * @param int $type
     * @return array
     */
    function _parseKey($key, $type)
    {
        if ($type != self::PUBLIC_FORMAT_RAW && !is_string($key)) {
            return false;
        }

        switch ($type) {
            case self::PUBLIC_FORMAT_RAW:
                if (!is_array($key)) {
                    return false;
                }
                $components = array();
                switch (true) {
                    case isset($key['e']):
                        $components['publicExponent'] = $key['e']->copy();
                        break;
                    case isset($key['exponent']):
                        $components['publicExponent'] = $key['exponent']->copy();
                        break;
                    case isset($key['publicExponent']):
                        $components['publicExponent'] = $key['publicExponent']->copy();
                        break;
                    case isset($key[0]):
                        $components['publicExponent'] = $key[0]->copy();
                }
                switch (true) {
                    case isset($key['n']):
                        $components['modulus'] = $key['n']->copy();
                        break;
                    case isset($key['modulo']):
                        $components['modulus'] = $key['modulo']->copy();
                        break;
                    case isset($key['modulus']):
                        $components['modulus'] = $key['modulus']->copy();
                        break;
                    case isset($key[1]):
                        $components['modulus'] = $key[1]->copy();
                }
                return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false;
            case self::PRIVATE_FORMAT_PKCS1:
            case self::PRIVATE_FORMAT_PKCS8:
            case self::PUBLIC_FORMAT_PKCS1:
                /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
                   "outside the scope" of PKCS#1.  PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
                   protect private keys, however, that's not what OpenSSL* does.  OpenSSL protects private keys by adding
                   two new "fields" to the key - DEK-Info and Proc-Type.  These fields are discussed here:

                   http://tools.ietf.org/html/rfc1421#section-4.6.1.1
                   http://tools.ietf.org/html/rfc1421#section-4.6.1.3

                   DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
                   DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
                   function.  As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
                   own implementation.  ie. the implementation *is* the standard and any bugs that may exist in that
                   implementation are part of the standard, as well.

                   * OpenSSL is the de facto standard.  It's utilized by OpenSSH and other projects */
                if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
                    $iv = pack('H*', trim($matches[2]));
                    $symkey = pack('H*', md5($this->password . substr($iv, 0, 8))); // symkey is short for symmetric key
                    $symkey.= pack('H*', md5($symkey . $this->password . substr($iv, 0, 8)));
                    // remove the Proc-Type / DEK-Info sections as they're no longer needed
                    $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
                    $ciphertext = $this->_extractBER($key);
                    if ($ciphertext === false) {
                        $ciphertext = $key;
                    }
                    switch ($matches[1]) {
                        case 'AES-256-CBC':
                            $crypto = new AES();
                            break;
                        case 'AES-128-CBC':
                            $symkey = substr($symkey, 0, 16);
                            $crypto = new AES();
                            break;
                        case 'DES-EDE3-CFB':
                            $crypto = new TripleDES(Base::MODE_CFB);
                            break;
                        case 'DES-EDE3-CBC':
                            $symkey = substr($symkey, 0, 24);
                            $crypto = new TripleDES();
                            break;
                        case 'DES-CBC':
                            $crypto = new DES();
                            break;
                        default:
                            return false;
                    }
                    $crypto->setKey($symkey);
                    $crypto->setIV($iv);
                    $decoded = $crypto->decrypt($ciphertext);
                } else {
                    $decoded = $this->_extractBER($key);
                }

                if ($decoded !== false) {
                    $key = $decoded;
                }

                $components = array();

                if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
                    return false;
                }
                if ($this->_decodeLength($key) != strlen($key)) {
                    return false;
                }

                $tag = ord($this->_string_shift($key));
                /* intended for keys for which OpenSSL's asn1parse returns the following:

                    0:d=0  hl=4 l= 631 cons: SEQUENCE
                    4:d=1  hl=2 l=   1 prim:  INTEGER           :00
                    7:d=1  hl=2 l=  13 cons:  SEQUENCE
                    9:d=2  hl=2 l=   9 prim:   OBJECT            :rsaEncryption
                   20:d=2  hl=2 l=   0 prim:   NULL
                   22:d=1  hl=4 l= 609 prim:  OCTET STRING

                   ie. PKCS8 keys*/

                if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") {
                    $this->_string_shift($key, 3);
                    $tag = self::ASN1_SEQUENCE;
                }

                if ($tag == self::ASN1_SEQUENCE) {
                    $temp = $this->_string_shift($key, $this->_decodeLength($key));
                    if (ord($this->_string_shift($temp)) != self::ASN1_OBJECT) {
                        return false;
                    }
                    $length = $this->_decodeLength($temp);
                    switch ($this->_string_shift($temp, $length)) {
                        case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption
                            break;
                        case "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03": // pbeWithMD5AndDES-CBC
                            /*
                               PBEParameter ::= SEQUENCE {
                                   salt OCTET STRING (SIZE(8)),
                                   iterationCount INTEGER }
                            */
                            if (ord($this->_string_shift($temp)) != self::ASN1_SEQUENCE) {
                                return false;
                            }
                            if ($this->_decodeLength($temp) != strlen($temp)) {
                                return false;
                            }
                            $this->_string_shift($temp); // assume it's an octet string
                            $salt = $this->_string_shift($temp, $this->_decodeLength($temp));
                            if (ord($this->_string_shift($temp)) != self::ASN1_INTEGER) {
                                return false;
                            }
                            $this->_decodeLength($temp);
                            list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT));
                            $this->_string_shift($key); // assume it's an octet string
                            $length = $this->_decodeLength($key);
                            if (strlen($key) != $length) {
                                return false;
                            }

                            $crypto = new DES();
                            $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount);
                            $key = $crypto->decrypt($key);
                            if ($key === false) {
                                return false;
                            }
                            return $this->_parseKey($key, self::PRIVATE_FORMAT_PKCS1);
                        default:
                            return false;
                    }
                    /* intended for keys for which OpenSSL's asn1parse returns the following:

                        0:d=0  hl=4 l= 290 cons: SEQUENCE
                        4:d=1  hl=2 l=  13 cons:  SEQUENCE
                        6:d=2  hl=2 l=   9 prim:   OBJECT            :rsaEncryption
                       17:d=2  hl=2 l=   0 prim:   NULL
                       19:d=1  hl=4 l= 271 prim:  BIT STRING */
                    $tag = ord($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag
                    $this->_decodeLength($key); // skip over the BIT STRING / OCTET STRING length
                    // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of
                    //  unused bits in the final subsequent octet. The number shall be in the range zero to seven."
                    //  -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2)
                    if ($tag == self::ASN1_BITSTRING) {
                        $this->_string_shift($key);
                    }
                    if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
                        return false;
                    }
                    if ($this->_decodeLength($key) != strlen($key)) {
                        return false;
                    }
                    $tag = ord($this->_string_shift($key));
                }
                if ($tag != self::ASN1_INTEGER) {
                    return false;
                }

                $length = $this->_decodeLength($key);
                $temp = $this->_string_shift($key, $length);
                if (strlen($temp) != 1 || ord($temp) > 2) {
                    $components['modulus'] = new BigInteger($temp, 256);
                    $this->_string_shift($key); // skip over self::ASN1_INTEGER
                    $length = $this->_decodeLength($key);
                    $components[$type == self::PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256);

                    return $components;
                }
                if (ord($this->_string_shift($key)) != self::ASN1_INTEGER) {
                    return false;
                }
                $length = $this->_decodeLength($key);
                $components['modulus'] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['publicExponent'] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['primes'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256));
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['exponents'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256));
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($key, $length), 256));

                if (!empty($key)) {
                    if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
                        return false;
                    }
                    $this->_decodeLength($key);
                    while (!empty($key)) {
                        if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
                            return false;
                        }
                        $this->_decodeLength($key);
                        $key = substr($key, 1);
                        $length = $this->_decodeLength($key);
                        $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256);
                        $this->_string_shift($key);
                        $length = $this->_decodeLength($key);
                        $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256);
                        $this->_string_shift($key);
                        $length = $this->_decodeLength($key);
                        $components['coefficients'][] = new BigInteger($this->_string_shift($key, $length), 256);
                    }
                }

                return $components;
            case self::PUBLIC_FORMAT_OPENSSH:
                $parts = explode(' ', $key, 3);

                $key = isset($parts[1]) ? base64_decode($parts[1]) : false;
                if ($key === false) {
                    return false;
                }

                $comment = isset($parts[2]) ? $parts[2] : false;

                $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa";

                if (strlen($key) <= 4) {
                    return false;
                }
                extract(unpack('Nlength', $this->_string_shift($key, 4)));
                $publicExponent = new BigInteger($this->_string_shift($key, $length), -256);
                if (strlen($key) <= 4) {
                    return false;
                }
                extract(unpack('Nlength', $this->_string_shift($key, 4)));
                $modulus = new BigInteger($this->_string_shift($key, $length), -256);

                if ($cleanup && strlen($key)) {
                    if (strlen($key) <= 4) {
                        return false;
                    }
                    extract(unpack('Nlength', $this->_string_shift($key, 4)));
                    $realModulus = new BigInteger($this->_string_shift($key, $length), -256);
                    return strlen($key) ? false : array(
                        'modulus' => $realModulus,
                        'publicExponent' => $modulus,
                        'comment' => $comment
                    );
                } else {
                    return strlen($key) ? false : array(
                        'modulus' => $modulus,
                        'publicExponent' => $publicExponent,
                        'comment' => $comment
                    );
                }
            // http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue
            // http://en.wikipedia.org/wiki/XML_Signature
            case self::PRIVATE_FORMAT_XML:
            case self::PUBLIC_FORMAT_XML:
                $this->components = array();

                $xml = xml_parser_create('UTF-8');
                xml_set_object($xml, $this);
                xml_set_element_handler($xml, '_start_element_handler', '_stop_element_handler');
                xml_set_character_data_handler($xml, '_data_handler');
                // add <xml></xml> to account for "dangling" tags like <BitStrength>...</BitStrength> that are sometimes added
                if (!xml_parse($xml, '<xml>' . $key . '</xml>')) {
                    return false;
                }

                return isset($this->components['modulus']) && isset($this->components['publicExponent']) ? $this->components : false;
            // from PuTTY's SSHPUBK.C
            case self::PRIVATE_FORMAT_PUTTY:
                $components = array();
                $key = preg_split('#\r\n|\r|\n#', $key);
                $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0]));
                if ($type != 'ssh-rsa') {
                    return false;
                }
                $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
                $comment = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));

                $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
                $public = base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
                $public = substr($public, 11);
                extract(unpack('Nlength', $this->_string_shift($public, 4)));
                $components['publicExponent'] = new BigInteger($this->_string_shift($public, $length), -256);
                extract(unpack('Nlength', $this->_string_shift($public, 4)));
                $components['modulus'] = new BigInteger($this->_string_shift($public, $length), -256);

                $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$publicLength + 4]));
                $private = base64_decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength))));

                switch ($encryption) {
                    case 'aes256-cbc':
                        $symkey = '';
                        $sequence = 0;
                        while (strlen($symkey) < 32) {
                            $temp = pack('Na*', $sequence++, $this->password);
                            $symkey.= pack('H*', sha1($temp));
                        }
                        $symkey = substr($symkey, 0, 32);
                        $crypto = new AES();
                }

                if ($encryption != 'none') {
                    $crypto->setKey($symkey);
                    $crypto->disablePadding();
                    $private = $crypto->decrypt($private);
                    if ($private === false) {
                        return false;
                    }
                }

                extract(unpack('Nlength', $this->_string_shift($private, 4)));
                if (strlen($private) < $length) {
                    return false;
                }
                $components['privateExponent'] = new BigInteger($this->_string_shift($private, $length), -256);
                extract(unpack('Nlength', $this->_string_shift($private, 4)));
                if (strlen($private) < $length) {
                    return false;
                }
                $components['primes'] = array(1 => new BigInteger($this->_string_shift($private, $length), -256));
                extract(unpack('Nlength', $this->_string_shift($private, 4)));
                if (strlen($private) < $length) {
                    return false;
                }
                $components['primes'][] = new BigInteger($this->_string_shift($private, $length), -256);

                $temp = $components['primes'][1]->subtract($this->one);
                $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp));
                $temp = $components['primes'][2]->subtract($this->one);
                $components['exponents'][] = $components['publicExponent']->modInverse($temp);

                extract(unpack('Nlength', $this->_string_shift($private, 4)));
                if (strlen($private) < $length) {
                    return false;
                }
                $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($private, $length), -256));

                return $components;
        }
    }

    /**
     * Returns the key size
     *
     * More specifically, this returns the size of the modulo in bits.
     *
     * @access public
     * @return int
     */
    function getSize()
    {
        return !isset($this->modulus) ? 0 : strlen($this->modulus->toBits());
    }

    /**
     * Start Element Handler
     *
     * Called by xml_set_element_handler()
     *
     * @access private
     * @param resource $parser
     * @param string $name
     * @param array $attribs
     */
    function _start_element_handler($parser, $name, $attribs)
    {
        //$name = strtoupper($name);
        switch ($name) {
            case 'MODULUS':
                $this->current = &$this->components['modulus'];
                break;
            case 'EXPONENT':
                $this->current = &$this->components['publicExponent'];
                break;
            case 'P':
                $this->current = &$this->components['primes'][1];
                break;
            case 'Q':
                $this->current = &$this->components['primes'][2];
                break;
            case 'DP':
                $this->current = &$this->components['exponents'][1];
                break;
            case 'DQ':
                $this->current = &$this->components['exponents'][2];
                break;
            case 'INVERSEQ':
                $this->current = &$this->components['coefficients'][2];
                break;
            case 'D':
                $this->current = &$this->components['privateExponent'];
        }
        $this->current = '';
    }

    /**
     * Stop Element Handler
     *
     * Called by xml_set_element_handler()
     *
     * @access private
     * @param resource $parser
     * @param string $name
     */
    function _stop_element_handler($parser, $name)
    {
        if (isset($this->current)) {
            $this->current = new BigInteger(base64_decode($this->current), 256);
            unset($this->current);
        }
    }

    /**
     * Data Handler
     *
     * Called by xml_set_character_data_handler()
     *
     * @access private
     * @param resource $parser
     * @param string $data
     */
    function _data_handler($parser, $data)
    {
        if (!isset($this->current) || is_object($this->current)) {
            return;
        }
        $this->current.= trim($data);
    }

    /**
     * Loads a public or private key
     *
     * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed)
     *
     * @access public
     * @param string $key
     * @param int $type optional
     */
    function loadKey($key, $type = false)
    {
        if ($key instanceof RSA) {
            $this->privateKeyFormat = $key->privateKeyFormat;
            $this->publicKeyFormat = $key->publicKeyFormat;
            $this->k = $key->k;
            $this->hLen = $key->hLen;
            $this->sLen = $key->sLen;
            $this->mgfHLen = $key->mgfHLen;
            $this->encryptionMode = $key->encryptionMode;
            $this->signatureMode = $key->signatureMode;
            $this->password = $key->password;
            $this->configFile = $key->configFile;
            $this->comment = $key->comment;

            if (is_object($key->hash)) {
                $this->hash = new Hash($key->hash->getHash());
            }
            if (is_object($key->mgfHash)) {
                $this->mgfHash = new Hash($key->mgfHash->getHash());
            }

            if (is_object($key->modulus)) {
                $this->modulus = $key->modulus->copy();
            }
            if (is_object($key->exponent)) {
                $this->exponent = $key->exponent->copy();
            }
            if (is_object($key->publicExponent)) {
                $this->publicExponent = $key->publicExponent->copy();
            }

            $this->primes = array();
            $this->exponents = array();
            $this->coefficients = array();

            foreach ($this->primes as $prime) {
                $this->primes[] = $prime->copy();
            }
            foreach ($this->exponents as $exponent) {
                $this->exponents[] = $exponent->copy();
            }
            foreach ($this->coefficients as $coefficient) {
                $this->coefficients[] = $coefficient->copy();
            }

            return true;
        }

        if ($type === false) {
            $types = array(
                self::PUBLIC_FORMAT_RAW,
                self::PRIVATE_FORMAT_PKCS1,
                self::PRIVATE_FORMAT_XML,
                self::PRIVATE_FORMAT_PUTTY,
                self::PUBLIC_FORMAT_OPENSSH
            );
            foreach ($types as $type) {
                $components = $this->_parseKey($key, $type);
                if ($components !== false) {
                    break;
                }
            }
        } else {
            $components = $this->_parseKey($key, $type);
        }

        if ($components === false) {
            return false;
        }

        if (isset($components['comment']) && $components['comment'] !== false) {
            $this->comment = $components['comment'];
        }
        $this->modulus = $components['modulus'];
        $this->k = strlen($this->modulus->toBytes());
        $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent'];
        if (isset($components['primes'])) {
            $this->primes = $components['primes'];
            $this->exponents = $components['exponents'];
            $this->coefficients = $components['coefficients'];
            $this->publicExponent = $components['publicExponent'];
        } else {
            $this->primes = array();
            $this->exponents = array();
            $this->coefficients = array();
            $this->publicExponent = false;
        }

        switch ($type) {
            case self::PUBLIC_FORMAT_OPENSSH:
            case self::PUBLIC_FORMAT_RAW:
                $this->setPublicKey();
                break;
            case self::PRIVATE_FORMAT_PKCS1:
                switch (true) {
                    case strpos($key, '-BEGIN PUBLIC KEY-') !== false:
                    case strpos($key, '-BEGIN RSA PUBLIC KEY-') !== false:
                        $this->setPublicKey();
                }
        }

        return true;
    }

    /**
     * Sets the password
     *
     * Private keys can be encrypted with a password.  To unset the password, pass in the empty string or false.
     * Or rather, pass in $password such that empty($password) && !is_string($password) is true.
     *
     * @see self::createKey()
     * @see self::loadKey()
     * @access public
     * @param string $password
     */
    function setPassword($password = false)
    {
        $this->password = $password;
    }

    /**
     * Defines the public key
     *
     * Some private key formats define the public exponent and some don't.  Those that don't define it are problematic when
     * used in certain contexts.  For example, in SSH-2, RSA authentication works by sending the public key along with a
     * message signed by the private key to the server.  The SSH-2 server looks the public key up in an index of public keys
     * and if it's present then proceeds to verify the signature.  Problem is, if your private key doesn't include the public
     * exponent this won't work unless you manually add the public exponent. phpseclib tries to guess if the key being used
     * is the public key but in the event that it guesses incorrectly you might still want to explicitly set the key as being
     * public.
     *
     * Do note that when a new key is loaded the index will be cleared.
     *
     * Returns true on success, false on failure
     *
     * @see self::getPublicKey()
     * @access public
     * @param string $key optional
     * @param int $type optional
     * @return bool
     */
    function setPublicKey($key = false, $type = false)
    {
        // if a public key has already been loaded return false
        if (!empty($this->publicExponent)) {
            return false;
        }

        if ($key === false && !empty($this->modulus)) {
            $this->publicExponent = $this->exponent;
            return true;
        }

        if ($type === false) {
            $types = array(
                self::PUBLIC_FORMAT_RAW,
                self::PUBLIC_FORMAT_PKCS1,
                self::PUBLIC_FORMAT_XML,
                self::PUBLIC_FORMAT_OPENSSH
            );
            foreach ($types as $type) {
                $components = $this->_parseKey($key, $type);
                if ($components !== false) {
                    break;
                }
            }
        } else {
            $components = $this->_parseKey($key, $type);
        }

        if ($components === false) {
            return false;
        }

        if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) {
            $this->modulus = $components['modulus'];
            $this->exponent = $this->publicExponent = $components['publicExponent'];
            return true;
        }

        $this->publicExponent = $components['publicExponent'];

        return true;
    }

    /**
     * Defines the private key
     *
     * If phpseclib guessed a private key was a public key and loaded it as such it might be desirable to force
     * phpseclib to treat the key as a private key. This function will do that.
     *
     * Do note that when a new key is loaded the index will be cleared.
     *
     * Returns true on success, false on failure
     *
     * @see self::getPublicKey()
     * @access public
     * @param string $key optional
     * @param int $type optional
     * @return bool
     */
    function setPrivateKey($key = false, $type = false)
    {
        if ($key === false && !empty($this->publicExponent)) {
            $this->publicExponent = false;
            return true;
        }

        $rsa = new RSA();
        if (!$rsa->loadKey($key, $type)) {
            return false;
        }
        $rsa->publicExponent = false;

        // don't overwrite the old key if the new key is invalid
        $this->loadKey($rsa);
        return true;
    }

    /**
     * Returns the public key
     *
     * The public key is only returned under two circumstances - if the private key had the public key embedded within it
     * or if the public key was set via setPublicKey().  If the currently loaded key is supposed to be the public key this
     * function won't return it since this library, for the most part, doesn't distinguish between public and private keys.
     *
     * @see self::getPublicKey()
     * @access public
     * @param string $key
     * @param int $type optional
     */
    function getPublicKey($type = self::PUBLIC_FORMAT_PKCS8)
    {
        if (empty($this->modulus) || empty($this->publicExponent)) {
            return false;
        }

        $oldFormat = $this->publicKeyFormat;
        $this->publicKeyFormat = $type;
        $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent);
        $this->publicKeyFormat = $oldFormat;
        return $temp;
    }

    /**
     * Returns the public key's fingerprint
     *
     * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is
     * no public key currently loaded, false is returned.
     * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716)
     *
     * @access public
     * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned
     * for invalid values.
     * @return mixed
     */
    function getPublicKeyFingerprint($algorithm = 'md5')
    {
        if (empty($this->modulus) || empty($this->publicExponent)) {
            return false;
        }

        $modulus = $this->modulus->toBytes(true);
        $publicExponent = $this->publicExponent->toBytes(true);

        $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus);

        switch ($algorithm) {
            case 'sha256':
                $hash = new Hash('sha256');
                $base = base64_encode($hash->hash($RSAPublicKey));
                return substr($base, 0, strlen($base) - 1);
            case 'md5':
                return substr(chunk_split(md5($RSAPublicKey), 2, ':'), 0, -1);
            default:
                return false;
        }
    }

    /**
     * Returns the private key
     *
     * The private key is only returned if the currently loaded key contains the constituent prime numbers.
     *
     * @see self::getPublicKey()
     * @access public
     * @param string $key
     * @param int $type optional
     * @return mixed
     */
    function getPrivateKey($type = self::PUBLIC_FORMAT_PKCS1)
    {
        if (empty($this->primes)) {
            return false;
        }

        $oldFormat = $this->privateKeyFormat;
        $this->privateKeyFormat = $type;
        $temp = $this->_convertPrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients);
        $this->privateKeyFormat = $oldFormat;
        return $temp;
    }

    /**
     * Returns a minimalistic private key
     *
     * Returns the private key without the prime number constituants.  Structurally identical to a public key that
     * hasn't been set as the public key
     *
     * @see self::getPrivateKey()
     * @access private
     * @param string $key
     * @param int $type optional
     */
    function _getPrivatePublicKey($mode = self::PUBLIC_FORMAT_PKCS8)
    {
        if (empty($this->modulus) || empty($this->exponent)) {
            return false;
        }

        $oldFormat = $this->publicKeyFormat;
        $this->publicKeyFormat = $mode;
        $temp = $this->_convertPublicKey($this->modulus, $this->exponent);
        $this->publicKeyFormat = $oldFormat;
        return $temp;
    }

    /**
     *  __toString() magic method
     *
     * @access public
     * @return string
     */
    function __toString()
    {
        $key = $this->getPrivateKey($this->privateKeyFormat);
        if ($key !== false) {
            return $key;
        }
        $key = $this->_getPrivatePublicKey($this->publicKeyFormat);
        return $key !== false ? $key : '';
    }

    /**
     *  __clone() magic method
     *
     * @access public
     * @return Crypt_RSA
     */
    function __clone()
    {
        $key = new RSA();
        $key->loadKey($this);
        return $key;
    }

    /**
     * Generates the smallest and largest numbers requiring $bits bits
     *
     * @access private
     * @param int $bits
     * @return array
     */
    function _generateMinMax($bits)
    {
        $bytes = $bits >> 3;
        $min = str_repeat(chr(0), $bytes);
        $max = str_repeat(chr(0xFF), $bytes);
        $msb = $bits & 7;
        if ($msb) {
            $min = chr(1 << ($msb - 1)) . $min;
            $max = chr((1 << $msb) - 1) . $max;
        } else {
            $min[0] = chr(0x80);
        }

        return array(
            'min' => new BigInteger($min, 256),
            'max' => new BigInteger($max, 256)
        );
    }

    /**
     * DER-decode the length
     *
     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
     *
     * @access private
     * @param string $string
     * @return int
     */
    function _decodeLength(&$string)
    {
        $length = ord($this->_string_shift($string));
        if ($length & 0x80) { // definite length, long form
            $length&= 0x7F;
            $temp = $this->_string_shift($string, $length);
            list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
        }
        return $length;
    }

    /**
     * DER-encode the length
     *
     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
     *
     * @access private
     * @param int $length
     * @return string
     */
    function _encodeLength($length)
    {
        if ($length <= 0x7F) {
            return chr($length);
        }

        $temp = ltrim(pack('N', $length), chr(0));
        return pack('Ca*', 0x80 | strlen($temp), $temp);
    }

    /**
     * String Shift
     *
     * Inspired by array_shift
     *
     * @param string $string
     * @param int $index
     * @return string
     * @access private
     */
    function _string_shift(&$string, $index = 1)
    {
        $substr = substr($string, 0, $index);
        $string = substr($string, $index);
        return $substr;
    }

    /**
     * Determines the private key format
     *
     * @see self::createKey()
     * @access public
     * @param int $format
     */
    function setPrivateKeyFormat($format)
    {
        $this->privateKeyFormat = $format;
    }

    /**
     * Determines the public key format
     *
     * @see self::createKey()
     * @access public
     * @param int $format
     */
    function setPublicKeyFormat($format)
    {
        $this->publicKeyFormat = $format;
    }

    /**
     * Determines which hashing function should be used
     *
     * Used with signature production / verification and (if the encryption mode is self::ENCRYPTION_OAEP) encryption and
     * decryption.  If $hash isn't supported, sha1 is used.
     *
     * @access public
     * @param string $hash
     */
    function setHash($hash)
    {
        // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support.  md5-96 and sha1-96, for example.
        switch ($hash) {
            case 'md2':
            case 'md5':
            case 'sha1':
            case 'sha256':
            case 'sha384':
            case 'sha512':
                $this->hash = new Hash($hash);
                $this->hashName = $hash;
                break;
            default:
                $this->hash = new Hash('sha1');
                $this->hashName = 'sha1';
        }
        $this->hLen = $this->hash->getLength();
    }

    /**
     * Determines which hashing function should be used for the mask generation function
     *
     * The mask generation function is used by self::ENCRYPTION_OAEP and self::SIGNATURE_PSS and although it's
     * best if Hash and MGFHash are set to the same thing this is not a requirement.
     *
     * @access public
     * @param string $hash
     */
    function setMGFHash($hash)
    {
        // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support.  md5-96 and sha1-96, for example.
        switch ($hash) {
            case 'md2':
            case 'md5':
            case 'sha1':
            case 'sha256':
            case 'sha384':
            case 'sha512':
                $this->mgfHash = new Hash($hash);
                break;
            default:
                $this->mgfHash = new Hash('sha1');
        }
        $this->mgfHLen = $this->mgfHash->getLength();
    }

    /**
     * Determines the salt length
     *
     * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}:
     *
     *    Typical salt lengths in octets are hLen (the length of the output
     *    of the hash function Hash) and 0.
     *
     * @access public
     * @param int $format
     */
    function setSaltLength($sLen)
    {
        $this->sLen = $sLen;
    }

    /**
     * Integer-to-Octet-String primitive
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}.
     *
     * @access private
     * @param \phpseclib\Math\BigInteger $x
     * @param int $xLen
     * @return string
     */
    function _i2osp($x, $xLen)
    {
        $x = $x->toBytes();
        if (strlen($x) > $xLen) {
            user_error('Integer too large');
            return false;
        }
        return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
    }

    /**
     * Octet-String-to-Integer primitive
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}.
     *
     * @access private
     * @param string $x
     * @return \phpseclib\Math\BigInteger
     */
    function _os2ip($x)
    {
        return new BigInteger($x, 256);
    }

    /**
     * Exponentiate with or without Chinese Remainder Theorem
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}.
     *
     * @access private
     * @param \phpseclib\Math\BigInteger $x
     * @return \phpseclib\Math\BigInteger
     */
    function _exponentiate($x)
    {
        switch (true) {
            case empty($this->primes):
            case $this->primes[1]->equals($this->zero):
            case empty($this->coefficients):
            case $this->coefficients[2]->equals($this->zero):
            case empty($this->exponents):
            case $this->exponents[1]->equals($this->zero):
                return $x->modPow($this->exponent, $this->modulus);
        }

        $num_primes = count($this->primes);

        if (defined('CRYPT_RSA_DISABLE_BLINDING')) {
            $m_i = array(
                1 => $x->modPow($this->exponents[1], $this->primes[1]),
                2 => $x->modPow($this->exponents[2], $this->primes[2])
            );
            $h = $m_i[1]->subtract($m_i[2]);
            $h = $h->multiply($this->coefficients[2]);
            list(, $h) = $h->divide($this->primes[1]);
            $m = $m_i[2]->add($h->multiply($this->primes[2]));

            $r = $this->primes[1];
            for ($i = 3; $i <= $num_primes; $i++) {
                $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]);

                $r = $r->multiply($this->primes[$i - 1]);

                $h = $m_i->subtract($m);
                $h = $h->multiply($this->coefficients[$i]);
                list(, $h) = $h->divide($this->primes[$i]);

                $m = $m->add($r->multiply($h));
            }
        } else {
            $smallest = $this->primes[1];
            for ($i = 2; $i <= $num_primes; $i++) {
                if ($smallest->compare($this->primes[$i]) > 0) {
                    $smallest = $this->primes[$i];
                }
            }

            $one = new BigInteger(1);

            $r = $one->random($one, $smallest->subtract($one));

            $m_i = array(
                1 => $this->_blind($x, $r, 1),
                2 => $this->_blind($x, $r, 2)
            );
            $h = $m_i[1]->subtract($m_i[2]);
            $h = $h->multiply($this->coefficients[2]);
            list(, $h) = $h->divide($this->primes[1]);
            $m = $m_i[2]->add($h->multiply($this->primes[2]));

            $r = $this->primes[1];
            for ($i = 3; $i <= $num_primes; $i++) {
                $m_i = $this->_blind($x, $r, $i);

                $r = $r->multiply($this->primes[$i - 1]);

                $h = $m_i->subtract($m);
                $h = $h->multiply($this->coefficients[$i]);
                list(, $h) = $h->divide($this->primes[$i]);

                $m = $m->add($r->multiply($h));
            }
        }

        return $m;
    }

    /**
     * Performs RSA Blinding
     *
     * Protects against timing attacks by employing RSA Blinding.
     * Returns $x->modPow($this->exponents[$i], $this->primes[$i])
     *
     * @access private
     * @param \phpseclib\Math\BigInteger $x
     * @param \phpseclib\Math\BigInteger $r
     * @param int $i
     * @return \phpseclib\Math\BigInteger
     */
    function _blind($x, $r, $i)
    {
        $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i]));
        $x = $x->modPow($this->exponents[$i], $this->primes[$i]);

        $r = $r->modInverse($this->primes[$i]);
        $x = $x->multiply($r);
        list(, $x) = $x->divide($this->primes[$i]);

        return $x;
    }

    /**
     * Performs blinded RSA equality testing
     *
     * Protects against a particular type of timing attack described.
     *
     * See {@link http://codahale.com/a-lesson-in-timing-attacks/ A Lesson In Timing Attacks (or, Don't use MessageDigest.isEquals)}
     *
     * Thanks for the heads up singpolyma!
     *
     * @access private
     * @param string $x
     * @param string $y
     * @return bool
     */
    function _equals($x, $y)
    {
        if (strlen($x) != strlen($y)) {
            return false;
        }

        $result = 0;
        for ($i = 0; $i < strlen($x); $i++) {
            $result |= ord($x[$i]) ^ ord($y[$i]);
        }

        return $result == 0;
    }

    /**
     * RSAEP
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}.
     *
     * @access private
     * @param \phpseclib\Math\BigInteger $m
     * @return \phpseclib\Math\BigInteger
     */
    function _rsaep($m)
    {
        if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
            user_error('Message representative out of range');
            return false;
        }
        return $this->_exponentiate($m);
    }

    /**
     * RSADP
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}.
     *
     * @access private
     * @param \phpseclib\Math\BigInteger $c
     * @return \phpseclib\Math\BigInteger
     */
    function _rsadp($c)
    {
        if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) {
            user_error('Ciphertext representative out of range');
            return false;
        }
        return $this->_exponentiate($c);
    }

    /**
     * RSASP1
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}.
     *
     * @access private
     * @param \phpseclib\Math\BigInteger $m
     * @return \phpseclib\Math\BigInteger
     */
    function _rsasp1($m)
    {
        if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
            user_error('Message representative out of range');
            return false;
        }
        return $this->_exponentiate($m);
    }

    /**
     * RSAVP1
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}.
     *
     * @access private
     * @param \phpseclib\Math\BigInteger $s
     * @return \phpseclib\Math\BigInteger
     */
    function _rsavp1($s)
    {
        if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) {
            user_error('Signature representative out of range');
            return false;
        }
        return $this->_exponentiate($s);
    }

    /**
     * MGF1
     *
     * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}.
     *
     * @access private
     * @param string $mgfSeed
     * @param int $mgfLen
     * @return string
     */
    function _mgf1($mgfSeed, $maskLen)
    {
        // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output.

        $t = '';
        $count = ceil($maskLen / $this->mgfHLen);
        for ($i = 0; $i < $count; $i++) {
            $c = pack('N', $i);
            $t.= $this->mgfHash->hash($mgfSeed . $c);
        }

        return substr($t, 0, $maskLen);
    }

    /**
     * RSAES-OAEP-ENCRYPT
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and
     * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}.
     *
     * @access private
     * @param string $m
     * @param string $l
     * @return string
     */
    function _rsaes_oaep_encrypt($m, $l = '')
    {
        $mLen = strlen($m);

        // Length checking

        // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
        // be output.

        if ($mLen > $this->k - 2 * $this->hLen - 2) {
            user_error('Message too long');
            return false;
        }

        // EME-OAEP encoding

        $lHash = $this->hash->hash($l);
        $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
        $db = $lHash . $ps . chr(1) . $m;
        $seed = Random::string($this->hLen);
        $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
        $maskedDB = $db ^ $dbMask;
        $seedMask = $this->_mgf1($maskedDB, $this->hLen);
        $maskedSeed = $seed ^ $seedMask;
        $em = chr(0) . $maskedSeed . $maskedDB;

        // RSA encryption

        $m = $this->_os2ip($em);
        $c = $this->_rsaep($m);
        $c = $this->_i2osp($c, $this->k);

        // Output the ciphertext C

        return $c;
    }

    /**
     * RSAES-OAEP-DECRYPT
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}.  The fact that the error
     * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2:
     *
     *    Note.  Care must be taken to ensure that an opponent cannot
     *    distinguish the different error conditions in Step 3.g, whether by
     *    error message or timing, or, more generally, learn partial
     *    information about the encoded message EM.  Otherwise an opponent may
     *    be able to obtain useful information about the decryption of the
     *    ciphertext C, leading to a chosen-ciphertext attack such as the one
     *    observed by Manger [36].
     *
     * As for $l...  to quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}:
     *
     *    Both the encryption and the decryption operations of RSAES-OAEP take
     *    the value of a label L as input.  In this version of PKCS #1, L is
     *    the empty string; other uses of the label are outside the scope of
     *    this document.
     *
     * @access private
     * @param string $c
     * @param string $l
     * @return string
     */
    function _rsaes_oaep_decrypt($c, $l = '')
    {
        // Length checking

        // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
        // be output.

        if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) {
            user_error('Decryption error');
            return false;
        }

        // RSA decryption

        $c = $this->_os2ip($c);
        $m = $this->_rsadp($c);
        if ($m === false) {
            user_error('Decryption error');
            return false;
        }
        $em = $this->_i2osp($m, $this->k);

        // EME-OAEP decoding

        $lHash = $this->hash->hash($l);
        $y = ord($em[0]);
        $maskedSeed = substr($em, 1, $this->hLen);
        $maskedDB = substr($em, $this->hLen + 1);
        $seedMask = $this->_mgf1($maskedDB, $this->hLen);
        $seed = $maskedSeed ^ $seedMask;
        $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
        $db = $maskedDB ^ $dbMask;
        $lHash2 = substr($db, 0, $this->hLen);
        $m = substr($db, $this->hLen);
        if ($lHash != $lHash2) {
            user_error('Decryption error');
            return false;
        }
        $m = ltrim($m, chr(0));
        if (ord($m[0]) != 1) {
            user_error('Decryption error');
            return false;
        }

        // Output the message M

        return substr($m, 1);
    }

    /**
     * Raw Encryption / Decryption
     *
     * Doesn't use padding and is not recommended.
     *
     * @access private
     * @param string $m
     * @return string
     */
    function _raw_encrypt($m)
    {
        $temp = $this->_os2ip($m);
        $temp = $this->_rsaep($temp);
        return  $this->_i2osp($temp, $this->k);
    }

    /**
     * RSAES-PKCS1-V1_5-ENCRYPT
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}.
     *
     * @access private
     * @param string $m
     * @return string
     */
    function _rsaes_pkcs1_v1_5_encrypt($m)
    {
        $mLen = strlen($m);

        // Length checking

        if ($mLen > $this->k - 11) {
            user_error('Message too long');
            return false;
        }

        // EME-PKCS1-v1_5 encoding

        $psLen = $this->k - $mLen - 3;
        $ps = '';
        while (strlen($ps) != $psLen) {
            $temp = Random::string($psLen - strlen($ps));
            $temp = str_replace("\x00", '', $temp);
            $ps.= $temp;
        }
        $type = 2;
        // see the comments of _rsaes_pkcs1_v1_5_decrypt() to understand why this is being done
        if (defined('CRYPT_RSA_PKCS15_COMPAT') && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) {
            $type = 1;
            // "The padding string PS shall consist of k-3-||D|| octets. ... for block type 01, they shall have value FF"
            $ps = str_repeat("\xFF", $psLen);
        }
        $em = chr(0) . chr($type) . $ps . chr(0) . $m;

        // RSA encryption
        $m = $this->_os2ip($em);
        $c = $this->_rsaep($m);
        $c = $this->_i2osp($c, $this->k);

        // Output the ciphertext C

        return $c;
    }

    /**
     * RSAES-PKCS1-V1_5-DECRYPT
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}.
     *
     * For compatibility purposes, this function departs slightly from the description given in RFC3447.
     * The reason being that RFC2313#section-8.1 (PKCS#1 v1.5) states that ciphertext's encrypted by the
     * private key should have the second byte set to either 0 or 1 and that ciphertext's encrypted by the
     * public key should have the second byte set to 2.  In RFC3447 (PKCS#1 v2.1), the second byte is supposed
     * to be 2 regardless of which key is used.  For compatibility purposes, we'll just check to make sure the
     * second byte is 2 or less.  If it is, we'll accept the decrypted string as valid.
     *
     * As a consequence of this, a private key encrypted ciphertext produced with \phpseclib\Crypt\RSA may not decrypt
     * with a strictly PKCS#1 v1.5 compliant RSA implementation.  Public key encrypted ciphertext's should but
     * not private key encrypted ciphertext's.
     *
     * @access private
     * @param string $c
     * @return string
     */
    function _rsaes_pkcs1_v1_5_decrypt($c)
    {
        // Length checking

        if (strlen($c) != $this->k) { // or if k < 11
            user_error('Decryption error');
            return false;
        }

        // RSA decryption

        $c = $this->_os2ip($c);
        $m = $this->_rsadp($c);

        if ($m === false) {
            user_error('Decryption error');
            return false;
        }
        $em = $this->_i2osp($m, $this->k);

        // EME-PKCS1-v1_5 decoding

        if (ord($em[0]) != 0 || ord($em[1]) > 2) {
            user_error('Decryption error');
            return false;
        }

        $ps = substr($em, 2, strpos($em, chr(0), 2) - 2);
        $m = substr($em, strlen($ps) + 3);

        if (strlen($ps) < 8) {
            user_error('Decryption error');
            return false;
        }

        // Output M

        return $m;
    }

    /**
     * EMSA-PSS-ENCODE
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}.
     *
     * @access private
     * @param string $m
     * @param int $emBits
     */
    function _emsa_pss_encode($m, $emBits)
    {
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
        // be output.

        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
        $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;

        $mHash = $this->hash->hash($m);
        if ($emLen < $this->hLen + $sLen + 2) {
            user_error('Encoding error');
            return false;
        }

        $salt = Random::string($sLen);
        $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
        $h = $this->hash->hash($m2);
        $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2);
        $db = $ps . chr(1) . $salt;
        $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1);
        $maskedDB = $db ^ $dbMask;
        $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
        $em = $maskedDB . $h . chr(0xBC);

        return $em;
    }

    /**
     * EMSA-PSS-VERIFY
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}.
     *
     * @access private
     * @param string $m
     * @param string $em
     * @param int $emBits
     * @return string
     */
    function _emsa_pss_verify($m, $em, $emBits)
    {
        // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
        // be output.

        $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8);
        $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;

        $mHash = $this->hash->hash($m);
        if ($emLen < $this->hLen + $sLen + 2) {
            return false;
        }

        if ($em[strlen($em) - 1] != chr(0xBC)) {
            return false;
        }

        $maskedDB = substr($em, 0, -$this->hLen - 1);
        $h = substr($em, -$this->hLen - 1, $this->hLen);
        $temp = chr(0xFF << ($emBits & 7));
        if ((~$maskedDB[0] & $temp) != $temp) {
            return false;
        }
        $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1);
        $db = $maskedDB ^ $dbMask;
        $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
        $temp = $emLen - $this->hLen - $sLen - 2;
        if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
            return false;
        }
        $salt = substr($db, $temp + 1); // should be $sLen long
        $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
        $h2 = $this->hash->hash($m2);
        return $this->_equals($h, $h2);
    }

    /**
     * RSASSA-PSS-SIGN
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}.
     *
     * @access private
     * @param string $m
     * @return string
     */
    function _rsassa_pss_sign($m)
    {
        // EMSA-PSS encoding

        $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1);

        // RSA signature

        $m = $this->_os2ip($em);
        $s = $this->_rsasp1($m);
        $s = $this->_i2osp($s, $this->k);

        // Output the signature S

        return $s;
    }

    /**
     * RSASSA-PSS-VERIFY
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}.
     *
     * @access private
     * @param string $m
     * @param string $s
     * @return string
     */
    function _rsassa_pss_verify($m, $s)
    {
        // Length checking

        if (strlen($s) != $this->k) {
            user_error('Invalid signature');
            return false;
        }

        // RSA verification

        $modBits = 8 * $this->k;

        $s2 = $this->_os2ip($s);
        $m2 = $this->_rsavp1($s2);
        if ($m2 === false) {
            user_error('Invalid signature');
            return false;
        }
        $em = $this->_i2osp($m2, $modBits >> 3);
        if ($em === false) {
            user_error('Invalid signature');
            return false;
        }

        // EMSA-PSS verification

        return $this->_emsa_pss_verify($m, $em, $modBits - 1);
    }

    /**
     * EMSA-PKCS1-V1_5-ENCODE
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}.
     *
     * @access private
     * @param string $m
     * @param int $emLen
     * @return string
     */
    function _emsa_pkcs1_v1_5_encode($m, $emLen)
    {
        $h = $this->hash->hash($m);
        if ($h === false) {
            return false;
        }

        // see http://tools.ietf.org/html/rfc3447#page-43
        switch ($this->hashName) {
            case 'md2':
                $t = pack('H*', '3020300c06082a864886f70d020205000410');
                break;
            case 'md5':
                $t = pack('H*', '3020300c06082a864886f70d020505000410');
                break;
            case 'sha1':
                $t = pack('H*', '3021300906052b0e03021a05000414');
                break;
            case 'sha256':
                $t = pack('H*', '3031300d060960864801650304020105000420');
                break;
            case 'sha384':
                $t = pack('H*', '3041300d060960864801650304020205000430');
                break;
            case 'sha512':
                $t = pack('H*', '3051300d060960864801650304020305000440');
        }
        $t.= $h;
        $tLen = strlen($t);

        if ($emLen < $tLen + 11) {
            user_error('Intended encoded message length too short');
            return false;
        }

        $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);

        $em = "\0\1$ps\0$t";

        return $em;
    }

    /**
     * RSASSA-PKCS1-V1_5-SIGN
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}.
     *
     * @access private
     * @param string $m
     * @return string
     */
    function _rsassa_pkcs1_v1_5_sign($m)
    {
        // EMSA-PKCS1-v1_5 encoding

        $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k);
        if ($em === false) {
            user_error('RSA modulus too short');
            return false;
        }

        // RSA signature

        $m = $this->_os2ip($em);
        $s = $this->_rsasp1($m);
        $s = $this->_i2osp($s, $this->k);

        // Output the signature S

        return $s;
    }

    /**
     * RSASSA-PKCS1-V1_5-VERIFY
     *
     * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}.
     *
     * @access private
     * @param string $m
     * @return string
     */
    function _rsassa_pkcs1_v1_5_verify($m, $s)
    {
        // Length checking

        if (strlen($s) != $this->k) {
            user_error('Invalid signature');
            return false;
        }

        // RSA verification

        $s = $this->_os2ip($s);
        $m2 = $this->_rsavp1($s);
        if ($m2 === false) {
            user_error('Invalid signature');
            return false;
        }
        $em = $this->_i2osp($m2, $this->k);
        if ($em === false) {
            user_error('Invalid signature');
            return false;
        }

        // EMSA-PKCS1-v1_5 encoding

        $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k);
        if ($em2 === false) {
            user_error('RSA modulus too short');
            return false;
        }

        // Compare
        return $this->_equals($em, $em2);
    }

    /**
     * Set Encryption Mode
     *
     * Valid values include self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1.
     *
     * @access public
     * @param int $mode
     */
    function setEncryptionMode($mode)
    {
        $this->encryptionMode = $mode;
    }

    /**
     * Set Signature Mode
     *
     * Valid values include self::SIGNATURE_PSS and self::SIGNATURE_PKCS1
     *
     * @access public
     * @param int $mode
     */
    function setSignatureMode($mode)
    {
        $this->signatureMode = $mode;
    }

    /**
     * Set public key comment.
     *
     * @access public
     * @param string $comment
     */
    function setComment($comment)
    {
        $this->comment = $comment;
    }

    /**
     * Get public key comment.
     *
     * @access public
     * @return string
     */
    function getComment()
    {
        return $this->comment;
    }

    /**
     * Encryption
     *
     * Both self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1 both place limits on how long $plaintext can be.
     * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
     * be concatenated together.
     *
     * @see self::decrypt()
     * @access public
     * @param string $plaintext
     * @return string
     */
    function encrypt($plaintext)
    {
        switch ($this->encryptionMode) {
            case self::ENCRYPTION_NONE:
                $plaintext = str_split($plaintext, $this->k);
                $ciphertext = '';
                foreach ($plaintext as $m) {
                    $ciphertext.= $this->_raw_encrypt($m);
                }
                return $ciphertext;
            case self::ENCRYPTION_PKCS1:
                $length = $this->k - 11;
                if ($length <= 0) {
                    return false;
                }

                $plaintext = str_split($plaintext, $length);
                $ciphertext = '';
                foreach ($plaintext as $m) {
                    $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m);
                }
                return $ciphertext;
            //case self::ENCRYPTION_OAEP:
            default:
                $length = $this->k - 2 * $this->hLen - 2;
                if ($length <= 0) {
                    return false;
                }

                $plaintext = str_split($plaintext, $length);
                $ciphertext = '';
                foreach ($plaintext as $m) {
                    $ciphertext.= $this->_rsaes_oaep_encrypt($m);
                }
                return $ciphertext;
        }
    }

    /**
     * Decryption
     *
     * @see self::encrypt()
     * @access public
     * @param string $plaintext
     * @return string
     */
    function decrypt($ciphertext)
    {
        if ($this->k <= 0) {
            return false;
        }

        $ciphertext = str_split($ciphertext, $this->k);
        $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $this->k, chr(0), STR_PAD_LEFT);

        $plaintext = '';

        switch ($this->encryptionMode) {
            case self::ENCRYPTION_NONE:
                $decrypt = '_raw_encrypt';
                break;
            case self::ENCRYPTION_PKCS1:
                $decrypt = '_rsaes_pkcs1_v1_5_decrypt';
                break;
            //case self::ENCRYPTION_OAEP:
            default:
                $decrypt = '_rsaes_oaep_decrypt';
        }

        foreach ($ciphertext as $c) {
            $temp = $this->$decrypt($c);
            if ($temp === false) {
                return false;
            }
            $plaintext.= $temp;
        }

        return $plaintext;
    }

    /**
     * Create a signature
     *
     * @see self::verify()
     * @access public
     * @param string $message
     * @return string
     */
    function sign($message)
    {
        if (empty($this->modulus) || empty($this->exponent)) {
            return false;
        }

        switch ($this->signatureMode) {
            case self::SIGNATURE_PKCS1:
                return $this->_rsassa_pkcs1_v1_5_sign($message);
            //case self::SIGNATURE_PSS:
            default:
                return $this->_rsassa_pss_sign($message);
        }
    }

    /**
     * Verifies a signature
     *
     * @see self::sign()
     * @access public
     * @param string $message
     * @param string $signature
     * @return bool
     */
    function verify($message, $signature)
    {
        if (empty($this->modulus) || empty($this->exponent)) {
            return false;
        }

        switch ($this->signatureMode) {
            case self::SIGNATURE_PKCS1:
                return $this->_rsassa_pkcs1_v1_5_verify($message, $signature);
            //case self::SIGNATURE_PSS:
            default:
                return $this->_rsassa_pss_verify($message, $signature);
        }
    }

    /**
     * Extract raw BER from Base64 encoding
     *
     * @access private
     * @param string $str
     * @return string
     */
    function _extractBER($str)
    {
        /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
         * above and beyond the ceritificate.
         * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
         *
         * Bag Attributes
         *     localKeyID: 01 00 00 00
         * subject=/O=organization/OU=org unit/CN=common name
         * issuer=/O=organization/CN=common name
         */
        $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
        // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
        $temp = preg_replace('#-+[^-]+-+#', '', $temp);
        // remove new lines
        $temp = str_replace(array("\r", "\n", ' '), '', $temp);
        $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
        return $temp != false ? $temp : $str;
    }
}
<?php

/**
 * Pure-PHP implementation of Triple DES.
 *
 * Uses mcrypt, if available, and an internal implementation, otherwise.  Operates in the EDE3 mode (encrypt-decrypt-encrypt).
 *
 * PHP version 5
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $des = new \phpseclib\Crypt\TripleDES();
 *
 *    $des->setKey('abcdefghijklmnopqrstuvwx');
 *
 *    $size = 10 * 1024;
 *    $plaintext = '';
 *    for ($i = 0; $i < $size; $i++) {
 *        $plaintext.= 'a';
 *    }
 *
 *    echo $des->decrypt($des->encrypt($plaintext));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   TripleDES
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP implementation of Triple DES.
 *
 * @package TripleDES
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class TripleDES extends DES
{
    /**
     * Encrypt / decrypt using inner chaining
     *
     * Inner chaining is used by SSH-1 and is generally considered to be less secure then outer chaining (self::MODE_CBC3).
     */
    const MODE_3CBC = -2;

    /**
     * Encrypt / decrypt using outer chaining
     *
     * Outer chaining is used by SSH-2 and when the mode is set to \phpseclib\Crypt\Base::MODE_CBC.
     */
    const MODE_CBC3 = Base::MODE_CBC;

    /**
     * Key Length (in bytes)
     *
     * @see \phpseclib\Crypt\TripleDES::setKeyLength()
     * @var int
     * @access private
     */
    var $key_length = 24;

    /**
     * The default salt used by setPassword()
     *
     * @see \phpseclib\Crypt\Base::password_default_salt
     * @see \phpseclib\Crypt\Base::setPassword()
     * @var string
     * @access private
     */
    var $password_default_salt = 'phpseclib';

    /**
     * The mcrypt specific name of the cipher
     *
     * @see \phpseclib\Crypt\DES::cipher_name_mcrypt
     * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
     * @var string
     * @access private
     */
    var $cipher_name_mcrypt = 'tripledes';

    /**
     * Optimizing value while CFB-encrypting
     *
     * @see \phpseclib\Crypt\Base::cfb_init_len
     * @var int
     * @access private
     */
    var $cfb_init_len = 750;

    /**
     * max possible size of $key
     *
     * @see self::setKey()
     * @see \phpseclib\Crypt\DES::setKey()
     * @var string
     * @access private
     */
    var $key_length_max = 24;

    /**
     * Internal flag whether using self::MODE_3CBC or not
     *
     * @var bool
     * @access private
     */
    var $mode_3cbc;

    /**
     * The \phpseclib\Crypt\DES objects
     *
     * Used only if $mode_3cbc === true
     *
     * @var array
     * @access private
     */
    var $des;

    /**
     * Default Constructor.
     *
     * Determines whether or not the mcrypt extension should be used.
     *
     * $mode could be:
     *
     * - \phpseclib\Crypt\Base::MODE_ECB
     *
     * - \phpseclib\Crypt\Base::MODE_CBC
     *
     * - \phpseclib\Crypt\Base::MODE_CTR
     *
     * - \phpseclib\Crypt\Base::MODE_CFB
     *
     * - \phpseclib\Crypt\Base::MODE_OFB
     *
     * - \phpseclib\Crypt\TripleDES::MODE_3CBC
     *
     * If not explicitly set, \phpseclib\Crypt\Base::MODE_CBC will be used.
     *
     * @see \phpseclib\Crypt\DES::__construct()
     * @see \phpseclib\Crypt\Base::__construct()
     * @param int $mode
     * @access public
     */
    function __construct($mode = Base::MODE_CBC)
    {
        switch ($mode) {
            // In case of self::MODE_3CBC, we init as CRYPT_DES_MODE_CBC
            // and additional flag us internally as 3CBC
            case self::MODE_3CBC:
                parent::__construct(Base::MODE_CBC);
                $this->mode_3cbc = true;

                // This three $des'es will do the 3CBC work (if $key > 64bits)
                $this->des = array(
                    new DES(Base::MODE_CBC),
                    new DES(Base::MODE_CBC),
                    new DES(Base::MODE_CBC),
                );

                // we're going to be doing the padding, ourselves, so disable it in the \phpseclib\Crypt\DES objects
                $this->des[0]->disablePadding();
                $this->des[1]->disablePadding();
                $this->des[2]->disablePadding();
                break;
            // If not 3CBC, we init as usual
            default:
                parent::__construct($mode);
        }
    }

    /**
     * Test for engine validity
     *
     * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
     *
     * @see \phpseclib\Crypt\Base::__construct()
     * @param int $engine
     * @access public
     * @return bool
     */
    function isValidEngine($engine)
    {
        if ($engine == self::ENGINE_OPENSSL) {
            $this->cipher_name_openssl_ecb = 'des-ede3';
            $mode = $this->_openssl_translate_mode();
            $this->cipher_name_openssl = $mode == 'ecb' ? 'des-ede3' : 'des-ede3-' . $mode;
        }

        return parent::isValidEngine($engine);
    }

    /**
     * Sets the initialization vector. (optional)
     *
     * SetIV is not required when \phpseclib\Crypt\Base::MODE_ECB is being used.  If not explicitly set, it'll be assumed
     * to be all zero's.
     *
     * @see \phpseclib\Crypt\Base::setIV()
     * @access public
     * @param string $iv
     */
    function setIV($iv)
    {
        parent::setIV($iv);
        if ($this->mode_3cbc) {
            $this->des[0]->setIV($iv);
            $this->des[1]->setIV($iv);
            $this->des[2]->setIV($iv);
        }
    }

    /**
     * Sets the key length.
     *
     * Valid key lengths are 64, 128 and 192
     *
     * @see \phpseclib\Crypt\Base:setKeyLength()
     * @access public
     * @param int $length
     */
    function setKeyLength($length)
    {
        $length >>= 3;
        switch (true) {
            case $length <= 8:
                $this->key_length = 8;
                break;
            case $length <= 16:
                $this->key_length = 16;
                break;
            default:
                $this->key_length = 24;
        }

        parent::setKeyLength($length);
    }

    /**
     * Sets the key.
     *
     * Keys can be of any length.  Triple DES, itself, can use 128-bit (eg. strlen($key) == 16) or
     * 192-bit (eg. strlen($key) == 24) keys.  This function pads and truncates $key as appropriate.
     *
     * DES also requires that every eighth bit be a parity bit, however, we'll ignore that.
     *
     * If the key is not explicitly set, it'll be assumed to be all null bytes.
     *
     * @access public
     * @see \phpseclib\Crypt\DES::setKey()
     * @see \phpseclib\Crypt\Base::setKey()
     * @param string $key
     */
    function setKey($key)
    {
        $length = $this->explicit_key_length ? $this->key_length : strlen($key);
        if ($length > 8) {
            $key = str_pad(substr($key, 0, 24), 24, chr(0));
            // if $key is between 64 and 128-bits, use the first 64-bits as the last, per this:
            // http://php.net/function.mcrypt-encrypt#47973
            $key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24);
        } else {
            $key = str_pad($key, 8, chr(0));
        }
        parent::setKey($key);

        // And in case of self::MODE_3CBC:
        // if key <= 64bits we not need the 3 $des to work,
        // because we will then act as regular DES-CBC with just a <= 64bit key.
        // So only if the key > 64bits (> 8 bytes) we will call setKey() for the 3 $des.
        if ($this->mode_3cbc && $length > 8) {
            $this->des[0]->setKey(substr($key,  0, 8));
            $this->des[1]->setKey(substr($key,  8, 8));
            $this->des[2]->setKey(substr($key, 16, 8));
        }
    }

    /**
     * Encrypts a message.
     *
     * @see \phpseclib\Crypt\Base::encrypt()
     * @access public
     * @param string $plaintext
     * @return string $cipertext
     */
    function encrypt($plaintext)
    {
        // parent::en/decrypt() is able to do all the work for all modes and keylengths,
        // except for: self::MODE_3CBC (inner chaining CBC) with a key > 64bits

        // if the key is smaller then 8, do what we'd normally do
        if ($this->mode_3cbc && strlen($this->key) > 8) {
            return $this->des[2]->encrypt(
                $this->des[1]->decrypt(
                    $this->des[0]->encrypt(
                        $this->_pad($plaintext)
                    )
                )
            );
        }

        return parent::encrypt($plaintext);
    }

    /**
     * Decrypts a message.
     *
     * @see \phpseclib\Crypt\Base::decrypt()
     * @access public
     * @param string $ciphertext
     * @return string $plaintext
     */
    function decrypt($ciphertext)
    {
        if ($this->mode_3cbc && strlen($this->key) > 8) {
            return $this->_unpad(
                $this->des[0]->decrypt(
                    $this->des[1]->encrypt(
                        $this->des[2]->decrypt(
                            str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, "\0")
                        )
                    )
                )
            );
        }

        return parent::decrypt($ciphertext);
    }

    /**
     * Treat consecutive "packets" as if they are a continuous buffer.
     *
     * Say you have a 16-byte plaintext $plaintext.  Using the default behavior, the two following code snippets
     * will yield different outputs:
     *
     * <code>
     *    echo $des->encrypt(substr($plaintext, 0, 8));
     *    echo $des->encrypt(substr($plaintext, 8, 8));
     * </code>
     * <code>
     *    echo $des->encrypt($plaintext);
     * </code>
     *
     * The solution is to enable the continuous buffer.  Although this will resolve the above discrepancy, it creates
     * another, as demonstrated with the following:
     *
     * <code>
     *    $des->encrypt(substr($plaintext, 0, 8));
     *    echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8)));
     * </code>
     * <code>
     *    echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8)));
     * </code>
     *
     * With the continuous buffer disabled, these would yield the same output.  With it enabled, they yield different
     * outputs.  The reason is due to the fact that the initialization vector's change after every encryption /
     * decryption round when the continuous buffer is enabled.  When it's disabled, they remain constant.
     *
     * Put another way, when the continuous buffer is enabled, the state of the \phpseclib\Crypt\DES() object changes after each
     * encryption / decryption round, whereas otherwise, it'd remain constant.  For this reason, it's recommended that
     * continuous buffers not be used.  They do offer better security and are, in fact, sometimes required (SSH uses them),
     * however, they are also less intuitive and more likely to cause you problems.
     *
     * @see \phpseclib\Crypt\Base::enableContinuousBuffer()
     * @see self::disableContinuousBuffer()
     * @access public
     */
    function enableContinuousBuffer()
    {
        parent::enableContinuousBuffer();
        if ($this->mode_3cbc) {
            $this->des[0]->enableContinuousBuffer();
            $this->des[1]->enableContinuousBuffer();
            $this->des[2]->enableContinuousBuffer();
        }
    }

    /**
     * Treat consecutive packets as if they are a discontinuous buffer.
     *
     * The default behavior.
     *
     * @see \phpseclib\Crypt\Base::disableContinuousBuffer()
     * @see self::enableContinuousBuffer()
     * @access public
     */
    function disableContinuousBuffer()
    {
        parent::disableContinuousBuffer();
        if ($this->mode_3cbc) {
            $this->des[0]->disableContinuousBuffer();
            $this->des[1]->disableContinuousBuffer();
            $this->des[2]->disableContinuousBuffer();
        }
    }

    /**
     * Creates the key schedule
     *
     * @see \phpseclib\Crypt\DES::_setupKey()
     * @see \phpseclib\Crypt\Base::_setupKey()
     * @access private
     */
    function _setupKey()
    {
        switch (true) {
            // if $key <= 64bits we configure our internal pure-php cipher engine
            // to act as regular [1]DES, not as 3DES. mcrypt.so::tripledes does the same.
            case strlen($this->key) <= 8:
                $this->des_rounds = 1;
                break;

            // otherwise, if $key > 64bits, we configure our engine to work as 3DES.
            default:
                $this->des_rounds = 3;

                // (only) if 3CBC is used we have, of course, to setup the $des[0-2] keys also separately.
                if ($this->mode_3cbc) {
                    $this->des[0]->_setupKey();
                    $this->des[1]->_setupKey();
                    $this->des[2]->_setupKey();

                    // because $des[0-2] will, now, do all the work we can return here
                    // not need unnecessary stress parent::_setupKey() with our, now unused, $key.
                    return;
                }
        }
        // setup our key
        parent::_setupKey();
    }

    /**
     * Sets the internal crypt engine
     *
     * @see \phpseclib\Crypt\Base::__construct()
     * @see \phpseclib\Crypt\Base::setPreferredEngine()
     * @param int $engine
     * @access public
     * @return int
     */
    function setPreferredEngine($engine)
    {
        if ($this->mode_3cbc) {
            $this->des[0]->setPreferredEngine($engine);
            $this->des[1]->setPreferredEngine($engine);
            $this->des[2]->setPreferredEngine($engine);
        }

        return parent::setPreferredEngine($engine);
    }
}
<?php

/**
 * Pure-PHP implementation of Twofish.
 *
 * Uses mcrypt, if available, and an internal implementation, otherwise.
 *
 * PHP version 5
 *
 * Useful resources are as follows:
 *
 *  - {@link http://en.wikipedia.org/wiki/Twofish Wikipedia description of Twofish}
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $twofish = new \phpseclib\Crypt\Twofish();
 *
 *    $twofish->setKey('12345678901234567890123456789012');
 *
 *    $plaintext = str_repeat('a', 1024);
 *
 *    echo $twofish->decrypt($twofish->encrypt($plaintext));
 * ?>
 * </code>
 *
 * @category  Crypt
 * @package   Twofish
 * @author    Jim Wigginton <terrafrost@php.net>
 * @author    Hans-Juergen Petrich <petrich@tronic-media.com>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt;

/**
 * Pure-PHP implementation of Twofish.
 *
 * @package Twofish
 * @author  Jim Wigginton <terrafrost@php.net>
 * @author  Hans-Juergen Petrich <petrich@tronic-media.com>
 * @access  public
 */
class Twofish extends Base
{
    /**
     * The mcrypt specific name of the cipher
     *
     * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
     * @var string
     * @access private
     */
    var $cipher_name_mcrypt = 'twofish';

    /**
     * Optimizing value while CFB-encrypting
     *
     * @see \phpseclib\Crypt\Base::cfb_init_len
     * @var int
     * @access private
     */
    var $cfb_init_len = 800;

    /**
     * Q-Table
     *
     * @var array
     * @access private
     */
    var $q0 = array(
        0xA9, 0x67, 0xB3, 0xE8, 0x04, 0xFD, 0xA3, 0x76,
        0x9A, 0x92, 0x80, 0x78, 0xE4, 0xDD, 0xD1, 0x38,
        0x0D, 0xC6, 0x35, 0x98, 0x18, 0xF7, 0xEC, 0x6C,
        0x43, 0x75, 0x37, 0x26, 0xFA, 0x13, 0x94, 0x48,
        0xF2, 0xD0, 0x8B, 0x30, 0x84, 0x54, 0xDF, 0x23,
        0x19, 0x5B, 0x3D, 0x59, 0xF3, 0xAE, 0xA2, 0x82,
        0x63, 0x01, 0x83, 0x2E, 0xD9, 0x51, 0x9B, 0x7C,
        0xA6, 0xEB, 0xA5, 0xBE, 0x16, 0x0C, 0xE3, 0x61,
        0xC0, 0x8C, 0x3A, 0xF5, 0x73, 0x2C, 0x25, 0x0B,
        0xBB, 0x4E, 0x89, 0x6B, 0x53, 0x6A, 0xB4, 0xF1,
        0xE1, 0xE6, 0xBD, 0x45, 0xE2, 0xF4, 0xB6, 0x66,
        0xCC, 0x95, 0x03, 0x56, 0xD4, 0x1C, 0x1E, 0xD7,
        0xFB, 0xC3, 0x8E, 0xB5, 0xE9, 0xCF, 0xBF, 0xBA,
        0xEA, 0x77, 0x39, 0xAF, 0x33, 0xC9, 0x62, 0x71,
        0x81, 0x79, 0x09, 0xAD, 0x24, 0xCD, 0xF9, 0xD8,
        0xE5, 0xC5, 0xB9, 0x4D, 0x44, 0x08, 0x86, 0xE7,
        0xA1, 0x1D, 0xAA, 0xED, 0x06, 0x70, 0xB2, 0xD2,
        0x41, 0x7B, 0xA0, 0x11, 0x31, 0xC2, 0x27, 0x90,
        0x20, 0xF6, 0x60, 0xFF, 0x96, 0x5C, 0xB1, 0xAB,
        0x9E, 0x9C, 0x52, 0x1B, 0x5F, 0x93, 0x0A, 0xEF,
        0x91, 0x85, 0x49, 0xEE, 0x2D, 0x4F, 0x8F, 0x3B,
        0x47, 0x87, 0x6D, 0x46, 0xD6, 0x3E, 0x69, 0x64,
        0x2A, 0xCE, 0xCB, 0x2F, 0xFC, 0x97, 0x05, 0x7A,
        0xAC, 0x7F, 0xD5, 0x1A, 0x4B, 0x0E, 0xA7, 0x5A,
        0x28, 0x14, 0x3F, 0x29, 0x88, 0x3C, 0x4C, 0x02,
        0xB8, 0xDA, 0xB0, 0x17, 0x55, 0x1F, 0x8A, 0x7D,
        0x57, 0xC7, 0x8D, 0x74, 0xB7, 0xC4, 0x9F, 0x72,
        0x7E, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34,
        0x6E, 0x50, 0xDE, 0x68, 0x65, 0xBC, 0xDB, 0xF8,
        0xC8, 0xA8, 0x2B, 0x40, 0xDC, 0xFE, 0x32, 0xA4,
        0xCA, 0x10, 0x21, 0xF0, 0xD3, 0x5D, 0x0F, 0x00,
        0x6F, 0x9D, 0x36, 0x42, 0x4A, 0x5E, 0xC1, 0xE0
    );

    /**
     * Q-Table
     *
     * @var array
     * @access private
     */
    var $q1 = array(
        0x75, 0xF3, 0xC6, 0xF4, 0xDB, 0x7B, 0xFB, 0xC8,
        0x4A, 0xD3, 0xE6, 0x6B, 0x45, 0x7D, 0xE8, 0x4B,
        0xD6, 0x32, 0xD8, 0xFD, 0x37, 0x71, 0xF1, 0xE1,
        0x30, 0x0F, 0xF8, 0x1B, 0x87, 0xFA, 0x06, 0x3F,
        0x5E, 0xBA, 0xAE, 0x5B, 0x8A, 0x00, 0xBC, 0x9D,
        0x6D, 0xC1, 0xB1, 0x0E, 0x80, 0x5D, 0xD2, 0xD5,
        0xA0, 0x84, 0x07, 0x14, 0xB5, 0x90, 0x2C, 0xA3,
        0xB2, 0x73, 0x4C, 0x54, 0x92, 0x74, 0x36, 0x51,
        0x38, 0xB0, 0xBD, 0x5A, 0xFC, 0x60, 0x62, 0x96,
        0x6C, 0x42, 0xF7, 0x10, 0x7C, 0x28, 0x27, 0x8C,
        0x13, 0x95, 0x9C, 0xC7, 0x24, 0x46, 0x3B, 0x70,
        0xCA, 0xE3, 0x85, 0xCB, 0x11, 0xD0, 0x93, 0xB8,
        0xA6, 0x83, 0x20, 0xFF, 0x9F, 0x77, 0xC3, 0xCC,
        0x03, 0x6F, 0x08, 0xBF, 0x40, 0xE7, 0x2B, 0xE2,
        0x79, 0x0C, 0xAA, 0x82, 0x41, 0x3A, 0xEA, 0xB9,
        0xE4, 0x9A, 0xA4, 0x97, 0x7E, 0xDA, 0x7A, 0x17,
        0x66, 0x94, 0xA1, 0x1D, 0x3D, 0xF0, 0xDE, 0xB3,
        0x0B, 0x72, 0xA7, 0x1C, 0xEF, 0xD1, 0x53, 0x3E,
        0x8F, 0x33, 0x26, 0x5F, 0xEC, 0x76, 0x2A, 0x49,
        0x81, 0x88, 0xEE, 0x21, 0xC4, 0x1A, 0xEB, 0xD9,
        0xC5, 0x39, 0x99, 0xCD, 0xAD, 0x31, 0x8B, 0x01,
        0x18, 0x23, 0xDD, 0x1F, 0x4E, 0x2D, 0xF9, 0x48,
        0x4F, 0xF2, 0x65, 0x8E, 0x78, 0x5C, 0x58, 0x19,
        0x8D, 0xE5, 0x98, 0x57, 0x67, 0x7F, 0x05, 0x64,
        0xAF, 0x63, 0xB6, 0xFE, 0xF5, 0xB7, 0x3C, 0xA5,
        0xCE, 0xE9, 0x68, 0x44, 0xE0, 0x4D, 0x43, 0x69,
        0x29, 0x2E, 0xAC, 0x15, 0x59, 0xA8, 0x0A, 0x9E,
        0x6E, 0x47, 0xDF, 0x34, 0x35, 0x6A, 0xCF, 0xDC,
        0x22, 0xC9, 0xC0, 0x9B, 0x89, 0xD4, 0xED, 0xAB,
        0x12, 0xA2, 0x0D, 0x52, 0xBB, 0x02, 0x2F, 0xA9,
        0xD7, 0x61, 0x1E, 0xB4, 0x50, 0x04, 0xF6, 0xC2,
        0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xBE, 0x91
    );

    /**
     * M-Table
     *
     * @var array
     * @access private
     */
    var $m0 = array(
        0xBCBC3275, 0xECEC21F3, 0x202043C6, 0xB3B3C9F4, 0xDADA03DB, 0x02028B7B, 0xE2E22BFB, 0x9E9EFAC8,
        0xC9C9EC4A, 0xD4D409D3, 0x18186BE6, 0x1E1E9F6B, 0x98980E45, 0xB2B2387D, 0xA6A6D2E8, 0x2626B74B,
        0x3C3C57D6, 0x93938A32, 0x8282EED8, 0x525298FD, 0x7B7BD437, 0xBBBB3771, 0x5B5B97F1, 0x474783E1,
        0x24243C30, 0x5151E20F, 0xBABAC6F8, 0x4A4AF31B, 0xBFBF4887, 0x0D0D70FA, 0xB0B0B306, 0x7575DE3F,
        0xD2D2FD5E, 0x7D7D20BA, 0x666631AE, 0x3A3AA35B, 0x59591C8A, 0x00000000, 0xCDCD93BC, 0x1A1AE09D,
        0xAEAE2C6D, 0x7F7FABC1, 0x2B2BC7B1, 0xBEBEB90E, 0xE0E0A080, 0x8A8A105D, 0x3B3B52D2, 0x6464BAD5,
        0xD8D888A0, 0xE7E7A584, 0x5F5FE807, 0x1B1B1114, 0x2C2CC2B5, 0xFCFCB490, 0x3131272C, 0x808065A3,
        0x73732AB2, 0x0C0C8173, 0x79795F4C, 0x6B6B4154, 0x4B4B0292, 0x53536974, 0x94948F36, 0x83831F51,
        0x2A2A3638, 0xC4C49CB0, 0x2222C8BD, 0xD5D5F85A, 0xBDBDC3FC, 0x48487860, 0xFFFFCE62, 0x4C4C0796,
        0x4141776C, 0xC7C7E642, 0xEBEB24F7, 0x1C1C1410, 0x5D5D637C, 0x36362228, 0x6767C027, 0xE9E9AF8C,
        0x4444F913, 0x1414EA95, 0xF5F5BB9C, 0xCFCF18C7, 0x3F3F2D24, 0xC0C0E346, 0x7272DB3B, 0x54546C70,
        0x29294CCA, 0xF0F035E3, 0x0808FE85, 0xC6C617CB, 0xF3F34F11, 0x8C8CE4D0, 0xA4A45993, 0xCACA96B8,
        0x68683BA6, 0xB8B84D83, 0x38382820, 0xE5E52EFF, 0xADAD569F, 0x0B0B8477, 0xC8C81DC3, 0x9999FFCC,
        0x5858ED03, 0x19199A6F, 0x0E0E0A08, 0x95957EBF, 0x70705040, 0xF7F730E7, 0x6E6ECF2B, 0x1F1F6EE2,
        0xB5B53D79, 0x09090F0C, 0x616134AA, 0x57571682, 0x9F9F0B41, 0x9D9D803A, 0x111164EA, 0x2525CDB9,
        0xAFAFDDE4, 0x4545089A, 0xDFDF8DA4, 0xA3A35C97, 0xEAEAD57E, 0x353558DA, 0xEDEDD07A, 0x4343FC17,
        0xF8F8CB66, 0xFBFBB194, 0x3737D3A1, 0xFAFA401D, 0xC2C2683D, 0xB4B4CCF0, 0x32325DDE, 0x9C9C71B3,
        0x5656E70B, 0xE3E3DA72, 0x878760A7, 0x15151B1C, 0xF9F93AEF, 0x6363BFD1, 0x3434A953, 0x9A9A853E,
        0xB1B1428F, 0x7C7CD133, 0x88889B26, 0x3D3DA65F, 0xA1A1D7EC, 0xE4E4DF76, 0x8181942A, 0x91910149,
        0x0F0FFB81, 0xEEEEAA88, 0x161661EE, 0xD7D77321, 0x9797F5C4, 0xA5A5A81A, 0xFEFE3FEB, 0x6D6DB5D9,
        0x7878AEC5, 0xC5C56D39, 0x1D1DE599, 0x7676A4CD, 0x3E3EDCAD, 0xCBCB6731, 0xB6B6478B, 0xEFEF5B01,
        0x12121E18, 0x6060C523, 0x6A6AB0DD, 0x4D4DF61F, 0xCECEE94E, 0xDEDE7C2D, 0x55559DF9, 0x7E7E5A48,
        0x2121B24F, 0x03037AF2, 0xA0A02665, 0x5E5E198E, 0x5A5A6678, 0x65654B5C, 0x62624E58, 0xFDFD4519,
        0x0606F48D, 0x404086E5, 0xF2F2BE98, 0x3333AC57, 0x17179067, 0x05058E7F, 0xE8E85E05, 0x4F4F7D64,
        0x89896AAF, 0x10109563, 0x74742FB6, 0x0A0A75FE, 0x5C5C92F5, 0x9B9B74B7, 0x2D2D333C, 0x3030D6A5,
        0x2E2E49CE, 0x494989E9, 0x46467268, 0x77775544, 0xA8A8D8E0, 0x9696044D, 0x2828BD43, 0xA9A92969,
        0xD9D97929, 0x8686912E, 0xD1D187AC, 0xF4F44A15, 0x8D8D1559, 0xD6D682A8, 0xB9B9BC0A, 0x42420D9E,
        0xF6F6C16E, 0x2F2FB847, 0xDDDD06DF, 0x23233934, 0xCCCC6235, 0xF1F1C46A, 0xC1C112CF, 0x8585EBDC,
        0x8F8F9E22, 0x7171A1C9, 0x9090F0C0, 0xAAAA539B, 0x0101F189, 0x8B8BE1D4, 0x4E4E8CED, 0x8E8E6FAB,
        0xABABA212, 0x6F6F3EA2, 0xE6E6540D, 0xDBDBF252, 0x92927BBB, 0xB7B7B602, 0x6969CA2F, 0x3939D9A9,
        0xD3D30CD7, 0xA7A72361, 0xA2A2AD1E, 0xC3C399B4, 0x6C6C4450, 0x07070504, 0x04047FF6, 0x272746C2,
        0xACACA716, 0xD0D07625, 0x50501386, 0xDCDCF756, 0x84841A55, 0xE1E15109, 0x7A7A25BE, 0x1313EF91
    );

    /**
     * M-Table
     *
     * @var array
     * @access private
     */
    var $m1 = array(
        0xA9D93939, 0x67901717, 0xB3719C9C, 0xE8D2A6A6, 0x04050707, 0xFD985252, 0xA3658080, 0x76DFE4E4,
        0x9A084545, 0x92024B4B, 0x80A0E0E0, 0x78665A5A, 0xE4DDAFAF, 0xDDB06A6A, 0xD1BF6363, 0x38362A2A,
        0x0D54E6E6, 0xC6432020, 0x3562CCCC, 0x98BEF2F2, 0x181E1212, 0xF724EBEB, 0xECD7A1A1, 0x6C774141,
        0x43BD2828, 0x7532BCBC, 0x37D47B7B, 0x269B8888, 0xFA700D0D, 0x13F94444, 0x94B1FBFB, 0x485A7E7E,
        0xF27A0303, 0xD0E48C8C, 0x8B47B6B6, 0x303C2424, 0x84A5E7E7, 0x54416B6B, 0xDF06DDDD, 0x23C56060,
        0x1945FDFD, 0x5BA33A3A, 0x3D68C2C2, 0x59158D8D, 0xF321ECEC, 0xAE316666, 0xA23E6F6F, 0x82165757,
        0x63951010, 0x015BEFEF, 0x834DB8B8, 0x2E918686, 0xD9B56D6D, 0x511F8383, 0x9B53AAAA, 0x7C635D5D,
        0xA63B6868, 0xEB3FFEFE, 0xA5D63030, 0xBE257A7A, 0x16A7ACAC, 0x0C0F0909, 0xE335F0F0, 0x6123A7A7,
        0xC0F09090, 0x8CAFE9E9, 0x3A809D9D, 0xF5925C5C, 0x73810C0C, 0x2C273131, 0x2576D0D0, 0x0BE75656,
        0xBB7B9292, 0x4EE9CECE, 0x89F10101, 0x6B9F1E1E, 0x53A93434, 0x6AC4F1F1, 0xB499C3C3, 0xF1975B5B,
        0xE1834747, 0xE66B1818, 0xBDC82222, 0x450E9898, 0xE26E1F1F, 0xF4C9B3B3, 0xB62F7474, 0x66CBF8F8,
        0xCCFF9999, 0x95EA1414, 0x03ED5858, 0x56F7DCDC, 0xD4E18B8B, 0x1C1B1515, 0x1EADA2A2, 0xD70CD3D3,
        0xFB2BE2E2, 0xC31DC8C8, 0x8E195E5E, 0xB5C22C2C, 0xE9894949, 0xCF12C1C1, 0xBF7E9595, 0xBA207D7D,
        0xEA641111, 0x77840B0B, 0x396DC5C5, 0xAF6A8989, 0x33D17C7C, 0xC9A17171, 0x62CEFFFF, 0x7137BBBB,
        0x81FB0F0F, 0x793DB5B5, 0x0951E1E1, 0xADDC3E3E, 0x242D3F3F, 0xCDA47676, 0xF99D5555, 0xD8EE8282,
        0xE5864040, 0xC5AE7878, 0xB9CD2525, 0x4D049696, 0x44557777, 0x080A0E0E, 0x86135050, 0xE730F7F7,
        0xA1D33737, 0x1D40FAFA, 0xAA346161, 0xED8C4E4E, 0x06B3B0B0, 0x706C5454, 0xB22A7373, 0xD2523B3B,
        0x410B9F9F, 0x7B8B0202, 0xA088D8D8, 0x114FF3F3, 0x3167CBCB, 0xC2462727, 0x27C06767, 0x90B4FCFC,
        0x20283838, 0xF67F0404, 0x60784848, 0xFF2EE5E5, 0x96074C4C, 0x5C4B6565, 0xB1C72B2B, 0xAB6F8E8E,
        0x9E0D4242, 0x9CBBF5F5, 0x52F2DBDB, 0x1BF34A4A, 0x5FA63D3D, 0x9359A4A4, 0x0ABCB9B9, 0xEF3AF9F9,
        0x91EF1313, 0x85FE0808, 0x49019191, 0xEE611616, 0x2D7CDEDE, 0x4FB22121, 0x8F42B1B1, 0x3BDB7272,
        0x47B82F2F, 0x8748BFBF, 0x6D2CAEAE, 0x46E3C0C0, 0xD6573C3C, 0x3E859A9A, 0x6929A9A9, 0x647D4F4F,
        0x2A948181, 0xCE492E2E, 0xCB17C6C6, 0x2FCA6969, 0xFCC3BDBD, 0x975CA3A3, 0x055EE8E8, 0x7AD0EDED,
        0xAC87D1D1, 0x7F8E0505, 0xD5BA6464, 0x1AA8A5A5, 0x4BB72626, 0x0EB9BEBE, 0xA7608787, 0x5AF8D5D5,
        0x28223636, 0x14111B1B, 0x3FDE7575, 0x2979D9D9, 0x88AAEEEE, 0x3C332D2D, 0x4C5F7979, 0x02B6B7B7,
        0xB896CACA, 0xDA583535, 0xB09CC4C4, 0x17FC4343, 0x551A8484, 0x1FF64D4D, 0x8A1C5959, 0x7D38B2B2,
        0x57AC3333, 0xC718CFCF, 0x8DF40606, 0x74695353, 0xB7749B9B, 0xC4F59797, 0x9F56ADAD, 0x72DAE3E3,
        0x7ED5EAEA, 0x154AF4F4, 0x229E8F8F, 0x12A2ABAB, 0x584E6262, 0x07E85F5F, 0x99E51D1D, 0x34392323,
        0x6EC1F6F6, 0x50446C6C, 0xDE5D3232, 0x68724646, 0x6526A0A0, 0xBC93CDCD, 0xDB03DADA, 0xF8C6BABA,
        0xC8FA9E9E, 0xA882D6D6, 0x2BCF6E6E, 0x40507070, 0xDCEB8585, 0xFE750A0A, 0x328A9393, 0xA48DDFDF,
        0xCA4C2929, 0x10141C1C, 0x2173D7D7, 0xF0CCB4B4, 0xD309D4D4, 0x5D108A8A, 0x0FE25151, 0x00000000,
        0x6F9A1919, 0x9DE01A1A, 0x368F9494, 0x42E6C7C7, 0x4AECC9C9, 0x5EFDD2D2, 0xC1AB7F7F, 0xE0D8A8A8
    );

    /**
     * M-Table
     *
     * @var array
     * @access private
     */
    var $m2 = array(
        0xBC75BC32, 0xECF3EC21, 0x20C62043, 0xB3F4B3C9, 0xDADBDA03, 0x027B028B, 0xE2FBE22B, 0x9EC89EFA,
        0xC94AC9EC, 0xD4D3D409, 0x18E6186B, 0x1E6B1E9F, 0x9845980E, 0xB27DB238, 0xA6E8A6D2, 0x264B26B7,
        0x3CD63C57, 0x9332938A, 0x82D882EE, 0x52FD5298, 0x7B377BD4, 0xBB71BB37, 0x5BF15B97, 0x47E14783,
        0x2430243C, 0x510F51E2, 0xBAF8BAC6, 0x4A1B4AF3, 0xBF87BF48, 0x0DFA0D70, 0xB006B0B3, 0x753F75DE,
        0xD25ED2FD, 0x7DBA7D20, 0x66AE6631, 0x3A5B3AA3, 0x598A591C, 0x00000000, 0xCDBCCD93, 0x1A9D1AE0,
        0xAE6DAE2C, 0x7FC17FAB, 0x2BB12BC7, 0xBE0EBEB9, 0xE080E0A0, 0x8A5D8A10, 0x3BD23B52, 0x64D564BA,
        0xD8A0D888, 0xE784E7A5, 0x5F075FE8, 0x1B141B11, 0x2CB52CC2, 0xFC90FCB4, 0x312C3127, 0x80A38065,
        0x73B2732A, 0x0C730C81, 0x794C795F, 0x6B546B41, 0x4B924B02, 0x53745369, 0x9436948F, 0x8351831F,
        0x2A382A36, 0xC4B0C49C, 0x22BD22C8, 0xD55AD5F8, 0xBDFCBDC3, 0x48604878, 0xFF62FFCE, 0x4C964C07,
        0x416C4177, 0xC742C7E6, 0xEBF7EB24, 0x1C101C14, 0x5D7C5D63, 0x36283622, 0x672767C0, 0xE98CE9AF,
        0x441344F9, 0x149514EA, 0xF59CF5BB, 0xCFC7CF18, 0x3F243F2D, 0xC046C0E3, 0x723B72DB, 0x5470546C,
        0x29CA294C, 0xF0E3F035, 0x088508FE, 0xC6CBC617, 0xF311F34F, 0x8CD08CE4, 0xA493A459, 0xCAB8CA96,
        0x68A6683B, 0xB883B84D, 0x38203828, 0xE5FFE52E, 0xAD9FAD56, 0x0B770B84, 0xC8C3C81D, 0x99CC99FF,
        0x580358ED, 0x196F199A, 0x0E080E0A, 0x95BF957E, 0x70407050, 0xF7E7F730, 0x6E2B6ECF, 0x1FE21F6E,
        0xB579B53D, 0x090C090F, 0x61AA6134, 0x57825716, 0x9F419F0B, 0x9D3A9D80, 0x11EA1164, 0x25B925CD,
        0xAFE4AFDD, 0x459A4508, 0xDFA4DF8D, 0xA397A35C, 0xEA7EEAD5, 0x35DA3558, 0xED7AEDD0, 0x431743FC,
        0xF866F8CB, 0xFB94FBB1, 0x37A137D3, 0xFA1DFA40, 0xC23DC268, 0xB4F0B4CC, 0x32DE325D, 0x9CB39C71,
        0x560B56E7, 0xE372E3DA, 0x87A78760, 0x151C151B, 0xF9EFF93A, 0x63D163BF, 0x345334A9, 0x9A3E9A85,
        0xB18FB142, 0x7C337CD1, 0x8826889B, 0x3D5F3DA6, 0xA1ECA1D7, 0xE476E4DF, 0x812A8194, 0x91499101,
        0x0F810FFB, 0xEE88EEAA, 0x16EE1661, 0xD721D773, 0x97C497F5, 0xA51AA5A8, 0xFEEBFE3F, 0x6DD96DB5,
        0x78C578AE, 0xC539C56D, 0x1D991DE5, 0x76CD76A4, 0x3EAD3EDC, 0xCB31CB67, 0xB68BB647, 0xEF01EF5B,
        0x1218121E, 0x602360C5, 0x6ADD6AB0, 0x4D1F4DF6, 0xCE4ECEE9, 0xDE2DDE7C, 0x55F9559D, 0x7E487E5A,
        0x214F21B2, 0x03F2037A, 0xA065A026, 0x5E8E5E19, 0x5A785A66, 0x655C654B, 0x6258624E, 0xFD19FD45,
        0x068D06F4, 0x40E54086, 0xF298F2BE, 0x335733AC, 0x17671790, 0x057F058E, 0xE805E85E, 0x4F644F7D,
        0x89AF896A, 0x10631095, 0x74B6742F, 0x0AFE0A75, 0x5CF55C92, 0x9BB79B74, 0x2D3C2D33, 0x30A530D6,
        0x2ECE2E49, 0x49E94989, 0x46684672, 0x77447755, 0xA8E0A8D8, 0x964D9604, 0x284328BD, 0xA969A929,
        0xD929D979, 0x862E8691, 0xD1ACD187, 0xF415F44A, 0x8D598D15, 0xD6A8D682, 0xB90AB9BC, 0x429E420D,
        0xF66EF6C1, 0x2F472FB8, 0xDDDFDD06, 0x23342339, 0xCC35CC62, 0xF16AF1C4, 0xC1CFC112, 0x85DC85EB,
        0x8F228F9E, 0x71C971A1, 0x90C090F0, 0xAA9BAA53, 0x018901F1, 0x8BD48BE1, 0x4EED4E8C, 0x8EAB8E6F,
        0xAB12ABA2, 0x6FA26F3E, 0xE60DE654, 0xDB52DBF2, 0x92BB927B, 0xB702B7B6, 0x692F69CA, 0x39A939D9,
        0xD3D7D30C, 0xA761A723, 0xA21EA2AD, 0xC3B4C399, 0x6C506C44, 0x07040705, 0x04F6047F, 0x27C22746,
        0xAC16ACA7, 0xD025D076, 0x50865013, 0xDC56DCF7, 0x8455841A, 0xE109E151, 0x7ABE7A25, 0x139113EF
    );

    /**
     * M-Table
     *
     * @var array
     * @access private
     */
    var $m3 = array(
        0xD939A9D9, 0x90176790, 0x719CB371, 0xD2A6E8D2, 0x05070405, 0x9852FD98, 0x6580A365, 0xDFE476DF,
        0x08459A08, 0x024B9202, 0xA0E080A0, 0x665A7866, 0xDDAFE4DD, 0xB06ADDB0, 0xBF63D1BF, 0x362A3836,
        0x54E60D54, 0x4320C643, 0x62CC3562, 0xBEF298BE, 0x1E12181E, 0x24EBF724, 0xD7A1ECD7, 0x77416C77,
        0xBD2843BD, 0x32BC7532, 0xD47B37D4, 0x9B88269B, 0x700DFA70, 0xF94413F9, 0xB1FB94B1, 0x5A7E485A,
        0x7A03F27A, 0xE48CD0E4, 0x47B68B47, 0x3C24303C, 0xA5E784A5, 0x416B5441, 0x06DDDF06, 0xC56023C5,
        0x45FD1945, 0xA33A5BA3, 0x68C23D68, 0x158D5915, 0x21ECF321, 0x3166AE31, 0x3E6FA23E, 0x16578216,
        0x95106395, 0x5BEF015B, 0x4DB8834D, 0x91862E91, 0xB56DD9B5, 0x1F83511F, 0x53AA9B53, 0x635D7C63,
        0x3B68A63B, 0x3FFEEB3F, 0xD630A5D6, 0x257ABE25, 0xA7AC16A7, 0x0F090C0F, 0x35F0E335, 0x23A76123,
        0xF090C0F0, 0xAFE98CAF, 0x809D3A80, 0x925CF592, 0x810C7381, 0x27312C27, 0x76D02576, 0xE7560BE7,
        0x7B92BB7B, 0xE9CE4EE9, 0xF10189F1, 0x9F1E6B9F, 0xA93453A9, 0xC4F16AC4, 0x99C3B499, 0x975BF197,
        0x8347E183, 0x6B18E66B, 0xC822BDC8, 0x0E98450E, 0x6E1FE26E, 0xC9B3F4C9, 0x2F74B62F, 0xCBF866CB,
        0xFF99CCFF, 0xEA1495EA, 0xED5803ED, 0xF7DC56F7, 0xE18BD4E1, 0x1B151C1B, 0xADA21EAD, 0x0CD3D70C,
        0x2BE2FB2B, 0x1DC8C31D, 0x195E8E19, 0xC22CB5C2, 0x8949E989, 0x12C1CF12, 0x7E95BF7E, 0x207DBA20,
        0x6411EA64, 0x840B7784, 0x6DC5396D, 0x6A89AF6A, 0xD17C33D1, 0xA171C9A1, 0xCEFF62CE, 0x37BB7137,
        0xFB0F81FB, 0x3DB5793D, 0x51E10951, 0xDC3EADDC, 0x2D3F242D, 0xA476CDA4, 0x9D55F99D, 0xEE82D8EE,
        0x8640E586, 0xAE78C5AE, 0xCD25B9CD, 0x04964D04, 0x55774455, 0x0A0E080A, 0x13508613, 0x30F7E730,
        0xD337A1D3, 0x40FA1D40, 0x3461AA34, 0x8C4EED8C, 0xB3B006B3, 0x6C54706C, 0x2A73B22A, 0x523BD252,
        0x0B9F410B, 0x8B027B8B, 0x88D8A088, 0x4FF3114F, 0x67CB3167, 0x4627C246, 0xC06727C0, 0xB4FC90B4,
        0x28382028, 0x7F04F67F, 0x78486078, 0x2EE5FF2E, 0x074C9607, 0x4B655C4B, 0xC72BB1C7, 0x6F8EAB6F,
        0x0D429E0D, 0xBBF59CBB, 0xF2DB52F2, 0xF34A1BF3, 0xA63D5FA6, 0x59A49359, 0xBCB90ABC, 0x3AF9EF3A,
        0xEF1391EF, 0xFE0885FE, 0x01914901, 0x6116EE61, 0x7CDE2D7C, 0xB2214FB2, 0x42B18F42, 0xDB723BDB,
        0xB82F47B8, 0x48BF8748, 0x2CAE6D2C, 0xE3C046E3, 0x573CD657, 0x859A3E85, 0x29A96929, 0x7D4F647D,
        0x94812A94, 0x492ECE49, 0x17C6CB17, 0xCA692FCA, 0xC3BDFCC3, 0x5CA3975C, 0x5EE8055E, 0xD0ED7AD0,
        0x87D1AC87, 0x8E057F8E, 0xBA64D5BA, 0xA8A51AA8, 0xB7264BB7, 0xB9BE0EB9, 0x6087A760, 0xF8D55AF8,
        0x22362822, 0x111B1411, 0xDE753FDE, 0x79D92979, 0xAAEE88AA, 0x332D3C33, 0x5F794C5F, 0xB6B702B6,
        0x96CAB896, 0x5835DA58, 0x9CC4B09C, 0xFC4317FC, 0x1A84551A, 0xF64D1FF6, 0x1C598A1C, 0x38B27D38,
        0xAC3357AC, 0x18CFC718, 0xF4068DF4, 0x69537469, 0x749BB774, 0xF597C4F5, 0x56AD9F56, 0xDAE372DA,
        0xD5EA7ED5, 0x4AF4154A, 0x9E8F229E, 0xA2AB12A2, 0x4E62584E, 0xE85F07E8, 0xE51D99E5, 0x39233439,
        0xC1F66EC1, 0x446C5044, 0x5D32DE5D, 0x72466872, 0x26A06526, 0x93CDBC93, 0x03DADB03, 0xC6BAF8C6,
        0xFA9EC8FA, 0x82D6A882, 0xCF6E2BCF, 0x50704050, 0xEB85DCEB, 0x750AFE75, 0x8A93328A, 0x8DDFA48D,
        0x4C29CA4C, 0x141C1014, 0x73D72173, 0xCCB4F0CC, 0x09D4D309, 0x108A5D10, 0xE2510FE2, 0x00000000,
        0x9A196F9A, 0xE01A9DE0, 0x8F94368F, 0xE6C742E6, 0xECC94AEC, 0xFDD25EFD, 0xAB7FC1AB, 0xD8A8E0D8
    );

    /**
     * The Key Schedule Array
     *
     * @var array
     * @access private
     */
    var $K = array();

    /**
     * The Key depended S-Table 0
     *
     * @var array
     * @access private
     */
    var $S0 = array();

    /**
     * The Key depended S-Table 1
     *
     * @var array
     * @access private
     */
    var $S1 = array();

    /**
     * The Key depended S-Table 2
     *
     * @var array
     * @access private
     */
    var $S2 = array();

    /**
     * The Key depended S-Table 3
     *
     * @var array
     * @access private
     */
    var $S3 = array();

    /**
     * Holds the last used key
     *
     * @var array
     * @access private
     */
    var $kl;

    /**
     * The Key Length (in bytes)
     *
     * @see Crypt_Twofish::setKeyLength()
     * @var int
     * @access private
     */
    var $key_length = 16;

    /**
     * Sets the key length.
     *
     * Valid key lengths are 128, 192 or 256 bits
     *
     * @access public
     * @param int $length
     */
    function setKeyLength($length)
    {
        switch (true) {
            case $length <= 128:
                $this->key_length = 16;
                break;
            case $length <= 192:
                $this->key_length = 24;
                break;
            default:
                $this->key_length = 32;
        }

        parent::setKeyLength($length);
    }

    /**
     * Setup the key (expansion)
     *
     * @see \phpseclib\Crypt\Base::_setupKey()
     * @access private
     */
    function _setupKey()
    {
        if (isset($this->kl['key']) && $this->key === $this->kl['key']) {
            // already expanded
            return;
        }
        $this->kl = array('key' => $this->key);

        /* Key expanding and generating the key-depended s-boxes */
        $le_longs = unpack('V*', $this->key);
        $key = unpack('C*', $this->key);
        $m0 = $this->m0;
        $m1 = $this->m1;
        $m2 = $this->m2;
        $m3 = $this->m3;
        $q0 = $this->q0;
        $q1 = $this->q1;

        $K = $S0 = $S1 = $S2 = $S3 = array();

        switch (strlen($this->key)) {
            case 16:
                list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[1], $le_longs[2]);
                list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[3], $le_longs[4]);
                for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) {
                    $A = $m0[$q0[$q0[$i] ^ $key[ 9]] ^ $key[1]] ^
                         $m1[$q0[$q1[$i] ^ $key[10]] ^ $key[2]] ^
                         $m2[$q1[$q0[$i] ^ $key[11]] ^ $key[3]] ^
                         $m3[$q1[$q1[$i] ^ $key[12]] ^ $key[4]];
                    $B = $m0[$q0[$q0[$j] ^ $key[13]] ^ $key[5]] ^
                         $m1[$q0[$q1[$j] ^ $key[14]] ^ $key[6]] ^
                         $m2[$q1[$q0[$j] ^ $key[15]] ^ $key[7]] ^
                         $m3[$q1[$q1[$j] ^ $key[16]] ^ $key[8]];
                    $B = ($B << 8) | ($B >> 24 & 0xff);
                    $K[] = $A+= $B;
                    $K[] = (($A+= $B) << 9 | $A >> 23 & 0x1ff);
                }
                for ($i = 0; $i < 256; ++$i) {
                    $S0[$i] = $m0[$q0[$q0[$i] ^ $s4] ^ $s0];
                    $S1[$i] = $m1[$q0[$q1[$i] ^ $s5] ^ $s1];
                    $S2[$i] = $m2[$q1[$q0[$i] ^ $s6] ^ $s2];
                    $S3[$i] = $m3[$q1[$q1[$i] ^ $s7] ^ $s3];
                }
                break;
            case 24:
                list($sb, $sa, $s9, $s8) = $this->_mdsrem($le_longs[1], $le_longs[2]);
                list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[3], $le_longs[4]);
                list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[5], $le_longs[6]);
                for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) {
                    $A = $m0[$q0[$q0[$q1[$i] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^
                         $m1[$q0[$q1[$q1[$i] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^
                         $m2[$q1[$q0[$q0[$i] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^
                         $m3[$q1[$q1[$q0[$i] ^ $key[20]] ^ $key[12]] ^ $key[4]];
                    $B = $m0[$q0[$q0[$q1[$j] ^ $key[21]] ^ $key[13]] ^ $key[5]] ^
                         $m1[$q0[$q1[$q1[$j] ^ $key[22]] ^ $key[14]] ^ $key[6]] ^
                         $m2[$q1[$q0[$q0[$j] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^
                         $m3[$q1[$q1[$q0[$j] ^ $key[24]] ^ $key[16]] ^ $key[8]];
                    $B = ($B << 8) | ($B >> 24 & 0xff);
                    $K[] = $A+= $B;
                    $K[] = (($A+= $B) << 9 | $A >> 23 & 0x1ff);
                }
                for ($i = 0; $i < 256; ++$i) {
                    $S0[$i] = $m0[$q0[$q0[$q1[$i] ^ $s8] ^ $s4] ^ $s0];
                    $S1[$i] = $m1[$q0[$q1[$q1[$i] ^ $s9] ^ $s5] ^ $s1];
                    $S2[$i] = $m2[$q1[$q0[$q0[$i] ^ $sa] ^ $s6] ^ $s2];
                    $S3[$i] = $m3[$q1[$q1[$q0[$i] ^ $sb] ^ $s7] ^ $s3];
                }
                break;
            default: // 32
                list($sf, $se, $sd, $sc) = $this->_mdsrem($le_longs[1], $le_longs[2]);
                list($sb, $sa, $s9, $s8) = $this->_mdsrem($le_longs[3], $le_longs[4]);
                list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[5], $le_longs[6]);
                list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[7], $le_longs[8]);
                for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) {
                    $A = $m0[$q0[$q0[$q1[$q1[$i] ^ $key[25]] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^
                         $m1[$q0[$q1[$q1[$q0[$i] ^ $key[26]] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^
                         $m2[$q1[$q0[$q0[$q0[$i] ^ $key[27]] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^
                         $m3[$q1[$q1[$q0[$q1[$i] ^ $key[28]] ^ $key[20]] ^ $key[12]] ^ $key[4]];
                    $B = $m0[$q0[$q0[$q1[$q1[$j] ^ $key[29]] ^ $key[21]] ^ $key[13]] ^ $key[5]] ^
                         $m1[$q0[$q1[$q1[$q0[$j] ^ $key[30]] ^ $key[22]] ^ $key[14]] ^ $key[6]] ^
                         $m2[$q1[$q0[$q0[$q0[$j] ^ $key[31]] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^
                         $m3[$q1[$q1[$q0[$q1[$j] ^ $key[32]] ^ $key[24]] ^ $key[16]] ^ $key[8]];
                    $B = ($B << 8) | ($B >> 24 & 0xff);
                    $K[] = $A+= $B;
                    $K[] = (($A+= $B) << 9 | $A >> 23 & 0x1ff);
                }
                for ($i = 0; $i < 256; ++$i) {
                    $S0[$i] = $m0[$q0[$q0[$q1[$q1[$i] ^ $sc] ^ $s8] ^ $s4] ^ $s0];
                    $S1[$i] = $m1[$q0[$q1[$q1[$q0[$i] ^ $sd] ^ $s9] ^ $s5] ^ $s1];
                    $S2[$i] = $m2[$q1[$q0[$q0[$q0[$i] ^ $se] ^ $sa] ^ $s6] ^ $s2];
                    $S3[$i] = $m3[$q1[$q1[$q0[$q1[$i] ^ $sf] ^ $sb] ^ $s7] ^ $s3];
                }
        }

        $this->K  = $K;
        $this->S0 = $S0;
        $this->S1 = $S1;
        $this->S2 = $S2;
        $this->S3 = $S3;
    }

    /**
     * _mdsrem function using by the twofish cipher algorithm
     *
     * @access private
     * @param string $A
     * @param string $B
     * @return array
     */
    function _mdsrem($A, $B)
    {
        // No gain by unrolling this loop.
        for ($i = 0; $i < 8; ++$i) {
            // Get most significant coefficient.
            $t = 0xff & ($B >> 24);

            // Shift the others up.
            $B = ($B << 8) | (0xff & ($A >> 24));
            $A<<= 8;

            $u = $t << 1;

            // Subtract the modular polynomial on overflow.
            if ($t & 0x80) {
                $u^= 0x14d;
            }

            // Remove t * (a * x^2 + 1).
            $B ^= $t ^ ($u << 16);

            // Form u = a*t + t/a = t*(a + 1/a).
            $u^= 0x7fffffff & ($t >> 1);

            // Add the modular polynomial on underflow.
            if ($t & 0x01) {
                $u^= 0xa6 ;
            }

            // Remove t * (a + 1/a) * (x^3 + x).
            $B^= ($u << 24) | ($u << 8);
        }

        return array(
            0xff & $B >> 24,
            0xff & $B >> 16,
            0xff & $B >>  8,
            0xff & $B);
    }

    /**
     * Encrypts a block
     *
     * @access private
     * @param string $in
     * @return string
     */
    function _encryptBlock($in)
    {
        $S0 = $this->S0;
        $S1 = $this->S1;
        $S2 = $this->S2;
        $S3 = $this->S3;
        $K  = $this->K;

        $in = unpack("V4", $in);
        $R0 = $K[0] ^ $in[1];
        $R1 = $K[1] ^ $in[2];
        $R2 = $K[2] ^ $in[3];
        $R3 = $K[3] ^ $in[4];

        $ki = 7;
        while ($ki < 39) {
            $t0 = $S0[ $R0        & 0xff] ^
                  $S1[($R0 >>  8) & 0xff] ^
                  $S2[($R0 >> 16) & 0xff] ^
                  $S3[($R0 >> 24) & 0xff];
            $t1 = $S0[($R1 >> 24) & 0xff] ^
                  $S1[ $R1        & 0xff] ^
                  $S2[($R1 >>  8) & 0xff] ^
                  $S3[($R1 >> 16) & 0xff];
            $R2^= $t0 + $t1 + $K[++$ki];
            $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31);
            $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ($t0 + ($t1 << 1) + $K[++$ki]);

            $t0 = $S0[ $R2        & 0xff] ^
                  $S1[($R2 >>  8) & 0xff] ^
                  $S2[($R2 >> 16) & 0xff] ^
                  $S3[($R2 >> 24) & 0xff];
            $t1 = $S0[($R3 >> 24) & 0xff] ^
                  $S1[ $R3        & 0xff] ^
                  $S2[($R3 >>  8) & 0xff] ^
                  $S3[($R3 >> 16) & 0xff];
            $R0^= ($t0 + $t1 + $K[++$ki]);
            $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31);
            $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ($t0 + ($t1 << 1) + $K[++$ki]);
        }

        // @codingStandardsIgnoreStart
        return pack("V4", $K[4] ^ $R2,
                          $K[5] ^ $R3,
                          $K[6] ^ $R0,
                          $K[7] ^ $R1);
        // @codingStandardsIgnoreEnd
    }

    /**
     * Decrypts a block
     *
     * @access private
     * @param string $in
     * @return string
     */
    function _decryptBlock($in)
    {
        $S0 = $this->S0;
        $S1 = $this->S1;
        $S2 = $this->S2;
        $S3 = $this->S3;
        $K  = $this->K;

        $in = unpack("V4", $in);
        $R0 = $K[4] ^ $in[1];
        $R1 = $K[5] ^ $in[2];
        $R2 = $K[6] ^ $in[3];
        $R3 = $K[7] ^ $in[4];

        $ki = 40;
        while ($ki > 8) {
            $t0 = $S0[$R0       & 0xff] ^
                  $S1[$R0 >>  8 & 0xff] ^
                  $S2[$R0 >> 16 & 0xff] ^
                  $S3[$R0 >> 24 & 0xff];
            $t1 = $S0[$R1 >> 24 & 0xff] ^
                  $S1[$R1       & 0xff] ^
                  $S2[$R1 >>  8 & 0xff] ^
                  $S3[$R1 >> 16 & 0xff];
            $R3^= $t0 + ($t1 << 1) + $K[--$ki];
            $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31;
            $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ($t0 + $t1 + $K[--$ki]);

            $t0 = $S0[$R2       & 0xff] ^
                  $S1[$R2 >>  8 & 0xff] ^
                  $S2[$R2 >> 16 & 0xff] ^
                  $S3[$R2 >> 24 & 0xff];
            $t1 = $S0[$R3 >> 24 & 0xff] ^
                  $S1[$R3       & 0xff] ^
                  $S2[$R3 >>  8 & 0xff] ^
                  $S3[$R3 >> 16 & 0xff];
            $R1^= $t0 + ($t1 << 1) + $K[--$ki];
            $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31;
            $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ($t0 + $t1 + $K[--$ki]);
        }

        // @codingStandardsIgnoreStart
        return pack("V4", $K[0] ^ $R2,
                          $K[1] ^ $R3,
                          $K[2] ^ $R0,
                          $K[3] ^ $R1);
        // @codingStandardsIgnoreEnd
    }

    /**
     * Setup the performance-optimized function for de/encrypt()
     *
     * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
     * @access private
     */
    function _setupInlineCrypt()
    {
        $lambda_functions =& self::_getLambdaFunctions();

        // Max. 10 Ultra-Hi-optimized inline-crypt functions. After that, we'll (still) create very fast code, but not the ultimate fast one.
        // (Currently, for Crypt_Twofish, one generated $lambda_function cost on php5.5@32bit ~140kb unfreeable mem and ~240kb on php5.5@64bit)
        $gen_hi_opt_code = (bool)(count($lambda_functions) < 10);

        // Generation of a unique hash for our generated code
        $code_hash = "Crypt_Twofish, {$this->mode}";
        if ($gen_hi_opt_code) {
            $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
        }

        if (!isset($lambda_functions[$code_hash])) {
            switch (true) {
                case $gen_hi_opt_code:
                    $K = $this->K;
                    $init_crypt = '
                        static $S0, $S1, $S2, $S3;
                        if (!$S0) {
                            for ($i = 0; $i < 256; ++$i) {
                                $S0[] = (int)$self->S0[$i];
                                $S1[] = (int)$self->S1[$i];
                                $S2[] = (int)$self->S2[$i];
                                $S3[] = (int)$self->S3[$i];
                            }
                        }
                    ';
                    break;
                default:
                    $K   = array();
                    for ($i = 0; $i < 40; ++$i) {
                        $K[] = '$K_' . $i;
                    }
                    $init_crypt = '
                        $S0 = $self->S0;
                        $S1 = $self->S1;
                        $S2 = $self->S2;
                        $S3 = $self->S3;
                        list(' . implode(',', $K) . ') = $self->K;
                    ';
            }

            // Generating encrypt code:
            $encrypt_block = '
                $in = unpack("V4", $in);
                $R0 = '.$K[0].' ^ $in[1];
                $R1 = '.$K[1].' ^ $in[2];
                $R2 = '.$K[2].' ^ $in[3];
                $R3 = '.$K[3].' ^ $in[4];
            ';
            for ($ki = 7, $i = 0; $i < 8; ++$i) {
                $encrypt_block.= '
                    $t0 = $S0[ $R0        & 0xff] ^
                          $S1[($R0 >>  8) & 0xff] ^
                          $S2[($R0 >> 16) & 0xff] ^
                          $S3[($R0 >> 24) & 0xff];
                    $t1 = $S0[($R1 >> 24) & 0xff] ^
                          $S1[ $R1        & 0xff] ^
                          $S2[($R1 >>  8) & 0xff] ^
                          $S3[($R1 >> 16) & 0xff];
                    $R2^= ($t0 + $t1 + '.$K[++$ki].');
                    $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31);
                    $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ($t0 + ($t1 << 1) + '.$K[++$ki].');

                    $t0 = $S0[ $R2        & 0xff] ^
                          $S1[($R2 >>  8) & 0xff] ^
                          $S2[($R2 >> 16) & 0xff] ^
                          $S3[($R2 >> 24) & 0xff];
                    $t1 = $S0[($R3 >> 24) & 0xff] ^
                          $S1[ $R3        & 0xff] ^
                          $S2[($R3 >>  8) & 0xff] ^
                          $S3[($R3 >> 16) & 0xff];
                    $R0^= ($t0 + $t1 + '.$K[++$ki].');
                    $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31);
                    $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ($t0 + ($t1 << 1) + '.$K[++$ki].');
                ';
            }
            $encrypt_block.= '
                $in = pack("V4", '.$K[4].' ^ $R2,
                                 '.$K[5].' ^ $R3,
                                 '.$K[6].' ^ $R0,
                                 '.$K[7].' ^ $R1);
            ';

            // Generating decrypt code:
            $decrypt_block = '
                $in = unpack("V4", $in);
                $R0 = '.$K[4].' ^ $in[1];
                $R1 = '.$K[5].' ^ $in[2];
                $R2 = '.$K[6].' ^ $in[3];
                $R3 = '.$K[7].' ^ $in[4];
            ';
            for ($ki = 40, $i = 0; $i < 8; ++$i) {
                $decrypt_block.= '
                    $t0 = $S0[$R0       & 0xff] ^
                          $S1[$R0 >>  8 & 0xff] ^
                          $S2[$R0 >> 16 & 0xff] ^
                          $S3[$R0 >> 24 & 0xff];
                    $t1 = $S0[$R1 >> 24 & 0xff] ^
                          $S1[$R1       & 0xff] ^
                          $S2[$R1 >>  8 & 0xff] ^
                          $S3[$R1 >> 16 & 0xff];
                    $R3^= $t0 + ($t1 << 1) + '.$K[--$ki].';
                    $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31;
                    $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ($t0 + $t1 + '.$K[--$ki].');

                    $t0 = $S0[$R2       & 0xff] ^
                          $S1[$R2 >>  8 & 0xff] ^
                          $S2[$R2 >> 16 & 0xff] ^
                          $S3[$R2 >> 24 & 0xff];
                    $t1 = $S0[$R3 >> 24 & 0xff] ^
                          $S1[$R3       & 0xff] ^
                          $S2[$R3 >>  8 & 0xff] ^
                          $S3[$R3 >> 16 & 0xff];
                    $R1^= $t0 + ($t1 << 1) + '.$K[--$ki].';
                    $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31;
                    $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ($t0 + $t1 + '.$K[--$ki].');
                ';
            }
            $decrypt_block.= '
                $in = pack("V4", '.$K[0].' ^ $R2,
                                 '.$K[1].' ^ $R3,
                                 '.$K[2].' ^ $R0,
                                 '.$K[3].' ^ $R1);
            ';

            $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
                array(
                   'init_crypt'    => $init_crypt,
                   'init_encrypt'  => '',
                   'init_decrypt'  => '',
                   'encrypt_block' => $encrypt_block,
                   'decrypt_block' => $decrypt_block
                )
            );
        }
        $this->inline_crypt = $lambda_functions[$code_hash];
    }
}
<?php

/**
 * Pure-PHP ANSI Decoder
 *
 * PHP version 5
 *
 * If you call read() in \phpseclib\Net\SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back.
 * They'd look like chr(0x1B) . '[00m' or whatever (0x1B = ESC).  They tell a
 * {@link http://en.wikipedia.org/wiki/Terminal_emulator terminal emulator} how to format the characters, what
 * color to display them in, etc. \phpseclib\File\ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator.
 *
 * @category  File
 * @package   ANSI
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2012 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\File;

/**
 * Pure-PHP ANSI Decoder
 *
 * @package ANSI
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class ANSI
{
    /**
     * Max Width
     *
     * @var int
     * @access private
     */
    var $max_x;

    /**
     * Max Height
     *
     * @var int
     * @access private
     */
    var $max_y;

    /**
     * Max History
     *
     * @var int
     * @access private
     */
    var $max_history;

    /**
     * History
     *
     * @var array
     * @access private
     */
    var $history;

    /**
     * History Attributes
     *
     * @var array
     * @access private
     */
    var $history_attrs;

    /**
     * Current Column
     *
     * @var int
     * @access private
     */
    var $x;

    /**
     * Current Row
     *
     * @var int
     * @access private
     */
    var $y;

    /**
     * Old Column
     *
     * @var int
     * @access private
     */
    var $old_x;

    /**
     * Old Row
     *
     * @var int
     * @access private
     */
    var $old_y;

    /**
     * An empty attribute cell
     *
     * @var object
     * @access private
     */
    var $base_attr_cell;

    /**
     * The current attribute cell
     *
     * @var object
     * @access private
     */
    var $attr_cell;

    /**
     * An empty attribute row
     *
     * @var array
     * @access private
     */
    var $attr_row;

    /**
     * The current screen text
     *
     * @var array
     * @access private
     */
    var $screen;

    /**
     * The current screen attributes
     *
     * @var array
     * @access private
     */
    var $attrs;

    /**
     * Current ANSI code
     *
     * @var string
     * @access private
     */
    var $ansi;

    /**
     * Tokenization
     *
     * @var array
     * @access private
     */
    var $tokenization;

    /**
     * Default Constructor.
     *
     * @return \phpseclib\File\ANSI
     * @access public
     */
    function __construct()
    {
        $attr_cell = new \stdClass();
        $attr_cell->bold = false;
        $attr_cell->underline = false;
        $attr_cell->blink = false;
        $attr_cell->background = 'black';
        $attr_cell->foreground = 'white';
        $attr_cell->reverse = false;
        $this->base_attr_cell = clone $attr_cell;
        $this->attr_cell = clone $attr_cell;

        $this->setHistory(200);
        $this->setDimensions(80, 24);
    }

    /**
     * Set terminal width and height
     *
     * Resets the screen as well
     *
     * @param int $x
     * @param int $y
     * @access public
     */
    function setDimensions($x, $y)
    {
        $this->max_x = $x - 1;
        $this->max_y = $y - 1;
        $this->x = $this->y = 0;
        $this->history = $this->history_attrs = array();
        $this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell);
        $this->screen = array_fill(0, $this->max_y + 1, '');
        $this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);
        $this->ansi = '';
    }

    /**
     * Set the number of lines that should be logged past the terminal height
     *
     * @param int $x
     * @param int $y
     * @access public
     */
    function setHistory($history)
    {
        $this->max_history = $history;
    }

    /**
     * Load a string
     *
     * @param string $source
     * @access public
     */
    function loadString($source)
    {
        $this->setDimensions($this->max_x + 1, $this->max_y + 1);
        $this->appendString($source);
    }

    /**
     * Appdend a string
     *
     * @param string $source
     * @access public
     */
    function appendString($source)
    {
        $this->tokenization = array('');
        for ($i = 0; $i < strlen($source); $i++) {
            if (strlen($this->ansi)) {
                $this->ansi.= $source[$i];
                $chr = ord($source[$i]);
                // http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
                // single character CSI's not currently supported
                switch (true) {
                    case $this->ansi == "\x1B=":
                        $this->ansi = '';
                        continue 2;
                    case strlen($this->ansi) == 2 && $chr >= 64 && $chr <= 95 && $chr != ord('['):
                    case strlen($this->ansi) > 2 && $chr >= 64 && $chr <= 126:
                        break;
                    default:
                        continue 2;
                }
                $this->tokenization[] = $this->ansi;
                $this->tokenization[] = '';
                // http://ascii-table.com/ansi-escape-sequences-vt-100.php
                switch ($this->ansi) {
                    case "\x1B[H": // Move cursor to upper left corner
                        $this->old_x = $this->x;
                        $this->old_y = $this->y;
                        $this->x = $this->y = 0;
                        break;
                    case "\x1B[J": // Clear screen from cursor down
                        $this->history = array_merge($this->history, array_slice(array_splice($this->screen, $this->y + 1), 0, $this->old_y));
                        $this->screen = array_merge($this->screen, array_fill($this->y, $this->max_y, ''));

                        $this->history_attrs = array_merge($this->history_attrs, array_slice(array_splice($this->attrs, $this->y + 1), 0, $this->old_y));
                        $this->attrs = array_merge($this->attrs, array_fill($this->y, $this->max_y, $this->attr_row));

                        if (count($this->history) == $this->max_history) {
                            array_shift($this->history);
                            array_shift($this->history_attrs);
                        }
                    case "\x1B[K": // Clear screen from cursor right
                        $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);

                        array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - $this->x - 1, $this->base_attr_cell));
                        break;
                    case "\x1B[2K": // Clear entire line
                        $this->screen[$this->y] = str_repeat(' ', $this->x);
                        $this->attrs[$this->y] = $this->attr_row;
                        break;
                    case "\x1B[?1h": // set cursor key to application
                    case "\x1B[?25h": // show the cursor
                    case "\x1B(B": // set united states g0 character set
                        break;
                    case "\x1BE": // Move to next line
                        $this->_newLine();
                        $this->x = 0;
                        break;
                    default:
                        switch (true) {
                            case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): // Move cursor down n lines
                                $this->old_y = $this->y;
                                $this->y+= $match[1];
                                break;
                            case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h
                                $this->old_x = $this->x;
                                $this->old_y = $this->y;
                                $this->x = $match[2] - 1;
                                $this->y = $match[1] - 1;
                                break;
                            case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines
                                $this->old_x = $this->x;
                                $this->x+= $match[1];
                                break;
                            case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines
                                $this->old_x = $this->x;
                                $this->x-= $match[1];
                                break;
                            case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window
                                break;
                            case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): // character attributes
                                $attr_cell = &$this->attr_cell;
                                $mods = explode(';', $match[1]);
                                foreach ($mods as $mod) {
                                    switch ($mod) {
                                        case 0: // Turn off character attributes
                                            $attr_cell = clone $this->base_attr_cell;
                                            break;
                                        case 1: // Turn bold mode on
                                            $attr_cell->bold = true;
                                            break;
                                        case 4: // Turn underline mode on
                                            $attr_cell->underline = true;
                                            break;
                                        case 5: // Turn blinking mode on
                                            $attr_cell->blink = true;
                                            break;
                                        case 7: // Turn reverse video on
                                            $attr_cell->reverse = !$attr_cell->reverse;
                                            $temp = $attr_cell->background;
                                            $attr_cell->background = $attr_cell->foreground;
                                            $attr_cell->foreground = $temp;
                                            break;
                                        default: // set colors
                                            //$front = $attr_cell->reverse ? &$attr_cell->background : &$attr_cell->foreground;
                                            $front = &$attr_cell->{ $attr_cell->reverse ? 'background' : 'foreground' };
                                            //$back = $attr_cell->reverse ? &$attr_cell->foreground : &$attr_cell->background;
                                            $back = &$attr_cell->{ $attr_cell->reverse ? 'foreground' : 'background' };
                                            switch ($mod) {
                                                // @codingStandardsIgnoreStart
                                                case 30: $front = 'black'; break;
                                                case 31: $front = 'red'; break;
                                                case 32: $front = 'green'; break;
                                                case 33: $front = 'yellow'; break;
                                                case 34: $front = 'blue'; break;
                                                case 35: $front = 'magenta'; break;
                                                case 36: $front = 'cyan'; break;
                                                case 37: $front = 'white'; break;

                                                case 40: $back = 'black'; break;
                                                case 41: $back = 'red'; break;
                                                case 42: $back = 'green'; break;
                                                case 43: $back = 'yellow'; break;
                                                case 44: $back = 'blue'; break;
                                                case 45: $back = 'magenta'; break;
                                                case 46: $back = 'cyan'; break;
                                                case 47: $back = 'white'; break;
                                                // @codingStandardsIgnoreEnd

                                                default:
                                                    //user_error('Unsupported attribute: ' . $mod);
                                                    $this->ansi = '';
                                                    break 2;
                                            }
                                    }
                                }
                                break;
                            default:
                                //user_error("{$this->ansi} is unsupported\r\n");
                        }
                }
                $this->ansi = '';
                continue;
            }

            $this->tokenization[count($this->tokenization) - 1].= $source[$i];
            switch ($source[$i]) {
                case "\r":
                    $this->x = 0;
                    break;
                case "\n":
                    $this->_newLine();
                    break;
                case "\x08": // backspace
                    if ($this->x) {
                        $this->x--;
                        $this->attrs[$this->y][$this->x] = clone $this->base_attr_cell;
                        $this->screen[$this->y] = substr_replace(
                            $this->screen[$this->y],
                            $source[$i],
                            $this->x,
                            1
                        );
                    }
                    break;
                case "\x0F": // shift
                    break;
                case "\x1B": // start ANSI escape code
                    $this->tokenization[count($this->tokenization) - 1] = substr($this->tokenization[count($this->tokenization) - 1], 0, -1);
                    //if (!strlen($this->tokenization[count($this->tokenization) - 1])) {
                    //    array_pop($this->tokenization);
                    //}
                    $this->ansi.= "\x1B";
                    break;
                default:
                    $this->attrs[$this->y][$this->x] = clone $this->attr_cell;
                    if ($this->x > strlen($this->screen[$this->y])) {
                        $this->screen[$this->y] = str_repeat(' ', $this->x);
                    }
                    $this->screen[$this->y] = substr_replace(
                        $this->screen[$this->y],
                        $source[$i],
                        $this->x,
                        1
                    );

                    if ($this->x > $this->max_x) {
                        $this->x = 0;
                        $this->y++;
                    } else {
                        $this->x++;
                    }
            }
        }
    }

    /**
     * Add a new line
     *
     * Also update the $this->screen and $this->history buffers
     *
     * @access private
     */
    function _newLine()
    {
        //if ($this->y < $this->max_y) {
        //    $this->y++;
        //}

        while ($this->y >= $this->max_y) {
            $this->history = array_merge($this->history, array(array_shift($this->screen)));
            $this->screen[] = '';

            $this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs)));
            $this->attrs[] = $this->attr_row;

            if (count($this->history) >= $this->max_history) {
                array_shift($this->history);
                array_shift($this->history_attrs);
            }

            $this->y--;
        }
        $this->y++;
    }

    /**
     * Returns the current coordinate without preformating
     *
     * @access private
     * @return string
     */
    function _processCoordinate($last_attr, $cur_attr, $char)
    {
        $output = '';

        if ($last_attr != $cur_attr) {
            $close = $open = '';
            if ($last_attr->foreground != $cur_attr->foreground) {
                if ($cur_attr->foreground != 'white') {
                    $open.= '<span style="color: ' . $cur_attr->foreground . '">';
                }
                if ($last_attr->foreground != 'white') {
                    $close = '</span>' . $close;
                }
            }
            if ($last_attr->background != $cur_attr->background) {
                if ($cur_attr->background != 'black') {
                    $open.= '<span style="background: ' . $cur_attr->background . '">';
                }
                if ($last_attr->background != 'black') {
                    $close = '</span>' . $close;
                }
            }
            if ($last_attr->bold != $cur_attr->bold) {
                if ($cur_attr->bold) {
                    $open.= '<b>';
                } else {
                    $close = '</b>' . $close;
                }
            }
            if ($last_attr->underline != $cur_attr->underline) {
                if ($cur_attr->underline) {
                    $open.= '<u>';
                } else {
                    $close = '</u>' . $close;
                }
            }
            if ($last_attr->blink != $cur_attr->blink) {
                if ($cur_attr->blink) {
                    $open.= '<blink>';
                } else {
                    $close = '</blink>' . $close;
                }
            }
            $output.= $close . $open;
        }

        $output.= htmlspecialchars($char);

        return $output;
    }

    /**
     * Returns the current screen without preformating
     *
     * @access private
     * @return string
     */
    function _getScreen()
    {
        $output = '';
        $last_attr = $this->base_attr_cell;
        for ($i = 0; $i <= $this->max_y; $i++) {
            for ($j = 0; $j <= $this->max_x; $j++) {
                $cur_attr = $this->attrs[$i][$j];
                $output.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : '');
                $last_attr = $this->attrs[$i][$j];
            }
            $output.= "\r\n";
        }
        $output = substr($output, 0, -2);
        // close any remaining open tags
        $output.= $this->_processCoordinate($last_attr, $this->base_attr_cell, '');
        return rtrim($output);
    }

    /**
     * Returns the current screen
     *
     * @access public
     * @return string
     */
    function getScreen()
    {
        return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $this->_getScreen() . '</pre>';
    }

    /**
     * Returns the current screen and the x previous lines
     *
     * @access public
     * @return string
     */
    function getHistory()
    {
        $scrollback = '';
        $last_attr = $this->base_attr_cell;
        for ($i = 0; $i < count($this->history); $i++) {
            for ($j = 0; $j <= $this->max_x + 1; $j++) {
                $cur_attr = $this->history_attrs[$i][$j];
                $scrollback.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : '');
                $last_attr = $this->history_attrs[$i][$j];
            }
            $scrollback.= "\r\n";
        }
        $base_attr_cell = $this->base_attr_cell;
        $this->base_attr_cell = $last_attr;
        $scrollback.= $this->_getScreen();
        $this->base_attr_cell = $base_attr_cell;

        return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $scrollback . '</span></pre>';
    }
}
<?php
/**
 * Pure-PHP ASN.1 Parser
 *
 * PHP version 5
 *
 * @category  File
 * @package   ASN1
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2012 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\File\ASN1;

/**
 * ASN.1 Element
 *
 * Bypass normal encoding rules in phpseclib\File\ASN1::encodeDER()
 *
 * @package ASN1
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class Element
{
    /**
     * Raw element value
     *
     * @var string
     * @access private
     */
    var $element;

    /**
     * Constructor
     *
     * @param string $encoded
     * @return \phpseclib\File\ASN1\Element
     * @access public
     */
    function __construct($encoded)
    {
        $this->element = $encoded;
    }
}
<?php

/**
 * Pure-PHP ASN.1 Parser
 *
 * PHP version 5
 *
 * ASN.1 provides the semantics for data encoded using various schemes.  The most commonly
 * utilized scheme is DER or the "Distinguished Encoding Rules".  PEM's are base64 encoded
 * DER blobs.
 *
 * \phpseclib\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
 *
 * Uses the 1988 ASN.1 syntax.
 *
 * @category  File
 * @package   ASN1
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2012 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\File;

use phpseclib\File\ASN1\Element;
use phpseclib\Math\BigInteger;

/**
 * Pure-PHP ASN.1 Parser
 *
 * @package ASN1
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class ASN1
{
    /**#@+
     * Tag Classes
     *
     * @access private
     * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
     */
    const CLASS_UNIVERSAL        = 0;
    const CLASS_APPLICATION      = 1;
    const CLASS_CONTEXT_SPECIFIC = 2;
    const CLASS_PRIVATE          = 3;
    /**#@-*/

    /**#@+
     * Tag Classes
     *
     * @access private
     * @link http://www.obj-sys.com/asn1tutorial/node124.html
    */
    const TYPE_BOOLEAN           = 1;
    const TYPE_INTEGER           = 2;
    const TYPE_BIT_STRING        = 3;
    const TYPE_OCTET_STRING      = 4;
    const TYPE_NULL              = 5;
    const TYPE_OBJECT_IDENTIFIER = 6;
    //const TYPE_OBJECT_DESCRIPTOR = 7;
    //const TYPE_INSTANCE_OF       = 8; // EXTERNAL
    const TYPE_REAL              = 9;
    const TYPE_ENUMERATED        = 10;
    //const TYPE_EMBEDDED          = 11;
    const TYPE_UTF8_STRING       = 12;
    //const TYPE_RELATIVE_OID      = 13;
    const TYPE_SEQUENCE          = 16; // SEQUENCE OF
    const TYPE_SET               = 17; // SET OF
    /**#@-*/
    /**#@+
     * More Tag Classes
     *
     * @access private
     * @link http://www.obj-sys.com/asn1tutorial/node10.html
    */
    const TYPE_NUMERIC_STRING   = 18;
    const TYPE_PRINTABLE_STRING = 19;
    const TYPE_TELETEX_STRING   = 20; // T61String
    const TYPE_VIDEOTEX_STRING  = 21;
    const TYPE_IA5_STRING       = 22;
    const TYPE_UTC_TIME         = 23;
    const TYPE_GENERALIZED_TIME = 24;
    const TYPE_GRAPHIC_STRING   = 25;
    const TYPE_VISIBLE_STRING   = 26; // ISO646String
    const TYPE_GENERAL_STRING   = 27;
    const TYPE_UNIVERSAL_STRING = 28;
    //const TYPE_CHARACTER_STRING = 29;
    const TYPE_BMP_STRING       = 30;
    /**#@-*/

    /**#@+
     * Tag Aliases
     *
     * These tags are kinda place holders for other tags.
     *
     * @access private
    */
    const TYPE_CHOICE = -1;
    const TYPE_ANY    = -2;
    /**#@-*/

    /**
     * ASN.1 object identifier
     *
     * @var array
     * @access private
     * @link http://en.wikipedia.org/wiki/Object_identifier
     */
    var $oids = array();

    /**
     * Default date format
     *
     * @var string
     * @access private
     * @link http://php.net/class.datetime
     */
    var $format = 'D, d M Y H:i:s O';

    /**
     * Default date format
     *
     * @var array
     * @access private
     * @see self::setTimeFormat()
     * @see self::asn1map()
     * @link http://php.net/class.datetime
     */
    var $encoded;

    /**
     * Filters
     *
     * If the mapping type is self::TYPE_ANY what do we actually encode it as?
     *
     * @var array
     * @access private
     * @see self::_encode_der()
     */
    var $filters;

    /**
     * Type mapping table for the ANY type.
     *
     * Structured or unknown types are mapped to a \phpseclib\File\ASN1\Element.
     * Unambiguous types get the direct mapping (int/real/bool).
     * Others are mapped as a choice, with an extra indexing level.
     *
     * @var array
     * @access public
     */
    var $ANYmap = array(
        self::TYPE_BOOLEAN              => true,
        self::TYPE_INTEGER              => true,
        self::TYPE_BIT_STRING           => 'bitString',
        self::TYPE_OCTET_STRING         => 'octetString',
        self::TYPE_NULL                 => 'null',
        self::TYPE_OBJECT_IDENTIFIER    => 'objectIdentifier',
        self::TYPE_REAL                 => true,
        self::TYPE_ENUMERATED           => 'enumerated',
        self::TYPE_UTF8_STRING          => 'utf8String',
        self::TYPE_NUMERIC_STRING       => 'numericString',
        self::TYPE_PRINTABLE_STRING     => 'printableString',
        self::TYPE_TELETEX_STRING       => 'teletexString',
        self::TYPE_VIDEOTEX_STRING      => 'videotexString',
        self::TYPE_IA5_STRING           => 'ia5String',
        self::TYPE_UTC_TIME             => 'utcTime',
        self::TYPE_GENERALIZED_TIME     => 'generalTime',
        self::TYPE_GRAPHIC_STRING       => 'graphicString',
        self::TYPE_VISIBLE_STRING       => 'visibleString',
        self::TYPE_GENERAL_STRING       => 'generalString',
        self::TYPE_UNIVERSAL_STRING     => 'universalString',
        //self::TYPE_CHARACTER_STRING     => 'characterString',
        self::TYPE_BMP_STRING           => 'bmpString'
    );

    /**
     * String type to character size mapping table.
     *
     * Non-convertable types are absent from this table.
     * size == 0 indicates variable length encoding.
     *
     * @var array
     * @access public
     */
    var $stringTypeSize = array(
        self::TYPE_UTF8_STRING      => 0,
        self::TYPE_BMP_STRING       => 2,
        self::TYPE_UNIVERSAL_STRING => 4,
        self::TYPE_PRINTABLE_STRING => 1,
        self::TYPE_TELETEX_STRING   => 1,
        self::TYPE_IA5_STRING       => 1,
        self::TYPE_VISIBLE_STRING   => 1,
    );

    /**
     * Parse BER-encoding
     *
     * Serves a similar purpose to openssl's asn1parse
     *
     * @param string $encoded
     * @return array
     * @access public
     */
    function decodeBER($encoded)
    {
        if ($encoded instanceof Element) {
            $encoded = $encoded->element;
        }

        $this->encoded = $encoded;
        // encapsulate in an array for BC with the old decodeBER
        return array($this->_decode_ber($encoded));
    }

    /**
     * Parse BER-encoding (Helper function)
     *
     * Sometimes we want to get the BER encoding of a particular tag.  $start lets us do that without having to reencode.
     * $encoded is passed by reference for the recursive calls done for self::TYPE_BIT_STRING and
     * self::TYPE_OCTET_STRING. In those cases, the indefinite length is used.
     *
     * @param string $encoded
     * @param int $start
     * @param int $encoded_pos
     * @return array
     * @access private
     */
    function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
    {
        $current = array('start' => $start);

        $type = ord($encoded[$encoded_pos++]);
        $start++;

        $constructed = ($type >> 5) & 1;

        $tag = $type & 0x1F;
        if ($tag == 0x1F) {
            $tag = 0;
            // process septets (since the eighth bit is ignored, it's not an octet)
            do {
                $loop = ord($encoded[0]) >> 7;
                $tag <<= 7;
                $tag |= ord($encoded[$encoded_pos++]) & 0x7F;
                $start++;
            } while ($loop);
        }

        // Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
        $length = ord($encoded[$encoded_pos++]);
        $start++;
        if ($length == 0x80) { // indefinite length
            // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
            //  immediately available." -- paragraph 8.1.3.2.c
            $length = strlen($encoded) - $encoded_pos;
        } elseif ($length & 0x80) { // definite length, long form
            // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
            // support it up to four.
            $length&= 0x7F;
            $temp = substr($encoded, $encoded_pos, $length);
            $encoded_pos += $length;
            // tags of indefinte length don't really have a header length; this length includes the tag
            $current+= array('headerlength' => $length + 2);
            $start+= $length;
            extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
        } else {
            $current+= array('headerlength' => 2);
        }

        if ($length > (strlen($encoded) - $encoded_pos)) {
            return false;
        }

        $content = substr($encoded, $encoded_pos, $length);
        $content_pos = 0;

        // at this point $length can be overwritten. it's only accurate for definite length things as is

        /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
           built-in types. It defines an application-independent data type that must be distinguishable from all other
           data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
           have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within
           a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the
           alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this
           data type; the term CONTEXT-SPECIFIC does not appear.

             -- http://www.obj-sys.com/asn1tutorial/node12.html */
        $class = ($type >> 6) & 3;
        switch ($class) {
            case self::CLASS_APPLICATION:
            case self::CLASS_PRIVATE:
            case self::CLASS_CONTEXT_SPECIFIC:
                if (!$constructed) {
                    return array(
                        'type'     => $class,
                        'constant' => $tag,
                        'content'  => $content,
                        'length'   => $length + $start - $current['start']
                    );
                }

                $newcontent = array();
                $remainingLength = $length;
                while ($remainingLength > 0) {
                    $temp = $this->_decode_ber($content, $start, $content_pos);
                    $length = $temp['length'];
                    // end-of-content octets - see paragraph 8.1.5
                    if (substr($content, $content_pos + $length, 2) == "\0\0") {
                        $length+= 2;
                        $start+= $length;
                        $newcontent[] = $temp;
                        break;
                    }
                    $start+= $length;
                    $remainingLength-= $length;
                    $newcontent[] = $temp;
                    $content_pos += $length;
                }

                return array(
                    'type'     => $class,
                    'constant' => $tag,
                    // the array encapsulation is for BC with the old format
                    'content'  => $newcontent,
                    // the only time when $content['headerlength'] isn't defined is when the length is indefinite.
                    // the absence of $content['headerlength'] is how we know if something is indefinite or not.
                    // technically, it could be defined to be 2 and then another indicator could be used but whatever.
                    'length'   => $start - $current['start']
                ) + $current;
        }

        $current+= array('type' => $tag);

        // decode UNIVERSAL tags
        switch ($tag) {
            case self::TYPE_BOOLEAN:
                // "The contents octets shall consist of a single octet." -- paragraph 8.2.1
                //if (strlen($content) != 1) {
                //    return false;
                //}
                $current['content'] = (bool) ord($content[$content_pos]);
                break;
            case self::TYPE_INTEGER:
            case self::TYPE_ENUMERATED:
                $current['content'] = new BigInteger(substr($content, $content_pos), -256);
                break;
            case self::TYPE_REAL: // not currently supported
                return false;
            case self::TYPE_BIT_STRING:
                // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
                // the number of unused bits in the final subsequent octet. The number shall be in the range zero to
                // seven.
                if (!$constructed) {
                    $current['content'] = substr($content, $content_pos);
                } else {
                    $temp = $this->_decode_ber($content, $start, $content_pos);
                    $length-= (strlen($content) - $content_pos);
                    $last = count($temp) - 1;
                    for ($i = 0; $i < $last; $i++) {
                        // all subtags should be bit strings
                        //if ($temp[$i]['type'] != self::TYPE_BIT_STRING) {
                        //    return false;
                        //}
                        $current['content'].= substr($temp[$i]['content'], 1);
                    }
                    // all subtags should be bit strings
                    //if ($temp[$last]['type'] != self::TYPE_BIT_STRING) {
                    //    return false;
                    //}
                    $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1);
                }
                break;
            case self::TYPE_OCTET_STRING:
                if (!$constructed) {
                    $current['content'] = substr($content, $content_pos);
                } else {
                    $current['content'] = '';
                    $length = 0;
                    while (substr($content, $content_pos, 2) != "\0\0") {
                        $temp = $this->_decode_ber($content, $length + $start, $content_pos);
                        $content_pos += $temp['length'];
                        // all subtags should be octet strings
                        //if ($temp['type'] != self::TYPE_OCTET_STRING) {
                        //    return false;
                        //}
                        $current['content'].= $temp['content'];
                        $length+= $temp['length'];
                    }
                    if (substr($content, $content_pos, 2) == "\0\0") {
                        $length+= 2; // +2 for the EOC
                    }
                }
                break;
            case self::TYPE_NULL:
                // "The contents octets shall not contain any octets." -- paragraph 8.8.2
                //if (strlen($content)) {
                //    return false;
                //}
                break;
            case self::TYPE_SEQUENCE:
            case self::TYPE_SET:
                $offset = 0;
                $current['content'] = array();
                $content_len = strlen($content);
                while ($content_pos < $content_len) {
                    // if indefinite length construction was used and we have an end-of-content string next
                    // see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
                    if (!isset($current['headerlength']) && substr($content, $content_pos, 2) == "\0\0") {
                        $length = $offset + 2; // +2 for the EOC
                        break 2;
                    }
                    $temp = $this->_decode_ber($content, $start + $offset, $content_pos);
                    $content_pos += $temp['length'];
                    $current['content'][] = $temp;
                    $offset+= $temp['length'];
                }
                break;
            case self::TYPE_OBJECT_IDENTIFIER:
                $temp = ord($content[$content_pos++]);
                $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40);
                $valuen = 0;
                // process septets
                $content_len = strlen($content);
                while ($content_pos < $content_len) {
                    $temp = ord($content[$content_pos++]);
                    $valuen <<= 7;
                    $valuen |= $temp & 0x7F;
                    if (~$temp & 0x80) {
                        $current['content'].= ".$valuen";
                        $valuen = 0;
                    }
                }
                // the eighth bit of the last byte should not be 1
                //if ($temp >> 7) {
                //    return false;
                //}
                break;
            /* Each character string type shall be encoded as if it had been declared:
               [UNIVERSAL x] IMPLICIT OCTET STRING

                 -- X.690-0207.pdf#page=23 (paragraph 8.21.3)

               Per that, we're not going to do any validation.  If there are any illegal characters in the string,
               we don't really care */
            case self::TYPE_NUMERIC_STRING:
                // 0,1,2,3,4,5,6,7,8,9, and space
            case self::TYPE_PRINTABLE_STRING:
                // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma,
                // hyphen, full stop, solidus, colon, equal sign, question mark
            case self::TYPE_TELETEX_STRING:
                // The Teletex character set in CCITT's T61, space, and delete
                // see http://en.wikipedia.org/wiki/Teletex#Character_sets
            case self::TYPE_VIDEOTEX_STRING:
                // The Videotex character set in CCITT's T.100 and T.101, space, and delete
            case self::TYPE_VISIBLE_STRING:
                // Printing character sets of international ASCII, and space
            case self::TYPE_IA5_STRING:
                // International Alphabet 5 (International ASCII)
            case self::TYPE_GRAPHIC_STRING:
                // All registered G sets, and space
            case self::TYPE_GENERAL_STRING:
                // All registered C and G sets, space and delete
            case self::TYPE_UTF8_STRING:
                // ????
            case self::TYPE_BMP_STRING:
                $current['content'] = substr($content, $content_pos);
                break;
            case self::TYPE_UTC_TIME:
            case self::TYPE_GENERALIZED_TIME:
                $current['content'] = $this->_decodeTime(substr($content, $content_pos), $tag);
            default:
        }

        $start+= $length;

        // ie. length is the length of the full TLV encoding - it's not just the length of the value
        return $current + array('length' => $start - $current['start']);
    }

    /**
     * ASN.1 Map
     *
     * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
     *
     * "Special" mappings may be applied on a per tag-name basis via $special.
     *
     * @param array $decoded
     * @param array $mapping
     * @param array $special
     * @return array
     * @access public
     */
    function asn1map($decoded, $mapping, $special = array())
    {
        if (isset($mapping['explicit']) && is_array($decoded['content'])) {
            $decoded = $decoded['content'][0];
        }

        switch (true) {
            case $mapping['type'] == self::TYPE_ANY:
                $intype = $decoded['type'];
                if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || (ord($this->encoded[$decoded['start']]) & 0x20)) {
                    return new Element(substr($this->encoded, $decoded['start'], $decoded['length']));
                }
                $inmap = $this->ANYmap[$intype];
                if (is_string($inmap)) {
                    return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special));
                }
                break;
            case $mapping['type'] == self::TYPE_CHOICE:
                foreach ($mapping['children'] as $key => $option) {
                    switch (true) {
                        case isset($option['constant']) && $option['constant'] == $decoded['constant']:
                        case !isset($option['constant']) && $option['type'] == $decoded['type']:
                            $value = $this->asn1map($decoded, $option, $special);
                            break;
                        case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE:
                            $v = $this->asn1map($decoded, $option, $special);
                            if (isset($v)) {
                                $value = $v;
                            }
                    }
                    if (isset($value)) {
                        if (isset($special[$key])) {
                            $value = call_user_func($special[$key], $value);
                        }
                        return array($key => $value);
                    }
                }
                return null;
            case isset($mapping['implicit']):
            case isset($mapping['explicit']):
            case $decoded['type'] == $mapping['type']:
                break;
            default:
                // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings,
                // let it through
                switch (true) {
                    case $decoded['type'] < 18: // self::TYPE_NUMERIC_STRING == 18
                    case $decoded['type'] > 30: // self::TYPE_BMP_STRING == 30
                    case $mapping['type'] < 18:
                    case $mapping['type'] > 30:
                        return null;
                }
        }

        if (isset($mapping['implicit'])) {
            $decoded['type'] = $mapping['type'];
        }

        switch ($decoded['type']) {
            case self::TYPE_SEQUENCE:
                $map = array();

                // ignore the min and max
                if (isset($mapping['min']) && isset($mapping['max'])) {
                    $child = $mapping['children'];
                    foreach ($decoded['content'] as $content) {
                        if (($map[] = $this->asn1map($content, $child, $special)) === null) {
                            return null;
                        }
                    }

                    return $map;
                }

                $n = count($decoded['content']);
                $i = 0;

                foreach ($mapping['children'] as $key => $child) {
                    $maymatch = $i < $n; // Match only existing input.
                    if ($maymatch) {
                        $temp = $decoded['content'][$i];

                        if ($child['type'] != self::TYPE_CHOICE) {
                            // Get the mapping and input class & constant.
                            $childClass = $tempClass = self::CLASS_UNIVERSAL;
                            $constant = null;
                            if (isset($temp['constant'])) {
                                $tempClass = isset($temp['class']) ? $temp['class'] : self::CLASS_CONTEXT_SPECIFIC;
                            }
                            if (isset($child['class'])) {
                                $childClass = $child['class'];
                                $constant = $child['cast'];
                            } elseif (isset($child['constant'])) {
                                $childClass = self::CLASS_CONTEXT_SPECIFIC;
                                $constant = $child['constant'];
                            }

                            if (isset($constant) && isset($temp['constant'])) {
                                // Can only match if constants and class match.
                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
                            } else {
                                // Can only match if no constant expected and type matches or is generic.
                                $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
                            }
                        }
                    }

                    if ($maymatch) {
                        // Attempt submapping.
                        $candidate = $this->asn1map($temp, $child, $special);
                        $maymatch = $candidate !== null;
                    }

                    if ($maymatch) {
                        // Got the match: use it.
                        if (isset($special[$key])) {
                            $candidate = call_user_func($special[$key], $candidate);
                        }
                        $map[$key] = $candidate;
                        $i++;
                    } elseif (isset($child['default'])) {
                        $map[$key] = $child['default']; // Use default.
                    } elseif (!isset($child['optional'])) {
                        return null; // Syntax error.
                    }
                }

                // Fail mapping if all input items have not been consumed.
                return $i < $n ? null: $map;

            // the main diff between sets and sequences is the encapsulation of the foreach in another for loop
            case self::TYPE_SET:
                $map = array();

                // ignore the min and max
                if (isset($mapping['min']) && isset($mapping['max'])) {
                    $child = $mapping['children'];
                    foreach ($decoded['content'] as $content) {
                        if (($map[] = $this->asn1map($content, $child, $special)) === null) {
                            return null;
                        }
                    }

                    return $map;
                }

                for ($i = 0; $i < count($decoded['content']); $i++) {
                    $temp = $decoded['content'][$i];
                    $tempClass = self::CLASS_UNIVERSAL;
                    if (isset($temp['constant'])) {
                        $tempClass = isset($temp['class']) ? $temp['class'] : self::CLASS_CONTEXT_SPECIFIC;
                    }

                    foreach ($mapping['children'] as $key => $child) {
                        if (isset($map[$key])) {
                            continue;
                        }
                        $maymatch = true;
                        if ($child['type'] != self::TYPE_CHOICE) {
                            $childClass = self::CLASS_UNIVERSAL;
                            $constant = null;
                            if (isset($child['class'])) {
                                $childClass = $child['class'];
                                $constant = $child['cast'];
                            } elseif (isset($child['constant'])) {
                                $childClass = self::CLASS_CONTEXT_SPECIFIC;
                                $constant = $child['constant'];
                            }

                            if (isset($constant) && isset($temp['constant'])) {
                                // Can only match if constants and class match.
                                $maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
                            } else {
                                // Can only match if no constant expected and type matches or is generic.
                                $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
                            }
                        }

                        if ($maymatch) {
                            // Attempt submapping.
                            $candidate = $this->asn1map($temp, $child, $special);
                            $maymatch = $candidate !== null;
                        }

                        if (!$maymatch) {
                            break;
                        }

                        // Got the match: use it.
                        if (isset($special[$key])) {
                            $candidate = call_user_func($special[$key], $candidate);
                        }
                        $map[$key] = $candidate;
                        break;
                    }
                }

                foreach ($mapping['children'] as $key => $child) {
                    if (!isset($map[$key])) {
                        if (isset($child['default'])) {
                            $map[$key] = $child['default'];
                        } elseif (!isset($child['optional'])) {
                            return null;
                        }
                    }
                }
                return $map;
            case self::TYPE_OBJECT_IDENTIFIER:
                return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
            case self::TYPE_UTC_TIME:
            case self::TYPE_GENERALIZED_TIME:
                if (isset($mapping['implicit'])) {
                    $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
                }
                return @date($this->format, $decoded['content']);
            case self::TYPE_BIT_STRING:
                if (isset($mapping['mapping'])) {
                    $offset = ord($decoded['content'][0]);
                    $size = (strlen($decoded['content']) - 1) * 8 - $offset;
                    /*
                       From X.680-0207.pdf#page=46 (21.7):

                       "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove)
                        arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should
                        therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
                        0 bits."
                    */
                    $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false);
                    for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
                        $current = ord($decoded['content'][$i]);
                        for ($j = $offset; $j < 8; $j++) {
                            $bits[] = (bool) ($current & (1 << $j));
                        }
                        $offset = 0;
                    }
                    $values = array();
                    $map = array_reverse($mapping['mapping']);
                    foreach ($map as $i => $value) {
                        if ($bits[$i]) {
                            $values[] = $value;
                        }
                    }
                    return $values;
                }
            case self::TYPE_OCTET_STRING:
                return base64_encode($decoded['content']);
            case self::TYPE_NULL:
                return '';
            case self::TYPE_BOOLEAN:
                return $decoded['content'];
            case self::TYPE_NUMERIC_STRING:
            case self::TYPE_PRINTABLE_STRING:
            case self::TYPE_TELETEX_STRING:
            case self::TYPE_VIDEOTEX_STRING:
            case self::TYPE_IA5_STRING:
            case self::TYPE_GRAPHIC_STRING:
            case self::TYPE_VISIBLE_STRING:
            case self::TYPE_GENERAL_STRING:
            case self::TYPE_UNIVERSAL_STRING:
            case self::TYPE_UTF8_STRING:
            case self::TYPE_BMP_STRING:
                return $decoded['content'];
            case self::TYPE_INTEGER:
            case self::TYPE_ENUMERATED:
                $temp = $decoded['content'];
                if (isset($mapping['implicit'])) {
                    $temp = new BigInteger($decoded['content'], -256);
                }
                if (isset($mapping['mapping'])) {
                    $temp = (int) $temp->toString();
                    return isset($mapping['mapping'][$temp]) ?
                        $mapping['mapping'][$temp] :
                        false;
                }
                return $temp;
        }
    }

    /**
     * ASN.1 Encode
     *
     * DER-encodes an ASN.1 semantic mapping ($mapping).  Some libraries would probably call this function
     * an ASN.1 compiler.
     *
     * "Special" mappings can be applied via $special.
     *
     * @param string $source
     * @param string $mapping
     * @param int $idx
     * @return string
     * @access public
     */
    function encodeDER($source, $mapping, $special = array())
    {
        $this->location = array();
        return $this->_encode_der($source, $mapping, null, $special);
    }

    /**
     * ASN.1 Encode (Helper function)
     *
     * @param string $source
     * @param string $mapping
     * @param int $idx
     * @return string
     * @access private
     */
    function _encode_der($source, $mapping, $idx = null, $special = array())
    {
        if ($source instanceof Element) {
            return $source->element;
        }

        // do not encode (implicitly optional) fields with value set to default
        if (isset($mapping['default']) && $source === $mapping['default']) {
            return '';
        }

        if (isset($idx)) {
            if (isset($special[$idx])) {
                $source = call_user_func($special[$idx], $source);
            }
            $this->location[] = $idx;
        }

        $tag = $mapping['type'];

        switch ($tag) {
            case self::TYPE_SET:    // Children order is not important, thus process in sequence.
            case self::TYPE_SEQUENCE:
                $tag|= 0x20; // set the constructed bit

                // ignore the min and max
                if (isset($mapping['min']) && isset($mapping['max'])) {
                    $value = array();
                    $child = $mapping['children'];

                    foreach ($source as $content) {
                        $temp = $this->_encode_der($content, $child, null, $special);
                        if ($temp === false) {
                            return false;
                        }
                        $value[]= $temp;
                    }
                    /* "The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared
                        as octet strings with the shorter components being padded at their trailing end with 0-octets.
                        NOTE - The padding octets are for comparison purposes only and do not appear in the encodings."

                       -- sec 11.6 of http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf  */
                    if ($mapping['type'] == self::TYPE_SET) {
                        sort($value);
                    }
                    $value = implode($value, '');
                    break;
                }

                $value = '';
                foreach ($mapping['children'] as $key => $child) {
                    if (!array_key_exists($key, $source)) {
                        if (!isset($child['optional'])) {
                            return false;
                        }
                        continue;
                    }

                    $temp = $this->_encode_der($source[$key], $child, $key, $special);
                    if ($temp === false) {
                        return false;
                    }

                    // An empty child encoding means it has been optimized out.
                    // Else we should have at least one tag byte.
                    if ($temp === '') {
                        continue;
                    }

                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
                    if (isset($child['constant'])) {
                        /*
                           From X.680-0207.pdf#page=58 (30.6):

                           "The tagging construction specifies explicit tagging if any of the following holds:
                            ...
                            c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or
                            AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or
                            an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
                         */
                        if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
                        } else {
                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
                            $temp = $subtag . substr($temp, 1);
                        }
                    }
                    $value.= $temp;
                }
                break;
            case self::TYPE_CHOICE:
                $temp = false;

                foreach ($mapping['children'] as $key => $child) {
                    if (!isset($source[$key])) {
                        continue;
                    }

                    $temp = $this->_encode_der($source[$key], $child, $key, $special);
                    if ($temp === false) {
                        return false;
                    }

                    // An empty child encoding means it has been optimized out.
                    // Else we should have at least one tag byte.
                    if ($temp === '') {
                        continue;
                    }

                    $tag = ord($temp[0]);

                    // if isset($child['constant']) is true then isset($child['optional']) should be true as well
                    if (isset($child['constant'])) {
                        if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
                            $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
                        } else {
                            $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
                            $temp = $subtag . substr($temp, 1);
                        }
                    }
                }

                if (isset($idx)) {
                    array_pop($this->location);
                }

                if ($temp && isset($mapping['cast'])) {
                    $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']);
                }

                return $temp;
            case self::TYPE_INTEGER:
            case self::TYPE_ENUMERATED:
                if (!isset($mapping['mapping'])) {
                    if (is_numeric($source)) {
                        $source = new BigInteger($source);
                    }
                    $value = $source->toBytes(true);
                } else {
                    $value = array_search($source, $mapping['mapping']);
                    if ($value === false) {
                        return false;
                    }
                    $value = new BigInteger($value);
                    $value = $value->toBytes(true);
                }
                if (!strlen($value)) {
                    $value = chr(0);
                }
                break;
            case self::TYPE_UTC_TIME:
            case self::TYPE_GENERALIZED_TIME:
                $format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
                $format.= 'mdHis';
                $value = @gmdate($format, strtotime($source)) . 'Z';
                break;
            case self::TYPE_BIT_STRING:
                if (isset($mapping['mapping'])) {
                    $bits = array_fill(0, count($mapping['mapping']), 0);
                    $size = 0;
                    for ($i = 0; $i < count($mapping['mapping']); $i++) {
                        if (in_array($mapping['mapping'][$i], $source)) {
                            $bits[$i] = 1;
                            $size = $i;
                        }
                    }

                    if (isset($mapping['min']) && $mapping['min'] >= 1 && $size < $mapping['min']) {
                        $size = $mapping['min'] - 1;
                    }

                    $offset = 8 - (($size + 1) & 7);
                    $offset = $offset !== 8 ? $offset : 0;

                    $value = chr($offset);

                    for ($i = $size + 1; $i < count($mapping['mapping']); $i++) {
                        unset($bits[$i]);
                    }

                    $bits = implode('', array_pad($bits, $size + $offset + 1, 0));
                    $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
                    foreach ($bytes as $byte) {
                        $value.= chr(bindec($byte));
                    }

                    break;
                }
            case self::TYPE_OCTET_STRING:
                /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
                   the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.

                   -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
                $value = base64_decode($source);
                break;
            case self::TYPE_OBJECT_IDENTIFIER:
                $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
                if ($oid === false) {
                    user_error('Invalid OID');
                    return false;
                }
                $value = '';
                $parts = explode('.', $oid);
                $value = chr(40 * $parts[0] + $parts[1]);
                for ($i = 2; $i < count($parts); $i++) {
                    $temp = '';
                    if (!$parts[$i]) {
                        $temp = "\0";
                    } else {
                        while ($parts[$i]) {
                            $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp;
                            $parts[$i] >>= 7;
                        }
                        $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
                    }
                    $value.= $temp;
                }
                break;
            case self::TYPE_ANY:
                $loc = $this->location;
                if (isset($idx)) {
                    array_pop($this->location);
                }

                switch (true) {
                    case !isset($source):
                        return $this->_encode_der(null, array('type' => self::TYPE_NULL) + $mapping, null, $special);
                    case is_int($source):
                    case $source instanceof BigInteger:
                        return $this->_encode_der($source, array('type' => self::TYPE_INTEGER) + $mapping, null, $special);
                    case is_float($source):
                        return $this->_encode_der($source, array('type' => self::TYPE_REAL) + $mapping, null, $special);
                    case is_bool($source):
                        return $this->_encode_der($source, array('type' => self::TYPE_BOOLEAN) + $mapping, null, $special);
                    case is_array($source) && count($source) == 1:
                        $typename = implode('', array_keys($source));
                        $outtype = array_search($typename, $this->ANYmap, true);
                        if ($outtype !== false) {
                            return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special);
                        }
                }

                $filters = $this->filters;
                foreach ($loc as $part) {
                    if (!isset($filters[$part])) {
                        $filters = false;
                        break;
                    }
                    $filters = $filters[$part];
                }
                if ($filters === false) {
                    user_error('No filters defined for ' . implode('/', $loc));
                    return false;
                }
                return $this->_encode_der($source, $filters + $mapping, null, $special);
            case self::TYPE_NULL:
                $value = '';
                break;
            case self::TYPE_NUMERIC_STRING:
            case self::TYPE_TELETEX_STRING:
            case self::TYPE_PRINTABLE_STRING:
            case self::TYPE_UNIVERSAL_STRING:
            case self::TYPE_UTF8_STRING:
            case self::TYPE_BMP_STRING:
            case self::TYPE_IA5_STRING:
            case self::TYPE_VISIBLE_STRING:
            case self::TYPE_VIDEOTEX_STRING:
            case self::TYPE_GRAPHIC_STRING:
            case self::TYPE_GENERAL_STRING:
                $value = $source;
                break;
            case self::TYPE_BOOLEAN:
                $value = $source ? "\xFF" : "\x00";
                break;
            default:
                user_error('Mapping provides no type definition for ' . implode('/', $this->location));
                return false;
        }

        if (isset($idx)) {
            array_pop($this->location);
        }

        if (isset($mapping['cast'])) {
            if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) {
                $value = chr($tag) . $this->_encodeLength(strlen($value)) . $value;
                $tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast'];
            } else {
                $tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
            }
        }

        return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
    }

    /**
     * DER-encode the length
     *
     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
     *
     * @access private
     * @param int $length
     * @return string
     */
    function _encodeLength($length)
    {
        if ($length <= 0x7F) {
            return chr($length);
        }

        $temp = ltrim(pack('N', $length), chr(0));
        return pack('Ca*', 0x80 | strlen($temp), $temp);
    }

    /**
     * BER-decode the time
     *
     * Called by _decode_ber() and in the case of implicit tags asn1map().
     *
     * @access private
     * @param string $content
     * @param int $tag
     * @return string
     */
    function _decodeTime($content, $tag)
    {
        /* UTCTime:
           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
           http://www.obj-sys.com/asn1tutorial/node15.html

           GeneralizedTime:
           http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
           http://www.obj-sys.com/asn1tutorial/node14.html */

        $pattern = $tag == self::TYPE_UTC_TIME ?
            '#(..)(..)(..)(..)(..)(..)(.*)#' :
            '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#';

        preg_match($pattern, $content, $matches);

        list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches;

        if ($tag == self::TYPE_UTC_TIME) {
            $year = $year >= 50 ? "19$year" : "20$year";
        }

        if ($timezone == 'Z') {
            $mktime = 'gmmktime';
            $timezone = 0;
        } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) {
            $mktime = 'gmmktime';
            $timezone = 60 * $matches[3] + 3600 * $matches[2];
            if ($matches[1] == '-') {
                $timezone = -$timezone;
            }
        } else {
            $mktime = 'mktime';
            $timezone = 0;
        }

        return @$mktime($hour, $minute, $second, $month, $day, $year) + $timezone;
    }

    /**
     * Set the time format
     *
     * Sets the time / date format for asn1map().
     *
     * @access public
     * @param string $format
     */
    function setTimeFormat($format)
    {
        $this->format = $format;
    }

    /**
     * Load OIDs
     *
     * Load the relevant OIDs for a particular ASN.1 semantic mapping.
     *
     * @access public
     * @param array $oids
     */
    function loadOIDs($oids)
    {
        $this->oids = $oids;
    }

    /**
     * Load filters
     *
     * See \phpseclib\File\X509, etc, for an example.
     *
     * @access public
     * @param array $filters
     */
    function loadFilters($filters)
    {
        $this->filters = $filters;
    }

    /**
     * String Shift
     *
     * Inspired by array_shift
     *
     * @param string $string
     * @param int $index
     * @return string
     * @access private
     */
    function _string_shift(&$string, $index = 1)
    {
        $substr = substr($string, 0, $index);
        $string = substr($string, $index);
        return $substr;
    }

    /**
     * String type conversion
     *
     * This is a lazy conversion, dealing only with character size.
     * No real conversion table is used.
     *
     * @param string $in
     * @param int $from
     * @param int $to
     * @return string
     * @access public
     */
    function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
    {
        if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
            return false;
        }
        $insize = $this->stringTypeSize[$from];
        $outsize = $this->stringTypeSize[$to];
        $inlength = strlen($in);
        $out = '';

        for ($i = 0; $i < $inlength;) {
            if ($inlength - $i < $insize) {
                return false;
            }

            // Get an input character as a 32-bit value.
            $c = ord($in[$i++]);
            switch (true) {
                case $insize == 4:
                    $c = ($c << 8) | ord($in[$i++]);
                    $c = ($c << 8) | ord($in[$i++]);
                case $insize == 2:
                    $c = ($c << 8) | ord($in[$i++]);
                case $insize == 1:
                    break;
                case ($c & 0x80) == 0x00:
                    break;
                case ($c & 0x40) == 0x00:
                    return false;
                default:
                    $bit = 6;
                    do {
                        if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) {
                            return false;
                        }
                        $c = ($c << 6) | (ord($in[$i++]) & 0x3F);
                        $bit += 5;
                        $mask = 1 << $bit;
                    } while ($c & $bit);
                    $c &= $mask - 1;
                    break;
            }

            // Convert and append the character to output string.
            $v = '';
            switch (true) {
                case $outsize == 4:
                    $v .= chr($c & 0xFF);
                    $c >>= 8;
                    $v .= chr($c & 0xFF);
                    $c >>= 8;
                case $outsize == 2:
                    $v .= chr($c & 0xFF);
                    $c >>= 8;
                case $outsize == 1:
                    $v .= chr($c & 0xFF);
                    $c >>= 8;
                    if ($c) {
                        return false;
                    }
                    break;
                case ($c & 0x80000000) != 0:
                    return false;
                case $c >= 0x04000000:
                    $v .= chr(0x80 | ($c & 0x3F));
                    $c = ($c >> 6) | 0x04000000;
                case $c >= 0x00200000:
                    $v .= chr(0x80 | ($c & 0x3F));
                    $c = ($c >> 6) | 0x00200000;
                case $c >= 0x00010000:
                    $v .= chr(0x80 | ($c & 0x3F));
                    $c = ($c >> 6) | 0x00010000;
                case $c >= 0x00000800:
                    $v .= chr(0x80 | ($c & 0x3F));
                    $c = ($c >> 6) | 0x00000800;
                case $c >= 0x00000080:
                    $v .= chr(0x80 | ($c & 0x3F));
                    $c = ($c >> 6) | 0x000000C0;
                default:
                    $v .= chr($c);
                    break;
            }
            $out .= strrev($v);
        }
        return $out;
    }
}
<?php

/**
 * Pure-PHP X.509 Parser
 *
 * PHP version 5
 *
 * Encode and decode X.509 certificates.
 *
 * The extensions are from {@link http://tools.ietf.org/html/rfc5280 RFC5280} and
 * {@link http://web.archive.org/web/19961027104704/http://www3.netscape.com/eng/security/cert-exts.html Netscape Certificate Extensions}.
 *
 * Note that loading an X.509 certificate and resaving it may invalidate the signature.  The reason being that the signature is based on a
 * portion of the certificate that contains optional parameters with default values.  ie. if the parameter isn't there the default value is
 * used.  Problem is, if the parameter is there and it just so happens to have the default value there are two ways that that parameter can
 * be encoded.  It can be encoded explicitly or left out all together.  This would effect the signature value and thus may invalidate the
 * the certificate all together unless the certificate is re-signed.
 *
 * @category  File
 * @package   X509
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2012 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\File;

use phpseclib\Crypt\Hash;
use phpseclib\Crypt\Random;
use phpseclib\Crypt\RSA;
use phpseclib\File\ASN1\Element;
use phpseclib\Math\BigInteger;

/**
 * Pure-PHP X.509 Parser
 *
 * @package X509
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class X509
{
    /**
     * Flag to only accept signatures signed by certificate authorities
     *
     * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs
     *
     * @access public
     */
    const VALIDATE_SIGNATURE_BY_CA = 1;

    /**#@+
     * @access public
     * @see \phpseclib\File\X509::getDN()
    */
    /**
     * Return internal array representation
     */
    const DN_ARRAY = 0;
    /**
     * Return string
     */
    const DN_STRING = 1;
    /**
     * Return ASN.1 name string
     */
    const DN_ASN1 = 2;
    /**
     * Return OpenSSL compatible array
     */
    const DN_OPENSSL = 3;
    /**
     * Return canonical ASN.1 RDNs string
     */
    const DN_CANON = 4;
    /**
     * Return name hash for file indexing
     */
    const DN_HASH = 5;
    /**#@-*/

    /**#@+
     * @access public
     * @see \phpseclib\File\X509::saveX509()
     * @see \phpseclib\File\X509::saveCSR()
     * @see \phpseclib\File\X509::saveCRL()
    */
    /**
     * Save as PEM
     *
     * ie. a base64-encoded PEM with a header and a footer
     */
    const FORMAT_PEM = 0;
    /**
     * Save as DER
     */
    const FORMAT_DER = 1;
    /**
     * Save as a SPKAC
     *
     * Only works on CSRs. Not currently supported.
     */
    const FORMAT_SPKAC = 2;
    /**
     * Auto-detect the format
     *
     * Used only by the load*() functions
     */
    const FORMAT_AUTO_DETECT = 3;
    /**#@-*/

    /**
     * Attribute value disposition.
     * If disposition is >= 0, this is the index of the target value.
     */
    const ATTR_ALL = -1; // All attribute values (array).
    const ATTR_APPEND = -2; // Add a value.
    const ATTR_REPLACE = -3; // Clear first, then add a value.

    /**
     * ASN.1 syntax for X.509 certificates
     *
     * @var array
     * @access private
     */
    var $Certificate;

    /**#@+
     * ASN.1 syntax for various extensions
     *
     * @access private
     */
    var $DirectoryString;
    var $PKCS9String;
    var $AttributeValue;
    var $Extensions;
    var $KeyUsage;
    var $ExtKeyUsageSyntax;
    var $BasicConstraints;
    var $KeyIdentifier;
    var $CRLDistributionPoints;
    var $AuthorityKeyIdentifier;
    var $CertificatePolicies;
    var $AuthorityInfoAccessSyntax;
    var $SubjectAltName;
    var $SubjectDirectoryAttributes;
    var $PrivateKeyUsagePeriod;
    var $IssuerAltName;
    var $PolicyMappings;
    var $NameConstraints;

    var $CPSuri;
    var $UserNotice;

    var $netscape_cert_type;
    var $netscape_comment;
    var $netscape_ca_policy_url;

    var $Name;
    var $RelativeDistinguishedName;
    var $CRLNumber;
    var $CRLReason;
    var $IssuingDistributionPoint;
    var $InvalidityDate;
    var $CertificateIssuer;
    var $HoldInstructionCode;
    var $SignedPublicKeyAndChallenge;
    /**#@-*/

    /**#@+
     * ASN.1 syntax for various DN attributes
     *
     * @access private
     */
    var $PostalAddress;
    /**#@-*/

    /**
     * ASN.1 syntax for Certificate Signing Requests (RFC2986)
     *
     * @var array
     * @access private
     */
    var $CertificationRequest;

    /**
     * ASN.1 syntax for Certificate Revocation Lists (RFC5280)
     *
     * @var array
     * @access private
     */
    var $CertificateList;

    /**
     * Distinguished Name
     *
     * @var array
     * @access private
     */
    var $dn;

    /**
     * Public key
     *
     * @var string
     * @access private
     */
    var $publicKey;

    /**
     * Private key
     *
     * @var string
     * @access private
     */
    var $privateKey;

    /**
     * Object identifiers for X.509 certificates
     *
     * @var array
     * @access private
     * @link http://en.wikipedia.org/wiki/Object_identifier
     */
    var $oids;

    /**
     * The certificate authorities
     *
     * @var array
     * @access private
     */
    var $CAs;

    /**
     * The currently loaded certificate
     *
     * @var array
     * @access private
     */
    var $currentCert;

    /**
     * The signature subject
     *
     * There's no guarantee \phpseclib\File\X509 is going to re-encode an X.509 cert in the same way it was originally
     * encoded so we take save the portion of the original cert that the signature would have made for.
     *
     * @var string
     * @access private
     */
    var $signatureSubject;

    /**
     * Certificate Start Date
     *
     * @var string
     * @access private
     */
    var $startDate;

    /**
     * Certificate End Date
     *
     * @var string
     * @access private
     */
    var $endDate;

    /**
     * Serial Number
     *
     * @var string
     * @access private
     */
    var $serialNumber;

    /**
     * Key Identifier
     *
     * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and
     * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}.
     *
     * @var string
     * @access private
     */
    var $currentKeyIdentifier;

    /**
     * CA Flag
     *
     * @var bool
     * @access private
     */
    var $caFlag = false;

    /**
     * SPKAC Challenge
     *
     * @var string
     * @access private
     */
    var $challenge;

    /**
     * Default Constructor.
     *
     * @return \phpseclib\File\X509
     * @access public
     */
    function __construct()
    {
        // Explicitly Tagged Module, 1988 Syntax
        // http://tools.ietf.org/html/rfc5280#appendix-A.1

        $this->DirectoryString = array(
            'type'     => ASN1::TYPE_CHOICE,
            'children' => array(
                'teletexString'   => array('type' => ASN1::TYPE_TELETEX_STRING),
                'printableString' => array('type' => ASN1::TYPE_PRINTABLE_STRING),
                'universalString' => array('type' => ASN1::TYPE_UNIVERSAL_STRING),
                'utf8String'      => array('type' => ASN1::TYPE_UTF8_STRING),
                'bmpString'       => array('type' => ASN1::TYPE_BMP_STRING)
            )
        );

        $this->PKCS9String = array(
            'type'     => ASN1::TYPE_CHOICE,
            'children' => array(
                'ia5String'       => array('type' => ASN1::TYPE_IA5_STRING),
                'directoryString' => $this->DirectoryString
            )
        );

        $this->AttributeValue = array('type' => ASN1::TYPE_ANY);

        $AttributeType = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);

        $AttributeTypeAndValue = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'type' => $AttributeType,
                'value'=> $this->AttributeValue
            )
        );

        /*
        In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
        but they can be useful at times when either there is no unique attribute in the entry or you
        want to ensure that the entry's DN contains some useful identifying information.

        - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
        */
        $this->RelativeDistinguishedName = array(
            'type'     => ASN1::TYPE_SET,
            'min'      => 1,
            'max'      => -1,
            'children' => $AttributeTypeAndValue
        );

        // http://tools.ietf.org/html/rfc5280#section-4.1.2.4
        $RDNSequence = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            // RDNSequence does not define a min or a max, which means it doesn't have one
            'min'      => 0,
            'max'      => -1,
            'children' => $this->RelativeDistinguishedName
        );

        $this->Name = array(
            'type'     => ASN1::TYPE_CHOICE,
            'children' => array(
                'rdnSequence' => $RDNSequence
            )
        );

        // http://tools.ietf.org/html/rfc5280#section-4.1.1.2
        $AlgorithmIdentifier = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'algorithm'  => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
                'parameters' => array(
                                    'type'     => ASN1::TYPE_ANY,
                                    'optional' => true
                                )
            )
        );

        /*
           A certificate using system MUST reject the certificate if it encounters
           a critical extension it does not recognize; however, a non-critical
           extension may be ignored if it is not recognized.

           http://tools.ietf.org/html/rfc5280#section-4.2
        */
        $Extension = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'extnId'   => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
                'critical' => array(
                                  'type'     => ASN1::TYPE_BOOLEAN,
                                  'optional' => true,
                                  'default'  => false
                              ),
                'extnValue' => array('type' => ASN1::TYPE_OCTET_STRING)
            )
        );

        $this->Extensions = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            // technically, it's MAX, but we'll assume anything < 0 is MAX
            'max'      => -1,
            // if 'children' isn't an array then 'min' and 'max' must be defined
            'children' => $Extension
        );

        $SubjectPublicKeyInfo = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'algorithm'        => $AlgorithmIdentifier,
                'subjectPublicKey' => array('type' => ASN1::TYPE_BIT_STRING)
            )
        );

        $UniqueIdentifier = array('type' => ASN1::TYPE_BIT_STRING);

        $Time = array(
            'type'     => ASN1::TYPE_CHOICE,
            'children' => array(
                'utcTime'     => array('type' => ASN1::TYPE_UTC_TIME),
                'generalTime' => array('type' => ASN1::TYPE_GENERALIZED_TIME)
            )
        );

        // http://tools.ietf.org/html/rfc5280#section-4.1.2.5
        $Validity = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'notBefore' => $Time,
                'notAfter'  => $Time
            )
        );

        $CertificateSerialNumber = array('type' => ASN1::TYPE_INTEGER);

        $Version = array(
            'type'    => ASN1::TYPE_INTEGER,
            'mapping' => array('v1', 'v2', 'v3')
        );

        // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm'])
        $TBSCertificate = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                // technically, default implies optional, but we'll define it as being optional, none-the-less, just to
                // reenforce that fact
                'version'             => array(
                                             'constant' => 0,
                                             'optional' => true,
                                             'explicit' => true,
                                             'default'  => 'v1'
                                         ) + $Version,
                'serialNumber'         => $CertificateSerialNumber,
                'signature'            => $AlgorithmIdentifier,
                'issuer'               => $this->Name,
                'validity'             => $Validity,
                'subject'              => $this->Name,
                'subjectPublicKeyInfo' => $SubjectPublicKeyInfo,
                // implicit means that the T in the TLV structure is to be rewritten, regardless of the type
                'issuerUniqueID'       => array(
                                               'constant' => 1,
                                               'optional' => true,
                                               'implicit' => true
                                           ) + $UniqueIdentifier,
                'subjectUniqueID'       => array(
                                               'constant' => 2,
                                               'optional' => true,
                                               'implicit' => true
                                           ) + $UniqueIdentifier,
                // <http://tools.ietf.org/html/rfc2459#page-74> doesn't use the EXPLICIT keyword but if
                // it's not IMPLICIT, it's EXPLICIT
                'extensions'            => array(
                                               'constant' => 3,
                                               'optional' => true,
                                               'explicit' => true
                                           ) + $this->Extensions
            )
        );

        $this->Certificate = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                 'tbsCertificate'     => $TBSCertificate,
                 'signatureAlgorithm' => $AlgorithmIdentifier,
                 'signature'          => array('type' => ASN1::TYPE_BIT_STRING)
            )
        );

        $this->KeyUsage = array(
            'type'    => ASN1::TYPE_BIT_STRING,
            'mapping' => array(
                'digitalSignature',
                'nonRepudiation',
                'keyEncipherment',
                'dataEncipherment',
                'keyAgreement',
                'keyCertSign',
                'cRLSign',
                'encipherOnly',
                'decipherOnly'
            )
        );

        $this->BasicConstraints = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'cA'                => array(
                                                 'type'     => ASN1::TYPE_BOOLEAN,
                                                 'optional' => true,
                                                 'default'  => false
                                       ),
                'pathLenConstraint' => array(
                                                 'type' => ASN1::TYPE_INTEGER,
                                                 'optional' => true
                                       )
            )
        );

        $this->KeyIdentifier = array('type' => ASN1::TYPE_OCTET_STRING);

        $OrganizationalUnitNames = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => 4, // ub-organizational-units
            'children' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
        );

        $PersonalName = array(
            'type'     => ASN1::TYPE_SET,
            'children' => array(
                'surname'              => array(
                                           'type' => ASN1::TYPE_PRINTABLE_STRING,
                                           'constant' => 0,
                                           'optional' => true,
                                           'implicit' => true
                                         ),
                'given-name'           => array(
                                           'type' => ASN1::TYPE_PRINTABLE_STRING,
                                           'constant' => 1,
                                           'optional' => true,
                                           'implicit' => true
                                         ),
                'initials'             => array(
                                           'type' => ASN1::TYPE_PRINTABLE_STRING,
                                           'constant' => 2,
                                           'optional' => true,
                                           'implicit' => true
                                         ),
                'generation-qualifier' => array(
                                           'type' => ASN1::TYPE_PRINTABLE_STRING,
                                           'constant' => 3,
                                           'optional' => true,
                                           'implicit' => true
                                         )
            )
        );

        $NumericUserIdentifier = array('type' => ASN1::TYPE_NUMERIC_STRING);

        $OrganizationName = array('type' => ASN1::TYPE_PRINTABLE_STRING);

        $PrivateDomainName = array(
            'type'     => ASN1::TYPE_CHOICE,
            'children' => array(
                'numeric'   => array('type' => ASN1::TYPE_NUMERIC_STRING),
                'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
            )
        );

        $TerminalIdentifier = array('type' => ASN1::TYPE_PRINTABLE_STRING);

        $NetworkAddress = array('type' => ASN1::TYPE_NUMERIC_STRING);

        $AdministrationDomainName = array(
            'type'     => ASN1::TYPE_CHOICE,
            // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or
            // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC
            'class'    => ASN1::CLASS_APPLICATION,
            'cast'     => 2,
            'children' => array(
                'numeric'   => array('type' => ASN1::TYPE_NUMERIC_STRING),
                'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
            )
        );

        $CountryName = array(
            'type'     => ASN1::TYPE_CHOICE,
            // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or
            // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC
            'class'    => ASN1::CLASS_APPLICATION,
            'cast'     => 1,
            'children' => array(
                'x121-dcc-code'        => array('type' => ASN1::TYPE_NUMERIC_STRING),
                'iso-3166-alpha2-code' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
            )
        );

        $AnotherName = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                 'type-id' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
                 'value'   => array(
                                  'type' => ASN1::TYPE_ANY,
                                  'constant' => 0,
                                  'optional' => true,
                                  'explicit' => true
                              )
            )
        );

        $ExtensionAttribute = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                 'extension-attribute-type'  => array(
                                                    'type' => ASN1::TYPE_PRINTABLE_STRING,
                                                    'constant' => 0,
                                                    'optional' => true,
                                                    'implicit' => true
                                                ),
                 'extension-attribute-value' => array(
                                                    'type' => ASN1::TYPE_ANY,
                                                    'constant' => 1,
                                                    'optional' => true,
                                                    'explicit' => true
                                                )
            )
        );

        $ExtensionAttributes = array(
            'type'     => ASN1::TYPE_SET,
            'min'      => 1,
            'max'      => 256, // ub-extension-attributes
            'children' => $ExtensionAttribute
        );

        $BuiltInDomainDefinedAttribute = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                 'type'  => array('type' => ASN1::TYPE_PRINTABLE_STRING),
                 'value' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
            )
        );

        $BuiltInDomainDefinedAttributes = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => 4, // ub-domain-defined-attributes
            'children' => $BuiltInDomainDefinedAttribute
        );

        $BuiltInStandardAttributes =  array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'country-name'               => array('optional' => true) + $CountryName,
                'administration-domain-name' => array('optional' => true) + $AdministrationDomainName,
                'network-address'            => array(
                                                 'constant' => 0,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $NetworkAddress,
                'terminal-identifier'        => array(
                                                 'constant' => 1,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $TerminalIdentifier,
                'private-domain-name'        => array(
                                                 'constant' => 2,
                                                 'optional' => true,
                                                 'explicit' => true
                                               ) + $PrivateDomainName,
                'organization-name'          => array(
                                                 'constant' => 3,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $OrganizationName,
                'numeric-user-identifier'    => array(
                                                 'constant' => 4,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $NumericUserIdentifier,
                'personal-name'              => array(
                                                 'constant' => 5,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $PersonalName,
                'organizational-unit-names'  => array(
                                                 'constant' => 6,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $OrganizationalUnitNames
            )
        );

        $ORAddress = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                 'built-in-standard-attributes'       => $BuiltInStandardAttributes,
                 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes,
                 'extension-attributes'               => array('optional' => true) + $ExtensionAttributes
            )
        );

        $EDIPartyName = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                 'nameAssigner' => array(
                                    'constant' => 0,
                                    'optional' => true,
                                    'implicit' => true
                                ) + $this->DirectoryString,
                 // partyName is technically required but \phpseclib\File\ASN1 doesn't currently support non-optional constants and
                 // setting it to optional gets the job done in any event.
                 'partyName'    => array(
                                    'constant' => 1,
                                    'optional' => true,
                                    'implicit' => true
                                ) + $this->DirectoryString
            )
        );

        $GeneralName = array(
            'type'     => ASN1::TYPE_CHOICE,
            'children' => array(
                'otherName'                 => array(
                                                 'constant' => 0,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $AnotherName,
                'rfc822Name'                => array(
                                                 'type' => ASN1::TYPE_IA5_STRING,
                                                 'constant' => 1,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ),
                'dNSName'                   => array(
                                                 'type' => ASN1::TYPE_IA5_STRING,
                                                 'constant' => 2,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ),
                'x400Address'               => array(
                                                 'constant' => 3,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $ORAddress,
                'directoryName'             => array(
                                                 'constant' => 4,
                                                 'optional' => true,
                                                 'explicit' => true
                                               ) + $this->Name,
                'ediPartyName'              => array(
                                                 'constant' => 5,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $EDIPartyName,
                'uniformResourceIdentifier' => array(
                                                 'type' => ASN1::TYPE_IA5_STRING,
                                                 'constant' => 6,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ),
                'iPAddress'                 => array(
                                                 'type' => ASN1::TYPE_OCTET_STRING,
                                                 'constant' => 7,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ),
                'registeredID'              => array(
                                                 'type' => ASN1::TYPE_OBJECT_IDENTIFIER,
                                                 'constant' => 8,
                                                 'optional' => true,
                                                 'implicit' => true
                                               )
            )
        );

        $GeneralNames = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => -1,
            'children' => $GeneralName
        );

        $this->IssuerAltName = $GeneralNames;

        $ReasonFlags = array(
            'type'    => ASN1::TYPE_BIT_STRING,
            'mapping' => array(
                'unused',
                'keyCompromise',
                'cACompromise',
                'affiliationChanged',
                'superseded',
                'cessationOfOperation',
                'certificateHold',
                'privilegeWithdrawn',
                'aACompromise'
            )
        );

        $DistributionPointName = array(
            'type'     => ASN1::TYPE_CHOICE,
            'children' => array(
                'fullName'                => array(
                                                 'constant' => 0,
                                                 'optional' => true,
                                                 'implicit' => true
                                       ) + $GeneralNames,
                'nameRelativeToCRLIssuer' => array(
                                                 'constant' => 1,
                                                 'optional' => true,
                                                 'implicit' => true
                                       ) + $this->RelativeDistinguishedName
            )
        );

        $DistributionPoint = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'distributionPoint' => array(
                                                 'constant' => 0,
                                                 'optional' => true,
                                                 'explicit' => true
                                       ) + $DistributionPointName,
                'reasons'           => array(
                                                 'constant' => 1,
                                                 'optional' => true,
                                                 'implicit' => true
                                       ) + $ReasonFlags,
                'cRLIssuer'         => array(
                                                 'constant' => 2,
                                                 'optional' => true,
                                                 'implicit' => true
                                       ) + $GeneralNames
            )
        );

        $this->CRLDistributionPoints = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => -1,
            'children' => $DistributionPoint
        );

        $this->AuthorityKeyIdentifier = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'keyIdentifier'             => array(
                                                 'constant' => 0,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $this->KeyIdentifier,
                'authorityCertIssuer'       => array(
                                                 'constant' => 1,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $GeneralNames,
                'authorityCertSerialNumber' => array(
                                                 'constant' => 2,
                                                 'optional' => true,
                                                 'implicit' => true
                                               ) + $CertificateSerialNumber
            )
        );

        $PolicyQualifierId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);

        $PolicyQualifierInfo = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'policyQualifierId' => $PolicyQualifierId,
                'qualifier'         => array('type' => ASN1::TYPE_ANY)
            )
        );

        $CertPolicyId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);

        $PolicyInformation = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'policyIdentifier' => $CertPolicyId,
                'policyQualifiers' => array(
                                          'type'     => ASN1::TYPE_SEQUENCE,
                                          'min'      => 0,
                                          'max'      => -1,
                                          'optional' => true,
                                          'children' => $PolicyQualifierInfo
                                      )
            )
        );

        $this->CertificatePolicies = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => -1,
            'children' => $PolicyInformation
        );

        $this->PolicyMappings = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => -1,
            'children' => array(
                              'type'     => ASN1::TYPE_SEQUENCE,
                              'children' => array(
                                  'issuerDomainPolicy' => $CertPolicyId,
                                  'subjectDomainPolicy' => $CertPolicyId
                              )
                       )
        );

        $KeyPurposeId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);

        $this->ExtKeyUsageSyntax = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => -1,
            'children' => $KeyPurposeId
        );

        $AccessDescription = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'accessMethod'   => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
                'accessLocation' => $GeneralName
            )
        );

        $this->AuthorityInfoAccessSyntax = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => -1,
            'children' => $AccessDescription
        );

        $this->SubjectAltName = $GeneralNames;

        $this->PrivateKeyUsagePeriod = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'notBefore' => array(
                                                 'constant' => 0,
                                                 'optional' => true,
                                                 'implicit' => true,
                                                 'type' => ASN1::TYPE_GENERALIZED_TIME),
                'notAfter'  => array(
                                                 'constant' => 1,
                                                 'optional' => true,
                                                 'implicit' => true,
                                                 'type' => ASN1::TYPE_GENERALIZED_TIME)
            )
        );

        $BaseDistance = array('type' => ASN1::TYPE_INTEGER);

        $GeneralSubtree = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'base'    => $GeneralName,
                'minimum' => array(
                                 'constant' => 0,
                                 'optional' => true,
                                 'implicit' => true,
                                 'default' => new BigInteger(0)
                             ) + $BaseDistance,
                'maximum' => array(
                                 'constant' => 1,
                                 'optional' => true,
                                 'implicit' => true,
                             ) + $BaseDistance
            )
        );

        $GeneralSubtrees = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => -1,
            'children' => $GeneralSubtree
        );

        $this->NameConstraints = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'permittedSubtrees' => array(
                                           'constant' => 0,
                                           'optional' => true,
                                           'implicit' => true
                                       ) + $GeneralSubtrees,
                'excludedSubtrees'  => array(
                                           'constant' => 1,
                                           'optional' => true,
                                           'implicit' => true
                                       ) + $GeneralSubtrees
            )
        );

        $this->CPSuri = array('type' => ASN1::TYPE_IA5_STRING);

        $DisplayText = array(
            'type'     => ASN1::TYPE_CHOICE,
            'children' => array(
                'ia5String'     => array('type' => ASN1::TYPE_IA5_STRING),
                'visibleString' => array('type' => ASN1::TYPE_VISIBLE_STRING),
                'bmpString'     => array('type' => ASN1::TYPE_BMP_STRING),
                'utf8String'    => array('type' => ASN1::TYPE_UTF8_STRING)
            )
        );

        $NoticeReference = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'organization'  => $DisplayText,
                'noticeNumbers' => array(
                                       'type'     => ASN1::TYPE_SEQUENCE,
                                       'min'      => 1,
                                       'max'      => 200,
                                       'children' => array('type' => ASN1::TYPE_INTEGER)
                                   )
            )
        );

        $this->UserNotice = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'noticeRef' => array(
                                           'optional' => true,
                                           'implicit' => true
                                       ) + $NoticeReference,
                'explicitText'  => array(
                                           'optional' => true,
                                           'implicit' => true
                                       ) + $DisplayText
            )
        );

        // mapping is from <http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html>
        $this->netscape_cert_type = array(
            'type'    => ASN1::TYPE_BIT_STRING,
            'mapping' => array(
                'SSLClient',
                'SSLServer',
                'Email',
                'ObjectSigning',
                'Reserved',
                'SSLCA',
                'EmailCA',
                'ObjectSigningCA'
            )
        );

        $this->netscape_comment = array('type' => ASN1::TYPE_IA5_STRING);
        $this->netscape_ca_policy_url = array('type' => ASN1::TYPE_IA5_STRING);

        // attribute is used in RFC2986 but we're using the RFC5280 definition

        $Attribute = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'type' => $AttributeType,
                'value'=> array(
                              'type'     => ASN1::TYPE_SET,
                              'min'      => 1,
                              'max'      => -1,
                              'children' => $this->AttributeValue
                          )
            )
        );

        $this->SubjectDirectoryAttributes = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'min'      => 1,
            'max'      => -1,
            'children' => $Attribute
        );

        // adapted from <http://tools.ietf.org/html/rfc2986>

        $Attributes = array(
            'type'     => ASN1::TYPE_SET,
            'min'      => 1,
            'max'      => -1,
            'children' => $Attribute
        );

        $CertificationRequestInfo = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'version'       => array(
                                       'type' => ASN1::TYPE_INTEGER,
                                       'mapping' => array('v1')
                                   ),
                'subject'       => $this->Name,
                'subjectPKInfo' => $SubjectPublicKeyInfo,
                'attributes'    => array(
                                       'constant' => 0,
                                       'optional' => true,
                                       'implicit' => true
                                   ) + $Attributes,
            )
        );

        $this->CertificationRequest = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'certificationRequestInfo' => $CertificationRequestInfo,
                'signatureAlgorithm'       => $AlgorithmIdentifier,
                'signature'                => array('type' => ASN1::TYPE_BIT_STRING)
            )
        );

        $RevokedCertificate = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                              'userCertificate'    => $CertificateSerialNumber,
                              'revocationDate'     => $Time,
                              'crlEntryExtensions' => array(
                                                          'optional' => true
                                                      ) + $this->Extensions
                          )
        );

        $TBSCertList = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'version'             => array(
                                             'optional' => true,
                                             'default'  => 'v1'
                                         ) + $Version,
                'signature'           => $AlgorithmIdentifier,
                'issuer'              => $this->Name,
                'thisUpdate'          => $Time,
                'nextUpdate'          => array(
                                             'optional' => true
                                         ) + $Time,
                'revokedCertificates' => array(
                                             'type'     => ASN1::TYPE_SEQUENCE,
                                             'optional' => true,
                                             'min'      => 0,
                                             'max'      => -1,
                                             'children' => $RevokedCertificate
                                         ),
                'crlExtensions'       => array(
                                             'constant' => 0,
                                             'optional' => true,
                                             'explicit' => true
                                         ) + $this->Extensions
            )
        );

        $this->CertificateList = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'tbsCertList'        => $TBSCertList,
                'signatureAlgorithm' => $AlgorithmIdentifier,
                'signature'          => array('type' => ASN1::TYPE_BIT_STRING)
            )
        );

        $this->CRLNumber = array('type' => ASN1::TYPE_INTEGER);

        $this->CRLReason = array('type' => ASN1::TYPE_ENUMERATED,
           'mapping' => array(
                            'unspecified',
                            'keyCompromise',
                            'cACompromise',
                            'affiliationChanged',
                            'superseded',
                            'cessationOfOperation',
                            'certificateHold',
                            // Value 7 is not used.
                            8 => 'removeFromCRL',
                            'privilegeWithdrawn',
                            'aACompromise'
            )
        );

        $this->IssuingDistributionPoint = array('type' => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'distributionPoint'          => array(
                                                    'constant' => 0,
                                                    'optional' => true,
                                                    'explicit' => true
                                                ) + $DistributionPointName,
                'onlyContainsUserCerts'      => array(
                                                    'type'     => ASN1::TYPE_BOOLEAN,
                                                    'constant' => 1,
                                                    'optional' => true,
                                                    'default'  => false,
                                                    'implicit' => true
                                                ),
                'onlyContainsCACerts'        => array(
                                                    'type'     => ASN1::TYPE_BOOLEAN,
                                                    'constant' => 2,
                                                    'optional' => true,
                                                    'default'  => false,
                                                    'implicit' => true
                                                ),
                'onlySomeReasons'           => array(
                                                    'constant' => 3,
                                                    'optional' => true,
                                                    'implicit' => true
                                                ) + $ReasonFlags,
                'indirectCRL'               => array(
                                                    'type'     => ASN1::TYPE_BOOLEAN,
                                                    'constant' => 4,
                                                    'optional' => true,
                                                    'default'  => false,
                                                    'implicit' => true
                                                ),
                'onlyContainsAttributeCerts' => array(
                                                    'type'     => ASN1::TYPE_BOOLEAN,
                                                    'constant' => 5,
                                                    'optional' => true,
                                                    'default'  => false,
                                                    'implicit' => true
                                                )
                          )
        );

        $this->InvalidityDate = array('type' => ASN1::TYPE_GENERALIZED_TIME);

        $this->CertificateIssuer = $GeneralNames;

        $this->HoldInstructionCode = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);

        $PublicKeyAndChallenge = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'spki'      => $SubjectPublicKeyInfo,
                'challenge' => array('type' => ASN1::TYPE_IA5_STRING)
            )
        );

        $this->SignedPublicKeyAndChallenge = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'children' => array(
                'publicKeyAndChallenge' => $PublicKeyAndChallenge,
                'signatureAlgorithm'    => $AlgorithmIdentifier,
                'signature'             => array('type' => ASN1::TYPE_BIT_STRING)
            )
        );

        $this->PostalAddress = array(
            'type'     => ASN1::TYPE_SEQUENCE,
            'optional' => true,
            'min'      => 1,
            'max'      => -1,
            'children' => $this->DirectoryString
        );

        // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
        $this->oids = array(
            '1.3.6.1.5.5.7' => 'id-pkix',
            '1.3.6.1.5.5.7.1' => 'id-pe',
            '1.3.6.1.5.5.7.2' => 'id-qt',
            '1.3.6.1.5.5.7.3' => 'id-kp',
            '1.3.6.1.5.5.7.48' => 'id-ad',
            '1.3.6.1.5.5.7.2.1' => 'id-qt-cps',
            '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice',
            '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp',
            '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers',
            '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping',
            '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository',
            '2.5.4' => 'id-at',
            '2.5.4.41' => 'id-at-name',
            '2.5.4.4' => 'id-at-surname',
            '2.5.4.42' => 'id-at-givenName',
            '2.5.4.43' => 'id-at-initials',
            '2.5.4.44' => 'id-at-generationQualifier',
            '2.5.4.3' => 'id-at-commonName',
            '2.5.4.7' => 'id-at-localityName',
            '2.5.4.8' => 'id-at-stateOrProvinceName',
            '2.5.4.10' => 'id-at-organizationName',
            '2.5.4.11' => 'id-at-organizationalUnitName',
            '2.5.4.12' => 'id-at-title',
            '2.5.4.13' => 'id-at-description',
            '2.5.4.46' => 'id-at-dnQualifier',
            '2.5.4.6' => 'id-at-countryName',
            '2.5.4.5' => 'id-at-serialNumber',
            '2.5.4.65' => 'id-at-pseudonym',
            '2.5.4.17' => 'id-at-postalCode',
            '2.5.4.9' => 'id-at-streetAddress',
            '2.5.4.45' => 'id-at-uniqueIdentifier',
            '2.5.4.72' => 'id-at-role',
            '2.5.4.16' => 'id-at-postalAddress',

            '0.9.2342.19200300.100.1.25' => 'id-domainComponent',
            '1.2.840.113549.1.9' => 'pkcs-9',
            '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress',
            '2.5.29' => 'id-ce',
            '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
            '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
            '2.5.29.15' => 'id-ce-keyUsage',
            '2.5.29.16' => 'id-ce-privateKeyUsagePeriod',
            '2.5.29.32' => 'id-ce-certificatePolicies',
            '2.5.29.32.0' => 'anyPolicy',

            '2.5.29.33' => 'id-ce-policyMappings',
            '2.5.29.17' => 'id-ce-subjectAltName',
            '2.5.29.18' => 'id-ce-issuerAltName',
            '2.5.29.9' => 'id-ce-subjectDirectoryAttributes',
            '2.5.29.19' => 'id-ce-basicConstraints',
            '2.5.29.30' => 'id-ce-nameConstraints',
            '2.5.29.36' => 'id-ce-policyConstraints',
            '2.5.29.31' => 'id-ce-cRLDistributionPoints',
            '2.5.29.37' => 'id-ce-extKeyUsage',
            '2.5.29.37.0' => 'anyExtendedKeyUsage',
            '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth',
            '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth',
            '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning',
            '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection',
            '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping',
            '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning',
            '2.5.29.54' => 'id-ce-inhibitAnyPolicy',
            '2.5.29.46' => 'id-ce-freshestCRL',
            '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess',
            '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess',
            '2.5.29.20' => 'id-ce-cRLNumber',
            '2.5.29.28' => 'id-ce-issuingDistributionPoint',
            '2.5.29.27' => 'id-ce-deltaCRLIndicator',
            '2.5.29.21' => 'id-ce-cRLReasons',
            '2.5.29.29' => 'id-ce-certificateIssuer',
            '2.5.29.23' => 'id-ce-holdInstructionCode',
            '1.2.840.10040.2' => 'holdInstruction',
            '1.2.840.10040.2.1' => 'id-holdinstruction-none',
            '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer',
            '1.2.840.10040.2.3' => 'id-holdinstruction-reject',
            '2.5.29.24' => 'id-ce-invalidityDate',

            '1.2.840.113549.2.2' => 'md2',
            '1.2.840.113549.2.5' => 'md5',
            '1.3.14.3.2.26' => 'id-sha1',
            '1.2.840.10040.4.1' => 'id-dsa',
            '1.2.840.10040.4.3' => 'id-dsa-with-sha1',
            '1.2.840.113549.1.1' => 'pkcs-1',
            '1.2.840.113549.1.1.1' => 'rsaEncryption',
            '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
            '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption',
            '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption',
            '1.2.840.10046.2.1' => 'dhpublicnumber',
            '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm',
            '1.2.840.10045' => 'ansi-X9-62',
            '1.2.840.10045.4' => 'id-ecSigType',
            '1.2.840.10045.4.1' => 'ecdsa-with-SHA1',
            '1.2.840.10045.1' => 'id-fieldType',
            '1.2.840.10045.1.1' => 'prime-field',
            '1.2.840.10045.1.2' => 'characteristic-two-field',
            '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis',
            '1.2.840.10045.1.2.3.1' => 'gnBasis',
            '1.2.840.10045.1.2.3.2' => 'tpBasis',
            '1.2.840.10045.1.2.3.3' => 'ppBasis',
            '1.2.840.10045.2' => 'id-publicKeyType',
            '1.2.840.10045.2.1' => 'id-ecPublicKey',
            '1.2.840.10045.3' => 'ellipticCurve',
            '1.2.840.10045.3.0' => 'c-TwoCurve',
            '1.2.840.10045.3.0.1' => 'c2pnb163v1',
            '1.2.840.10045.3.0.2' => 'c2pnb163v2',
            '1.2.840.10045.3.0.3' => 'c2pnb163v3',
            '1.2.840.10045.3.0.4' => 'c2pnb176w1',
            '1.2.840.10045.3.0.5' => 'c2pnb191v1',
            '1.2.840.10045.3.0.6' => 'c2pnb191v2',
            '1.2.840.10045.3.0.7' => 'c2pnb191v3',
            '1.2.840.10045.3.0.8' => 'c2pnb191v4',
            '1.2.840.10045.3.0.9' => 'c2pnb191v5',
            '1.2.840.10045.3.0.10' => 'c2pnb208w1',
            '1.2.840.10045.3.0.11' => 'c2pnb239v1',
            '1.2.840.10045.3.0.12' => 'c2pnb239v2',
            '1.2.840.10045.3.0.13' => 'c2pnb239v3',
            '1.2.840.10045.3.0.14' => 'c2pnb239v4',
            '1.2.840.10045.3.0.15' => 'c2pnb239v5',
            '1.2.840.10045.3.0.16' => 'c2pnb272w1',
            '1.2.840.10045.3.0.17' => 'c2pnb304w1',
            '1.2.840.10045.3.0.18' => 'c2pnb359v1',
            '1.2.840.10045.3.0.19' => 'c2pnb368w1',
            '1.2.840.10045.3.0.20' => 'c2pnb431r1',
            '1.2.840.10045.3.1' => 'primeCurve',
            '1.2.840.10045.3.1.1' => 'prime192v1',
            '1.2.840.10045.3.1.2' => 'prime192v2',
            '1.2.840.10045.3.1.3' => 'prime192v3',
            '1.2.840.10045.3.1.4' => 'prime239v1',
            '1.2.840.10045.3.1.5' => 'prime239v2',
            '1.2.840.10045.3.1.6' => 'prime239v3',
            '1.2.840.10045.3.1.7' => 'prime256v1',
            '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP',
            '1.2.840.113549.1.1.9' => 'id-pSpecified',
            '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS',
            '1.2.840.113549.1.1.8' => 'id-mgf1',
            '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
            '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption',
            '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
            '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption',
            '2.16.840.1.101.3.4.2.4' => 'id-sha224',
            '2.16.840.1.101.3.4.2.1' => 'id-sha256',
            '2.16.840.1.101.3.4.2.2' => 'id-sha384',
            '2.16.840.1.101.3.4.2.3' => 'id-sha512',
            '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94',
            '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001',
            '1.2.643.2.2.20' => 'id-GostR3410-2001',
            '1.2.643.2.2.19' => 'id-GostR3410-94',
            // Netscape Object Identifiers from "Netscape Certificate Extensions"
            '2.16.840.1.113730' => 'netscape',
            '2.16.840.1.113730.1' => 'netscape-cert-extension',
            '2.16.840.1.113730.1.1' => 'netscape-cert-type',
            '2.16.840.1.113730.1.13' => 'netscape-comment',
            '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
            // the following are X.509 extensions not supported by phpseclib
            '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype',
            '1.2.840.113533.7.65.0' => 'entrustVersInfo',
            '2.16.840.1.113733.1.6.9' => 'verisignPrivate',
            // for Certificate Signing Requests
            // see http://tools.ietf.org/html/rfc2985
            '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', // PKCS #9 unstructured name
            '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', // Challenge password for certificate revocations
            '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' // Certificate extension request
        );
    }

    /**
     * Load X.509 certificate
     *
     * Returns an associative array describing the X.509 cert or a false if the cert failed to load
     *
     * @param string $cert
     * @param int $mode
     * @access public
     * @return mixed
     */
    function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT)
    {
        if (is_array($cert) && isset($cert['tbsCertificate'])) {
            unset($this->currentCert);
            unset($this->currentKeyIdentifier);
            $this->dn = $cert['tbsCertificate']['subject'];
            if (!isset($this->dn)) {
                return false;
            }
            $this->currentCert = $cert;

            $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
            $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;

            unset($this->signatureSubject);

            return $cert;
        }

        $asn1 = new ASN1();

        if ($mode != self::FORMAT_DER) {
            $newcert = $this->_extractBER($cert);
            if ($mode == self::FORMAT_PEM && $cert == $newcert) {
                return false;
            }
            $cert = $newcert;
        }

        if ($cert === false) {
            $this->currentCert = false;
            return false;
        }

        $asn1->loadOIDs($this->oids);
        $decoded = $asn1->decodeBER($cert);

        if (!empty($decoded)) {
            $x509 = $asn1->asn1map($decoded[0], $this->Certificate);
        }
        if (!isset($x509) || $x509 === false) {
            $this->currentCert = false;
            return false;
        }

        $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);

        if ($this->_isSubArrayValid($x509, 'tbsCertificate/extensions')) {
            $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1);
        }
        $this->_mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence', $asn1);
        $this->_mapInDNs($x509, 'tbsCertificate/subject/rdnSequence', $asn1);

        $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'];
        $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key);

        $this->currentCert = $x509;
        $this->dn = $x509['tbsCertificate']['subject'];

        $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier');
        $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null;

        return $x509;
    }

    /**
     * Save X.509 certificate
     *
     * @param array $cert
     * @param int $format optional
     * @access public
     * @return string
     */
    function saveX509($cert, $format = self::FORMAT_PEM)
    {
        if (!is_array($cert) || !isset($cert['tbsCertificate'])) {
            return false;
        }

        switch (true) {
            // "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()"
            case !($algorithm = $this->_subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')):
            case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
                break;
            default:
                switch ($algorithm) {
                    case 'rsaEncryption':
                        $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']
                            = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])));
                        /* "[For RSA keys] the parameters field MUST have ASN.1 type NULL for this algorithm identifier."
                           -- https://tools.ietf.org/html/rfc3279#section-2.3.1

                           given that and the fact that RSA keys appear ot be the only key type for which the parameters field can be blank,
                           it seems like perhaps the ASN.1 description ought not say the parameters field is OPTIONAL, but whatever.
                         */
                        $cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = null;
                        // https://tools.ietf.org/html/rfc3279#section-2.2.1
                        $cert['signatureAlgorithm']['parameters'] = null;
                        $cert['tbsCertificate']['signature']['parameters'] = null;
                }
        }

        $asn1 = new ASN1();
        $asn1->loadOIDs($this->oids);

        $filters = array();
        $type_utf8_string = array('type' => ASN1::TYPE_UTF8_STRING);
        $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string;
        $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string;
        $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string;
        $filters['tbsCertificate']['subject']['rdnSequence']['value'] = $type_utf8_string;
        $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = $type_utf8_string;
        $filters['signatureAlgorithm']['parameters'] = $type_utf8_string;
        $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
        //$filters['policyQualifiers']['qualifier'] = $type_utf8_string;
        $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
        $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string;

        /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib\File\ASN1::TYPE_IA5_STRING.
           \phpseclib\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
           characters.
         */
        $filters['policyQualifiers']['qualifier']
            = array('type' => ASN1::TYPE_IA5_STRING);

        $asn1->loadFilters($filters);

        $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1);
        $this->_mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence', $asn1);
        $this->_mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence', $asn1);

        $cert = $asn1->encodeDER($cert, $this->Certificate);

        switch ($format) {
            case self::FORMAT_DER:
                return $cert;
            // case self::FORMAT_PEM:
            default:
                return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert), 64) . '-----END CERTIFICATE-----';
        }
    }

    /**
     * Map extension values from octet string to extension-specific internal
     *   format.
     *
     * @param array ref $root
     * @param string $path
     * @param object $asn1
     * @access private
     */
    function _mapInExtensions(&$root, $path, $asn1)
    {
        $extensions = &$this->_subArrayUnchecked($root, $path);

        if ($extensions) {
            for ($i = 0; $i < count($extensions); $i++) {
                $id = $extensions[$i]['extnId'];
                $value = &$extensions[$i]['extnValue'];
                $value = base64_decode($value);
                $decoded = $asn1->decodeBER($value);
                /* [extnValue] contains the DER encoding of an ASN.1 value
                   corresponding to the extension type identified by extnID */
                $map = $this->_getMapping($id);
                if (!is_bool($map)) {
                    $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => array($this, '_decodeIP')));
                    $value = $mapped === false ? $decoded[0] : $mapped;

                    if ($id == 'id-ce-certificatePolicies') {
                        for ($j = 0; $j < count($value); $j++) {
                            if (!isset($value[$j]['policyQualifiers'])) {
                                continue;
                            }
                            for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
                                $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
                                $map = $this->_getMapping($subid);
                                $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
                                if ($map !== false) {
                                    $decoded = $asn1->decodeBER($subvalue);
                                    $mapped = $asn1->asn1map($decoded[0], $map);
                                    $subvalue = $mapped === false ? $decoded[0] : $mapped;
                                }
                            }
                        }
                    }
                } else {
                    $value = base64_encode($value);
                }
            }
        }
    }

    /**
     * Map extension values from extension-specific internal format to
     *   octet string.
     *
     * @param array ref $root
     * @param string $path
     * @param object $asn1
     * @access private
     */
    function _mapOutExtensions(&$root, $path, $asn1)
    {
        $extensions = &$this->_subArray($root, $path);

        if (is_array($extensions)) {
            $size = count($extensions);
            for ($i = 0; $i < $size; $i++) {
                if ($extensions[$i] instanceof Element) {
                    continue;
                }

                $id = $extensions[$i]['extnId'];
                $value = &$extensions[$i]['extnValue'];

                switch ($id) {
                    case 'id-ce-certificatePolicies':
                        for ($j = 0; $j < count($value); $j++) {
                            if (!isset($value[$j]['policyQualifiers'])) {
                                continue;
                            }
                            for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
                                $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
                                $map = $this->_getMapping($subid);
                                $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
                                if ($map !== false) {
                                    // by default \phpseclib\File\ASN1 will try to render qualifier as a \phpseclib\File\ASN1::TYPE_IA5_STRING since it's
                                    // actual type is \phpseclib\File\ASN1::TYPE_ANY
                                    $subvalue = new Element($asn1->encodeDER($subvalue, $map));
                                }
                            }
                        }
                        break;
                    case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string
                        if (isset($value['authorityCertSerialNumber'])) {
                            if ($value['authorityCertSerialNumber']->toBytes() == '') {
                                $temp = chr((ASN1::CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0";
                                $value['authorityCertSerialNumber'] = new Element($temp);
                            }
                        }
                }

                /* [extnValue] contains the DER encoding of an ASN.1 value
                   corresponding to the extension type identified by extnID */
                $map = $this->_getMapping($id);
                if (is_bool($map)) {
                    if (!$map) {
                        user_error($id . ' is not a currently supported extension');
                        unset($extensions[$i]);
                    }
                } else {
                    $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP')));
                    $value = base64_encode($temp);
                }
            }
        }
    }

    /**
     * Map attribute values from ANY type to attribute-specific internal
     *   format.
     *
     * @param array ref $root
     * @param string $path
     * @param object $asn1
     * @access private
     */
    function _mapInAttributes(&$root, $path, $asn1)
    {
        $attributes = &$this->_subArray($root, $path);

        if (is_array($attributes)) {
            for ($i = 0; $i < count($attributes); $i++) {
                $id = $attributes[$i]['type'];
                /* $value contains the DER encoding of an ASN.1 value
                   corresponding to the attribute type identified by type */
                $map = $this->_getMapping($id);
                if (is_array($attributes[$i]['value'])) {
                    $values = &$attributes[$i]['value'];
                    for ($j = 0; $j < count($values); $j++) {
                        $value = $asn1->encodeDER($values[$j], $this->AttributeValue);
                        $decoded = $asn1->decodeBER($value);
                        if (!is_bool($map)) {
                            $mapped = $asn1->asn1map($decoded[0], $map);
                            if ($mapped !== false) {
                                $values[$j] = $mapped;
                            }
                            if ($id == 'pkcs-9-at-extensionRequest' && $this->_isSubArrayValid($values, $j)) {
                                $this->_mapInExtensions($values, $j, $asn1);
                            }
                        } elseif ($map) {
                            $values[$j] = base64_encode($value);
                        }
                    }
                }
            }
        }
    }

    /**
     * Map attribute values from attribute-specific internal format to
     *   ANY type.
     *
     * @param array ref $root
     * @param string $path
     * @param object $asn1
     * @access private
     */
    function _mapOutAttributes(&$root, $path, $asn1)
    {
        $attributes = &$this->_subArray($root, $path);

        if (is_array($attributes)) {
            $size = count($attributes);
            for ($i = 0; $i < $size; $i++) {
                /* [value] contains the DER encoding of an ASN.1 value
                   corresponding to the attribute type identified by type */
                $id = $attributes[$i]['type'];
                $map = $this->_getMapping($id);
                if ($map === false) {
                    user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
                    unset($attributes[$i]);
                } elseif (is_array($attributes[$i]['value'])) {
                    $values = &$attributes[$i]['value'];
                    for ($j = 0; $j < count($values); $j++) {
                        switch ($id) {
                            case 'pkcs-9-at-extensionRequest':
                                $this->_mapOutExtensions($values, $j, $asn1);
                                break;
                        }

                        if (!is_bool($map)) {
                            $temp = $asn1->encodeDER($values[$j], $map);
                            $decoded = $asn1->decodeBER($temp);
                            $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue);
                        }
                    }
                }
            }
        }
    }

    /**
     * Map DN values from ANY type to DN-specific internal
     *   format.
     *
     * @param array ref $root
     * @param string $path
     * @param object $asn1
     * @access private
     */
    function _mapInDNs(&$root, $path, $asn1)
    {
        $dns = &$this->_subArray($root, $path);

        if (is_array($dns)) {
            for ($i = 0; $i < count($dns); $i++) {
                for ($j = 0; $j < count($dns[$i]); $j++) {
                    $type = $dns[$i][$j]['type'];
                    $value = &$dns[$i][$j]['value'];
                    if (is_object($value) && $value instanceof Element) {
                        $map = $this->_getMapping($type);
                        if (!is_bool($map)) {
                            $decoded = $asn1->decodeBER($value);
                            $value = $asn1->asn1map($decoded[0], $map);
                        }
                    }
                }
            }
        }
    }

    /**
     * Map DN values from DN-specific internal format to
     *   ANY type.
     *
     * @param array ref $root
     * @param string $path
     * @param object $asn1
     * @access private
     */
    function _mapOutDNs(&$root, $path, $asn1)
    {
        $dns = &$this->_subArray($root, $path);

        if (is_array($dns)) {
            $size = count($dns);
            for ($i = 0; $i < $size; $i++) {
                for ($j = 0; $j < count($dns[$i]); $j++) {
                    $type = $dns[$i][$j]['type'];
                    $value = &$dns[$i][$j]['value'];
                    if (is_object($value) && $value instanceof Element) {
                        continue;
                    }

                    $map = $this->_getMapping($type);
                    if (!is_bool($map)) {
                        $value = new Element($asn1->encodeDER($value, $map));
                    }
                }
            }
        }
    }

    /**
     * Associate an extension ID to an extension mapping
     *
     * @param string $extnId
     * @access private
     * @return mixed
     */
    function _getMapping($extnId)
    {
        if (!is_string($extnId)) { // eg. if it's a \phpseclib\File\ASN1\Element object
            return true;
        }

        switch ($extnId) {
            case 'id-ce-keyUsage':
                return $this->KeyUsage;
            case 'id-ce-basicConstraints':
                return $this->BasicConstraints;
            case 'id-ce-subjectKeyIdentifier':
                return $this->KeyIdentifier;
            case 'id-ce-cRLDistributionPoints':
                return $this->CRLDistributionPoints;
            case 'id-ce-authorityKeyIdentifier':
                return $this->AuthorityKeyIdentifier;
            case 'id-ce-certificatePolicies':
                return $this->CertificatePolicies;
            case 'id-ce-extKeyUsage':
                return $this->ExtKeyUsageSyntax;
            case 'id-pe-authorityInfoAccess':
                return $this->AuthorityInfoAccessSyntax;
            case 'id-ce-subjectAltName':
                return $this->SubjectAltName;
            case 'id-ce-subjectDirectoryAttributes':
                return $this->SubjectDirectoryAttributes;
            case 'id-ce-privateKeyUsagePeriod':
                return $this->PrivateKeyUsagePeriod;
            case 'id-ce-issuerAltName':
                return $this->IssuerAltName;
            case 'id-ce-policyMappings':
                return $this->PolicyMappings;
            case 'id-ce-nameConstraints':
                return $this->NameConstraints;

            case 'netscape-cert-type':
                return $this->netscape_cert_type;
            case 'netscape-comment':
                return $this->netscape_comment;
            case 'netscape-ca-policy-url':
                return $this->netscape_ca_policy_url;

            // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
            // back around to asn1map() and we don't want it decoded again.
            //case 'id-qt-cps':
            //    return $this->CPSuri;
            case 'id-qt-unotice':
                return $this->UserNotice;

            // the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
            case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
            case 'entrustVersInfo':
            // http://support.microsoft.com/kb/287547
            case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION
            case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION
            // "SET Secure Electronic Transaction Specification"
            // http://www.maithean.com/docs/set_bk3.pdf
            case '2.23.42.7.0': // id-set-hashedRootKey
                return true;

            // CSR attributes
            case 'pkcs-9-at-unstructuredName':
                return $this->PKCS9String;
            case 'pkcs-9-at-challengePassword':
                return $this->DirectoryString;
            case 'pkcs-9-at-extensionRequest':
                return $this->Extensions;

            // CRL extensions.
            case 'id-ce-cRLNumber':
                return $this->CRLNumber;
            case 'id-ce-deltaCRLIndicator':
                return $this->CRLNumber;
            case 'id-ce-issuingDistributionPoint':
                return $this->IssuingDistributionPoint;
            case 'id-ce-freshestCRL':
                return $this->CRLDistributionPoints;
            case 'id-ce-cRLReasons':
                return $this->CRLReason;
            case 'id-ce-invalidityDate':
                return $this->InvalidityDate;
            case 'id-ce-certificateIssuer':
                return $this->CertificateIssuer;
            case 'id-ce-holdInstructionCode':
                return $this->HoldInstructionCode;
            case 'id-at-postalAddress':
                return $this->PostalAddress;
        }

        return false;
    }

    /**
     * Load an X.509 certificate as a certificate authority
     *
     * @param string $cert
     * @access public
     * @return bool
     */
    function loadCA($cert)
    {
        $olddn = $this->dn;
        $oldcert = $this->currentCert;
        $oldsigsubj = $this->signatureSubject;
        $oldkeyid = $this->currentKeyIdentifier;

        $cert = $this->loadX509($cert);
        if (!$cert) {
            $this->dn = $olddn;
            $this->currentCert = $oldcert;
            $this->signatureSubject = $oldsigsubj;
            $this->currentKeyIdentifier = $oldkeyid;

            return false;
        }

        /* From RFC5280 "PKIX Certificate and CRL Profile":

           If the keyUsage extension is present, then the subject public key
           MUST NOT be used to verify signatures on certificates or CRLs unless
           the corresponding keyCertSign or cRLSign bit is set. */
        //$keyUsage = $this->getExtension('id-ce-keyUsage');
        //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
        //    return false;
        //}

        /* From RFC5280 "PKIX Certificate and CRL Profile":

           The cA boolean indicates whether the certified public key may be used
           to verify certificate signatures.  If the cA boolean is not asserted,
           then the keyCertSign bit in the key usage extension MUST NOT be
           asserted.  If the basic constraints extension is not present in a
           version 3 certificate, or the extension is present but the cA boolean
           is not asserted, then the certified public key MUST NOT be used to
           verify certificate signatures. */
        //$basicConstraints = $this->getExtension('id-ce-basicConstraints');
        //if (!$basicConstraints || !$basicConstraints['cA']) {
        //    return false;
        //}

        $this->CAs[] = $cert;

        $this->dn = $olddn;
        $this->currentCert = $oldcert;
        $this->signatureSubject = $oldsigsubj;

        return true;
    }

    /**
     * Validate an X.509 certificate against a URL
     *
     * From RFC2818 "HTTP over TLS":
     *
     * Matching is performed using the matching rules specified by
     * [RFC2459].  If more than one identity of a given type is present in
     * the certificate (e.g., more than one dNSName name, a match in any one
     * of the set is considered acceptable.) Names may contain the wildcard
     * character * which is considered to match any single domain name
     * component or component fragment. E.g., *.a.com matches foo.a.com but
     * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
     *
     * @param string $url
     * @access public
     * @return bool
     */
    function validateURL($url)
    {
        if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
            return false;
        }

        $components = parse_url($url);
        if (!isset($components['host'])) {
            return false;
        }

        if ($names = $this->getExtension('id-ce-subjectAltName')) {
            foreach ($names as $key => $value) {
                $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value);
                switch ($key) {
                    case 'dNSName':
                        /* From RFC2818 "HTTP over TLS":

                           If a subjectAltName extension of type dNSName is present, that MUST
                           be used as the identity. Otherwise, the (most specific) Common Name
                           field in the Subject field of the certificate MUST be used. Although
                           the use of the Common Name is existing practice, it is deprecated and
                           Certification Authorities are encouraged to use the dNSName instead. */
                        if (preg_match('#^' . $value . '$#', $components['host'])) {
                            return true;
                        }
                        break;
                    case 'iPAddress':
                        /* From RFC2818 "HTTP over TLS":

                           In some cases, the URI is specified as an IP address rather than a
                           hostname. In this case, the iPAddress subjectAltName must be present
                           in the certificate and must exactly match the IP in the URI. */
                        if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
                            return true;
                        }
                }
            }
            return false;
        }

        if ($value = $this->getDNProp('id-at-commonName')) {
            $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]);
            return preg_match('#^' . $value . '$#', $components['host']);
        }

        return false;
    }

    /**
     * Validate a date
     *
     * If $date isn't defined it is assumed to be the current date.
     *
     * @param int $date optional
     * @access public
     */
    function validateDate($date = null)
    {
        if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
            return false;
        }

        if (!isset($date)) {
            $date = time();
        }

        $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
        $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime'];

        $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
        $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];

        switch (true) {
            case $date < @strtotime($notBefore):
            case $date > @strtotime($notAfter):
                return false;
        }

        return true;
    }

    /**
     * Validate a signature
     *
     * Works on X.509 certs, CSR's and CRL's.
     * Returns true if the signature is verified, false if it is not correct or null on error
     *
     * By default returns false for self-signed certs. Call validateSignature(false) to make this support
     * self-signed.
     *
     * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
     *
     * @param bool $caonly optional
     * @access public
     * @return mixed
     */
    function validateSignature($caonly = true)
    {
        if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
            return null;
        }

        /* TODO:
           "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")."
            -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6

           implement pathLenConstraint in the id-ce-basicConstraints extension */

        switch (true) {
            case isset($this->currentCert['tbsCertificate']):
                // self-signed cert
                switch (true) {
                    case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']:
                    case defined('FILE_X509_IGNORE_TYPE') && $this->getIssuerDN(self::DN_STRING) === $this->getDN(self::DN_STRING):
                        $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
                        $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier');
                        switch (true) {
                            case !is_array($authorityKey):
                            case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
                                $signingCert = $this->currentCert; // working cert
                        }
                }

                if (!empty($this->CAs)) {
                    for ($i = 0; $i < count($this->CAs); $i++) {
                        // even if the cert is a self-signed one we still want to see if it's a CA;
                        // if not, we'll conditionally return an error
                        $ca = $this->CAs[$i];
                        switch (true) {
                            case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']:
                            case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertificate']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
                                $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
                                $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
                                switch (true) {
                                    case !is_array($authorityKey):
                                    case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
                                        $signingCert = $ca; // working cert
                                        break 3;
                                }
                        }
                    }
                    if (count($this->CAs) == $i && $caonly) {
                        return false;
                    }
                } elseif (!isset($signingCert) || $caonly) {
                    return false;
                }
                return $this->_validateSignature(
                    $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
                    $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
                    $this->currentCert['signatureAlgorithm']['algorithm'],
                    substr(base64_decode($this->currentCert['signature']), 1),
                    $this->signatureSubject
                );
            case isset($this->currentCert['certificationRequestInfo']):
                return $this->_validateSignature(
                    $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
                    $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
                    $this->currentCert['signatureAlgorithm']['algorithm'],
                    substr(base64_decode($this->currentCert['signature']), 1),
                    $this->signatureSubject
                );
            case isset($this->currentCert['publicKeyAndChallenge']):
                return $this->_validateSignature(
                    $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'],
                    $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'],
                    $this->currentCert['signatureAlgorithm']['algorithm'],
                    substr(base64_decode($this->currentCert['signature']), 1),
                    $this->signatureSubject
                );
            case isset($this->currentCert['tbsCertList']):
                if (!empty($this->CAs)) {
                    for ($i = 0; $i < count($this->CAs); $i++) {
                        $ca = $this->CAs[$i];
                        switch (true) {
                            case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']:
                            case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertList']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
                                $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
                                $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
                                switch (true) {
                                    case !is_array($authorityKey):
                                    case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
                                        $signingCert = $ca; // working cert
                                        break 3;
                                }
                        }
                    }
                }
                if (!isset($signingCert)) {
                    return false;
                }
                return $this->_validateSignature(
                    $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
                    $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
                    $this->currentCert['signatureAlgorithm']['algorithm'],
                    substr(base64_decode($this->currentCert['signature']), 1),
                    $this->signatureSubject
                );
            default:
                return false;
        }
    }

    /**
     * Validates a signature
     *
     * Returns true if the signature is verified, false if it is not correct or null on error
     *
     * @param string $publicKeyAlgorithm
     * @param string $publicKey
     * @param string $signatureAlgorithm
     * @param string $signature
     * @param string $signatureSubject
     * @access private
     * @return int
     */
    function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
    {
        switch ($publicKeyAlgorithm) {
            case 'rsaEncryption':
                $rsa = new RSA();
                $rsa->loadKey($publicKey);

                switch ($signatureAlgorithm) {
                    case 'md2WithRSAEncryption':
                    case 'md5WithRSAEncryption':
                    case 'sha1WithRSAEncryption':
                    case 'sha224WithRSAEncryption':
                    case 'sha256WithRSAEncryption':
                    case 'sha384WithRSAEncryption':
                    case 'sha512WithRSAEncryption':
                        $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
                        $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
                        if (!@$rsa->verify($signatureSubject, $signature)) {
                            return false;
                        }
                        break;
                    default:
                        return null;
                }
                break;
            default:
                return null;
        }

        return true;
    }

    /**
     * Reformat public keys
     *
     * Reformats a public key to a format supported by phpseclib (if applicable)
     *
     * @param string $algorithm
     * @param string $key
     * @access private
     * @return string
     */
    function _reformatKey($algorithm, $key)
    {
        switch ($algorithm) {
            case 'rsaEncryption':
                return
                    "-----BEGIN RSA PUBLIC KEY-----\r\n" .
                    // subjectPublicKey is stored as a bit string in X.509 certs.  the first byte of a bit string represents how many bits
                    // in the last byte should be ignored.  the following only supports non-zero stuff but as none of the X.509 certs Firefox
                    // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do.
                    chunk_split(base64_encode(substr(base64_decode($key), 1)), 64) .
                    '-----END RSA PUBLIC KEY-----';
            default:
                return $key;
        }
    }

    /**
     * Decodes an IP address
     *
     * Takes in a base64 encoded "blob" and returns a human readable IP address
     *
     * @param string $ip
     * @access private
     * @return string
     */
    function _decodeIP($ip)
    {
        return inet_ntop(base64_decode($ip));
    }

    /**
     * Encodes an IP address
     *
     * Takes a human readable IP address into a base64-encoded "blob"
     *
     * @param string $ip
     * @access private
     * @return string
     */
    function _encodeIP($ip)
    {
        return base64_encode(inet_pton($ip));
    }

    /**
     * "Normalizes" a Distinguished Name property
     *
     * @param string $propName
     * @access private
     * @return mixed
     */
    function _translateDNProp($propName)
    {
        switch (strtolower($propName)) {
            case 'id-at-countryname':
            case 'countryname':
            case 'c':
                return 'id-at-countryName';
            case 'id-at-organizationname':
            case 'organizationname':
            case 'o':
                return 'id-at-organizationName';
            case 'id-at-dnqualifier':
            case 'dnqualifier':
                return 'id-at-dnQualifier';
            case 'id-at-commonname':
            case 'commonname':
            case 'cn':
                return 'id-at-commonName';
            case 'id-at-stateorprovincename':
            case 'stateorprovincename':
            case 'state':
            case 'province':
            case 'provincename':
            case 'st':
                return 'id-at-stateOrProvinceName';
            case 'id-at-localityname':
            case 'localityname':
            case 'l':
                return 'id-at-localityName';
            case 'id-emailaddress':
            case 'emailaddress':
                return 'pkcs-9-at-emailAddress';
            case 'id-at-serialnumber':
            case 'serialnumber':
                return 'id-at-serialNumber';
            case 'id-at-postalcode':
            case 'postalcode':
                return 'id-at-postalCode';
            case 'id-at-streetaddress':
            case 'streetaddress':
                return 'id-at-streetAddress';
            case 'id-at-name':
            case 'name':
                return 'id-at-name';
            case 'id-at-givenname':
            case 'givenname':
                return 'id-at-givenName';
            case 'id-at-surname':
            case 'surname':
            case 'sn':
                return 'id-at-surname';
            case 'id-at-initials':
            case 'initials':
                return 'id-at-initials';
            case 'id-at-generationqualifier':
            case 'generationqualifier':
                return 'id-at-generationQualifier';
            case 'id-at-organizationalunitname':
            case 'organizationalunitname':
            case 'ou':
                return 'id-at-organizationalUnitName';
            case 'id-at-pseudonym':
            case 'pseudonym':
                return 'id-at-pseudonym';
            case 'id-at-title':
            case 'title':
                return 'id-at-title';
            case 'id-at-description':
            case 'description':
                return 'id-at-description';
            case 'id-at-role':
            case 'role':
                return 'id-at-role';
            case 'id-at-uniqueidentifier':
            case 'uniqueidentifier':
            case 'x500uniqueidentifier':
                return 'id-at-uniqueIdentifier';
            case 'postaladdress':
            case 'id-at-postaladdress':
                return 'id-at-postalAddress';
            default:
                return false;
        }
    }

    /**
     * Set a Distinguished Name property
     *
     * @param string $propName
     * @param mixed $propValue
     * @param string $type optional
     * @access public
     * @return bool
     */
    function setDNProp($propName, $propValue, $type = 'utf8String')
    {
        if (empty($this->dn)) {
            $this->dn = array('rdnSequence' => array());
        }

        if (($propName = $this->_translateDNProp($propName)) === false) {
            return false;
        }

        foreach ((array) $propValue as $v) {
            if (!is_array($v) && isset($type)) {
                $v = array($type => $v);
            }
            $this->dn['rdnSequence'][] = array(
                array(
                    'type' => $propName,
                    'value'=> $v
                )
            );
        }

        return true;
    }

    /**
     * Remove Distinguished Name properties
     *
     * @param string $propName
     * @access public
     */
    function removeDNProp($propName)
    {
        if (empty($this->dn)) {
            return;
        }

        if (($propName = $this->_translateDNProp($propName)) === false) {
            return;
        }

        $dn = &$this->dn['rdnSequence'];
        $size = count($dn);
        for ($i = 0; $i < $size; $i++) {
            if ($dn[$i][0]['type'] == $propName) {
                unset($dn[$i]);
            }
        }

        $dn = array_values($dn);
    }

    /**
     * Get Distinguished Name properties
     *
     * @param string $propName
     * @param array $dn optional
     * @param bool $withType optional
     * @return mixed
     * @access public
     */
    function getDNProp($propName, $dn = null, $withType = false)
    {
        if (!isset($dn)) {
            $dn = $this->dn;
        }

        if (empty($dn)) {
            return false;
        }

        if (($propName = $this->_translateDNProp($propName)) === false) {
            return false;
        }

        $asn1 = new ASN1();
        $asn1->loadOIDs($this->oids);
        $filters = array();
        $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
        $asn1->loadFilters($filters);
        $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
        $dn = $dn['rdnSequence'];
        $result = array();
        for ($i = 0; $i < count($dn); $i++) {
            if ($dn[$i][0]['type'] == $propName) {
                $v = $dn[$i][0]['value'];
                if (!$withType) {
                    if (is_array($v)) {
                        foreach ($v as $type => $s) {
                            $type = array_search($type, $asn1->ANYmap, true);
                            if ($type !== false && isset($asn1->stringTypeSize[$type])) {
                                $s = $asn1->convert($s, $type);
                                if ($s !== false) {
                                    $v = $s;
                                    break;
                                }
                            }
                        }
                        if (is_array($v)) {
                            $v = array_pop($v); // Always strip data type.
                        }
                    } elseif (is_object($v) && $v instanceof Element) {
                        $map = $this->_getMapping($propName);
                        if (!is_bool($map)) {
                            $decoded = $asn1->decodeBER($v);
                            $v = $asn1->asn1map($decoded[0], $map);
                        }
                    }
                }
                $result[] = $v;
            }
        }

        return $result;
    }

    /**
     * Set a Distinguished Name
     *
     * @param mixed $dn
     * @param bool $merge optional
     * @param string $type optional
     * @access public
     * @return bool
     */
    function setDN($dn, $merge = false, $type = 'utf8String')
    {
        if (!$merge) {
            $this->dn = null;
        }

        if (is_array($dn)) {
            if (isset($dn['rdnSequence'])) {
                $this->dn = $dn; // No merge here.
                return true;
            }

            // handles stuff generated by openssl_x509_parse()
            foreach ($dn as $prop => $value) {
                if (!$this->setDNProp($prop, $value, $type)) {
                    return false;
                }
            }
            return true;
        }

        // handles everything else
        $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=|postalAddress=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
        for ($i = 1; $i < count($results); $i+=2) {
            $prop = trim($results[$i], ', =/');
            $value = $results[$i + 1];
            if (!$this->setDNProp($prop, $value, $type)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Get the Distinguished Name for a certificates subject
     *
     * @param mixed $format optional
     * @param array $dn optional
     * @access public
     * @return bool
     */
    function getDN($format = self::DN_ARRAY, $dn = null)
    {
        if (!isset($dn)) {
            $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn;
        }

        switch ((int) $format) {
            case self::DN_ARRAY:
                return $dn;
            case self::DN_ASN1:
                $asn1 = new ASN1();
                $asn1->loadOIDs($this->oids);
                $filters = array();
                $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
                $asn1->loadFilters($filters);
                $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
                return $asn1->encodeDER($dn, $this->Name);
            case self::DN_CANON:
                //  No SEQUENCE around RDNs and all string values normalized as
                // trimmed lowercase UTF-8 with all spacing as one blank.
                // constructed RDNs will not be canonicalized
                $asn1 = new ASN1();
                $asn1->loadOIDs($this->oids);
                $filters = array();
                $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
                $asn1->loadFilters($filters);
                $result = '';
                $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
                foreach ($dn['rdnSequence'] as $rdn) {
                    foreach ($rdn as $i => $attr) {
                        $attr = &$rdn[$i];
                        if (is_array($attr['value'])) {
                            foreach ($attr['value'] as $type => $v) {
                                $type = array_search($type, $asn1->ANYmap, true);
                                if ($type !== false && isset($asn1->stringTypeSize[$type])) {
                                    $v = $asn1->convert($v, $type);
                                    if ($v !== false) {
                                        $v = preg_replace('/\s+/', ' ', $v);
                                        $attr['value'] = strtolower(trim($v));
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName);
                }
                return $result;
            case self::DN_HASH:
                $dn = $this->getDN(self::DN_CANON, $dn);
                $hash = new Hash('sha1');
                $hash = $hash->hash($dn);
                extract(unpack('Vhash', $hash));
                return strtolower(bin2hex(pack('N', $hash)));
        }

        // Default is to return a string.
        $start = true;
        $output = '';

        $result = array();
        $asn1 = new ASN1();
        $asn1->loadOIDs($this->oids);
        $filters = array();
        $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
        $asn1->loadFilters($filters);
        $this->_mapOutDNs($dn, 'rdnSequence', $asn1);

        foreach ($dn['rdnSequence'] as $field) {
            $prop = $field[0]['type'];
            $value = $field[0]['value'];

            $delim = ', ';
            switch ($prop) {
                case 'id-at-countryName':
                    $desc = 'C';
                    break;
                case 'id-at-stateOrProvinceName':
                    $desc = 'ST';
                    break;
                case 'id-at-organizationName':
                    $desc = 'O';
                    break;
                case 'id-at-organizationalUnitName':
                    $desc = 'OU';
                    break;
                case 'id-at-commonName':
                    $desc = 'CN';
                    break;
                case 'id-at-localityName':
                    $desc = 'L';
                    break;
                case 'id-at-surname':
                    $desc = 'SN';
                    break;
                case 'id-at-uniqueIdentifier':
                    $delim = '/';
                    $desc = 'x500UniqueIdentifier';
                    break;
                case 'id-at-postalAddress':
                    $delim = '/';
                    $desc = 'postalAddress';
                    break;
                default:
                    $delim = '/';
                    $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop);
            }

            if (!$start) {
                $output.= $delim;
            }
            if (is_array($value)) {
                foreach ($value as $type => $v) {
                    $type = array_search($type, $asn1->ANYmap, true);
                    if ($type !== false && isset($asn1->stringTypeSize[$type])) {
                        $v = $asn1->convert($v, $type);
                        if ($v !== false) {
                            $value = $v;
                            break;
                        }
                    }
                }
                if (is_array($value)) {
                    $value = array_pop($value); // Always strip data type.
                }
            } elseif (is_object($value) && $value instanceof Element) {
                $callback = create_function('$x', 'return "\x" . bin2hex($x[0]);');
                $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
            }
            $output.= $desc . '=' . $value;
            $result[$desc] = isset($result[$desc]) ?
                array_merge((array) $dn[$prop], array($value)) :
                $value;
            $start = false;
        }

        return $format == self::DN_OPENSSL ? $result : $output;
    }

    /**
     * Get the Distinguished Name for a certificate/crl issuer
     *
     * @param int $format optional
     * @access public
     * @return mixed
     */
    function getIssuerDN($format = self::DN_ARRAY)
    {
        switch (true) {
            case !isset($this->currentCert) || !is_array($this->currentCert):
                break;
            case isset($this->currentCert['tbsCertificate']):
                return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']);
            case isset($this->currentCert['tbsCertList']):
                return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']);
        }

        return false;
    }

    /**
     * Get the Distinguished Name for a certificate/csr subject
     * Alias of getDN()
     *
     * @param int $format optional
     * @access public
     * @return mixed
     */
    function getSubjectDN($format = self::DN_ARRAY)
    {
        switch (true) {
            case !empty($this->dn):
                return $this->getDN($format);
            case !isset($this->currentCert) || !is_array($this->currentCert):
                break;
            case isset($this->currentCert['tbsCertificate']):
                return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']);
            case isset($this->currentCert['certificationRequestInfo']):
                return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']);
        }

        return false;
    }

    /**
     * Get an individual Distinguished Name property for a certificate/crl issuer
     *
     * @param string $propName
     * @param bool $withType optional
     * @access public
     * @return mixed
     */
    function getIssuerDNProp($propName, $withType = false)
    {
        switch (true) {
            case !isset($this->currentCert) || !is_array($this->currentCert):
                break;
            case isset($this->currentCert['tbsCertificate']):
                return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType);
            case isset($this->currentCert['tbsCertList']):
                return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType);
        }

        return false;
    }

    /**
     * Get an individual Distinguished Name property for a certificate/csr subject
     *
     * @param string $propName
     * @param bool $withType optional
     * @access public
     * @return mixed
     */
    function getSubjectDNProp($propName, $withType = false)
    {
        switch (true) {
            case !empty($this->dn):
                return $this->getDNProp($propName, null, $withType);
            case !isset($this->currentCert) || !is_array($this->currentCert):
                break;
            case isset($this->currentCert['tbsCertificate']):
                return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType);
            case isset($this->currentCert['certificationRequestInfo']):
                return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType);
        }

        return false;
    }

    /**
     * Get the certificate chain for the current cert
     *
     * @access public
     * @return mixed
     */
    function getChain()
    {
        $chain = array($this->currentCert);

        if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
            return false;
        }
        if (empty($this->CAs)) {
            return $chain;
        }
        while (true) {
            $currentCert = $chain[count($chain) - 1];
            for ($i = 0; $i < count($this->CAs); $i++) {
                $ca = $this->CAs[$i];
                if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
                    $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert);
                    $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
                    switch (true) {
                        case !is_array($authorityKey):
                        case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
                            if ($currentCert === $ca) {
                                break 3;
                            }
                            $chain[] = $ca;
                            break 2;
                    }
                }
            }
            if ($i == count($this->CAs)) {
                break;
            }
        }
        foreach ($chain as $key => $value) {
            $chain[$key] = new X509();
            $chain[$key]->loadX509($value);
        }
        return $chain;
    }

    /**
     * Set public key
     *
     * Key needs to be a \phpseclib\Crypt\RSA object
     *
     * @param object $key
     * @access public
     * @return bool
     */
    function setPublicKey($key)
    {
        $key->setPublicKey();
        $this->publicKey = $key;
    }

    /**
     * Set private key
     *
     * Key needs to be a \phpseclib\Crypt\RSA object
     *
     * @param object $key
     * @access public
     */
    function setPrivateKey($key)
    {
        $this->privateKey = $key;
    }

    /**
     * Set challenge
     *
     * Used for SPKAC CSR's
     *
     * @param string $challenge
     * @access public
     */
    function setChallenge($challenge)
    {
        $this->challenge = $challenge;
    }

    /**
     * Gets the public key
     *
     * Returns a \phpseclib\Crypt\RSA object or a false.
     *
     * @access public
     * @return mixed
     */
    function getPublicKey()
    {
        if (isset($this->publicKey)) {
            return $this->publicKey;
        }

        if (isset($this->currentCert) && is_array($this->currentCert)) {
            foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) {
                $keyinfo = $this->_subArray($this->currentCert, $path);
                if (!empty($keyinfo)) {
                    break;
                }
            }
        }
        if (empty($keyinfo)) {
            return false;
        }

        $key = $keyinfo['subjectPublicKey'];

        switch ($keyinfo['algorithm']['algorithm']) {
            case 'rsaEncryption':
                $publicKey = new RSA();
                $publicKey->loadKey($key);
                $publicKey->setPublicKey();
                break;
            default:
                return false;
        }

        return $publicKey;
    }

    /**
     * Load a Certificate Signing Request
     *
     * @param string $csr
     * @access public
     * @return mixed
     */
    function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT)
    {
        if (is_array($csr) && isset($csr['certificationRequestInfo'])) {
            unset($this->currentCert);
            unset($this->currentKeyIdentifier);
            unset($this->signatureSubject);
            $this->dn = $csr['certificationRequestInfo']['subject'];
            if (!isset($this->dn)) {
                return false;
            }

            $this->currentCert = $csr;
            return $csr;
        }

        // see http://tools.ietf.org/html/rfc2986

        $asn1 = new ASN1();

        if ($mode != self::FORMAT_DER) {
            $newcsr = $this->_extractBER($csr);
            if ($mode == self::FORMAT_PEM && $csr == $newcsr) {
                return false;
            }
            $csr = $newcsr;
        }
        $orig = $csr;

        if ($csr === false) {
            $this->currentCert = false;
            return false;
        }

        $asn1->loadOIDs($this->oids);
        $decoded = $asn1->decodeBER($csr);

        if (empty($decoded)) {
            $this->currentCert = false;
            return false;
        }

        $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest);
        if (!isset($csr) || $csr === false) {
            $this->currentCert = false;
            return false;
        }

        $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
        $this->_mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1);

        $this->dn = $csr['certificationRequestInfo']['subject'];

        $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);

        $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'];
        $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'];
        $key = $this->_reformatKey($algorithm, $key);

        switch ($algorithm) {
            case 'rsaEncryption':
                $this->publicKey = new RSA();
                $this->publicKey->loadKey($key);
                $this->publicKey->setPublicKey();
                break;
            default:
                $this->publicKey = null;
        }

        $this->currentKeyIdentifier = null;
        $this->currentCert = $csr;

        return $csr;
    }

    /**
     * Save CSR request
     *
     * @param array $csr
     * @param int $format optional
     * @access public
     * @return string
     */
    function saveCSR($csr, $format = self::FORMAT_PEM)
    {
        if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) {
            return false;
        }

        switch (true) {
            case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
            case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
                break;
            default:
                switch ($algorithm) {
                    case 'rsaEncryption':
                        $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']
                            = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])));
                        $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null;
                        $csr['signatureAlgorithm']['parameters'] = null;
                        $csr['certificationRequestInfo']['signature']['parameters'] = null;
                }
        }

        $asn1 = new ASN1();

        $asn1->loadOIDs($this->oids);

        $filters = array();
        $filters['certificationRequestInfo']['subject']['rdnSequence']['value']
            = array('type' => ASN1::TYPE_UTF8_STRING);

        $asn1->loadFilters($filters);

        $this->_mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1);
        $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
        $csr = $asn1->encodeDER($csr, $this->CertificationRequest);

        switch ($format) {
            case self::FORMAT_DER:
                return $csr;
            // case self::FORMAT_PEM:
            default:
                return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
        }
    }

    /**
     * Load a SPKAC CSR
     *
     * SPKAC's are produced by the HTML5 keygen element:
     *
     * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen
     *
     * @param string $csr
     * @access public
     * @return mixed
     */
    function loadSPKAC($spkac)
    {
        if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) {
            unset($this->currentCert);
            unset($this->currentKeyIdentifier);
            unset($this->signatureSubject);
            $this->currentCert = $spkac;
            return $spkac;
        }

        // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge

        $asn1 = new ASN1();

        // OpenSSL produces SPKAC's that are preceded by the string SPKAC=
        $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
        $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
        if ($temp != false) {
            $spkac = $temp;
        }
        $orig = $spkac;

        if ($spkac === false) {
            $this->currentCert = false;
            return false;
        }

        $asn1->loadOIDs($this->oids);
        $decoded = $asn1->decodeBER($spkac);

        if (empty($decoded)) {
            $this->currentCert = false;
            return false;
        }

        $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge);

        if (!isset($spkac) || $spkac === false) {
            $this->currentCert = false;
            return false;
        }

        $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);

        $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm'];
        $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'];
        $key = $this->_reformatKey($algorithm, $key);

        switch ($algorithm) {
            case 'rsaEncryption':
                $this->publicKey = new RSA();
                $this->publicKey->loadKey($key);
                $this->publicKey->setPublicKey();
                break;
            default:
                $this->publicKey = null;
        }

        $this->currentKeyIdentifier = null;
        $this->currentCert = $spkac;

        return $spkac;
    }

    /**
     * Save a SPKAC CSR request
     *
     * @param array $csr
     * @param int $format optional
     * @access public
     * @return string
     */
    function saveSPKAC($spkac, $format = self::FORMAT_PEM)
    {
        if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
            return false;
        }

        $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
        switch (true) {
            case !$algorithm:
            case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']):
                break;
            default:
                switch ($algorithm) {
                    case 'rsaEncryption':
                        $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']
                            = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])));
                }
        }

        $asn1 = new ASN1();

        $asn1->loadOIDs($this->oids);
        $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge);

        switch ($format) {
            case self::FORMAT_DER:
                return $spkac;
            // case self::FORMAT_PEM:
            default:
                // OpenSSL's implementation of SPKAC requires the SPKAC be preceded by SPKAC= and since there are pretty much
                // no other SPKAC decoders phpseclib will use that same format
                return 'SPKAC=' . base64_encode($spkac);
        }
    }

    /**
     * Load a Certificate Revocation List
     *
     * @param string $crl
     * @access public
     * @return mixed
     */
    function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT)
    {
        if (is_array($crl) && isset($crl['tbsCertList'])) {
            $this->currentCert = $crl;
            unset($this->signatureSubject);
            return $crl;
        }

        $asn1 = new ASN1();

        if ($mode != self::FORMAT_DER) {
            $newcrl = $this->_extractBER($crl);
            if ($mode == self::FORMAT_PEM && $crl == $newcrl) {
                return false;
            }
            $crl = $newcrl;
        }
        $orig = $crl;

        if ($crl === false) {
            $this->currentCert = false;
            return false;
        }

        $asn1->loadOIDs($this->oids);
        $decoded = $asn1->decodeBER($crl);

        if (empty($decoded)) {
            $this->currentCert = false;
            return false;
        }

        $crl = $asn1->asn1map($decoded[0], $this->CertificateList);
        if (!isset($crl) || $crl === false) {
            $this->currentCert = false;
            return false;
        }

        $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);

        $this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
        if ($this->_isSubArrayValid($crl, 'tbsCertList/crlExtensions')) {
            $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
        }
        if ($this->_isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) {
            $rclist_ref = &$this->_subArrayUnchecked($crl, 'tbsCertList/revokedCertificates');
            if ($rclist_ref) {
                $rclist = $crl['tbsCertList']['revokedCertificates'];
                foreach ($rclist as $i => $extension) {
                    if ($this->_isSubArrayValid($rclist, "$i/crlEntryExtensions", $asn1)) {
                        $this->_mapInExtensions($rclist_ref, "$i/crlEntryExtensions", $asn1);
                    }
                }
            }
        }

        $this->currentKeyIdentifier = null;
        $this->currentCert = $crl;

        return $crl;
    }

    /**
     * Save Certificate Revocation List.
     *
     * @param array $crl
     * @param int $format optional
     * @access public
     * @return string
     */
    function saveCRL($crl, $format = self::FORMAT_PEM)
    {
        if (!is_array($crl) || !isset($crl['tbsCertList'])) {
            return false;
        }

        $asn1 = new ASN1();

        $asn1->loadOIDs($this->oids);

        $filters = array();
        $filters['tbsCertList']['issuer']['rdnSequence']['value']
            = array('type' => ASN1::TYPE_UTF8_STRING);
        $filters['tbsCertList']['signature']['parameters']
            = array('type' => ASN1::TYPE_UTF8_STRING);
        $filters['signatureAlgorithm']['parameters']
            = array('type' => ASN1::TYPE_UTF8_STRING);

        if (empty($crl['tbsCertList']['signature']['parameters'])) {
            $filters['tbsCertList']['signature']['parameters']
                = array('type' => ASN1::TYPE_NULL);
        }

        if (empty($crl['signatureAlgorithm']['parameters'])) {
            $filters['signatureAlgorithm']['parameters']
                = array('type' => ASN1::TYPE_NULL);
        }

        $asn1->loadFilters($filters);

        $this->_mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
        $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
        $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
        if (is_array($rclist)) {
            foreach ($rclist as $i => $extension) {
                $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1);
            }
        }

        $crl = $asn1->encodeDER($crl, $this->CertificateList);

        switch ($format) {
            case self::FORMAT_DER:
                return $crl;
            // case self::FORMAT_PEM:
            default:
                return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----';
        }
    }

    /**
     * Helper function to build a time field according to RFC 3280 section
     *  - 4.1.2.5 Validity
     *  - 5.1.2.4 This Update
     *  - 5.1.2.5 Next Update
     *  - 5.1.2.6 Revoked Certificates
     * by choosing utcTime iff year of date given is before 2050 and generalTime else.
     *
     * @param string $date in format date('D, d M Y H:i:s O')
     * @access private
     * @return array
     */
    function _timeField($date)
    {
        $year = @gmdate("Y", @strtotime($date)); // the same way ASN1.php parses this
        if ($year < 2050) {
            return array('utcTime' => $date);
        } else {
            return array('generalTime' => $date);
        }
    }

    /**
     * Sign an X.509 certificate
     *
     * $issuer's private key needs to be loaded.
     * $subject can be either an existing X.509 cert (if you want to resign it),
     * a CSR or something with the DN and public key explicitly set.
     *
     * @param \phpseclib\File\X509 $issuer
     * @param \phpseclib\File\X509 $subject
     * @param string $signatureAlgorithm optional
     * @access public
     * @return mixed
     */
    function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
    {
        if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
            return false;
        }

        if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) {
            return false;
        }

        $currentCert = isset($this->currentCert) ? $this->currentCert : null;
        $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;

        if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
            $this->currentCert = $subject->currentCert;
            $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm;
            $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;

            if (!empty($this->startDate)) {
                $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate);
            }
            if (!empty($this->endDate)) {
                $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate);
            }
            if (!empty($this->serialNumber)) {
                $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
            }
            if (!empty($subject->dn)) {
                $this->currentCert['tbsCertificate']['subject'] = $subject->dn;
            }
            if (!empty($subject->publicKey)) {
                $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
            }
            $this->removeExtension('id-ce-authorityKeyIdentifier');
            if (isset($subject->domains)) {
                $this->removeExtension('id-ce-subjectAltName');
            }
        } elseif (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) {
            return false;
        } else {
            if (!isset($subject->publicKey)) {
                return false;
            }

            $startDate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');
            $endDate = !empty($this->endDate) ? $this->endDate : @date('D, d M Y H:i:s O', strtotime('+1 year'));
            /* "The serial number MUST be a positive integer"
               "Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
                -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2

               for the integer to be positive the leading bit needs to be 0 hence the
               application of a bitmap
            */
            $serialNumber = !empty($this->serialNumber) ?
                $this->serialNumber :
                new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256);

            $this->currentCert = array(
                'tbsCertificate' =>
                    array(
                        'version' => 'v3',
                        'serialNumber' => $serialNumber, // $this->setserialNumber()
                        'signature' => array('algorithm' => $signatureAlgorithm),
                        'issuer' => false, // this is going to be overwritten later
                        'validity' => array(
                            'notBefore' => $this->_timeField($startDate), // $this->setStartDate()
                            'notAfter' => $this->_timeField($endDate)   // $this->setEndDate()
                        ),
                        'subject' => $subject->dn,
                        'subjectPublicKeyInfo' => $subjectPublicKey
                    ),
                    'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
                    'signature'          => false // this is going to be overwritten later
            );

            // Copy extensions from CSR.
            $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);

            if (!empty($csrexts)) {
                $this->currentCert['tbsCertificate']['extensions'] = $csrexts;
            }
        }

        $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;

        if (isset($issuer->currentKeyIdentifier)) {
            $this->setExtension('id-ce-authorityKeyIdentifier', array(
                    //'authorityCertIssuer' => array(
                    //    array(
                    //        'directoryName' => $issuer->dn
                    //    )
                    //),
                    'keyIdentifier' => $issuer->currentKeyIdentifier
                ));
            //$extensions = &$this->currentCert['tbsCertificate']['extensions'];
            //if (isset($issuer->serialNumber)) {
            //    $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
            //}
            //unset($extensions);
        }

        if (isset($subject->currentKeyIdentifier)) {
            $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier);
        }

        $altName = array();

        if (isset($subject->domains) && count($subject->domains) > 1) {
            $altName = array_map(array('X509', '_dnsName'), $subject->domains);
        }

        if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
            // should an IP address appear as the CN if no domain name is specified? idk
            //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
            $ipAddresses = array();
            foreach ($subject->ipAddresses as $ipAddress) {
                $encoded = $subject->_ipAddress($ipAddress);
                if ($encoded !== false) {
                    $ipAddresses[] = $encoded;
                }
            }
            if (count($ipAddresses)) {
                $altName = array_merge($altName, $ipAddresses);
            }
        }

        if (!empty($altName)) {
            $this->setExtension('id-ce-subjectAltName', $altName);
        }

        if ($this->caFlag) {
            $keyUsage = $this->getExtension('id-ce-keyUsage');
            if (!$keyUsage) {
                $keyUsage = array();
            }

            $this->setExtension(
                'id-ce-keyUsage',
                array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign'))))
            );

            $basicConstraints = $this->getExtension('id-ce-basicConstraints');
            if (!$basicConstraints) {
                $basicConstraints = array();
            }

            $this->setExtension(
                'id-ce-basicConstraints',
                array_unique(array_merge(array('cA' => true), $basicConstraints)),
                true
            );

            if (!isset($subject->currentKeyIdentifier)) {
                $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false);
            }
        }

        // resync $this->signatureSubject
        // save $tbsCertificate in case there are any \phpseclib\File\ASN1\Element objects in it
        $tbsCertificate = $this->currentCert['tbsCertificate'];
        $this->loadX509($this->saveX509($this->currentCert));

        $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
        $result['tbsCertificate'] = $tbsCertificate;

        $this->currentCert = $currentCert;
        $this->signatureSubject = $signatureSubject;

        return $result;
    }

    /**
     * Sign a CSR
     *
     * @access public
     * @return mixed
     */
    function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption')
    {
        if (!is_object($this->privateKey) || empty($this->dn)) {
            return false;
        }

        $origPublicKey = $this->publicKey;
        $class = get_class($this->privateKey);
        $this->publicKey = new $class();
        $this->publicKey->loadKey($this->privateKey->getPublicKey());
        $this->publicKey->setPublicKey();
        if (!($publicKey = $this->_formatSubjectPublicKey())) {
            return false;
        }
        $this->publicKey = $origPublicKey;

        $currentCert = isset($this->currentCert) ? $this->currentCert : null;
        $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;

        if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) {
            $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
            if (!empty($this->dn)) {
                $this->currentCert['certificationRequestInfo']['subject'] = $this->dn;
            }
            $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
        } else {
            $this->currentCert = array(
                'certificationRequestInfo' =>
                    array(
                        'version' => 'v1',
                        'subject' => $this->dn,
                        'subjectPKInfo' => $publicKey
                    ),
                    'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
                    'signature'          => false // this is going to be overwritten later
            );
        }

        // resync $this->signatureSubject
        // save $certificationRequestInfo in case there are any \phpseclib\File\ASN1\Element objects in it
        $certificationRequestInfo = $this->currentCert['certificationRequestInfo'];
        $this->loadCSR($this->saveCSR($this->currentCert));

        $result = $this->_sign($this->privateKey, $signatureAlgorithm);
        $result['certificationRequestInfo'] = $certificationRequestInfo;

        $this->currentCert = $currentCert;
        $this->signatureSubject = $signatureSubject;

        return $result;
    }

    /**
     * Sign a SPKAC
     *
     * @access public
     * @return mixed
     */
    function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption')
    {
        if (!is_object($this->privateKey)) {
            return false;
        }

        $origPublicKey = $this->publicKey;
        $class = get_class($this->privateKey);
        $this->publicKey = new $class();
        $this->publicKey->loadKey($this->privateKey->getPublicKey());
        $this->publicKey->setPublicKey();
        $publicKey = $this->_formatSubjectPublicKey();
        if (!$publicKey) {
            return false;
        }
        $this->publicKey = $origPublicKey;

        $currentCert = isset($this->currentCert) ? $this->currentCert : null;
        $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;

        // re-signing a SPKAC seems silly but since everything else supports re-signing why not?
        if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
            $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
            $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
            if (!empty($this->challenge)) {
                // the bitwise AND ensures that the output is a valid IA5String
                $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
            }
        } else {
            $this->currentCert = array(
                'publicKeyAndChallenge' =>
                    array(
                        'spki' => $publicKey,
                        // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
                        // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
                        // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way
                        // we could alternatively do this instead if we ignored the specs:
                        // Random::string(8) & str_repeat("\x7F", 8)
                        'challenge' => !empty($this->challenge) ? $this->challenge : ''
                    ),
                    'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
                    'signature'          => false // this is going to be overwritten later
            );
        }

        // resync $this->signatureSubject
        // save $publicKeyAndChallenge in case there are any \phpseclib\File\ASN1\Element objects in it
        $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
        $this->loadSPKAC($this->saveSPKAC($this->currentCert));

        $result = $this->_sign($this->privateKey, $signatureAlgorithm);
        $result['publicKeyAndChallenge'] = $publicKeyAndChallenge;

        $this->currentCert = $currentCert;
        $this->signatureSubject = $signatureSubject;

        return $result;
    }

    /**
     * Sign a CRL
     *
     * $issuer's private key needs to be loaded.
     *
     * @param \phpseclib\File\X509 $issuer
     * @param \phpseclib\File\X509 $crl
     * @param string $signatureAlgorithm optional
     * @access public
     * @return mixed
     */
    function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption')
    {
        if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
            return false;
        }

        $currentCert = isset($this->currentCert) ? $this->currentCert : null;
        $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
        $thisUpdate = !empty($this->startDate) ? $this->startDate : @date('D, d M Y H:i:s O');

        if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
            $this->currentCert = $crl->currentCert;
            $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
            $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
        } else {
            $this->currentCert = array(
                'tbsCertList' =>
                    array(
                        'version' => 'v2',
                        'signature' => array('algorithm' => $signatureAlgorithm),
                        'issuer' => false, // this is going to be overwritten later
                        'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate()
                    ),
                    'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
                    'signature'          => false // this is going to be overwritten later
            );
        }

        $tbsCertList = &$this->currentCert['tbsCertList'];
        $tbsCertList['issuer'] = $issuer->dn;
        $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate);

        if (!empty($this->endDate)) {
            $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate()
        } else {
            unset($tbsCertList['nextUpdate']);
        }

        if (!empty($this->serialNumber)) {
            $crlNumber = $this->serialNumber;
        } else {
            $crlNumber = $this->getExtension('id-ce-cRLNumber');
            // "The CRL number is a non-critical CRL extension that conveys a
            //  monotonically increasing sequence number for a given CRL scope and
            //  CRL issuer.  This extension allows users to easily determine when a
            //  particular CRL supersedes another CRL."
            // -- https://tools.ietf.org/html/rfc5280#section-5.2.3
            $crlNumber = $crlNumber !== false ? $crlNumber->add(new BigInteger(1)) : null;
        }

        $this->removeExtension('id-ce-authorityKeyIdentifier');
        $this->removeExtension('id-ce-issuerAltName');

        // Be sure version >= v2 if some extension found.
        $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0;
        if (!$version) {
            if (!empty($tbsCertList['crlExtensions'])) {
                $version = 1; // v2.
            } elseif (!empty($tbsCertList['revokedCertificates'])) {
                foreach ($tbsCertList['revokedCertificates'] as $cert) {
                    if (!empty($cert['crlEntryExtensions'])) {
                        $version = 1; // v2.
                    }
                }
            }

            if ($version) {
                $tbsCertList['version'] = $version;
            }
        }

        // Store additional extensions.
        if (!empty($tbsCertList['version'])) { // At least v2.
            if (!empty($crlNumber)) {
                $this->setExtension('id-ce-cRLNumber', $crlNumber);
            }

            if (isset($issuer->currentKeyIdentifier)) {
                $this->setExtension('id-ce-authorityKeyIdentifier', array(
                        //'authorityCertIssuer' => array(
                        //    array(
                        //        'directoryName' => $issuer->dn
                        //    )
                        //),
                        'keyIdentifier' => $issuer->currentKeyIdentifier
                    ));
                //$extensions = &$tbsCertList['crlExtensions'];
                //if (isset($issuer->serialNumber)) {
                //    $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
                //}
                //unset($extensions);
            }

            $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert);

            if ($issuerAltName !== false) {
                $this->setExtension('id-ce-issuerAltName', $issuerAltName);
            }
        }

        if (empty($tbsCertList['revokedCertificates'])) {
            unset($tbsCertList['revokedCertificates']);
        }

        unset($tbsCertList);

        // resync $this->signatureSubject
        // save $tbsCertList in case there are any \phpseclib\File\ASN1\Element objects in it
        $tbsCertList = $this->currentCert['tbsCertList'];
        $this->loadCRL($this->saveCRL($this->currentCert));

        $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
        $result['tbsCertList'] = $tbsCertList;

        $this->currentCert = $currentCert;
        $this->signatureSubject = $signatureSubject;

        return $result;
    }

    /**
     * X.509 certificate signing helper function.
     *
     * @param object $key
     * @param \phpseclib\File\X509 $subject
     * @param string $signatureAlgorithm
     * @access public
     * @return mixed
     */
    function _sign($key, $signatureAlgorithm)
    {
        if ($key instanceof RSA) {
            switch ($signatureAlgorithm) {
                case 'md2WithRSAEncryption':
                case 'md5WithRSAEncryption':
                case 'sha1WithRSAEncryption':
                case 'sha224WithRSAEncryption':
                case 'sha256WithRSAEncryption':
                case 'sha384WithRSAEncryption':
                case 'sha512WithRSAEncryption':
                    $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
                    $key->setSignatureMode(RSA::SIGNATURE_PKCS1);

                    $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject));
                    return $this->currentCert;
            }
        }

        return false;
    }

    /**
     * Set certificate start date
     *
     * @param string $date
     * @access public
     */
    function setStartDate($date)
    {
        $this->startDate = @date('D, d M Y H:i:s O', @strtotime($date));
    }

    /**
     * Set certificate end date
     *
     * @param string $date
     * @access public
     */
    function setEndDate($date)
    {
        /*
          To indicate that a certificate has no well-defined expiration date,
          the notAfter SHOULD be assigned the GeneralizedTime value of
          99991231235959Z.

          -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
        */
        if (strtolower($date) == 'lifetime') {
            $temp = '99991231235959Z';
            $asn1 = new ASN1();
            $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
            $this->endDate = new Element($temp);
        } else {
            $this->endDate = @date('D, d M Y H:i:s O', @strtotime($date));
        }
    }

    /**
     * Set Serial Number
     *
     * @param string $serial
     * @param $base optional
     * @access public
     */
    function setSerialNumber($serial, $base = -256)
    {
        $this->serialNumber = new BigInteger($serial, $base);
    }

    /**
     * Turns the certificate into a certificate authority
     *
     * @access public
     */
    function makeCA()
    {
        $this->caFlag = true;
    }

    /**
     * Check for validity of subarray
     *
     * This is intended for use in conjunction with _subArrayUnchecked(),
     * implementing the checks included in _subArray() but without copying
     * a potentially large array by passing its reference by-value to is_array().
     *
     * @param array $root
     * @param string $path
     * @return boolean
     * @access private
     */
    function _isSubArrayValid($root, $path)
    {
        if (!is_array($root)) {
            return false;
        }

        foreach (explode('/', $path) as $i) {
            if (!is_array($root)) {
                return false;
            }

            if (!isset($root[$i])) {
                return true;
            }

            $root = $root[$i];
        }

        return true;
    }

    /**
     * Get a reference to a subarray
     *
     * This variant of _subArray() does no is_array() checking,
     * so $root should be checked with _isSubArrayValid() first.
     *
     * This is here for performance reasons:
     * Passing a reference (i.e. $root) by-value (i.e. to is_array())
     * creates a copy. If $root is an especially large array, this is expensive.
     *
     * @param array $root
     * @param string $path  absolute path with / as component separator
     * @param bool $create optional
     * @access private
     * @return array|false
     */
    function &_subArrayUnchecked(&$root, $path, $create = false)
    {
        $false = false;

        foreach (explode('/', $path) as $i) {
            if (!isset($root[$i])) {
                if (!$create) {
                    return $false;
                }

                $root[$i] = array();
            }

            $root = &$root[$i];
        }

        return $root;
    }

    /**
     * Get a reference to a subarray
     *
     * @param array $root
     * @param string $path  absolute path with / as component separator
     * @param bool $create optional
     * @access private
     * @return array|false
     */
    function &_subArray(&$root, $path, $create = false)
    {
        $false = false;

        if (!is_array($root)) {
            return $false;
        }

        foreach (explode('/', $path) as $i) {
            if (!is_array($root)) {
                return $false;
            }

            if (!isset($root[$i])) {
                if (!$create) {
                    return $false;
                }

                $root[$i] = array();
            }

            $root = &$root[$i];
        }

        return $root;
    }

    /**
     * Get a reference to an extension subarray
     *
     * @param array $root
     * @param string $path optional absolute path with / as component separator
     * @param bool $create optional
     * @access private
     * @return array|false
     */
    function &_extensions(&$root, $path = null, $create = false)
    {
        if (!isset($root)) {
            $root = $this->currentCert;
        }

        switch (true) {
            case !empty($path):
            case !is_array($root):
                break;
            case isset($root['tbsCertificate']):
                $path = 'tbsCertificate/extensions';
                break;
            case isset($root['tbsCertList']):
                $path = 'tbsCertList/crlExtensions';
                break;
            case isset($root['certificationRequestInfo']):
                $pth = 'certificationRequestInfo/attributes';
                $attributes = &$this->_subArray($root, $pth, $create);

                if (is_array($attributes)) {
                    foreach ($attributes as $key => $value) {
                        if ($value['type'] == 'pkcs-9-at-extensionRequest') {
                            $path = "$pth/$key/value/0";
                            break 2;
                        }
                    }
                    if ($create) {
                        $key = count($attributes);
                        $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array());
                        $path = "$pth/$key/value/0";
                    }
                }
                break;
        }

        $extensions = &$this->_subArray($root, $path, $create);

        if (!is_array($extensions)) {
            $false = false;
            return $false;
        }

        return $extensions;
    }

    /**
     * Remove an Extension
     *
     * @param string $id
     * @param string $path optional
     * @access private
     * @return bool
     */
    function _removeExtension($id, $path = null)
    {
        $extensions = &$this->_extensions($this->currentCert, $path);

        if (!is_array($extensions)) {
            return false;
        }

        $result = false;
        foreach ($extensions as $key => $value) {
            if ($value['extnId'] == $id) {
                unset($extensions[$key]);
                $result = true;
            }
        }

        $extensions = array_values($extensions);
        return $result;
    }

    /**
     * Get an Extension
     *
     * Returns the extension if it exists and false if not
     *
     * @param string $id
     * @param array $cert optional
     * @param string $path optional
     * @access private
     * @return mixed
     */
    function _getExtension($id, $cert = null, $path = null)
    {
        $extensions = $this->_extensions($cert, $path);

        if (!is_array($extensions)) {
            return false;
        }

        foreach ($extensions as $key => $value) {
            if ($value['extnId'] == $id) {
                return $value['extnValue'];
            }
        }

        return false;
    }

    /**
     * Returns a list of all extensions in use
     *
     * @param array $cert optional
     * @param string $path optional
     * @access private
     * @return array
     */
    function _getExtensions($cert = null, $path = null)
    {
        $exts = $this->_extensions($cert, $path);
        $extensions = array();

        if (is_array($exts)) {
            foreach ($exts as $extension) {
                $extensions[] = $extension['extnId'];
            }
        }

        return $extensions;
    }

    /**
     * Set an Extension
     *
     * @param string $id
     * @param mixed $value
     * @param bool $critical optional
     * @param bool $replace optional
     * @param string $path optional
     * @access private
     * @return bool
     */
    function _setExtension($id, $value, $critical = false, $replace = true, $path = null)
    {
        $extensions = &$this->_extensions($this->currentCert, $path, true);

        if (!is_array($extensions)) {
            return false;
        }

        $newext = array('extnId'  => $id, 'critical' => $critical, 'extnValue' => $value);

        foreach ($extensions as $key => $value) {
            if ($value['extnId'] == $id) {
                if (!$replace) {
                    return false;
                }

                $extensions[$key] = $newext;
                return true;
            }
        }

        $extensions[] = $newext;
        return true;
    }

    /**
     * Remove a certificate, CSR or CRL Extension
     *
     * @param string $id
     * @access public
     * @return bool
     */
    function removeExtension($id)
    {
        return $this->_removeExtension($id);
    }

    /**
     * Get a certificate, CSR or CRL Extension
     *
     * Returns the extension if it exists and false if not
     *
     * @param string $id
     * @param array $cert optional
     * @access public
     * @return mixed
     */
    function getExtension($id, $cert = null)
    {
        return $this->_getExtension($id, $cert);
    }

    /**
     * Returns a list of all extensions in use in certificate, CSR or CRL
     *
     * @param array $cert optional
     * @access public
     * @return array
     */
    function getExtensions($cert = null)
    {
        return $this->_getExtensions($cert);
    }

    /**
     * Set a certificate, CSR or CRL Extension
     *
     * @param string $id
     * @param mixed $value
     * @param bool $critical optional
     * @param bool $replace optional
     * @access public
     * @return bool
     */
    function setExtension($id, $value, $critical = false, $replace = true)
    {
        return $this->_setExtension($id, $value, $critical, $replace);
    }

    /**
     * Remove a CSR attribute.
     *
     * @param string $id
     * @param int $disposition optional
     * @access public
     * @return bool
     */
    function removeAttribute($id, $disposition = self::ATTR_ALL)
    {
        $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes');

        if (!is_array($attributes)) {
            return false;
        }

        $result = false;
        foreach ($attributes as $key => $attribute) {
            if ($attribute['type'] == $id) {
                $n = count($attribute['value']);
                switch (true) {
                    case $disposition == self::ATTR_APPEND:
                    case $disposition == self::ATTR_REPLACE:
                        return false;
                    case $disposition >= $n:
                        $disposition -= $n;
                        break;
                    case $disposition == self::ATTR_ALL:
                    case $n == 1:
                        unset($attributes[$key]);
                        $result = true;
                        break;
                    default:
                        unset($attributes[$key]['value'][$disposition]);
                        $attributes[$key]['value'] = array_values($attributes[$key]['value']);
                        $result = true;
                        break;
                }
                if ($result && $disposition != self::ATTR_ALL) {
                    break;
                }
            }
        }

        $attributes = array_values($attributes);
        return $result;
    }

    /**
     * Get a CSR attribute
     *
     * Returns the attribute if it exists and false if not
     *
     * @param string $id
     * @param int $disposition optional
     * @param array $csr optional
     * @access public
     * @return mixed
     */
    function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null)
    {
        if (empty($csr)) {
            $csr = $this->currentCert;
        }

        $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');

        if (!is_array($attributes)) {
            return false;
        }

        foreach ($attributes as $key => $attribute) {
            if ($attribute['type'] == $id) {
                $n = count($attribute['value']);
                switch (true) {
                    case $disposition == self::ATTR_APPEND:
                    case $disposition == self::ATTR_REPLACE:
                        return false;
                    case $disposition == self::ATTR_ALL:
                        return $attribute['value'];
                    case $disposition >= $n:
                        $disposition -= $n;
                        break;
                    default:
                        return $attribute['value'][$disposition];
                }
            }
        }

        return false;
    }

    /**
     * Returns a list of all CSR attributes in use
     *
     * @param array $csr optional
     * @access public
     * @return array
     */
    function getAttributes($csr = null)
    {
        if (empty($csr)) {
            $csr = $this->currentCert;
        }

        $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
        $attrs = array();

        if (is_array($attributes)) {
            foreach ($attributes as $attribute) {
                $attrs[] = $attribute['type'];
            }
        }

        return $attrs;
    }

    /**
     * Set a CSR attribute
     *
     * @param string $id
     * @param mixed $value
     * @param bool $disposition optional
     * @access public
     * @return bool
     */
    function setAttribute($id, $value, $disposition = self::ATTR_ALL)
    {
        $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true);

        if (!is_array($attributes)) {
            return false;
        }

        switch ($disposition) {
            case self::ATTR_REPLACE:
                $disposition = self::ATTR_APPEND;
            case self::ATTR_ALL:
                $this->removeAttribute($id);
                break;
        }

        foreach ($attributes as $key => $attribute) {
            if ($attribute['type'] == $id) {
                $n = count($attribute['value']);
                switch (true) {
                    case $disposition == self::ATTR_APPEND:
                        $last = $key;
                        break;
                    case $disposition >= $n:
                        $disposition -= $n;
                        break;
                    default:
                        $attributes[$key]['value'][$disposition] = $value;
                        return true;
                }
            }
        }

        switch (true) {
            case $disposition >= 0:
                return false;
            case isset($last):
                $attributes[$last]['value'][] = $value;
                break;
            default:
                $attributes[] = array('type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: array($value));
                break;
        }

        return true;
    }

    /**
     * Sets the subject key identifier
     *
     * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
     *
     * @param string $value
     * @access public
     */
    function setKeyIdentifier($value)
    {
        if (empty($value)) {
            unset($this->currentKeyIdentifier);
        } else {
            $this->currentKeyIdentifier = base64_encode($value);
        }
    }

    /**
     * Compute a public key identifier.
     *
     * Although key identifiers may be set to any unique value, this function
     * computes key identifiers from public key according to the two
     * recommended methods (4.2.1.2 RFC 3280).
     * Highly polymorphic: try to accept all possible forms of key:
     * - Key object
     * - \phpseclib\File\X509 object with public or private key defined
     * - Certificate or CSR array
     * - \phpseclib\File\ASN1\Element object
     * - PEM or DER string
     *
     * @param mixed $key optional
     * @param int $method optional
     * @access public
     * @return string binary key identifier
     */
    function computeKeyIdentifier($key = null, $method = 1)
    {
        if (is_null($key)) {
            $key = $this;
        }

        switch (true) {
            case is_string($key):
                break;
            case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
                return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method);
            case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
                return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method);
            case !is_object($key):
                return false;
            case $key instanceof Element:
                // Assume the element is a bitstring-packed key.
                $asn1 = new ASN1();
                $decoded = $asn1->decodeBER($key->element);
                if (empty($decoded)) {
                    return false;
                }
                $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING));
                if (empty($raw)) {
                    return false;
                }
                $raw = base64_decode($raw);
                // If the key is private, compute identifier from its corresponding public key.
                $key = new RSA();
                if (!$key->loadKey($raw)) {
                    return false;   // Not an unencrypted RSA key.
                }
                if ($key->getPrivateKey() !== false) {  // If private.
                    return $this->computeKeyIdentifier($key, $method);
                }
                $key = $raw;    // Is a public key.
                break;
            case $key instanceof X509:
                if (isset($key->publicKey)) {
                    return $this->computeKeyIdentifier($key->publicKey, $method);
                }
                if (isset($key->privateKey)) {
                    return $this->computeKeyIdentifier($key->privateKey, $method);
                }
                if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) {
                    return $this->computeKeyIdentifier($key->currentCert, $method);
                }
                return false;
            default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA).
                $key = $key->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1);
                break;
        }

        // If in PEM format, convert to binary.
        $key = $this->_extractBER($key);

        // Now we have the key string: compute its sha-1 sum.
        $hash = new Hash('sha1');
        $hash = $hash->hash($key);

        if ($method == 2) {
            $hash = substr($hash, -8);
            $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40);
        }

        return $hash;
    }

    /**
     * Format a public key as appropriate
     *
     * @access private
     * @return array
     */
    function _formatSubjectPublicKey()
    {
        if ($this->publicKey instanceof RSA) {
            // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason.
            // the former is a good example of how to do fuzzing on the public key
            //return new Element(base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey())));
            return array(
                'algorithm' => array('algorithm' => 'rsaEncryption'),
                'subjectPublicKey' => $this->publicKey->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1)
            );
        }

        return false;
    }

    /**
     * Set the domain name's which the cert is to be valid for
     *
     * @access public
     * @return array
     */
    function setDomain()
    {
        $this->domains = func_get_args();
        $this->removeDNProp('id-at-commonName');
        $this->setDNProp('id-at-commonName', $this->domains[0]);
    }

    /**
     * Set the IP Addresses's which the cert is to be valid for
     *
     * @access public
     * @param string $ipAddress optional
     */
    function setIPAddress()
    {
        $this->ipAddresses = func_get_args();
        /*
        if (!isset($this->domains)) {
            $this->removeDNProp('id-at-commonName');
            $this->setDNProp('id-at-commonName', $this->ipAddresses[0]);
        }
        */
    }

    /**
     * Helper function to build domain array
     *
     * @access private
     * @param string $domain
     * @return array
     */
    function _dnsName($domain)
    {
        return array('dNSName' => $domain);
    }

    /**
     * Helper function to build IP Address array
     *
     * (IPv6 is not currently supported)
     *
     * @access private
     * @param string $address
     * @return array
     */
    function _iPAddress($address)
    {
        return array('iPAddress' => $address);
    }

    /**
     * Get the index of a revoked certificate.
     *
     * @param array $rclist
     * @param string $serial
     * @param bool $create optional
     * @access private
     * @return int|false
     */
    function _revokedCertificate(&$rclist, $serial, $create = false)
    {
        $serial = new BigInteger($serial);

        foreach ($rclist as $i => $rc) {
            if (!($serial->compare($rc['userCertificate']))) {
                return $i;
            }
        }

        if (!$create) {
            return false;
        }

        $i = count($rclist);
        $rclist[] = array('userCertificate' => $serial,
                          'revocationDate'  => $this->_timeField(@date('D, d M Y H:i:s O')));
        return $i;
    }

    /**
     * Revoke a certificate.
     *
     * @param string $serial
     * @param string $date optional
     * @access public
     * @return bool
     */
    function revoke($serial, $date = null)
    {
        if (isset($this->currentCert['tbsCertList'])) {
            if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
                if ($this->_revokedCertificate($rclist, $serial) === false) { // If not yet revoked
                    if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
                        if (!empty($date)) {
                            $rclist[$i]['revocationDate'] = $this->_timeField($date);
                        }

                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * Unrevoke a certificate.
     *
     * @param string $serial
     * @access public
     * @return bool
     */
    function unrevoke($serial)
    {
        if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
            if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
                unset($rclist[$i]);
                $rclist = array_values($rclist);
                return true;
            }
        }

        return false;
    }

    /**
     * Get a revoked certificate.
     *
     * @param string $serial
     * @access public
     * @return mixed
     */
    function getRevoked($serial)
    {
        if (is_array($rclist = $this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
            if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
                return $rclist[$i];
            }
        }

        return false;
    }

    /**
     * List revoked certificates
     *
     * @param array $crl optional
     * @access public
     * @return array
     */
    function listRevoked($crl = null)
    {
        if (!isset($crl)) {
            $crl = $this->currentCert;
        }

        if (!isset($crl['tbsCertList'])) {
            return false;
        }

        $result = array();

        if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
            foreach ($rclist as $rc) {
                $result[] = $rc['userCertificate']->toString();
            }
        }

        return $result;
    }

    /**
     * Remove a Revoked Certificate Extension
     *
     * @param string $serial
     * @param string $id
     * @access public
     * @return bool
     */
    function removeRevokedCertificateExtension($serial, $id)
    {
        if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
            if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
                return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
            }
        }

        return false;
    }

    /**
     * Get a Revoked Certificate Extension
     *
     * Returns the extension if it exists and false if not
     *
     * @param string $serial
     * @param string $id
     * @param array $crl optional
     * @access public
     * @return mixed
     */
    function getRevokedCertificateExtension($serial, $id, $crl = null)
    {
        if (!isset($crl)) {
            $crl = $this->currentCert;
        }

        if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
            if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
                return $this->_getExtension($id, $crl,  "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
            }
        }

        return false;
    }

    /**
     * Returns a list of all extensions in use for a given revoked certificate
     *
     * @param string $serial
     * @param array $crl optional
     * @access public
     * @return array
     */
    function getRevokedCertificateExtensions($serial, $crl = null)
    {
        if (!isset($crl)) {
            $crl = $this->currentCert;
        }

        if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
            if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
                return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
            }
        }

        return false;
    }

    /**
     * Set a Revoked Certificate Extension
     *
     * @param string $serial
     * @param string $id
     * @param mixed $value
     * @param bool $critical optional
     * @param bool $replace optional
     * @access public
     * @return bool
     */
    function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
    {
        if (isset($this->currentCert['tbsCertList'])) {
            if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
                if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
                    return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
                }
            }
        }

        return false;
    }

    /**
     * Extract raw BER from Base64 encoding
     *
     * @access private
     * @param string $str
     * @return string
     */
    function _extractBER($str)
    {
        /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
         * above and beyond the ceritificate.
         * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
         *
         * Bag Attributes
         *     localKeyID: 01 00 00 00
         * subject=/O=organization/OU=org unit/CN=common name
         * issuer=/O=organization/CN=common name
         */
        $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
        // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
        $temp = preg_replace('#-+[^-]+-+#', '', $temp);
        // remove new lines
        $temp = str_replace(array("\r", "\n", ' '), '', $temp);
        $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
        return $temp != false ? $temp : $str;
    }

    /**
     * Returns the OID corresponding to a name
     *
     * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if
     * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version
     * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able
     * to work from version to version.
     *
     * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that
     * what's being passed to it already is an OID and return that instead. A few examples.
     *
     * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1'
     * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1'
     * getOID('zzz') == 'zzz'
     *
     * @access public
     * @return string
     */
    function getOID($name)
    {
        static $reverseMap;
        if (!isset($reverseMap)) {
            $reverseMap = array_flip($this->oids);
        }
        return isset($reverseMap[$name]) ? $reverseMap[$name] : $name;
    }
}
<?php

/**
 * Pure-PHP arbitrary precision integer arithmetic library.
 *
 * Supports base-2, base-10, base-16, and base-256 numbers.  Uses the GMP or BCMath extensions, if available,
 * and an internal implementation, otherwise.
 *
 * PHP version 5
 *
 * {@internal (all DocBlock comments regarding implementation - such as the one that follows - refer to the
 * {@link self::MODE_INTERNAL self::MODE_INTERNAL} mode)
 *
 * BigInteger uses base-2**26 to perform operations such as multiplication and division and
 * base-2**52 (ie. two base 2**26 digits) to perform addition and subtraction.  Because the largest possible
 * value when multiplying two base-2**26 numbers together is a base-2**52 number, double precision floating
 * point numbers - numbers that should be supported on most hardware and whose significand is 53 bits - are
 * used.  As a consequence, bitwise operators such as >> and << cannot be used, nor can the modulo operator %,
 * which only supports integers.  Although this fact will slow this library down, the fact that such a high
 * base is being used should more than compensate.
 *
 * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format.  ie.
 * (new \phpseclib\Math\BigInteger(pow(2, 26)))->value = array(0, 1)
 *
 * Useful resources are as follows:
 *
 *  - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)}
 *  - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)}
 *  - Java's BigInteger classes.  See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip
 *
 * Here's an example of how to use this library:
 * <code>
 * <?php
 *    $a = new \phpseclib\Math\BigInteger(2);
 *    $b = new \phpseclib\Math\BigInteger(3);
 *
 *    $c = $a->add($b);
 *
 *    echo $c->toString(); // outputs 5
 * ?>
 * </code>
 *
 * @category  Math
 * @package   BigInteger
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2006 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://pear.php.net/package/Math_BigInteger
 */

namespace phpseclib\Math;

use phpseclib\Crypt\Random;

/**
 * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256
 * numbers.
 *
 * @package BigInteger
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class BigInteger
{
    /**#@+
     * Reduction constants
     *
     * @access private
     * @see BigInteger::_reduce()
     */
    /**
     * @see BigInteger::_montgomery()
     * @see BigInteger::_prepMontgomery()
     */
    const MONTGOMERY = 0;
    /**
     * @see BigInteger::_barrett()
     */
    const BARRETT = 1;
    /**
     * @see BigInteger::_mod2()
     */
    const POWEROF2 = 2;
    /**
     * @see BigInteger::_remainder()
     */
    const CLASSIC = 3;
    /**
     * @see BigInteger::__clone()
     */
    const NONE = 4;
    /**#@-*/

    /**#@+
     * Array constants
     *
     * Rather than create a thousands and thousands of new BigInteger objects in repeated function calls to add() and
     * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them.
     *
     * @access private
    */
    /**
     * $result[self::VALUE] contains the value.
     */
    const VALUE = 0;
    /**
     * $result[self::SIGN] contains the sign.
     */
    const SIGN = 1;
    /**#@-*/

    /**#@+
     * @access private
     * @see BigInteger::_montgomery()
     * @see BigInteger::_barrett()
    */
    /**
     * Cache constants
     *
     * $cache[self::VARIABLE] tells us whether or not the cached data is still valid.
     */
    const VARIABLE = 0;
    /**
     * $cache[self::DATA] contains the cached data.
     */
    const DATA = 1;
    /**#@-*/

    /**#@+
     * Mode constants.
     *
     * @access private
     * @see BigInteger::__construct()
    */
    /**
     * To use the pure-PHP implementation
     */
    const MODE_INTERNAL = 1;
    /**
     * To use the BCMath library
     *
     * (if enabled; otherwise, the internal implementation will be used)
     */
    const MODE_BCMATH = 2;
    /**
     * To use the GMP library
     *
     * (if present; otherwise, either the BCMath or the internal implementation will be used)
     */
    const MODE_GMP = 3;
    /**#@-*/

    /**
     * Karatsuba Cutoff
     *
     * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication?
     *
     * @access private
     */
    const KARATSUBA_CUTOFF = 25;

    /**#@+
     * Static properties used by the pure-PHP implementation.
     *
     * @see __construct()
     */
    protected static $base;
    protected static $baseFull;
    protected static $maxDigit;
    protected static $msb;

    /**
     * $max10 in greatest $max10Len satisfying
     * $max10 = 10**$max10Len <= 2**$base.
     */
    protected static $max10;

    /**
     * $max10Len in greatest $max10Len satisfying
     * $max10 = 10**$max10Len <= 2**$base.
     */
    protected static $max10Len;
    protected static $maxDigit2;
    /**#@-*/

    /**
     * Holds the BigInteger's value.
     *
     * @var array
     * @access private
     */
    var $value;

    /**
     * Holds the BigInteger's magnitude.
     *
     * @var bool
     * @access private
     */
    var $is_negative = false;

    /**
     * Precision
     *
     * @see self::setPrecision()
     * @access private
     */
    var $precision = -1;

    /**
     * Precision Bitmask
     *
     * @see self::setPrecision()
     * @access private
     */
    var $bitmask = false;

    /**
     * Mode independent value used for serialization.
     *
     * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for
     * a variable that'll be serializable regardless of whether or not extensions are being used.  Unlike $this->value,
     * however, $this->hex is only calculated when $this->__sleep() is called.
     *
     * @see self::__sleep()
     * @see self::__wakeup()
     * @var string
     * @access private
     */
    var $hex;

    /**
     * Converts base-2, base-10, base-16, and binary strings (base-256) to BigIntegers.
     *
     * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using
     * two's compliment.  The sole exception to this is -10, which is treated the same as 10 is.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('0x32', 16); // 50 in base-16
     *
     *    echo $a->toString(); // outputs 50
     * ?>
     * </code>
     *
     * @param $x base-10 number or base-$base number if $base set.
     * @param int $base
     * @return \phpseclib\Math\BigInteger
     * @access public
     */
    function __construct($x = 0, $base = 10)
    {
        if (!defined('MATH_BIGINTEGER_MODE')) {
            switch (true) {
                case extension_loaded('gmp'):
                    define('MATH_BIGINTEGER_MODE', self::MODE_GMP);
                    break;
                case extension_loaded('bcmath'):
                    define('MATH_BIGINTEGER_MODE', self::MODE_BCMATH);
                    break;
                default:
                    define('MATH_BIGINTEGER_MODE', self::MODE_INTERNAL);
            }
        }

        if (extension_loaded('openssl') && !defined('MATH_BIGINTEGER_OPENSSL_DISABLE') && !defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
            // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work
            ob_start();
            @phpinfo();
            $content = ob_get_contents();
            ob_end_clean();

            preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches);

            $versions = array();
            if (!empty($matches[1])) {
                for ($i = 0; $i < count($matches[1]); $i++) {
                    $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i])));

                    // Remove letter part in OpenSSL version
                    if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) {
                        $versions[$matches[1][$i]] = $fullVersion;
                    } else {
                        $versions[$matches[1][$i]] = $m[0];
                    }
                }
            }

            // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+
            switch (true) {
                case !isset($versions['Header']):
                case !isset($versions['Library']):
                case $versions['Header'] == $versions['Library']:
                case version_compare($versions['Header'], '1.0.0') >= 0 && version_compare($versions['Library'], '1.0.0') >= 0:
                    define('MATH_BIGINTEGER_OPENSSL_ENABLED', true);
                    break;
                default:
                    define('MATH_BIGINTEGER_OPENSSL_DISABLE', true);
            }
        }

        if (!defined('PHP_INT_SIZE')) {
            define('PHP_INT_SIZE', 4);
        }

        if (empty(self::$base) && MATH_BIGINTEGER_MODE == self::MODE_INTERNAL) {
            switch (PHP_INT_SIZE) {
                case 8: // use 64-bit integers if int size is 8 bytes
                    self::$base      = 31;
                    self::$baseFull  = 0x80000000;
                    self::$maxDigit  = 0x7FFFFFFF;
                    self::$msb       = 0x40000000;
                    self::$max10     = 1000000000;
                    self::$max10Len  = 9;
                    self::$maxDigit2 = pow(2, 62);
                    break;
                //case 4: // use 64-bit floats if int size is 4 bytes
                default:
                    self::$base      = 26;
                    self::$baseFull  = 0x4000000;
                    self::$maxDigit  = 0x3FFFFFF;
                    self::$msb       = 0x2000000;
                    self::$max10     = 10000000;
                    self::$max10Len  = 7;
                    self::$maxDigit2 = pow(2, 52); // pow() prevents truncation
            }
        }

        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                switch (true) {
                    case is_resource($x) && get_resource_type($x) == 'GMP integer':
                    // PHP 5.6 switched GMP from using resources to objects
                    case $x instanceof \GMP:
                        $this->value = $x;
                        return;
                }
                $this->value = gmp_init(0);
                break;
            case self::MODE_BCMATH:
                $this->value = '0';
                break;
            default:
                $this->value = array();
        }

        // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48
        // '0' is the only value like this per http://php.net/empty
        if (empty($x) && (abs($base) != 256 || $x !== '0')) {
            return;
        }

        switch ($base) {
            case -256:
                if (ord($x[0]) & 0x80) {
                    $x = ~$x;
                    $this->is_negative = true;
                }
            case 256:
                switch (MATH_BIGINTEGER_MODE) {
                    case self::MODE_GMP:
                        $sign = $this->is_negative ? '-' : '';
                        $this->value = gmp_init($sign . '0x' . bin2hex($x));
                        break;
                    case self::MODE_BCMATH:
                        // round $len to the nearest 4 (thanks, DavidMJ!)
                        $len = (strlen($x) + 3) & 0xFFFFFFFC;

                        $x = str_pad($x, $len, chr(0), STR_PAD_LEFT);

                        for ($i = 0; $i < $len; $i+= 4) {
                            $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32
                            $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0);
                        }

                        if ($this->is_negative) {
                            $this->value = '-' . $this->value;
                        }

                        break;
                    // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb)
                    default:
                        while (strlen($x)) {
                            $this->value[] = $this->_bytes2int($this->_base256_rshift($x, self::$base));
                        }
                }

                if ($this->is_negative) {
                    if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) {
                        $this->is_negative = false;
                    }
                    $temp = $this->add(new static('-1'));
                    $this->value = $temp->value;
                }
                break;
            case 16:
            case -16:
                if ($base > 0 && $x[0] == '-') {
                    $this->is_negative = true;
                    $x = substr($x, 1);
                }

                $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x);

                $is_negative = false;
                if ($base < 0 && hexdec($x[0]) >= 8) {
                    $this->is_negative = $is_negative = true;
                    $x = bin2hex(~pack('H*', $x));
                }

                switch (MATH_BIGINTEGER_MODE) {
                    case self::MODE_GMP:
                        $temp = $this->is_negative ? '-0x' . $x : '0x' . $x;
                        $this->value = gmp_init($temp);
                        $this->is_negative = false;
                        break;
                    case self::MODE_BCMATH:
                        $x = (strlen($x) & 1) ? '0' . $x : $x;
                        $temp = new static(pack('H*', $x), 256);
                        $this->value = $this->is_negative ? '-' . $temp->value : $temp->value;
                        $this->is_negative = false;
                        break;
                    default:
                        $x = (strlen($x) & 1) ? '0' . $x : $x;
                        $temp = new static(pack('H*', $x), 256);
                        $this->value = $temp->value;
                }

                if ($is_negative) {
                    $temp = $this->add(new static('-1'));
                    $this->value = $temp->value;
                }
                break;
            case 10:
            case -10:
                // (?<!^)(?:-).*: find any -'s that aren't at the beginning and then any characters that follow that
                // (?<=^|-)0*: find any 0's that are preceded by the start of the string or by a - (ie. octals)
                // [^-0-9].*: find any non-numeric characters and then any characters that follow that
                $x = preg_replace('#(?<!^)(?:-).*|(?<=^|-)0*|[^-0-9].*#', '', $x);

                switch (MATH_BIGINTEGER_MODE) {
                    case self::MODE_GMP:
                        $this->value = gmp_init($x);
                        break;
                    case self::MODE_BCMATH:
                        // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different
                        // results then doing it on '-1' does (modInverse does $x[0])
                        $this->value = $x === '-' ? '0' : (string) $x;
                        break;
                    default:
                        $temp = new static();

                        $multiplier = new static();
                        $multiplier->value = array(self::$max10);

                        if ($x[0] == '-') {
                            $this->is_negative = true;
                            $x = substr($x, 1);
                        }

                        $x = str_pad($x, strlen($x) + ((self::$max10Len - 1) * strlen($x)) % self::$max10Len, 0, STR_PAD_LEFT);
                        while (strlen($x)) {
                            $temp = $temp->multiply($multiplier);
                            $temp = $temp->add(new static($this->_int2bytes(substr($x, 0, self::$max10Len)), 256));
                            $x = substr($x, self::$max10Len);
                        }

                        $this->value = $temp->value;
                }
                break;
            case 2: // base-2 support originally implemented by Lluis Pamies - thanks!
            case -2:
                if ($base > 0 && $x[0] == '-') {
                    $this->is_negative = true;
                    $x = substr($x, 1);
                }

                $x = preg_replace('#^([01]*).*#', '$1', $x);
                $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT);

                $str = '0x';
                while (strlen($x)) {
                    $part = substr($x, 0, 4);
                    $str.= dechex(bindec($part));
                    $x = substr($x, 4);
                }

                if ($this->is_negative) {
                    $str = '-' . $str;
                }

                $temp = new static($str, 8 * $base); // ie. either -16 or +16
                $this->value = $temp->value;
                $this->is_negative = $temp->is_negative;

                break;
            default:
                // base not supported, so we'll let $this == 0
        }
    }

    /**
     * Converts a BigInteger to a byte string (eg. base-256).
     *
     * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
     * saved as two's compliment.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('65');
     *
     *    echo $a->toBytes(); // outputs chr(65)
     * ?>
     * </code>
     *
     * @param bool $twos_compliment
     * @return string
     * @access public
     * @internal Converts a base-2**26 number to base-2**8
     */
    function toBytes($twos_compliment = false)
    {
        if ($twos_compliment) {
            $comparison = $this->compare(new static());
            if ($comparison == 0) {
                return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
            }

            $temp = $comparison < 0 ? $this->add(new static(1)) : $this->copy();
            $bytes = $temp->toBytes();

            if (empty($bytes)) { // eg. if the number we're trying to convert is -1
                $bytes = chr(0);
            }

            if (ord($bytes[0]) & 0x80) {
                $bytes = chr(0) . $bytes;
            }

            return $comparison < 0 ? ~$bytes : $bytes;
        }

        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                if (gmp_cmp($this->value, gmp_init(0)) == 0) {
                    return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
                }

                $temp = gmp_strval(gmp_abs($this->value), 16);
                $temp = (strlen($temp) & 1) ? '0' . $temp : $temp;
                $temp = pack('H*', $temp);

                return $this->precision > 0 ?
                    substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) :
                    ltrim($temp, chr(0));
            case self::MODE_BCMATH:
                if ($this->value === '0') {
                    return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
                }

                $value = '';
                $current = $this->value;

                if ($current[0] == '-') {
                    $current = substr($current, 1);
                }

                while (bccomp($current, '0', 0) > 0) {
                    $temp = bcmod($current, '16777216');
                    $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value;
                    $current = bcdiv($current, '16777216', 0);
                }

                return $this->precision > 0 ?
                    substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) :
                    ltrim($value, chr(0));
        }

        if (!count($this->value)) {
            return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
        }
        $result = $this->_int2bytes($this->value[count($this->value) - 1]);

        $temp = $this->copy();

        for ($i = count($temp->value) - 2; $i >= 0; --$i) {
            $temp->_base256_lshift($result, self::$base);
            $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT);
        }

        return $this->precision > 0 ?
            str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) :
            $result;
    }

    /**
     * Converts a BigInteger to a hex string (eg. base-16)).
     *
     * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
     * saved as two's compliment.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('65');
     *
     *    echo $a->toHex(); // outputs '41'
     * ?>
     * </code>
     *
     * @param bool $twos_compliment
     * @return string
     * @access public
     * @internal Converts a base-2**26 number to base-2**8
     */
    function toHex($twos_compliment = false)
    {
        return bin2hex($this->toBytes($twos_compliment));
    }

    /**
     * Converts a BigInteger to a bit string (eg. base-2).
     *
     * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
     * saved as two's compliment.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('65');
     *
     *    echo $a->toBits(); // outputs '1000001'
     * ?>
     * </code>
     *
     * @param bool $twos_compliment
     * @return string
     * @access public
     * @internal Converts a base-2**26 number to base-2**2
     */
    function toBits($twos_compliment = false)
    {
        $hex = $this->toHex($twos_compliment);
        $bits = '';
        for ($i = strlen($hex) - 8, $start = strlen($hex) & 7; $i >= $start; $i-=8) {
            $bits = str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT) . $bits;
        }
        if ($start) { // hexdec('') == 0
            $bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8, '0', STR_PAD_LEFT) . $bits;
        }
        $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0');

        if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) {
            return '0' . $result;
        }

        return $result;
    }

    /**
     * Converts a BigInteger to a base-10 number.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('50');
     *
     *    echo $a->toString(); // outputs 50
     * ?>
     * </code>
     *
     * @return string
     * @access public
     * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10)
     */
    function toString()
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                return gmp_strval($this->value);
            case self::MODE_BCMATH:
                if ($this->value === '0') {
                    return '0';
                }

                return ltrim($this->value, '0');
        }

        if (!count($this->value)) {
            return '0';
        }

        $temp = $this->copy();
        $temp->is_negative = false;

        $divisor = new static();
        $divisor->value = array(self::$max10);
        $result = '';
        while (count($temp->value)) {
            list($temp, $mod) = $temp->divide($divisor);
            $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', self::$max10Len, '0', STR_PAD_LEFT) . $result;
        }
        $result = ltrim($result, '0');
        if (empty($result)) {
            $result = '0';
        }

        if ($this->is_negative) {
            $result = '-' . $result;
        }

        return $result;
    }

    /**
     * Copy an object
     *
     * PHP5 passes objects by reference while PHP4 passes by value.  As such, we need a function to guarantee
     * that all objects are passed by value, when appropriate.  More information can be found here:
     *
     * {@link http://php.net/language.oop5.basic#51624}
     *
     * @access public
     * @see self::__clone()
     * @return \phpseclib\Math\BigInteger
     */
    function copy()
    {
        $temp = new static();
        $temp->value = $this->value;
        $temp->is_negative = $this->is_negative;
        $temp->precision = $this->precision;
        $temp->bitmask = $this->bitmask;
        return $temp;
    }

    /**
     *  __toString() magic method
     *
     * Will be called, automatically, if you're supporting just PHP5.  If you're supporting PHP4, you'll need to call
     * toString().
     *
     * @access public
     * @internal Implemented per a suggestion by Techie-Michael - thanks!
     */
    function __toString()
    {
        return $this->toString();
    }

    /**
     * __clone() magic method
     *
     * Although you can call BigInteger::__toString() directly in PHP5, you cannot call BigInteger::__clone() directly
     * in PHP5.  You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5
     * only syntax of $y = clone $x.  As such, if you're trying to write an application that works on both PHP4 and
     * PHP5, call BigInteger::copy(), instead.
     *
     * @access public
     * @see self::copy()
     * @return \phpseclib\Math\BigInteger
     */
    function __clone()
    {
        return $this->copy();
    }

    /**
     *  __sleep() magic method
     *
     * Will be called, automatically, when serialize() is called on a BigInteger object.
     *
     * @see self::__wakeup()
     * @access public
     */
    function __sleep()
    {
        $this->hex = $this->toHex(true);
        $vars = array('hex');
        if ($this->precision > 0) {
            $vars[] = 'precision';
        }
        return $vars;
    }

    /**
     *  __wakeup() magic method
     *
     * Will be called, automatically, when unserialize() is called on a BigInteger object.
     *
     * @see self::__sleep()
     * @access public
     */
    function __wakeup()
    {
        $temp = new static($this->hex, -16);
        $this->value = $temp->value;
        $this->is_negative = $temp->is_negative;
        if ($this->precision > 0) {
            // recalculate $this->bitmask
            $this->setPrecision($this->precision);
        }
    }

    /**
     *  __debugInfo() magic method
     *
     * Will be called, automatically, when print_r() or var_dump() are called
     *
     * @access public
     */
    function __debugInfo()
    {
        $opts = array();
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $engine = 'gmp';
                break;
            case self::MODE_BCMATH:
                $engine = 'bcmath';
                break;
            case self::MODE_INTERNAL:
                $engine = 'internal';
                $opts[] = PHP_INT_SIZE == 8 ? '64-bit' : '32-bit';
        }
        if (MATH_BIGINTEGER_MODE != self::MODE_GMP && defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
            $opts[] = 'OpenSSL';
        }
        if (!empty($opts)) {
            $engine.= ' (' . implode($opts, ', ') . ')';
        }
        return array(
            'value' => '0x' . $this->toHex(true),
            'engine' => $engine
        );
    }

    /**
     * Adds two BigIntegers.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('10');
     *    $b = new \phpseclib\Math\BigInteger('20');
     *
     *    $c = $a->add($b);
     *
     *    echo $c->toString(); // outputs 30
     * ?>
     * </code>
     *
     * @param \phpseclib\Math\BigInteger $y
     * @return \phpseclib\Math\BigInteger
     * @access public
     * @internal Performs base-2**52 addition
     */
    function add($y)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $temp = new static();
                $temp->value = gmp_add($this->value, $y->value);

                return $this->_normalize($temp);
            case self::MODE_BCMATH:
                $temp = new static();
                $temp->value = bcadd($this->value, $y->value, 0);

                return $this->_normalize($temp);
        }

        $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative);

        $result = new static();
        $result->value = $temp[self::VALUE];
        $result->is_negative = $temp[self::SIGN];

        return $this->_normalize($result);
    }

    /**
     * Performs addition.
     *
     * @param array $x_value
     * @param bool $x_negative
     * @param array $y_value
     * @param bool $y_negative
     * @return array
     * @access private
     */
    function _add($x_value, $x_negative, $y_value, $y_negative)
    {
        $x_size = count($x_value);
        $y_size = count($y_value);

        if ($x_size == 0) {
            return array(
                self::VALUE => $y_value,
                self::SIGN => $y_negative
            );
        } elseif ($y_size == 0) {
            return array(
                self::VALUE => $x_value,
                self::SIGN => $x_negative
            );
        }

        // subtract, if appropriate
        if ($x_negative != $y_negative) {
            if ($x_value == $y_value) {
                return array(
                    self::VALUE => array(),
                    self::SIGN => false
                );
            }

            $temp = $this->_subtract($x_value, false, $y_value, false);
            $temp[self::SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ?
                                          $x_negative : $y_negative;

            return $temp;
        }

        if ($x_size < $y_size) {
            $size = $x_size;
            $value = $y_value;
        } else {
            $size = $y_size;
            $value = $x_value;
        }

        $value[count($value)] = 0; // just in case the carry adds an extra digit

        $carry = 0;
        for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) {
            $sum = $x_value[$j] * self::$baseFull + $x_value[$i] + $y_value[$j] * self::$baseFull + $y_value[$i] + $carry;
            $carry = $sum >= self::$maxDigit2; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1
            $sum = $carry ? $sum - self::$maxDigit2 : $sum;

            $temp = self::$base === 26 ? intval($sum / 0x4000000) : ($sum >> 31);

            $value[$i] = (int) ($sum - self::$baseFull * $temp); // eg. a faster alternative to fmod($sum, 0x4000000)
            $value[$j] = $temp;
        }

        if ($j == $size) { // ie. if $y_size is odd
            $sum = $x_value[$i] + $y_value[$i] + $carry;
            $carry = $sum >= self::$baseFull;
            $value[$i] = $carry ? $sum - self::$baseFull : $sum;
            ++$i; // ie. let $i = $j since we've just done $value[$i]
        }

        if ($carry) {
            for (; $value[$i] == self::$maxDigit; ++$i) {
                $value[$i] = 0;
            }
            ++$value[$i];
        }

        return array(
            self::VALUE => $this->_trim($value),
            self::SIGN => $x_negative
        );
    }

    /**
     * Subtracts two BigIntegers.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('10');
     *    $b = new \phpseclib\Math\BigInteger('20');
     *
     *    $c = $a->subtract($b);
     *
     *    echo $c->toString(); // outputs -10
     * ?>
     * </code>
     *
     * @param \phpseclib\Math\BigInteger $y
     * @return \phpseclib\Math\BigInteger
     * @access public
     * @internal Performs base-2**52 subtraction
     */
    function subtract($y)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $temp = new static();
                $temp->value = gmp_sub($this->value, $y->value);

                return $this->_normalize($temp);
            case self::MODE_BCMATH:
                $temp = new static();
                $temp->value = bcsub($this->value, $y->value, 0);

                return $this->_normalize($temp);
        }

        $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative);

        $result = new static();
        $result->value = $temp[self::VALUE];
        $result->is_negative = $temp[self::SIGN];

        return $this->_normalize($result);
    }

    /**
     * Performs subtraction.
     *
     * @param array $x_value
     * @param bool $x_negative
     * @param array $y_value
     * @param bool $y_negative
     * @return array
     * @access private
     */
    function _subtract($x_value, $x_negative, $y_value, $y_negative)
    {
        $x_size = count($x_value);
        $y_size = count($y_value);

        if ($x_size == 0) {
            return array(
                self::VALUE => $y_value,
                self::SIGN => !$y_negative
            );
        } elseif ($y_size == 0) {
            return array(
                self::VALUE => $x_value,
                self::SIGN => $x_negative
            );
        }

        // add, if appropriate (ie. -$x - +$y or +$x - -$y)
        if ($x_negative != $y_negative) {
            $temp = $this->_add($x_value, false, $y_value, false);
            $temp[self::SIGN] = $x_negative;

            return $temp;
        }

        $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative);

        if (!$diff) {
            return array(
                self::VALUE => array(),
                self::SIGN => false
            );
        }

        // switch $x and $y around, if appropriate.
        if ((!$x_negative && $diff < 0) || ($x_negative && $diff > 0)) {
            $temp = $x_value;
            $x_value = $y_value;
            $y_value = $temp;

            $x_negative = !$x_negative;

            $x_size = count($x_value);
            $y_size = count($y_value);
        }

        // at this point, $x_value should be at least as big as - if not bigger than - $y_value

        $carry = 0;
        for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) {
            $sum = $x_value[$j] * self::$baseFull + $x_value[$i] - $y_value[$j] * self::$baseFull - $y_value[$i] - $carry;
            $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1
            $sum = $carry ? $sum + self::$maxDigit2 : $sum;

            $temp = self::$base === 26 ? intval($sum / 0x4000000) : ($sum >> 31);

            $x_value[$i] = (int) ($sum - self::$baseFull * $temp);
            $x_value[$j] = $temp;
        }

        if ($j == $y_size) { // ie. if $y_size is odd
            $sum = $x_value[$i] - $y_value[$i] - $carry;
            $carry = $sum < 0;
            $x_value[$i] = $carry ? $sum + self::$baseFull : $sum;
            ++$i;
        }

        if ($carry) {
            for (; !$x_value[$i]; ++$i) {
                $x_value[$i] = self::$maxDigit;
            }
            --$x_value[$i];
        }

        return array(
            self::VALUE => $this->_trim($x_value),
            self::SIGN => $x_negative
        );
    }

    /**
     * Multiplies two BigIntegers
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('10');
     *    $b = new \phpseclib\Math\BigInteger('20');
     *
     *    $c = $a->multiply($b);
     *
     *    echo $c->toString(); // outputs 200
     * ?>
     * </code>
     *
     * @param \phpseclib\Math\BigInteger $x
     * @return \phpseclib\Math\BigInteger
     * @access public
     */
    function multiply($x)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $temp = new static();
                $temp->value = gmp_mul($this->value, $x->value);

                return $this->_normalize($temp);
            case self::MODE_BCMATH:
                $temp = new static();
                $temp->value = bcmul($this->value, $x->value, 0);

                return $this->_normalize($temp);
        }

        $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative);

        $product = new static();
        $product->value = $temp[self::VALUE];
        $product->is_negative = $temp[self::SIGN];

        return $this->_normalize($product);
    }

    /**
     * Performs multiplication.
     *
     * @param array $x_value
     * @param bool $x_negative
     * @param array $y_value
     * @param bool $y_negative
     * @return array
     * @access private
     */
    function _multiply($x_value, $x_negative, $y_value, $y_negative)
    {
        //if ( $x_value == $y_value ) {
        //    return array(
        //        self::VALUE => $this->_square($x_value),
        //        self::SIGN => $x_sign != $y_value
        //    );
        //}

        $x_length = count($x_value);
        $y_length = count($y_value);

        if (!$x_length || !$y_length) { // a 0 is being multiplied
            return array(
                self::VALUE => array(),
                self::SIGN => false
            );
        }

        return array(
            self::VALUE => min($x_length, $y_length) < 2 * self::KARATSUBA_CUTOFF ?
                $this->_trim($this->_regularMultiply($x_value, $y_value)) :
                $this->_trim($this->_karatsuba($x_value, $y_value)),
            self::SIGN => $x_negative != $y_negative
        );
    }

    /**
     * Performs long multiplication on two BigIntegers
     *
     * Modeled after 'multiply' in MutableBigInteger.java.
     *
     * @param array $x_value
     * @param array $y_value
     * @return array
     * @access private
     */
    function _regularMultiply($x_value, $y_value)
    {
        $x_length = count($x_value);
        $y_length = count($y_value);

        if (!$x_length || !$y_length) { // a 0 is being multiplied
            return array();
        }

        if ($x_length < $y_length) {
            $temp = $x_value;
            $x_value = $y_value;
            $y_value = $temp;

            $x_length = count($x_value);
            $y_length = count($y_value);
        }

        $product_value = $this->_array_repeat(0, $x_length + $y_length);

        // the following for loop could be removed if the for loop following it
        // (the one with nested for loops) initially set $i to 0, but
        // doing so would also make the result in one set of unnecessary adds,
        // since on the outermost loops first pass, $product->value[$k] is going
        // to always be 0

        $carry = 0;

        for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0
            $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0
            $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
            $product_value[$j] = (int) ($temp - self::$baseFull * $carry);
        }

        $product_value[$j] = $carry;

        // the above for loop is what the previous comment was talking about.  the
        // following for loop is the "one with nested for loops"
        for ($i = 1; $i < $y_length; ++$i) {
            $carry = 0;

            for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) {
                $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry;
                $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
                $product_value[$k] = (int) ($temp - self::$baseFull * $carry);
            }

            $product_value[$k] = $carry;
        }

        return $product_value;
    }

    /**
     * Performs Karatsuba multiplication on two BigIntegers
     *
     * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and
     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}.
     *
     * @param array $x_value
     * @param array $y_value
     * @return array
     * @access private
     */
    function _karatsuba($x_value, $y_value)
    {
        $m = min(count($x_value) >> 1, count($y_value) >> 1);

        if ($m < self::KARATSUBA_CUTOFF) {
            return $this->_regularMultiply($x_value, $y_value);
        }

        $x1 = array_slice($x_value, $m);
        $x0 = array_slice($x_value, 0, $m);
        $y1 = array_slice($y_value, $m);
        $y0 = array_slice($y_value, 0, $m);

        $z2 = $this->_karatsuba($x1, $y1);
        $z0 = $this->_karatsuba($x0, $y0);

        $z1 = $this->_add($x1, false, $x0, false);
        $temp = $this->_add($y1, false, $y0, false);
        $z1 = $this->_karatsuba($z1[self::VALUE], $temp[self::VALUE]);
        $temp = $this->_add($z2, false, $z0, false);
        $z1 = $this->_subtract($z1, false, $temp[self::VALUE], false);

        $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2);
        $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]);

        $xy = $this->_add($z2, false, $z1[self::VALUE], $z1[self::SIGN]);
        $xy = $this->_add($xy[self::VALUE], $xy[self::SIGN], $z0, false);

        return $xy[self::VALUE];
    }

    /**
     * Performs squaring
     *
     * @param array $x
     * @return array
     * @access private
     */
    function _square($x = false)
    {
        return count($x) < 2 * self::KARATSUBA_CUTOFF ?
            $this->_trim($this->_baseSquare($x)) :
            $this->_trim($this->_karatsubaSquare($x));
    }

    /**
     * Performs traditional squaring on two BigIntegers
     *
     * Squaring can be done faster than multiplying a number by itself can be.  See
     * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} /
     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information.
     *
     * @param array $value
     * @return array
     * @access private
     */
    function _baseSquare($value)
    {
        if (empty($value)) {
            return array();
        }
        $square_value = $this->_array_repeat(0, 2 * count($value));

        for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) {
            $i2 = $i << 1;

            $temp = $square_value[$i2] + $value[$i] * $value[$i];
            $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
            $square_value[$i2] = (int) ($temp - self::$baseFull * $carry);

            // note how we start from $i+1 instead of 0 as we do in multiplication.
            for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) {
                $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry;
                $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
                $square_value[$k] = (int) ($temp - self::$baseFull * $carry);
            }

            // the following line can yield values larger 2**15.  at this point, PHP should switch
            // over to floats.
            $square_value[$i + $max_index + 1] = $carry;
        }

        return $square_value;
    }

    /**
     * Performs Karatsuba "squaring" on two BigIntegers
     *
     * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and
     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}.
     *
     * @param array $value
     * @return array
     * @access private
     */
    function _karatsubaSquare($value)
    {
        $m = count($value) >> 1;

        if ($m < self::KARATSUBA_CUTOFF) {
            return $this->_baseSquare($value);
        }

        $x1 = array_slice($value, $m);
        $x0 = array_slice($value, 0, $m);

        $z2 = $this->_karatsubaSquare($x1);
        $z0 = $this->_karatsubaSquare($x0);

        $z1 = $this->_add($x1, false, $x0, false);
        $z1 = $this->_karatsubaSquare($z1[self::VALUE]);
        $temp = $this->_add($z2, false, $z0, false);
        $z1 = $this->_subtract($z1, false, $temp[self::VALUE], false);

        $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2);
        $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]);

        $xx = $this->_add($z2, false, $z1[self::VALUE], $z1[self::SIGN]);
        $xx = $this->_add($xx[self::VALUE], $xx[self::SIGN], $z0, false);

        return $xx[self::VALUE];
    }

    /**
     * Divides two BigIntegers.
     *
     * Returns an array whose first element contains the quotient and whose second element contains the
     * "common residue".  If the remainder would be positive, the "common residue" and the remainder are the
     * same.  If the remainder would be negative, the "common residue" is equal to the sum of the remainder
     * and the divisor (basically, the "common residue" is the first positive modulo).
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('10');
     *    $b = new \phpseclib\Math\BigInteger('20');
     *
     *    list($quotient, $remainder) = $a->divide($b);
     *
     *    echo $quotient->toString(); // outputs 0
     *    echo "\r\n";
     *    echo $remainder->toString(); // outputs 10
     * ?>
     * </code>
     *
     * @param \phpseclib\Math\BigInteger $y
     * @return array
     * @access public
     * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}.
     */
    function divide($y)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $quotient = new static();
                $remainder = new static();

                list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value);

                if (gmp_sign($remainder->value) < 0) {
                    $remainder->value = gmp_add($remainder->value, gmp_abs($y->value));
                }

                return array($this->_normalize($quotient), $this->_normalize($remainder));
            case self::MODE_BCMATH:
                $quotient = new static();
                $remainder = new static();

                $quotient->value = bcdiv($this->value, $y->value, 0);
                $remainder->value = bcmod($this->value, $y->value);

                if ($remainder->value[0] == '-') {
                    $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0);
                }

                return array($this->_normalize($quotient), $this->_normalize($remainder));
        }

        if (count($y->value) == 1) {
            list($q, $r) = $this->_divide_digit($this->value, $y->value[0]);
            $quotient = new static();
            $remainder = new static();
            $quotient->value = $q;
            $remainder->value = array($r);
            $quotient->is_negative = $this->is_negative != $y->is_negative;
            return array($this->_normalize($quotient), $this->_normalize($remainder));
        }

        static $zero;
        if (!isset($zero)) {
            $zero = new static();
        }

        $x = $this->copy();
        $y = $y->copy();

        $x_sign = $x->is_negative;
        $y_sign = $y->is_negative;

        $x->is_negative = $y->is_negative = false;

        $diff = $x->compare($y);

        if (!$diff) {
            $temp = new static();
            $temp->value = array(1);
            $temp->is_negative = $x_sign != $y_sign;
            return array($this->_normalize($temp), $this->_normalize(new static()));
        }

        if ($diff < 0) {
            // if $x is negative, "add" $y.
            if ($x_sign) {
                $x = $y->subtract($x);
            }
            return array($this->_normalize(new static()), $this->_normalize($x));
        }

        // normalize $x and $y as described in HAC 14.23 / 14.24
        $msb = $y->value[count($y->value) - 1];
        for ($shift = 0; !($msb & self::$msb); ++$shift) {
            $msb <<= 1;
        }
        $x->_lshift($shift);
        $y->_lshift($shift);
        $y_value = &$y->value;

        $x_max = count($x->value) - 1;
        $y_max = count($y->value) - 1;

        $quotient = new static();
        $quotient_value = &$quotient->value;
        $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1);

        static $temp, $lhs, $rhs;
        if (!isset($temp)) {
            $temp = new static();
            $lhs =  new static();
            $rhs =  new static();
        }
        $temp_value = &$temp->value;
        $rhs_value =  &$rhs->value;

        // $temp = $y << ($x_max - $y_max-1) in base 2**26
        $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value);

        while ($x->compare($temp) >= 0) {
            // calculate the "common residue"
            ++$quotient_value[$x_max - $y_max];
            $x = $x->subtract($temp);
            $x_max = count($x->value) - 1;
        }

        for ($i = $x_max; $i >= $y_max + 1; --$i) {
            $x_value = &$x->value;
            $x_window = array(
                isset($x_value[$i]) ? $x_value[$i] : 0,
                isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0,
                isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0
            );
            $y_window = array(
                $y_value[$y_max],
                ($y_max > 0) ? $y_value[$y_max - 1] : 0
            );

            $q_index = $i - $y_max - 1;
            if ($x_window[0] == $y_window[0]) {
                $quotient_value[$q_index] = self::$maxDigit;
            } else {
                $quotient_value[$q_index] = $this->_safe_divide(
                    $x_window[0] * self::$baseFull + $x_window[1],
                    $y_window[0]
                );
            }

            $temp_value = array($y_window[1], $y_window[0]);

            $lhs->value = array($quotient_value[$q_index]);
            $lhs = $lhs->multiply($temp);

            $rhs_value = array($x_window[2], $x_window[1], $x_window[0]);

            while ($lhs->compare($rhs) > 0) {
                --$quotient_value[$q_index];

                $lhs->value = array($quotient_value[$q_index]);
                $lhs = $lhs->multiply($temp);
            }

            $adjust = $this->_array_repeat(0, $q_index);
            $temp_value = array($quotient_value[$q_index]);
            $temp = $temp->multiply($y);
            $temp_value = &$temp->value;
            $temp_value = array_merge($adjust, $temp_value);

            $x = $x->subtract($temp);

            if ($x->compare($zero) < 0) {
                $temp_value = array_merge($adjust, $y_value);
                $x = $x->add($temp);

                --$quotient_value[$q_index];
            }

            $x_max = count($x_value) - 1;
        }

        // unnormalize the remainder
        $x->_rshift($shift);

        $quotient->is_negative = $x_sign != $y_sign;

        // calculate the "common residue", if appropriate
        if ($x_sign) {
            $y->_rshift($shift);
            $x = $y->subtract($x);
        }

        return array($this->_normalize($quotient), $this->_normalize($x));
    }

    /**
     * Divides a BigInteger by a regular integer
     *
     * abc / x = a00 / x + b0 / x + c / x
     *
     * @param array $dividend
     * @param array $divisor
     * @return array
     * @access private
     */
    function _divide_digit($dividend, $divisor)
    {
        $carry = 0;
        $result = array();

        for ($i = count($dividend) - 1; $i >= 0; --$i) {
            $temp = self::$baseFull * $carry + $dividend[$i];
            $result[$i] = $this->_safe_divide($temp, $divisor);
            $carry = (int) ($temp - $divisor * $result[$i]);
        }

        return array($result, $carry);
    }

    /**
     * Performs modular exponentiation.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger('10');
     *    $b = new \phpseclib\Math\BigInteger('20');
     *    $c = new \phpseclib\Math\BigInteger('30');
     *
     *    $c = $a->modPow($b, $c);
     *
     *    echo $c->toString(); // outputs 10
     * ?>
     * </code>
     *
     * @param \phpseclib\Math\BigInteger $e
     * @param \phpseclib\Math\BigInteger $n
     * @return \phpseclib\Math\BigInteger
     * @access public
     * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and
     *    and although the approach involving repeated squaring does vastly better, it, too, is impractical
     *    for our purposes.  The reason being that division - by far the most complicated and time-consuming
     *    of the basic operations (eg. +,-,*,/) - occurs multiple times within it.
     *
     *    Modular reductions resolve this issue.  Although an individual modular reduction takes more time
     *    then an individual division, when performed in succession (with the same modulo), they're a lot faster.
     *
     *    The two most commonly used modular reductions are Barrett and Montgomery reduction.  Montgomery reduction,
     *    although faster, only works when the gcd of the modulo and of the base being used is 1.  In RSA, when the
     *    base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because
     *    the product of two odd numbers is odd), but what about when RSA isn't used?
     *
     *    In contrast, Barrett reduction has no such constraint.  As such, some bigint implementations perform a
     *    Barrett reduction after every operation in the modpow function.  Others perform Barrett reductions when the
     *    modulo is even and Montgomery reductions when the modulo is odd.  BigInteger.java's modPow method, however,
     *    uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and
     *    the other, a power of two - and recombine them, later.  This is the method that this modPow function uses.
     *    {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates.
     */
    function modPow($e, $n)
    {
        $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs();

        if ($e->compare(new static()) < 0) {
            $e = $e->abs();

            $temp = $this->modInverse($n);
            if ($temp === false) {
                return false;
            }

            return $this->_normalize($temp->modPow($e, $n));
        }

        if (MATH_BIGINTEGER_MODE == self::MODE_GMP) {
            $temp = new static();
            $temp->value = gmp_powm($this->value, $e->value, $n->value);

            return $this->_normalize($temp);
        }

        if ($this->compare(new static()) < 0 || $this->compare($n) > 0) {
            list(, $temp) = $this->divide($n);
            return $temp->modPow($e, $n);
        }

        if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
            $components = array(
                'modulus' => $n->toBytes(true),
                'publicExponent' => $e->toBytes(true)
            );

            $components = array(
                'modulus' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['modulus'])), $components['modulus']),
                'publicExponent' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent'])
            );

            $RSAPublicKey = pack(
                'Ca*a*a*',
                48,
                $this->_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])),
                $components['modulus'],
                $components['publicExponent']
            );

            $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
            $RSAPublicKey = chr(0) . $RSAPublicKey;
            $RSAPublicKey = chr(3) . $this->_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey;

            $encapsulated = pack(
                'Ca*a*',
                48,
                $this->_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)),
                $rsaOID . $RSAPublicKey
            );

            $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
                             chunk_split(base64_encode($encapsulated)) .
                             '-----END PUBLIC KEY-----';

            $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT);

            if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING)) {
                return new static($result, 256);
            }
        }

        if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) {
            $temp = new static();
            $temp->value = bcpowmod($this->value, $e->value, $n->value, 0);

            return $this->_normalize($temp);
        }

        if (empty($e->value)) {
            $temp = new static();
            $temp->value = array(1);
            return $this->_normalize($temp);
        }

        if ($e->value == array(1)) {
            list(, $temp) = $this->divide($n);
            return $this->_normalize($temp);
        }

        if ($e->value == array(2)) {
            $temp = new static();
            $temp->value = $this->_square($this->value);
            list(, $temp) = $temp->divide($n);
            return $this->_normalize($temp);
        }

        return $this->_normalize($this->_slidingWindow($e, $n, self::BARRETT));

        // the following code, although not callable, can be run independently of the above code
        // although the above code performed better in my benchmarks the following could might
        // perform better under different circumstances. in lieu of deleting it it's just been
        // made uncallable

        // is the modulo odd?
        if ($n->value[0] & 1) {
            return $this->_normalize($this->_slidingWindow($e, $n, self::MONTGOMERY));
        }
        // if it's not, it's even

        // find the lowest set bit (eg. the max pow of 2 that divides $n)
        for ($i = 0; $i < count($n->value); ++$i) {
            if ($n->value[$i]) {
                $temp = decbin($n->value[$i]);
                $j = strlen($temp) - strrpos($temp, '1') - 1;
                $j+= 26 * $i;
                break;
            }
        }
        // at this point, 2^$j * $n/(2^$j) == $n

        $mod1 = $n->copy();
        $mod1->_rshift($j);
        $mod2 = new static();
        $mod2->value = array(1);
        $mod2->_lshift($j);

        $part1 = ($mod1->value != array(1)) ? $this->_slidingWindow($e, $mod1, self::MONTGOMERY) : new static();
        $part2 = $this->_slidingWindow($e, $mod2, self::POWEROF2);

        $y1 = $mod2->modInverse($mod1);
        $y2 = $mod1->modInverse($mod2);

        $result = $part1->multiply($mod2);
        $result = $result->multiply($y1);

        $temp = $part2->multiply($mod1);
        $temp = $temp->multiply($y2);

        $result = $result->add($temp);
        list(, $result) = $result->divide($n);

        return $this->_normalize($result);
    }

    /**
     * Performs modular exponentiation.
     *
     * Alias for modPow().
     *
     * @param \phpseclib\Math\BigInteger $e
     * @param \phpseclib\Math\BigInteger $n
     * @return \phpseclib\Math\BigInteger
     * @access public
     */
    function powMod($e, $n)
    {
        return $this->modPow($e, $n);
    }

    /**
     * Sliding Window k-ary Modular Exponentiation
     *
     * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} /
     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}.  In a departure from those algorithims,
     * however, this function performs a modular reduction after every multiplication and squaring operation.
     * As such, this function has the same preconditions that the reductions being used do.
     *
     * @param \phpseclib\Math\BigInteger $e
     * @param \phpseclib\Math\BigInteger $n
     * @param int $mode
     * @return \phpseclib\Math\BigInteger
     * @access private
     */
    function _slidingWindow($e, $n, $mode)
    {
        static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function
        //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1

        $e_value = $e->value;
        $e_length = count($e_value) - 1;
        $e_bits = decbin($e_value[$e_length]);
        for ($i = $e_length - 1; $i >= 0; --$i) {
            $e_bits.= str_pad(decbin($e_value[$i]), self::$base, '0', STR_PAD_LEFT);
        }

        $e_length = strlen($e_bits);

        // calculate the appropriate window size.
        // $window_size == 3 if $window_ranges is between 25 and 81, for example.
        for ($i = 0, $window_size = 1; $i < count($window_ranges) && $e_length > $window_ranges[$i]; ++$window_size, ++$i) {
        }

        $n_value = $n->value;

        // precompute $this^0 through $this^$window_size
        $powers = array();
        $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode);
        $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode);

        // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end
        // in a 1.  ie. it's supposed to be odd.
        $temp = 1 << ($window_size - 1);
        for ($i = 1; $i < $temp; ++$i) {
            $i2 = $i << 1;
            $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode);
        }

        $result = array(1);
        $result = $this->_prepareReduce($result, $n_value, $mode);

        for ($i = 0; $i < $e_length;) {
            if (!$e_bits[$i]) {
                $result = $this->_squareReduce($result, $n_value, $mode);
                ++$i;
            } else {
                for ($j = $window_size - 1; $j > 0; --$j) {
                    if (!empty($e_bits[$i + $j])) {
                        break;
                    }
                }

                // eg. the length of substr($e_bits, $i, $j + 1)
                for ($k = 0; $k <= $j; ++$k) {
                    $result = $this->_squareReduce($result, $n_value, $mode);
                }

                $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode);

                $i += $j + 1;
            }
        }

        $temp = new static();
        $temp->value = $this->_reduce($result, $n_value, $mode);

        return $temp;
    }

    /**
     * Modular reduction
     *
     * For most $modes this will return the remainder.
     *
     * @see self::_slidingWindow()
     * @access private
     * @param array $x
     * @param array $n
     * @param int $mode
     * @return array
     */
    function _reduce($x, $n, $mode)
    {
        switch ($mode) {
            case self::MONTGOMERY:
                return $this->_montgomery($x, $n);
            case self::BARRETT:
                return $this->_barrett($x, $n);
            case self::POWEROF2:
                $lhs = new static();
                $lhs->value = $x;
                $rhs = new static();
                $rhs->value = $n;
                return $x->_mod2($n);
            case self::CLASSIC:
                $lhs = new static();
                $lhs->value = $x;
                $rhs = new static();
                $rhs->value = $n;
                list(, $temp) = $lhs->divide($rhs);
                return $temp->value;
            case self::NONE:
                return $x;
            default:
                // an invalid $mode was provided
        }
    }

    /**
     * Modular reduction preperation
     *
     * @see self::_slidingWindow()
     * @access private
     * @param array $x
     * @param array $n
     * @param int $mode
     * @return array
     */
    function _prepareReduce($x, $n, $mode)
    {
        if ($mode == self::MONTGOMERY) {
            return $this->_prepMontgomery($x, $n);
        }
        return $this->_reduce($x, $n, $mode);
    }

    /**
     * Modular multiply
     *
     * @see self::_slidingWindow()
     * @access private
     * @param array $x
     * @param array $y
     * @param array $n
     * @param int $mode
     * @return array
     */
    function _multiplyReduce($x, $y, $n, $mode)
    {
        if ($mode == self::MONTGOMERY) {
            return $this->_montgomeryMultiply($x, $y, $n);
        }
        $temp = $this->_multiply($x, false, $y, false);
        return $this->_reduce($temp[self::VALUE], $n, $mode);
    }

    /**
     * Modular square
     *
     * @see self::_slidingWindow()
     * @access private
     * @param array $x
     * @param array $n
     * @param int $mode
     * @return array
     */
    function _squareReduce($x, $n, $mode)
    {
        if ($mode == self::MONTGOMERY) {
            return $this->_montgomeryMultiply($x, $x, $n);
        }
        return $this->_reduce($this->_square($x), $n, $mode);
    }

    /**
     * Modulos for Powers of Two
     *
     * Calculates $x%$n, where $n = 2**$e, for some $e.  Since this is basically the same as doing $x & ($n-1),
     * we'll just use this function as a wrapper for doing that.
     *
     * @see self::_slidingWindow()
     * @access private
     * @param \phpseclib\Math\BigInteger
     * @return \phpseclib\Math\BigInteger
     */
    function _mod2($n)
    {
        $temp = new static();
        $temp->value = array(1);
        return $this->bitwise_and($n->subtract($temp));
    }

    /**
     * Barrett Modular Reduction
     *
     * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} /
     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information.  Modified slightly,
     * so as not to require negative numbers (initially, this script didn't support negative numbers).
     *
     * Employs "folding", as described at
     * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}.  To quote from
     * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x."
     *
     * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that
     * usable on account of (1) its not using reasonable radix points as discussed in
     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable
     * radix points, it only works when there are an even number of digits in the denominator.  The reason for (2) is that
     * (x >> 1) + (x >> 1) != x / 2 + x / 2.  If x is even, they're the same, but if x is odd, they're not.  See the in-line
     * comments for details.
     *
     * @see self::_slidingWindow()
     * @access private
     * @param array $n
     * @param array $m
     * @return array
     */
    function _barrett($n, $m)
    {
        static $cache = array(
            self::VARIABLE => array(),
            self::DATA => array()
        );

        $m_length = count($m);

        // if ($this->_compare($n, $this->_square($m)) >= 0) {
        if (count($n) > 2 * $m_length) {
            $lhs = new static();
            $rhs = new static();
            $lhs->value = $n;
            $rhs->value = $m;
            list(, $temp) = $lhs->divide($rhs);
            return $temp->value;
        }

        // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced
        if ($m_length < 5) {
            return $this->_regularBarrett($n, $m);
        }

        // n = 2 * m.length

        if (($key = array_search($m, $cache[self::VARIABLE])) === false) {
            $key = count($cache[self::VARIABLE]);
            $cache[self::VARIABLE][] = $m;

            $lhs = new static();
            $lhs_value = &$lhs->value;
            $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1));
            $lhs_value[] = 1;
            $rhs = new static();
            $rhs->value = $m;

            list($u, $m1) = $lhs->divide($rhs);
            $u = $u->value;
            $m1 = $m1->value;

            $cache[self::DATA][] = array(
                'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1)
                'm1'=> $m1 // m.length
            );
        } else {
            extract($cache[self::DATA][$key]);
        }

        $cutoff = $m_length + ($m_length >> 1);
        $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1)
        $msd = array_slice($n, $cutoff);    // m.length >> 1
        $lsd = $this->_trim($lsd);
        $temp = $this->_multiply($msd, false, $m1, false);
        $n = $this->_add($lsd, false, $temp[self::VALUE], false); // m.length + (m.length >> 1) + 1

        if ($m_length & 1) {
            return $this->_regularBarrett($n[self::VALUE], $m);
        }

        // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2
        $temp = array_slice($n[self::VALUE], $m_length - 1);
        // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2
        // if odd:  ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1
        $temp = $this->_multiply($temp, false, $u, false);
        // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1
        // if odd:  (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1)
        $temp = array_slice($temp[self::VALUE], ($m_length >> 1) + 1);
        // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1
        // if odd:  (m.length - (m.length >> 1)) + m.length     = 2 * m.length - (m.length >> 1)
        $temp = $this->_multiply($temp, false, $m, false);

        // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit
        // number from a m.length + (m.length >> 1) + 1 digit number.  ie. there'd be an extra digit and the while loop
        // following this comment would loop a lot (hence our calling _regularBarrett() in that situation).

        $result = $this->_subtract($n[self::VALUE], false, $temp[self::VALUE], false);

        while ($this->_compare($result[self::VALUE], $result[self::SIGN], $m, false) >= 0) {
            $result = $this->_subtract($result[self::VALUE], $result[self::SIGN], $m, false);
        }

        return $result[self::VALUE];
    }

    /**
     * (Regular) Barrett Modular Reduction
     *
     * For numbers with more than four digits BigInteger::_barrett() is faster.  The difference between that and this
     * is that this function does not fold the denominator into a smaller form.
     *
     * @see self::_slidingWindow()
     * @access private
     * @param array $x
     * @param array $n
     * @return array
     */
    function _regularBarrett($x, $n)
    {
        static $cache = array(
            self::VARIABLE => array(),
            self::DATA => array()
        );

        $n_length = count($n);

        if (count($x) > 2 * $n_length) {
            $lhs = new static();
            $rhs = new static();
            $lhs->value = $x;
            $rhs->value = $n;
            list(, $temp) = $lhs->divide($rhs);
            return $temp->value;
        }

        if (($key = array_search($n, $cache[self::VARIABLE])) === false) {
            $key = count($cache[self::VARIABLE]);
            $cache[self::VARIABLE][] = $n;
            $lhs = new static();
            $lhs_value = &$lhs->value;
            $lhs_value = $this->_array_repeat(0, 2 * $n_length);
            $lhs_value[] = 1;
            $rhs = new static();
            $rhs->value = $n;
            list($temp, ) = $lhs->divide($rhs); // m.length
            $cache[self::DATA][] = $temp->value;
        }

        // 2 * m.length - (m.length - 1) = m.length + 1
        $temp = array_slice($x, $n_length - 1);
        // (m.length + 1) + m.length = 2 * m.length + 1
        $temp = $this->_multiply($temp, false, $cache[self::DATA][$key], false);
        // (2 * m.length + 1) - (m.length - 1) = m.length + 2
        $temp = array_slice($temp[self::VALUE], $n_length + 1);

        // m.length + 1
        $result = array_slice($x, 0, $n_length + 1);
        // m.length + 1
        $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1);
        // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1)

        if ($this->_compare($result, false, $temp[self::VALUE], $temp[self::SIGN]) < 0) {
            $corrector_value = $this->_array_repeat(0, $n_length + 1);
            $corrector_value[count($corrector_value)] = 1;
            $result = $this->_add($result, false, $corrector_value, false);
            $result = $result[self::VALUE];
        }

        // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits
        $result = $this->_subtract($result, false, $temp[self::VALUE], $temp[self::SIGN]);
        while ($this->_compare($result[self::VALUE], $result[self::SIGN], $n, false) > 0) {
            $result = $this->_subtract($result[self::VALUE], $result[self::SIGN], $n, false);
        }

        return $result[self::VALUE];
    }

    /**
     * Performs long multiplication up to $stop digits
     *
     * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved.
     *
     * @see self::_regularBarrett()
     * @param array $x_value
     * @param bool $x_negative
     * @param array $y_value
     * @param bool $y_negative
     * @param int $stop
     * @return array
     * @access private
     */
    function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop)
    {
        $x_length = count($x_value);
        $y_length = count($y_value);

        if (!$x_length || !$y_length) { // a 0 is being multiplied
            return array(
                self::VALUE => array(),
                self::SIGN => false
            );
        }

        if ($x_length < $y_length) {
            $temp = $x_value;
            $x_value = $y_value;
            $y_value = $temp;

            $x_length = count($x_value);
            $y_length = count($y_value);
        }

        $product_value = $this->_array_repeat(0, $x_length + $y_length);

        // the following for loop could be removed if the for loop following it
        // (the one with nested for loops) initially set $i to 0, but
        // doing so would also make the result in one set of unnecessary adds,
        // since on the outermost loops first pass, $product->value[$k] is going
        // to always be 0

        $carry = 0;

        for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i
            $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0
            $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
            $product_value[$j] = (int) ($temp - self::$baseFull * $carry);
        }

        if ($j < $stop) {
            $product_value[$j] = $carry;
        }

        // the above for loop is what the previous comment was talking about.  the
        // following for loop is the "one with nested for loops"

        for ($i = 1; $i < $y_length; ++$i) {
            $carry = 0;

            for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) {
                $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry;
                $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
                $product_value[$k] = (int) ($temp - self::$baseFull * $carry);
            }

            if ($k < $stop) {
                $product_value[$k] = $carry;
            }
        }

        return array(
            self::VALUE => $this->_trim($product_value),
            self::SIGN => $x_negative != $y_negative
        );
    }

    /**
     * Montgomery Modular Reduction
     *
     * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n.
     * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be
     * improved upon (basically, by using the comba method).  gcd($n, 2) must be equal to one for this function
     * to work correctly.
     *
     * @see self::_prepMontgomery()
     * @see self::_slidingWindow()
     * @access private
     * @param array $x
     * @param array $n
     * @return array
     */
    function _montgomery($x, $n)
    {
        static $cache = array(
            self::VARIABLE => array(),
            self::DATA => array()
        );

        if (($key = array_search($n, $cache[self::VARIABLE])) === false) {
            $key = count($cache[self::VARIABLE]);
            $cache[self::VARIABLE][] = $x;
            $cache[self::DATA][] = $this->_modInverse67108864($n);
        }

        $k = count($n);

        $result = array(self::VALUE => $x);

        for ($i = 0; $i < $k; ++$i) {
            $temp = $result[self::VALUE][$i] * $cache[self::DATA][$key];
            $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
            $temp = $this->_regularMultiply(array($temp), $n);
            $temp = array_merge($this->_array_repeat(0, $i), $temp);
            $result = $this->_add($result[self::VALUE], false, $temp, false);
        }

        $result[self::VALUE] = array_slice($result[self::VALUE], $k);

        if ($this->_compare($result, false, $n, false) >= 0) {
            $result = $this->_subtract($result[self::VALUE], false, $n, false);
        }

        return $result[self::VALUE];
    }

    /**
     * Montgomery Multiply
     *
     * Interleaves the montgomery reduction and long multiplication algorithms together as described in
     * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36}
     *
     * @see self::_prepMontgomery()
     * @see self::_montgomery()
     * @access private
     * @param array $x
     * @param array $y
     * @param array $m
     * @return array
     */
    function _montgomeryMultiply($x, $y, $m)
    {
        $temp = $this->_multiply($x, false, $y, false);
        return $this->_montgomery($temp[self::VALUE], $m);

        // the following code, although not callable, can be run independently of the above code
        // although the above code performed better in my benchmarks the following could might
        // perform better under different circumstances. in lieu of deleting it it's just been
        // made uncallable

        static $cache = array(
            self::VARIABLE => array(),
            self::DATA => array()
        );

        if (($key = array_search($m, $cache[self::VARIABLE])) === false) {
            $key = count($cache[self::VARIABLE]);
            $cache[self::VARIABLE][] = $m;
            $cache[self::DATA][] = $this->_modInverse67108864($m);
        }

        $n = max(count($x), count($y), count($m));
        $x = array_pad($x, $n, 0);
        $y = array_pad($y, $n, 0);
        $m = array_pad($m, $n, 0);
        $a = array(self::VALUE => $this->_array_repeat(0, $n + 1));
        for ($i = 0; $i < $n; ++$i) {
            $temp = $a[self::VALUE][0] + $x[$i] * $y[0];
            $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
            $temp = $temp * $cache[self::DATA][$key];
            $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
            $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false);
            $a = $this->_add($a[self::VALUE], false, $temp[self::VALUE], false);
            $a[self::VALUE] = array_slice($a[self::VALUE], 1);
        }
        if ($this->_compare($a[self::VALUE], false, $m, false) >= 0) {
            $a = $this->_subtract($a[self::VALUE], false, $m, false);
        }
        return $a[self::VALUE];
    }

    /**
     * Prepare a number for use in Montgomery Modular Reductions
     *
     * @see self::_montgomery()
     * @see self::_slidingWindow()
     * @access private
     * @param array $x
     * @param array $n
     * @return array
     */
    function _prepMontgomery($x, $n)
    {
        $lhs = new static();
        $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x);
        $rhs = new static();
        $rhs->value = $n;

        list(, $temp) = $lhs->divide($rhs);
        return $temp->value;
    }

    /**
     * Modular Inverse of a number mod 2**26 (eg. 67108864)
     *
     * Based off of the bnpInvDigit function implemented and justified in the following URL:
     *
     * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js}
     *
     * The following URL provides more info:
     *
     * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85}
     *
     * As for why we do all the bitmasking...  strange things can happen when converting from floats to ints. For
     * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields
     * int(-2147483648).  To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't
     * auto-converted to floats.  The outermost bitmask is present because without it, there's no guarantee that
     * the "residue" returned would be the so-called "common residue".  We use fmod, in the last step, because the
     * maximum possible $x is 26 bits and the maximum $result is 16 bits.  Thus, we have to be able to handle up to
     * 40 bits, which only 64-bit floating points will support.
     *
     * Thanks to Pedro Gimeno Fortea for input!
     *
     * @see self::_montgomery()
     * @access private
     * @param array $x
     * @return int
     */
    function _modInverse67108864($x) // 2**26 == 67,108,864
    {
        $x = -$x[0];
        $result = $x & 0x3; // x**-1 mod 2**2
        $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4
        $result = ($result * (2 - ($x & 0xFF) * $result))  & 0xFF; // x**-1 mod 2**8
        $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16
        $result = fmod($result * (2 - fmod($x * $result, self::$baseFull)), self::$baseFull); // x**-1 mod 2**26
        return $result & self::$maxDigit;
    }

    /**
     * Calculates modular inverses.
     *
     * Say you have (30 mod 17 * x mod 17) mod 17 == 1.  x can be found using modular inverses.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger(30);
     *    $b = new \phpseclib\Math\BigInteger(17);
     *
     *    $c = $a->modInverse($b);
     *    echo $c->toString(); // outputs 4
     *
     *    echo "\r\n";
     *
     *    $d = $a->multiply($c);
     *    list(, $d) = $d->divide($b);
     *    echo $d; // outputs 1 (as per the definition of modular inverse)
     * ?>
     * </code>
     *
     * @param \phpseclib\Math\BigInteger $n
     * @return \phpseclib\Math\BigInteger|false
     * @access public
     * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information.
     */
    function modInverse($n)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $temp = new static();
                $temp->value = gmp_invert($this->value, $n->value);

                return ($temp->value === false) ? false : $this->_normalize($temp);
        }

        static $zero, $one;
        if (!isset($zero)) {
            $zero = new static();
            $one = new static(1);
        }

        // $x mod -$n == $x mod $n.
        $n = $n->abs();

        if ($this->compare($zero) < 0) {
            $temp = $this->abs();
            $temp = $temp->modInverse($n);
            return $this->_normalize($n->subtract($temp));
        }

        extract($this->extendedGCD($n));

        if (!$gcd->equals($one)) {
            return false;
        }

        $x = $x->compare($zero) < 0 ? $x->add($n) : $x;

        return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x);
    }

    /**
     * Calculates the greatest common divisor and Bezout's identity.
     *
     * Say you have 693 and 609.  The GCD is 21.  Bezout's identity states that there exist integers x and y such that
     * 693*x + 609*y == 21.  In point of fact, there are actually an infinite number of x and y combinations and which
     * combination is returned is dependent upon which mode is in use.  See
     * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger(693);
     *    $b = new \phpseclib\Math\BigInteger(609);
     *
     *    extract($a->extendedGCD($b));
     *
     *    echo $gcd->toString() . "\r\n"; // outputs 21
     *    echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21
     * ?>
     * </code>
     *
     * @param \phpseclib\Math\BigInteger $n
     * @return \phpseclib\Math\BigInteger
     * @access public
     * @internal Calculates the GCD using the binary xGCD algorithim described in
     *    {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}.  As the text above 14.61 notes,
     *    the more traditional algorithim requires "relatively costly multiple-precision divisions".
     */
    function extendedGCD($n)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                extract(gmp_gcdext($this->value, $n->value));

                return array(
                    'gcd' => $this->_normalize(new static($g)),
                    'x'   => $this->_normalize(new static($s)),
                    'y'   => $this->_normalize(new static($t))
                );
            case self::MODE_BCMATH:
                // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works
                // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway.  as is,
                // the basic extended euclidean algorithim is what we're using.

                $u = $this->value;
                $v = $n->value;

                $a = '1';
                $b = '0';
                $c = '0';
                $d = '1';

                while (bccomp($v, '0', 0) != 0) {
                    $q = bcdiv($u, $v, 0);

                    $temp = $u;
                    $u = $v;
                    $v = bcsub($temp, bcmul($v, $q, 0), 0);

                    $temp = $a;
                    $a = $c;
                    $c = bcsub($temp, bcmul($a, $q, 0), 0);

                    $temp = $b;
                    $b = $d;
                    $d = bcsub($temp, bcmul($b, $q, 0), 0);
                }

                return array(
                    'gcd' => $this->_normalize(new static($u)),
                    'x'   => $this->_normalize(new static($a)),
                    'y'   => $this->_normalize(new static($b))
                );
        }

        $y = $n->copy();
        $x = $this->copy();
        $g = new static();
        $g->value = array(1);

        while (!(($x->value[0] & 1)|| ($y->value[0] & 1))) {
            $x->_rshift(1);
            $y->_rshift(1);
            $g->_lshift(1);
        }

        $u = $x->copy();
        $v = $y->copy();

        $a = new static();
        $b = new static();
        $c = new static();
        $d = new static();

        $a->value = $d->value = $g->value = array(1);
        $b->value = $c->value = array();

        while (!empty($u->value)) {
            while (!($u->value[0] & 1)) {
                $u->_rshift(1);
                if ((!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1))) {
                    $a = $a->add($y);
                    $b = $b->subtract($x);
                }
                $a->_rshift(1);
                $b->_rshift(1);
            }

            while (!($v->value[0] & 1)) {
                $v->_rshift(1);
                if ((!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1))) {
                    $c = $c->add($y);
                    $d = $d->subtract($x);
                }
                $c->_rshift(1);
                $d->_rshift(1);
            }

            if ($u->compare($v) >= 0) {
                $u = $u->subtract($v);
                $a = $a->subtract($c);
                $b = $b->subtract($d);
            } else {
                $v = $v->subtract($u);
                $c = $c->subtract($a);
                $d = $d->subtract($b);
            }
        }

        return array(
            'gcd' => $this->_normalize($g->multiply($v)),
            'x'   => $this->_normalize($c),
            'y'   => $this->_normalize($d)
        );
    }

    /**
     * Calculates the greatest common divisor
     *
     * Say you have 693 and 609.  The GCD is 21.
     *
     * Here's an example:
     * <code>
     * <?php
     *    $a = new \phpseclib\Math\BigInteger(693);
     *    $b = new \phpseclib\Math\BigInteger(609);
     *
     *    $gcd = a->extendedGCD($b);
     *
     *    echo $gcd->toString() . "\r\n"; // outputs 21
     * ?>
     * </code>
     *
     * @param \phpseclib\Math\BigInteger $n
     * @return \phpseclib\Math\BigInteger
     * @access public
     */
    function gcd($n)
    {
        extract($this->extendedGCD($n));
        return $gcd;
    }

    /**
     * Absolute value.
     *
     * @return \phpseclib\Math\BigInteger
     * @access public
     */
    function abs()
    {
        $temp = new static();

        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $temp->value = gmp_abs($this->value);
                break;
            case self::MODE_BCMATH:
                $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value;
                break;
            default:
                $temp->value = $this->value;
        }

        return $temp;
    }

    /**
     * Compares two numbers.
     *
     * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite.  The reason for this is
     * demonstrated thusly:
     *
     * $x  > $y: $x->compare($y)  > 0
     * $x  < $y: $x->compare($y)  < 0
     * $x == $y: $x->compare($y) == 0
     *
     * Note how the same comparison operator is used.  If you want to test for equality, use $x->equals($y).
     *
     * @param \phpseclib\Math\BigInteger $y
     * @return int < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal.
     * @access public
     * @see self::equals()
     * @internal Could return $this->subtract($x), but that's not as fast as what we do do.
     */
    function compare($y)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                return gmp_cmp($this->value, $y->value);
            case self::MODE_BCMATH:
                return bccomp($this->value, $y->value, 0);
        }

        return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative);
    }

    /**
     * Compares two numbers.
     *
     * @param array $x_value
     * @param bool $x_negative
     * @param array $y_value
     * @param bool $y_negative
     * @return int
     * @see self::compare()
     * @access private
     */
    function _compare($x_value, $x_negative, $y_value, $y_negative)
    {
        if ($x_negative != $y_negative) {
            return (!$x_negative && $y_negative) ? 1 : -1;
        }

        $result = $x_negative ? -1 : 1;

        if (count($x_value) != count($y_value)) {
            return (count($x_value) > count($y_value)) ? $result : -$result;
        }
        $size = max(count($x_value), count($y_value));

        $x_value = array_pad($x_value, $size, 0);
        $y_value = array_pad($y_value, $size, 0);

        for ($i = count($x_value) - 1; $i >= 0; --$i) {
            if ($x_value[$i] != $y_value[$i]) {
                return ($x_value[$i] > $y_value[$i]) ? $result : -$result;
            }
        }

        return 0;
    }

    /**
     * Tests the equality of two numbers.
     *
     * If you need to see if one number is greater than or less than another number, use BigInteger::compare()
     *
     * @param \phpseclib\Math\BigInteger $x
     * @return bool
     * @access public
     * @see self::compare()
     */
    function equals($x)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                return gmp_cmp($this->value, $x->value) == 0;
            default:
                return $this->value === $x->value && $this->is_negative == $x->is_negative;
        }
    }

    /**
     * Set Precision
     *
     * Some bitwise operations give different results depending on the precision being used.  Examples include left
     * shift, not, and rotates.
     *
     * @param int $bits
     * @access public
     */
    function setPrecision($bits)
    {
        $this->precision = $bits;
        if (MATH_BIGINTEGER_MODE != self::MODE_BCMATH) {
            $this->bitmask = new static(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256);
        } else {
            $this->bitmask = new static(bcpow('2', $bits, 0));
        }

        $temp = $this->_normalize($this);
        $this->value = $temp->value;
    }

    /**
     * Logical And
     *
     * @param \phpseclib\Math\BigInteger $x
     * @access public
     * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
     * @return \phpseclib\Math\BigInteger
     */
    function bitwise_and($x)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $temp = new static();
                $temp->value = gmp_and($this->value, $x->value);

                return $this->_normalize($temp);
            case self::MODE_BCMATH:
                $left = $this->toBytes();
                $right = $x->toBytes();

                $length = max(strlen($left), strlen($right));

                $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
                $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);

                return $this->_normalize(new static($left & $right, 256));
        }

        $result = $this->copy();

        $length = min(count($x->value), count($this->value));

        $result->value = array_slice($result->value, 0, $length);

        for ($i = 0; $i < $length; ++$i) {
            $result->value[$i]&= $x->value[$i];
        }

        return $this->_normalize($result);
    }

    /**
     * Logical Or
     *
     * @param \phpseclib\Math\BigInteger $x
     * @access public
     * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
     * @return \phpseclib\Math\BigInteger
     */
    function bitwise_or($x)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $temp = new static();
                $temp->value = gmp_or($this->value, $x->value);

                return $this->_normalize($temp);
            case self::MODE_BCMATH:
                $left = $this->toBytes();
                $right = $x->toBytes();

                $length = max(strlen($left), strlen($right));

                $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
                $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);

                return $this->_normalize(new static($left | $right, 256));
        }

        $length = max(count($this->value), count($x->value));
        $result = $this->copy();
        $result->value = array_pad($result->value, $length, 0);
        $x->value = array_pad($x->value, $length, 0);

        for ($i = 0; $i < $length; ++$i) {
            $result->value[$i]|= $x->value[$i];
        }

        return $this->_normalize($result);
    }

    /**
     * Logical Exclusive-Or
     *
     * @param \phpseclib\Math\BigInteger $x
     * @access public
     * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
     * @return \phpseclib\Math\BigInteger
     */
    function bitwise_xor($x)
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                $temp = new static();
                $temp->value = gmp_xor($this->value, $x->value);

                return $this->_normalize($temp);
            case self::MODE_BCMATH:
                $left = $this->toBytes();
                $right = $x->toBytes();

                $length = max(strlen($left), strlen($right));

                $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
                $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);

                return $this->_normalize(new static($left ^ $right, 256));
        }

        $length = max(count($this->value), count($x->value));
        $result = $this->copy();
        $result->value = array_pad($result->value, $length, 0);
        $x->value = array_pad($x->value, $length, 0);

        for ($i = 0; $i < $length; ++$i) {
            $result->value[$i]^= $x->value[$i];
        }

        return $this->_normalize($result);
    }

    /**
     * Logical Not
     *
     * @access public
     * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
     * @return \phpseclib\Math\BigInteger
     */
    function bitwise_not()
    {
        // calculuate "not" without regard to $this->precision
        // (will always result in a smaller number.  ie. ~1 isn't 1111 1110 - it's 0)
        $temp = $this->toBytes();
        if ($temp == '') {
            return '';
        }
        $pre_msb = decbin(ord($temp[0]));
        $temp = ~$temp;
        $msb = decbin(ord($temp[0]));
        if (strlen($msb) == 8) {
            $msb = substr($msb, strpos($msb, '0'));
        }
        $temp[0] = chr(bindec($msb));

        // see if we need to add extra leading 1's
        $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8;
        $new_bits = $this->precision - $current_bits;
        if ($new_bits <= 0) {
            return $this->_normalize(new static($temp, 256));
        }

        // generate as many leading 1's as we need to.
        $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3);
        $this->_base256_lshift($leading_ones, $current_bits);

        $temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT);

        return $this->_normalize(new static($leading_ones | $temp, 256));
    }

    /**
     * Logical Right Shift
     *
     * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift.
     *
     * @param int $shift
     * @return \phpseclib\Math\BigInteger
     * @access public
     * @internal The only version that yields any speed increases is the internal version.
     */
    function bitwise_rightShift($shift)
    {
        $temp = new static();

        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                static $two;

                if (!isset($two)) {
                    $two = gmp_init('2');
                }

                $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift));

                break;
            case self::MODE_BCMATH:
                $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0);

                break;
            default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten
                     // and I don't want to do that...
                $temp->value = $this->value;
                $temp->_rshift($shift);
        }

        return $this->_normalize($temp);
    }

    /**
     * Logical Left Shift
     *
     * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift.
     *
     * @param int $shift
     * @return \phpseclib\Math\BigInteger
     * @access public
     * @internal The only version that yields any speed increases is the internal version.
     */
    function bitwise_leftShift($shift)
    {
        $temp = new static();

        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                static $two;

                if (!isset($two)) {
                    $two = gmp_init('2');
                }

                $temp->value = gmp_mul($this->value, gmp_pow($two, $shift));

                break;
            case self::MODE_BCMATH:
                $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0);

                break;
            default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten
                     // and I don't want to do that...
                $temp->value = $this->value;
                $temp->_lshift($shift);
        }

        return $this->_normalize($temp);
    }

    /**
     * Logical Left Rotate
     *
     * Instead of the top x bits being dropped they're appended to the shifted bit string.
     *
     * @param int $shift
     * @return \phpseclib\Math\BigInteger
     * @access public
     */
    function bitwise_leftRotate($shift)
    {
        $bits = $this->toBytes();

        if ($this->precision > 0) {
            $precision = $this->precision;
            if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) {
                $mask = $this->bitmask->subtract(new static(1));
                $mask = $mask->toBytes();
            } else {
                $mask = $this->bitmask->toBytes();
            }
        } else {
            $temp = ord($bits[0]);
            for ($i = 0; $temp >> $i; ++$i) {
            }
            $precision = 8 * strlen($bits) - 8 + $i;
            $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3);
        }

        if ($shift < 0) {
            $shift+= $precision;
        }
        $shift%= $precision;

        if (!$shift) {
            return $this->copy();
        }

        $left = $this->bitwise_leftShift($shift);
        $left = $left->bitwise_and(new static($mask, 256));
        $right = $this->bitwise_rightShift($precision - $shift);
        $result = MATH_BIGINTEGER_MODE != self::MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right);
        return $this->_normalize($result);
    }

    /**
     * Logical Right Rotate
     *
     * Instead of the bottom x bits being dropped they're prepended to the shifted bit string.
     *
     * @param int $shift
     * @return \phpseclib\Math\BigInteger
     * @access public
     */
    function bitwise_rightRotate($shift)
    {
        return $this->bitwise_leftRotate(-$shift);
    }

    /**
     * Generates a random BigInteger
     *
     * Byte length is equal to $length. Uses \phpseclib\Crypt\Random if it's loaded and mt_rand if it's not.
     *
     * @param int $length
     * @return \phpseclib\Math\BigInteger
     * @access private
     */
    function _random_number_helper($size)
    {
        if (class_exists('\phpseclib\Crypt\Random')) {
            $random = Random::string($size);
        } else {
            $random = '';

            if ($size & 1) {
                $random.= chr(mt_rand(0, 255));
            }

            $blocks = $size >> 1;
            for ($i = 0; $i < $blocks; ++$i) {
                // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems
                $random.= pack('n', mt_rand(0, 0xFFFF));
            }
        }

        return new static($random, 256);
    }

    /**
     * Generate a random number
     *
     * Returns a random number between $min and $max where $min and $max
     * can be defined using one of the two methods:
     *
     * $min->random($max)
     * $max->random($min)
     *
     * @param \phpseclib\Math\BigInteger $arg1
     * @param \phpseclib\Math\BigInteger $arg2
     * @return \phpseclib\Math\BigInteger
     * @access public
     * @internal The API for creating random numbers used to be $a->random($min, $max), where $a was a BigInteger object.
     *           That method is still supported for BC purposes.
     */
    function random($arg1, $arg2 = false)
    {
        if ($arg1 === false) {
            return false;
        }

        if ($arg2 === false) {
            $max = $arg1;
            $min = $this;
        } else {
            $min = $arg1;
            $max = $arg2;
        }

        $compare = $max->compare($min);

        if (!$compare) {
            return $this->_normalize($min);
        } elseif ($compare < 0) {
            // if $min is bigger then $max, swap $min and $max
            $temp = $max;
            $max = $min;
            $min = $temp;
        }

        static $one;
        if (!isset($one)) {
            $one = new static(1);
        }

        $max = $max->subtract($min->subtract($one));
        $size = strlen(ltrim($max->toBytes(), chr(0)));

        /*
            doing $random % $max doesn't work because some numbers will be more likely to occur than others.
            eg. if $max is 140 and $random's max is 255 then that'd mean both $random = 5 and $random = 145
            would produce 5 whereas the only value of random that could produce 139 would be 139. ie.
            not all numbers would be equally likely. some would be more likely than others.

            creating a whole new random number until you find one that is within the range doesn't work
            because, for sufficiently small ranges, the likelihood that you'd get a number within that range
            would be pretty small. eg. with $random's max being 255 and if your $max being 1 the probability
            would be pretty high that $random would be greater than $max.

            phpseclib works around this using the technique described here:

            http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string
        */
        $random_max = new static(chr(1) . str_repeat("\0", $size), 256);
        $random = $this->_random_number_helper($size);

        list($max_multiple) = $random_max->divide($max);
        $max_multiple = $max_multiple->multiply($max);

        while ($random->compare($max_multiple) >= 0) {
            $random = $random->subtract($max_multiple);
            $random_max = $random_max->subtract($max_multiple);
            $random = $random->bitwise_leftShift(8);
            $random = $random->add($this->_random_number_helper(1));
            $random_max = $random_max->bitwise_leftShift(8);
            list($max_multiple) = $random_max->divide($max);
            $max_multiple = $max_multiple->multiply($max);
        }
        list(, $random) = $random->divide($max);

        return $this->_normalize($random->add($min));
    }

    /**
     * Generate a random prime number.
     *
     * If there's not a prime within the given range, false will be returned.
     * If more than $timeout seconds have elapsed, give up and return false.
     *
     * @param \phpseclib\Math\BigInteger $arg1
     * @param \phpseclib\Math\BigInteger $arg2
     * @param int $timeout
     * @return Math_BigInteger|false
     * @access public
     * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}.
     */
    function randomPrime($arg1, $arg2 = false, $timeout = false)
    {
        if ($arg1 === false) {
            return false;
        }

        if ($arg2 === false) {
            $max = $arg1;
            $min = $this;
        } else {
            $min = $arg1;
            $max = $arg2;
        }

        $compare = $max->compare($min);

        if (!$compare) {
            return $min->isPrime() ? $min : false;
        } elseif ($compare < 0) {
            // if $min is bigger then $max, swap $min and $max
            $temp = $max;
            $max = $min;
            $min = $temp;
        }

        static $one, $two;
        if (!isset($one)) {
            $one = new static(1);
            $two = new static(2);
        }

        $start = time();

        $x = $this->random($min, $max);

        // gmp_nextprime() requires PHP 5 >= 5.2.0 per <http://php.net/gmp-nextprime>.
        if (MATH_BIGINTEGER_MODE == self::MODE_GMP && extension_loaded('gmp')) {
            $p = new static();
            $p->value = gmp_nextprime($x->value);

            if ($p->compare($max) <= 0) {
                return $p;
            }

            if (!$min->equals($x)) {
                $x = $x->subtract($one);
            }

            return $x->randomPrime($min, $x);
        }

        if ($x->equals($two)) {
            return $x;
        }

        $x->_make_odd();
        if ($x->compare($max) > 0) {
            // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range
            if ($min->equals($max)) {
                return false;
            }
            $x = $min->copy();
            $x->_make_odd();
        }

        $initial_x = $x->copy();

        while (true) {
            if ($timeout !== false && time() - $start > $timeout) {
                return false;
            }

            if ($x->isPrime()) {
                return $x;
            }

            $x = $x->add($two);

            if ($x->compare($max) > 0) {
                $x = $min->copy();
                if ($x->equals($two)) {
                    return $x;
                }
                $x->_make_odd();
            }

            if ($x->equals($initial_x)) {
                return false;
            }
        }
    }

    /**
     * Make the current number odd
     *
     * If the current number is odd it'll be unchanged.  If it's even, one will be added to it.
     *
     * @see self::randomPrime()
     * @access private
     */
    function _make_odd()
    {
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                gmp_setbit($this->value, 0);
                break;
            case self::MODE_BCMATH:
                if ($this->value[strlen($this->value) - 1] % 2 == 0) {
                    $this->value = bcadd($this->value, '1');
                }
                break;
            default:
                $this->value[0] |= 1;
        }
    }

    /**
     * Checks a numer to see if it's prime
     *
     * Assuming the $t parameter is not set, this function has an error rate of 2**-80.  The main motivation for the
     * $t parameter is distributability.  BigInteger::randomPrime() can be distributed across multiple pageloads
     * on a website instead of just one.
     *
     * @param \phpseclib\Math\BigInteger $t
     * @return bool
     * @access public
     * @internal Uses the
     *     {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}.  See
     *     {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}.
     */
    function isPrime($t = false)
    {
        $length = strlen($this->toBytes());

        if (!$t) {
            // see HAC 4.49 "Note (controlling the error probability)"
            // @codingStandardsIgnoreStart
                 if ($length >= 163) { $t =  2; } // floor(1300 / 8)
            else if ($length >= 106) { $t =  3; } // floor( 850 / 8)
            else if ($length >= 81 ) { $t =  4; } // floor( 650 / 8)
            else if ($length >= 68 ) { $t =  5; } // floor( 550 / 8)
            else if ($length >= 56 ) { $t =  6; } // floor( 450 / 8)
            else if ($length >= 50 ) { $t =  7; } // floor( 400 / 8)
            else if ($length >= 43 ) { $t =  8; } // floor( 350 / 8)
            else if ($length >= 37 ) { $t =  9; } // floor( 300 / 8)
            else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8)
            else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8)
            else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8)
            else                     { $t = 27; }
            // @codingStandardsIgnoreEnd
        }

        // ie. gmp_testbit($this, 0)
        // ie. isEven() or !isOdd()
        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                return gmp_prob_prime($this->value, $t) != 0;
            case self::MODE_BCMATH:
                if ($this->value === '2') {
                    return true;
                }
                if ($this->value[strlen($this->value) - 1] % 2 == 0) {
                    return false;
                }
                break;
            default:
                if ($this->value == array(2)) {
                    return true;
                }
                if (~$this->value[0] & 1) {
                    return false;
                }
        }

        static $primes, $zero, $one, $two;

        if (!isset($primes)) {
            $primes = array(
                3,    5,    7,    11,   13,   17,   19,   23,   29,   31,   37,   41,   43,   47,   53,   59,
                61,   67,   71,   73,   79,   83,   89,   97,   101,  103,  107,  109,  113,  127,  131,  137,
                139,  149,  151,  157,  163,  167,  173,  179,  181,  191,  193,  197,  199,  211,  223,  227,
                229,  233,  239,  241,  251,  257,  263,  269,  271,  277,  281,  283,  293,  307,  311,  313,
                317,  331,  337,  347,  349,  353,  359,  367,  373,  379,  383,  389,  397,  401,  409,  419,
                421,  431,  433,  439,  443,  449,  457,  461,  463,  467,  479,  487,  491,  499,  503,  509,
                521,  523,  541,  547,  557,  563,  569,  571,  577,  587,  593,  599,  601,  607,  613,  617,
                619,  631,  641,  643,  647,  653,  659,  661,  673,  677,  683,  691,  701,  709,  719,  727,
                733,  739,  743,  751,  757,  761,  769,  773,  787,  797,  809,  811,  821,  823,  827,  829,
                839,  853,  857,  859,  863,  877,  881,  883,  887,  907,  911,  919,  929,  937,  941,  947,
                953,  967,  971,  977,  983,  991,  997
            );

            if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) {
                for ($i = 0; $i < count($primes); ++$i) {
                    $primes[$i] = new static($primes[$i]);
                }
            }

            $zero = new static();
            $one = new static(1);
            $two = new static(2);
        }

        if ($this->equals($one)) {
            return false;
        }

        // see HAC 4.4.1 "Random search for probable primes"
        if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) {
            foreach ($primes as $prime) {
                list(, $r) = $this->divide($prime);
                if ($r->equals($zero)) {
                    return $this->equals($prime);
                }
            }
        } else {
            $value = $this->value;
            foreach ($primes as $prime) {
                list(, $r) = $this->_divide_digit($value, $prime);
                if (!$r) {
                    return count($value) == 1 && $value[0] == $prime;
                }
            }
        }

        $n   = $this->copy();
        $n_1 = $n->subtract($one);
        $n_2 = $n->subtract($two);

        $r = $n_1->copy();
        $r_value = $r->value;
        // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s));
        if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) {
            $s = 0;
            // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier
            while ($r->value[strlen($r->value) - 1] % 2 == 0) {
                $r->value = bcdiv($r->value, '2', 0);
                ++$s;
            }
        } else {
            for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) {
                $temp = ~$r_value[$i] & 0xFFFFFF;
                for ($j = 1; ($temp >> $j) & 1; ++$j) {
                }
                if ($j != 25) {
                    break;
                }
            }
            $s = 26 * $i + $j - 1;
            $r->_rshift($s);
        }

        for ($i = 0; $i < $t; ++$i) {
            $a = $this->random($two, $n_2);
            $y = $a->modPow($r, $n);

            if (!$y->equals($one) && !$y->equals($n_1)) {
                for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) {
                    $y = $y->modPow($two, $n);
                    if ($y->equals($one)) {
                        return false;
                    }
                }

                if (!$y->equals($n_1)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Logical Left Shift
     *
     * Shifts BigInteger's by $shift bits.
     *
     * @param int $shift
     * @access private
     */
    function _lshift($shift)
    {
        if ($shift == 0) {
            return;
        }

        $num_digits = (int) ($shift / self::$base);
        $shift %= self::$base;
        $shift = 1 << $shift;

        $carry = 0;

        for ($i = 0; $i < count($this->value); ++$i) {
            $temp = $this->value[$i] * $shift + $carry;
            $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
            $this->value[$i] = (int) ($temp - $carry * self::$baseFull);
        }

        if ($carry) {
            $this->value[count($this->value)] = $carry;
        }

        while ($num_digits--) {
            array_unshift($this->value, 0);
        }
    }

    /**
     * Logical Right Shift
     *
     * Shifts BigInteger's by $shift bits.
     *
     * @param int $shift
     * @access private
     */
    function _rshift($shift)
    {
        if ($shift == 0) {
            return;
        }

        $num_digits = (int) ($shift / self::$base);
        $shift %= self::$base;
        $carry_shift = self::$base - $shift;
        $carry_mask = (1 << $shift) - 1;

        if ($num_digits) {
            $this->value = array_slice($this->value, $num_digits);
        }

        $carry = 0;

        for ($i = count($this->value) - 1; $i >= 0; --$i) {
            $temp = $this->value[$i] >> $shift | $carry;
            $carry = ($this->value[$i] & $carry_mask) << $carry_shift;
            $this->value[$i] = $temp;
        }

        $this->value = $this->_trim($this->value);
    }

    /**
     * Normalize
     *
     * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision
     *
     * @param \phpseclib\Math\BigInteger
     * @return \phpseclib\Math\BigInteger
     * @see self::_trim()
     * @access private
     */
    function _normalize($result)
    {
        $result->precision = $this->precision;
        $result->bitmask = $this->bitmask;

        switch (MATH_BIGINTEGER_MODE) {
            case self::MODE_GMP:
                if ($this->bitmask !== false) {
                    $result->value = gmp_and($result->value, $result->bitmask->value);
                }

                return $result;
            case self::MODE_BCMATH:
                if (!empty($result->bitmask->value)) {
                    $result->value = bcmod($result->value, $result->bitmask->value);
                }

                return $result;
        }

        $value = &$result->value;

        if (!count($value)) {
            return $result;
        }

        $value = $this->_trim($value);

        if (!empty($result->bitmask->value)) {
            $length = min(count($value), count($this->bitmask->value));
            $value = array_slice($value, 0, $length);

            for ($i = 0; $i < $length; ++$i) {
                $value[$i] = $value[$i] & $this->bitmask->value[$i];
            }
        }

        return $result;
    }

    /**
     * Trim
     *
     * Removes leading zeros
     *
     * @param array $value
     * @return \phpseclib\Math\BigInteger
     * @access private
     */
    function _trim($value)
    {
        for ($i = count($value) - 1; $i >= 0; --$i) {
            if ($value[$i]) {
                break;
            }
            unset($value[$i]);
        }

        return $value;
    }

    /**
     * Array Repeat
     *
     * @param $input Array
     * @param $multiplier mixed
     * @return array
     * @access private
     */
    function _array_repeat($input, $multiplier)
    {
        return ($multiplier) ? array_fill(0, $multiplier, $input) : array();
    }

    /**
     * Logical Left Shift
     *
     * Shifts binary strings $shift bits, essentially multiplying by 2**$shift.
     *
     * @param $x String
     * @param $shift Integer
     * @return string
     * @access private
     */
    function _base256_lshift(&$x, $shift)
    {
        if ($shift == 0) {
            return;
        }

        $num_bytes = $shift >> 3; // eg. floor($shift/8)
        $shift &= 7; // eg. $shift % 8

        $carry = 0;
        for ($i = strlen($x) - 1; $i >= 0; --$i) {
            $temp = ord($x[$i]) << $shift | $carry;
            $x[$i] = chr($temp);
            $carry = $temp >> 8;
        }
        $carry = ($carry != 0) ? chr($carry) : '';
        $x = $carry . $x . str_repeat(chr(0), $num_bytes);
    }

    /**
     * Logical Right Shift
     *
     * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder.
     *
     * @param $x String
     * @param $shift Integer
     * @return string
     * @access private
     */
    function _base256_rshift(&$x, $shift)
    {
        if ($shift == 0) {
            $x = ltrim($x, chr(0));
            return '';
        }

        $num_bytes = $shift >> 3; // eg. floor($shift/8)
        $shift &= 7; // eg. $shift % 8

        $remainder = '';
        if ($num_bytes) {
            $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes;
            $remainder = substr($x, $start);
            $x = substr($x, 0, -$num_bytes);
        }

        $carry = 0;
        $carry_shift = 8 - $shift;
        for ($i = 0; $i < strlen($x); ++$i) {
            $temp = (ord($x[$i]) >> $shift) | $carry;
            $carry = (ord($x[$i]) << $carry_shift) & 0xFF;
            $x[$i] = chr($temp);
        }
        $x = ltrim($x, chr(0));

        $remainder = chr($carry >> $carry_shift) . $remainder;

        return ltrim($remainder, chr(0));
    }

    // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long
    // at 32-bits, while java's longs are 64-bits.

    /**
     * Converts 32-bit integers to bytes.
     *
     * @param int $x
     * @return string
     * @access private
     */
    function _int2bytes($x)
    {
        return ltrim(pack('N', $x), chr(0));
    }

    /**
     * Converts bytes to 32-bit integers
     *
     * @param string $x
     * @return int
     * @access private
     */
    function _bytes2int($x)
    {
        $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT));
        return $temp['int'];
    }

    /**
     * DER-encode an integer
     *
     * The ability to DER-encode integers is needed to create RSA public keys for use with OpenSSL
     *
     * @see self::modPow()
     * @access private
     * @param int $length
     * @return string
     */
    function _encodeASN1Length($length)
    {
        if ($length <= 0x7F) {
            return chr($length);
        }

        $temp = ltrim(pack('N', $length), chr(0));
        return pack('Ca*', 0x80 | strlen($temp), $temp);
    }

    /**
     * Single digit division
     *
     * Even if int64 is being used the division operator will return a float64 value
     * if the dividend is not evenly divisible by the divisor. Since a float64 doesn't
     * have the precision of int64 this is a problem so, when int64 is being used,
     * we'll guarantee that the dividend is divisible by first subtracting the remainder.
     *
     * @access private
     * @param int $x
     * @param int $y
     * @return int
     */
    function _safe_divide($x, $y)
    {
        if (self::$base === 26) {
            return (int) ($x / $y);
        }

        // self::$base === 31
        return ($x - ($x % $y)) / $y;
    }
}
<?php

/**
 * Pure-PHP implementation of SCP.
 *
 * PHP version 5
 *
 * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
 *    if (!$ssh->login('username', 'password')) {
 *        exit('bad login');
 *    }
 *    $scp = new \phpseclib\Net\SCP($ssh);
 *
 *    $scp->put('abcd', str_repeat('x', 1024*1024));
 * ?>
 * </code>
 *
 * @category  Net
 * @package   SCP
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2010 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Net;

/**
 * Pure-PHP implementations of SCP.
 *
 * @package SCP
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class SCP
{
    /**#@+
     * @access public
     * @see \phpseclib\Net\SCP::put()
     */
    /**
     * Reads data from a local file.
     */
    const SOURCE_LOCAL_FILE = 1;
    /**
     * Reads data from a string.
     */
    const SOURCE_STRING = 2;
    /**#@-*/

    /**#@+
     * @access private
     * @see \phpseclib\Net\SCP::_send()
     * @see \phpseclib\Net\SCP::_receive()
    */
    /**
     * SSH1 is being used.
     */
    const MODE_SSH1 = 1;
    /**
     * SSH2 is being used.
     */
    const MODE_SSH2 =  2;
    /**#@-*/

    /**
     * SSH Object
     *
     * @var object
     * @access private
     */
    var $ssh;

    /**
     * Packet Size
     *
     * @var int
     * @access private
     */
    var $packet_size;

    /**
     * Mode
     *
     * @var int
     * @access private
     */
    var $mode;

    /**
     * Default Constructor.
     *
     * Connects to an SSH server
     *
     * @param \phpseclib\Net\SSH1|\phpseclin\Net\SSH2 $ssh
     * @return \phpseclib\Net\SCP
     * @access public
     */
    function __construct($ssh)
    {
        if ($ssh instanceof SSH2) {
            $this->mode = self::MODE_SSH2;
        } elseif ($ssh instanceof SSH1) {
            $this->packet_size = 50000;
            $this->mode = self::MODE_SSH1;
        } else {
            return;
        }

        $this->ssh = $ssh;
    }

    /**
     * Uploads a file to the SCP server.
     *
     * By default, \phpseclib\Net\SCP::put() does not read from the local filesystem.  $data is dumped directly into $remote_file.
     * So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SCP::get(), you will get a file, twelve bytes
     * long, containing 'filename.ext' as its contents.
     *
     * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior.  With self::SOURCE_LOCAL_FILE, $remote_file will
     * contain as many bytes as filename.ext does on your local filesystem.  If your filename.ext is 1MB then that is how
     * large $remote_file will be, as well.
     *
     * Currently, only binary mode is supported.  As such, if the line endings need to be adjusted, you will need to take
     * care of that, yourself.
     *
     * @param string $remote_file
     * @param string $data
     * @param int $mode
     * @param callable $callback
     * @return bool
     * @access public
     */
    function put($remote_file, $data, $mode = self::SOURCE_STRING, $callback = null)
    {
        if (!isset($this->ssh)) {
            return false;
        }

        if (!$this->ssh->exec('scp -t ' . escapeshellarg($remote_file), false)) { // -t = to
            return false;
        }

        $temp = $this->_receive();
        if ($temp !== chr(0)) {
            return false;
        }

        if ($this->mode == self::MODE_SSH2) {
            $this->packet_size = $this->ssh->packet_size_client_to_server[SSH2::CHANNEL_EXEC] - 4;
        }

        $remote_file = basename($remote_file);

        if ($mode == self::SOURCE_STRING) {
            $size = strlen($data);
        } else {
            if (!is_file($data)) {
                user_error("$data is not a valid file", E_USER_NOTICE);
                return false;
            }

            $fp = @fopen($data, 'rb');
            if (!$fp) {
                return false;
            }
            $size = filesize($data);
        }

        $this->_send('C0644 ' . $size . ' ' . $remote_file . "\n");

        $temp = $this->_receive();
        if ($temp !== chr(0)) {
            return false;
        }

        $sent = 0;
        while ($sent < $size) {
            $temp = $mode & self::SOURCE_STRING ? substr($data, $sent, $this->packet_size) : fread($fp, $this->packet_size);
            $this->_send($temp);
            $sent+= strlen($temp);

            if (is_callable($callback)) {
                call_user_func($callback, $sent);
            }
        }
        $this->_close();

        if ($mode != self::SOURCE_STRING) {
            fclose($fp);
        }

        return true;
    }

    /**
     * Downloads a file from the SCP server.
     *
     * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
     * the operation was unsuccessful.  If $local_file is defined, returns true or false depending on the success of the
     * operation
     *
     * @param string $remote_file
     * @param string $local_file
     * @return mixed
     * @access public
     */
    function get($remote_file, $local_file = false)
    {
        if (!isset($this->ssh)) {
            return false;
        }

        if (!$this->ssh->exec('scp -f ' . escapeshellarg($remote_file), false)) { // -f = from
            return false;
        }

        $this->_send("\0");

        if (!preg_match('#(?<perms>[^ ]+) (?<size>\d+) (?<name>.+)#', rtrim($this->_receive()), $info)) {
            return false;
        }

        $this->_send("\0");

        $size = 0;

        if ($local_file !== false) {
            $fp = @fopen($local_file, 'wb');
            if (!$fp) {
                return false;
            }
        }

        $content = '';
        while ($size < $info['size']) {
            $data = $this->_receive();
            // SCP usually seems to split stuff out into 16k chunks
            $size+= strlen($data);

            if ($local_file === false) {
                $content.= $data;
            } else {
                fputs($fp, $data);
            }
        }

        $this->_close();

        if ($local_file !== false) {
            fclose($fp);
            return true;
        }

        return $content;
    }

    /**
     * Sends a packet to an SSH server
     *
     * @param string $data
     * @access private
     */
    function _send($data)
    {
        switch ($this->mode) {
            case self::MODE_SSH2:
                $this->ssh->_send_channel_packet(SSH2::CHANNEL_EXEC, $data);
                break;
            case self::MODE_SSH1:
                $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($data), $data);
                $this->ssh->_send_binary_packet($data);
        }
    }

    /**
     * Receives a packet from an SSH server
     *
     * @return string
     * @access private
     */
    function _receive()
    {
        switch ($this->mode) {
            case self::MODE_SSH2:
                return $this->ssh->_get_channel_packet(SSH2::CHANNEL_EXEC, true);
            case self::MODE_SSH1:
                if (!$this->ssh->bitmap) {
                    return false;
                }
                while (true) {
                    $response = $this->ssh->_get_binary_packet();
                    switch ($response[SSH1::RESPONSE_TYPE]) {
                        case NET_SSH1_SMSG_STDOUT_DATA:
                            extract(unpack('Nlength', $response[SSH1::RESPONSE_DATA]));
                            return $this->ssh->_string_shift($response[SSH1::RESPONSE_DATA], $length);
                        case NET_SSH1_SMSG_STDERR_DATA:
                            break;
                        case NET_SSH1_SMSG_EXITSTATUS:
                            $this->ssh->_send_binary_packet(chr(NET_SSH1_CMSG_EXIT_CONFIRMATION));
                            fclose($this->ssh->fsock);
                            $this->ssh->bitmap = 0;
                            return false;
                        default:
                            user_error('Unknown packet received', E_USER_NOTICE);
                            return false;
                    }
                }
        }
    }

    /**
     * Closes the connection to an SSH server
     *
     * @access private
     */
    function _close()
    {
        switch ($this->mode) {
            case self::MODE_SSH2:
                $this->ssh->_close_channel(SSH2::CHANNEL_EXEC, true);
                break;
            case self::MODE_SSH1:
                $this->ssh->disconnect();
        }
    }
}
<?php

/**
 * SFTP Stream Wrapper
 *
 * Creates an sftp:// protocol handler that can be used with, for example, fopen(), dir(), etc.
 *
 * PHP version 5
 *
 * @category  Net
 * @package   SFTP
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2013 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Net\SFTP;

use phpseclib\Crypt\RSA;
use phpseclib\Net\SFTP;

/**
 * SFTP Stream Wrapper
 *
 * @package SFTP
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class Stream
{
    /**
     * SFTP instances
     *
     * Rather than re-create the connection we re-use instances if possible
     *
     * @var array
     */
    static $instances;

    /**
     * SFTP instance
     *
     * @var object
     * @access private
     */
    var $sftp;

    /**
     * Path
     *
     * @var string
     * @access private
     */
    var $path;

    /**
     * Mode
     *
     * @var string
     * @access private
     */
    var $mode;

    /**
     * Position
     *
     * @var int
     * @access private
     */
    var $pos;

    /**
     * Size
     *
     * @var int
     * @access private
     */
    var $size;

    /**
     * Directory entries
     *
     * @var array
     * @access private
     */
    var $entries;

    /**
     * EOF flag
     *
     * @var bool
     * @access private
     */
    var $eof;

    /**
     * Context resource
     *
     * Technically this needs to be publically accessible so PHP can set it directly
     *
     * @var resource
     * @access public
     */
    var $context;

    /**
     * Notification callback function
     *
     * @var callable
     * @access public
     */
    var $notification;

    /**
     * Registers this class as a URL wrapper.
     *
     * @param string $protocol The wrapper name to be registered.
     * @return bool True on success, false otherwise.
     * @access public
     */
    static function register($protocol = 'sftp')
    {
        if (in_array($protocol, stream_get_wrappers(), true)) {
            return false;
        }
        return stream_wrapper_register($protocol, get_called_class());
    }

    /**
     * The Constructor
     *
     * @access public
     */
    function __construct()
    {
        if (defined('NET_SFTP_STREAM_LOGGING')) {
            echo "__construct()\r\n";
        }
    }

    /**
     * Path Parser
     *
     * Extract a path from a URI and actually connect to an SSH server if appropriate
     *
     * If "notification" is set as a context parameter the message code for successful login is
     * NET_SSH2_MSG_USERAUTH_SUCCESS. For a failed login it's NET_SSH2_MSG_USERAUTH_FAILURE.
     *
     * @param string $path
     * @return string
     * @access private
     */
    function _parse_path($path)
    {
        $orig = $path;
        extract(parse_url($path) + array('port' => 22));
        if (isset($query)) {
            $path.= '?' . $query;
        } elseif (preg_match('/(\?|\?#)$/', $orig)) {
            $path.= '?';
        }
        if (isset($fragment)) {
            $path.= '#' . $fragment;
        } elseif ($orig[strlen($orig) - 1] == '#') {
            $path.= '#';
        }

        if (!isset($host)) {
            return false;
        }

        if (isset($this->context)) {
            $context = stream_context_get_params($this->context);
            if (isset($context['notification'])) {
                $this->notification = $context['notification'];
            }
        }

        if ($host[0] == '$') {
            $host = substr($host, 1);
            global $$host;
            if (($$host instanceof SFTP) === false) {
                return false;
            }
            $this->sftp = $$host;
        } else {
            if (isset($this->context)) {
                $context = stream_context_get_options($this->context);
            }
            if (isset($context[$scheme]['session'])) {
                $sftp = $context[$scheme]['session'];
            }
            if (isset($context[$scheme]['sftp'])) {
                $sftp = $context[$scheme]['sftp'];
            }
            if (isset($sftp) && $sftp instanceof SFTP) {
                $this->sftp = $sftp;
                return $path;
            }
            if (isset($context[$scheme]['username'])) {
                $user = $context[$scheme]['username'];
            }
            if (isset($context[$scheme]['password'])) {
                $pass = $context[$scheme]['password'];
            }
            if (isset($context[$scheme]['privkey']) && $context[$scheme]['privkey'] instanceof RSA) {
                $pass = $context[$scheme]['privkey'];
            }

            if (!isset($user) || !isset($pass)) {
                return false;
            }

            // casting $pass to a string is necessary in the event that it's a \phpseclib\Crypt\RSA object
            if (isset(self::$instances[$host][$port][$user][(string) $pass])) {
                $this->sftp = self::$instances[$host][$port][$user][(string) $pass];
            } else {
                $this->sftp = new SFTP($host, $port);
                $this->sftp->disableStatCache();
                if (isset($this->notification) && is_callable($this->notification)) {
                    /* if !is_callable($this->notification) we could do this:

                       user_error('fopen(): failed to call user notifier', E_USER_WARNING);

                       the ftp wrapper gives errors like that when the notifier isn't callable.
                       i've opted not to do that, however, since the ftp wrapper gives the line
                       on which the fopen occurred as the line number - not the line that the
                       user_error is on.
                    */
                    call_user_func($this->notification, STREAM_NOTIFY_CONNECT, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0);
                    call_user_func($this->notification, STREAM_NOTIFY_AUTH_REQUIRED, STREAM_NOTIFY_SEVERITY_INFO, '', 0, 0, 0);
                    if (!$this->sftp->login($user, $pass)) {
                        call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_ERR, 'Login Failure', NET_SSH2_MSG_USERAUTH_FAILURE, 0, 0);
                        return false;
                    }
                    call_user_func($this->notification, STREAM_NOTIFY_AUTH_RESULT, STREAM_NOTIFY_SEVERITY_INFO, 'Login Success', NET_SSH2_MSG_USERAUTH_SUCCESS, 0, 0);
                } else {
                    if (!$this->sftp->login($user, $pass)) {
                        return false;
                    }
                }
                self::$instances[$host][$port][$user][(string) $pass] = $this->sftp;
            }
        }

        return $path;
    }

    /**
     * Opens file or URL
     *
     * @param string $path
     * @param string $mode
     * @param int $options
     * @param string $opened_path
     * @return bool
     * @access public
     */
    function _stream_open($path, $mode, $options, &$opened_path)
    {
        $path = $this->_parse_path($path);

        if ($path === false) {
            return false;
        }
        $this->path = $path;

        $this->size = $this->sftp->size($path);
        $this->mode = preg_replace('#[bt]$#', '', $mode);
        $this->eof = false;

        if ($this->size === false) {
            if ($this->mode[0] == 'r') {
                return false;
            } else {
                $this->sftp->touch($path);
                $this->size = 0;
            }
        } else {
            switch ($this->mode[0]) {
                case 'x':
                    return false;
                case 'w':
                    $this->sftp->truncate($path, 0);
                    $this->size = 0;
            }
        }

        $this->pos = $this->mode[0] != 'a' ? 0 : $this->size;

        return true;
    }

    /**
     * Read from stream
     *
     * @param int $count
     * @return mixed
     * @access public
     */
    function _stream_read($count)
    {
        switch ($this->mode) {
            case 'w':
            case 'a':
            case 'x':
            case 'c':
                return false;
        }

        // commented out because some files - eg. /dev/urandom - will say their size is 0 when in fact it's kinda infinite
        //if ($this->pos >= $this->size) {
        //    $this->eof = true;
        //    return false;
        //}

        $result = $this->sftp->get($this->path, false, $this->pos, $count);
        if (isset($this->notification) && is_callable($this->notification)) {
            if ($result === false) {
                call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0);
                return 0;
            }
            // seems that PHP calls stream_read in 8k chunks
            call_user_func($this->notification, STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, strlen($result), $this->size);
        }

        if (empty($result)) { // ie. false or empty string
            $this->eof = true;
            return false;
        }
        $this->pos+= strlen($result);

        return $result;
    }

    /**
     * Write to stream
     *
     * @param string $data
     * @return mixed
     * @access public
     */
    function _stream_write($data)
    {
        switch ($this->mode) {
            case 'r':
                return false;
        }

        $result = $this->sftp->put($this->path, $data, SFTP::SOURCE_STRING, $this->pos);
        if (isset($this->notification) && is_callable($this->notification)) {
            if (!$result) {
                call_user_func($this->notification, STREAM_NOTIFY_FAILURE, STREAM_NOTIFY_SEVERITY_ERR, $this->sftp->getLastSFTPError(), NET_SFTP_OPEN, 0, 0);
                return 0;
            }
            // seems that PHP splits up strings into 8k blocks before calling stream_write
            call_user_func($this->notification, STREAM_NOTIFY_PROGRESS, STREAM_NOTIFY_SEVERITY_INFO, '', 0, strlen($data), strlen($data));
        }

        if ($result === false) {
            return false;
        }
        $this->pos+= strlen($data);
        if ($this->pos > $this->size) {
            $this->size = $this->pos;
        }
        $this->eof = false;
        return strlen($data);
    }

    /**
     * Retrieve the current position of a stream
     *
     * @return int
     * @access public
     */
    function _stream_tell()
    {
        return $this->pos;
    }

    /**
     * Tests for end-of-file on a file pointer
     *
     * In my testing there are four classes functions that normally effect the pointer:
     * fseek, fputs  / fwrite, fgets / fread and ftruncate.
     *
     * Only fgets / fread, however, results in feof() returning true. do fputs($fp, 'aaa') on a blank file and feof()
     * will return false. do fread($fp, 1) and feof() will then return true. do fseek($fp, 10) on ablank file and feof()
     * will return false. do fread($fp, 1) and feof() will then return true.
     *
     * @return bool
     * @access public
     */
    function _stream_eof()
    {
        return $this->eof;
    }

    /**
     * Seeks to specific location in a stream
     *
     * @param int $offset
     * @param int $whence
     * @return bool
     * @access public
     */
    function _stream_seek($offset, $whence)
    {
        switch ($whence) {
            case SEEK_SET:
                if ($offset >= $this->size || $offset < 0) {
                    return false;
                }
                break;
            case SEEK_CUR:
                $offset+= $this->pos;
                break;
            case SEEK_END:
                $offset+= $this->size;
        }

        $this->pos = $offset;
        $this->eof = false;
        return true;
    }

    /**
     * Change stream options
     *
     * @param string $path
     * @param int $option
     * @param mixed $var
     * @return bool
     * @access public
     */
    function _stream_metadata($path, $option, $var)
    {
        $path = $this->_parse_path($path);
        if ($path === false) {
            return false;
        }

        // stream_metadata was introduced in PHP 5.4.0 but as of 5.4.11 the constants haven't been defined
        // see http://www.php.net/streamwrapper.stream-metadata and https://bugs.php.net/64246
        //     and https://github.com/php/php-src/blob/master/main/php_streams.h#L592
        switch ($option) {
            case 1: // PHP_STREAM_META_TOUCH
                return $this->sftp->touch($path, $var[0], $var[1]);
            case 2: // PHP_STREAM_OWNER_NAME
            case 3: // PHP_STREAM_GROUP_NAME
                return false;
            case 4: // PHP_STREAM_META_OWNER
                return $this->sftp->chown($path, $var);
            case 5: // PHP_STREAM_META_GROUP
                return $this->sftp->chgrp($path, $var);
            case 6: // PHP_STREAM_META_ACCESS
                return $this->sftp->chmod($path, $var) !== false;
        }
    }

    /**
     * Retrieve the underlaying resource
     *
     * @param int $cast_as
     * @return resource
     * @access public
     */
    function _stream_cast($cast_as)
    {
        return $this->sftp->fsock;
    }

    /**
     * Advisory file locking
     *
     * @param int $operation
     * @return bool
     * @access public
     */
    function _stream_lock($operation)
    {
        return false;
    }

    /**
     * Renames a file or directory
     *
     * Attempts to rename oldname to newname, moving it between directories if necessary.
     * If newname exists, it will be overwritten.  This is a departure from what \phpseclib\Net\SFTP
     * does.
     *
     * @param string $path_from
     * @param string $path_to
     * @return bool
     * @access public
     */
    function _rename($path_from, $path_to)
    {
        $path1 = parse_url($path_from);
        $path2 = parse_url($path_to);
        unset($path1['path'], $path2['path']);
        if ($path1 != $path2) {
            return false;
        }

        $path_from = $this->_parse_path($path_from);
        $path_to = parse_url($path_to);
        if ($path_from === false) {
            return false;
        }

        $path_to = $path_to['path']; // the $component part of parse_url() was added in PHP 5.1.2
        // "It is an error if there already exists a file with the name specified by newpath."
        //  -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-6.5
        if (!$this->sftp->rename($path_from, $path_to)) {
            if ($this->sftp->stat($path_to)) {
                return $this->sftp->delete($path_to, true) && $this->sftp->rename($path_from, $path_to);
            }
            return false;
        }

        return true;
    }

    /**
     * Open directory handle
     *
     * The only $options is "whether or not to enforce safe_mode (0x04)". Since safe mode was deprecated in 5.3 and
     * removed in 5.4 I'm just going to ignore it.
     *
     * Also, nlist() is the best that this function is realistically going to be able to do. When an SFTP client
     * sends a SSH_FXP_READDIR packet you don't generally get info on just one file but on multiple files. Quoting
     * the SFTP specs:
     *
     *    The SSH_FXP_NAME response has the following format:
     *
     *        uint32     id
     *        uint32     count
     *        repeats count times:
     *                string     filename
     *                string     longname
     *                ATTRS      attrs
     *
     * @param string $path
     * @param int $options
     * @return bool
     * @access public
     */
    function _dir_opendir($path, $options)
    {
        $path = $this->_parse_path($path);
        if ($path === false) {
            return false;
        }
        $this->pos = 0;
        $this->entries = $this->sftp->nlist($path);
        return $this->entries !== false;
    }

    /**
     * Read entry from directory handle
     *
     * @return mixed
     * @access public
     */
    function _dir_readdir()
    {
        if (isset($this->entries[$this->pos])) {
            return $this->entries[$this->pos++];
        }
        return false;
    }

    /**
     * Rewind directory handle
     *
     * @return bool
     * @access public
     */
    function _dir_rewinddir()
    {
        $this->pos = 0;
        return true;
    }

    /**
     * Close directory handle
     *
     * @return bool
     * @access public
     */
    function _dir_closedir()
    {
        return true;
    }

    /**
     * Create a directory
     *
     * Only valid $options is STREAM_MKDIR_RECURSIVE
     *
     * @param string $path
     * @param int $mode
     * @param int $options
     * @return bool
     * @access public
     */
    function _mkdir($path, $mode, $options)
    {
        $path = $this->_parse_path($path);
        if ($path === false) {
            return false;
        }

        return $this->sftp->mkdir($path, $mode, $options & STREAM_MKDIR_RECURSIVE);
    }

    /**
     * Removes a directory
     *
     * Only valid $options is STREAM_MKDIR_RECURSIVE per <http://php.net/streamwrapper.rmdir>, however,
     * <http://php.net/rmdir>  does not have a $recursive parameter as mkdir() does so I don't know how
     * STREAM_MKDIR_RECURSIVE is supposed to be set. Also, when I try it out with rmdir() I get 8 as
     * $options. What does 8 correspond to?
     *
     * @param string $path
     * @param int $mode
     * @param int $options
     * @return bool
     * @access public
     */
    function _rmdir($path, $options)
    {
        $path = $this->_parse_path($path);
        if ($path === false) {
            return false;
        }

        return $this->sftp->rmdir($path);
    }

    /**
     * Flushes the output
     *
     * See <http://php.net/fflush>. Always returns true because \phpseclib\Net\SFTP doesn't cache stuff before writing
     *
     * @return bool
     * @access public
     */
    function _stream_flush()
    {
        return true;
    }

    /**
     * Retrieve information about a file resource
     *
     * @return mixed
     * @access public
     */
    function _stream_stat()
    {
        $results = $this->sftp->stat($this->path);
        if ($results === false) {
            return false;
        }
        return $results;
    }

    /**
     * Delete a file
     *
     * @param string $path
     * @return bool
     * @access public
     */
    function _unlink($path)
    {
        $path = $this->_parse_path($path);
        if ($path === false) {
            return false;
        }

        return $this->sftp->delete($path, false);
    }

    /**
     * Retrieve information about a file
     *
     * Ignores the STREAM_URL_STAT_QUIET flag because the entirety of \phpseclib\Net\SFTP\Stream is quiet by default
     * might be worthwhile to reconstruct bits 12-16 (ie. the file type) if mode doesn't have them but we'll
     * cross that bridge when and if it's reached
     *
     * @param string $path
     * @param int $flags
     * @return mixed
     * @access public
     */
    function _url_stat($path, $flags)
    {
        $path = $this->_parse_path($path);
        if ($path === false) {
            return false;
        }

        $results = $flags & STREAM_URL_STAT_LINK ? $this->sftp->lstat($path) : $this->sftp->stat($path);
        if ($results === false) {
            return false;
        }

        return $results;
    }

    /**
     * Truncate stream
     *
     * @param int $new_size
     * @return bool
     * @access public
     */
    function _stream_truncate($new_size)
    {
        if (!$this->sftp->truncate($this->path, $new_size)) {
            return false;
        }

        $this->eof = false;
        $this->size = $new_size;

        return true;
    }

    /**
     * Change stream options
     *
     * STREAM_OPTION_WRITE_BUFFER isn't supported for the same reason stream_flush isn't.
     * The other two aren't supported because of limitations in \phpseclib\Net\SFTP.
     *
     * @param int $option
     * @param int $arg1
     * @param int $arg2
     * @return bool
     * @access public
     */
    function _stream_set_option($option, $arg1, $arg2)
    {
        return false;
    }

    /**
     * Close an resource
     *
     * @access public
     */
    function _stream_close()
    {
    }

    /**
     * __call Magic Method
     *
     * When you're utilizing an SFTP stream you're not calling the methods in this class directly - PHP is calling them for you.
     * Which kinda begs the question... what methods is PHP calling and what parameters is it passing to them? This function
     * lets you figure that out.
     *
     * If NET_SFTP_STREAM_LOGGING is defined all calls will be output on the screen and then (regardless of whether or not
     * NET_SFTP_STREAM_LOGGING is enabled) the parameters will be passed through to the appropriate method.
     *
     * @param string
     * @param array
     * @return mixed
     * @access public
     */
    function __call($name, $arguments)
    {
        if (defined('NET_SFTP_STREAM_LOGGING')) {
            echo $name . '(';
            $last = count($arguments) - 1;
            foreach ($arguments as $i => $argument) {
                var_export($argument);
                if ($i != $last) {
                    echo ',';
                }
            }
            echo ")\r\n";
        }
        $name = '_' . $name;
        if (!method_exists($this, $name)) {
            return false;
        }
        return call_user_func_array(array($this, $name), $arguments);
    }
}
<?php

/**
 * Pure-PHP implementation of SFTP.
 *
 * PHP version 5
 *
 * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
 * implemented by the popular OpenSSH SFTP server".  If you want SFTPv4/5/6 support, provide me with access
 * to an SFTPv4/5/6 server.
 *
 * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $sftp = new \phpseclib\Net\SFTP('www.domain.tld');
 *    if (!$sftp->login('username', 'password')) {
 *        exit('Login Failed');
 *    }
 *
 *    echo $sftp->pwd() . "\r\n";
 *    $sftp->put('filename.ext', 'hello, world!');
 *    print_r($sftp->nlist());
 * ?>
 * </code>
 *
 * @category  Net
 * @package   SFTP
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2009 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Net;

/**
 * Pure-PHP implementations of SFTP.
 *
 * @package SFTP
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class SFTP extends SSH2
{
    /**
     * SFTP channel constant
     *
     * \phpseclib\Net\SSH2::exec() uses 0 and \phpseclib\Net\SSH2::read() / \phpseclib\Net\SSH2::write() use 1.
     *
     * @see \phpseclib\Net\SSH2::_send_channel_packet()
     * @see \phpseclib\Net\SSH2::_get_channel_packet()
     * @access private
     */
    const CHANNEL = 0x100;

    /**#@+
     * @access public
     * @see \phpseclib\Net\SFTP::put()
    */
    /**
     * Reads data from a local file.
     */
    const SOURCE_LOCAL_FILE = 1;
    /**
     * Reads data from a string.
     */
    // this value isn't really used anymore but i'm keeping it reserved for historical reasons
    const SOURCE_STRING = 2;
    /**
     * Reads data from callback:
     * function callback($length) returns string to proceed, null for EOF
     */
    const SOURCE_CALLBACK = 16;
    /**
     * Resumes an upload
     */
    const RESUME = 4;
    /**
     * Append a local file to an already existing remote file
     */
    const RESUME_START = 8;
    /**#@-*/

    /**
     * Packet Types
     *
     * @see self::__construct()
     * @var array
     * @access private
     */
    var $packet_types = array();

    /**
     * Status Codes
     *
     * @see self::__construct()
     * @var array
     * @access private
     */
    var $status_codes = array();

    /**
     * The Request ID
     *
     * The request ID exists in the off chance that a packet is sent out-of-order.  Of course, this library doesn't support
     * concurrent actions, so it's somewhat academic, here.
     *
     * @var int
     * @see self::_send_sftp_packet()
     * @access private
     */
    var $request_id = false;

    /**
     * The Packet Type
     *
     * The request ID exists in the off chance that a packet is sent out-of-order.  Of course, this library doesn't support
     * concurrent actions, so it's somewhat academic, here.
     *
     * @var int
     * @see self::_get_sftp_packet()
     * @access private
     */
    var $packet_type = -1;

    /**
     * Packet Buffer
     *
     * @var string
     * @see self::_get_sftp_packet()
     * @access private
     */
    var $packet_buffer = '';

    /**
     * Extensions supported by the server
     *
     * @var array
     * @see self::_initChannel()
     * @access private
     */
    var $extensions = array();

    /**
     * Server SFTP version
     *
     * @var int
     * @see self::_initChannel()
     * @access private
     */
    var $version;

    /**
     * Current working directory
     *
     * @var string
     * @see self::_realpath()
     * @see self::chdir()
     * @access private
     */
    var $pwd = false;

    /**
     * Packet Type Log
     *
     * @see self::getLog()
     * @var array
     * @access private
     */
    var $packet_type_log = array();

    /**
     * Packet Log
     *
     * @see self::getLog()
     * @var array
     * @access private
     */
    var $packet_log = array();

    /**
     * Error information
     *
     * @see self::getSFTPErrors()
     * @see self::getLastSFTPError()
     * @var string
     * @access private
     */
    var $sftp_errors = array();

    /**
     * Stat Cache
     *
     * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
     * we'll cache the results.
     *
     * @see self::_update_stat_cache()
     * @see self::_remove_from_stat_cache()
     * @see self::_query_stat_cache()
     * @var array
     * @access private
     */
    var $stat_cache = array();

    /**
     * Max SFTP Packet Size
     *
     * @see self::__construct()
     * @see self::get()
     * @var array
     * @access private
     */
    var $max_sftp_packet;

    /**
     * Stat Cache Flag
     *
     * @see self::disableStatCache()
     * @see self::enableStatCache()
     * @var bool
     * @access private
     */
    var $use_stat_cache = true;

    /**
     * Sort Options
     *
     * @see self::_comparator()
     * @see self::setListOrder()
     * @var array
     * @access private
     */
    var $sortOptions = array();

    /**
     * Default Constructor.
     *
     * Connects to an SFTP server
     *
     * @param string $host
     * @param int $port
     * @param int $timeout
     * @return \phpseclib\Net\SFTP
     * @access public
     */
    function __construct($host, $port = 22, $timeout = 10)
    {
        parent::__construct($host, $port, $timeout);

        $this->max_sftp_packet = 1 << 15;

        $this->packet_types = array(
            1  => 'NET_SFTP_INIT',
            2  => 'NET_SFTP_VERSION',
            /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
                   SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
               pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
            3  => 'NET_SFTP_OPEN',
            4  => 'NET_SFTP_CLOSE',
            5  => 'NET_SFTP_READ',
            6  => 'NET_SFTP_WRITE',
            7  => 'NET_SFTP_LSTAT',
            9  => 'NET_SFTP_SETSTAT',
            11 => 'NET_SFTP_OPENDIR',
            12 => 'NET_SFTP_READDIR',
            13 => 'NET_SFTP_REMOVE',
            14 => 'NET_SFTP_MKDIR',
            15 => 'NET_SFTP_RMDIR',
            16 => 'NET_SFTP_REALPATH',
            17 => 'NET_SFTP_STAT',
            /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
                   SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
               pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
            18 => 'NET_SFTP_RENAME',
            19 => 'NET_SFTP_READLINK',
            20 => 'NET_SFTP_SYMLINK',

            101=> 'NET_SFTP_STATUS',
            102=> 'NET_SFTP_HANDLE',
            /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
                   SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
               pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
            103=> 'NET_SFTP_DATA',
            104=> 'NET_SFTP_NAME',
            105=> 'NET_SFTP_ATTRS',

            200=> 'NET_SFTP_EXTENDED'
        );
        $this->status_codes = array(
            0 => 'NET_SFTP_STATUS_OK',
            1 => 'NET_SFTP_STATUS_EOF',
            2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
            3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
            4 => 'NET_SFTP_STATUS_FAILURE',
            5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
            6 => 'NET_SFTP_STATUS_NO_CONNECTION',
            7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
            8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
            9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
            10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
            11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
            12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
            13 => 'NET_SFTP_STATUS_NO_MEDIA',
            14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
            15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
            16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
            17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
            18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
            19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
            20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
            21 => 'NET_SFTP_STATUS_LINK_LOOP',
            22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
            23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
            24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
            25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
            26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
            27 => 'NET_SFTP_STATUS_DELETE_PENDING',
            28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
            29 => 'NET_SFTP_STATUS_OWNER_INVALID',
            30 => 'NET_SFTP_STATUS_GROUP_INVALID',
            31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
        );
        // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
        // the order, in this case, matters quite a lot - see \phpseclib\Net\SFTP::_parseAttributes() to understand why
        $this->attributes = array(
            0x00000001 => 'NET_SFTP_ATTR_SIZE',
            0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
            0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
            0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
            // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
            // yields inconsistent behavior depending on how php is compiled.  so we left shift -1 (which, in
            // two's compliment, consists of all 1 bits) by 31.  on 64-bit systems this'll yield 0xFFFFFFFF80000000.
            // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
              -1 << 31 => 'NET_SFTP_ATTR_EXTENDED'
        );
        // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
        // the flag definitions change somewhat in SFTPv5+.  if SFTPv5+ support is added to this library, maybe name
        // the array for that $this->open5_flags and similarly alter the constant names.
        $this->open_flags = array(
            0x00000001 => 'NET_SFTP_OPEN_READ',
            0x00000002 => 'NET_SFTP_OPEN_WRITE',
            0x00000004 => 'NET_SFTP_OPEN_APPEND',
            0x00000008 => 'NET_SFTP_OPEN_CREATE',
            0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
            0x00000020 => 'NET_SFTP_OPEN_EXCL'
        );
        // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
        // see \phpseclib\Net\SFTP::_parseLongname() for an explanation
        $this->file_types = array(
            1 => 'NET_SFTP_TYPE_REGULAR',
            2 => 'NET_SFTP_TYPE_DIRECTORY',
            3 => 'NET_SFTP_TYPE_SYMLINK',
            4 => 'NET_SFTP_TYPE_SPECIAL',
            5 => 'NET_SFTP_TYPE_UNKNOWN',
            // the followin types were first defined for use in SFTPv5+
            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
            6 => 'NET_SFTP_TYPE_SOCKET',
            7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
            8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
            9 => 'NET_SFTP_TYPE_FIFO'
        );
        $this->_define_array(
            $this->packet_types,
            $this->status_codes,
            $this->attributes,
            $this->open_flags,
            $this->file_types
        );

        if (!defined('NET_SFTP_QUEUE_SIZE')) {
            define('NET_SFTP_QUEUE_SIZE', 50);
        }
    }

    /**
     * Login
     *
     * @param string $username
     * @param string $password
     * @return bool
     * @access public
     */
    function login($username)
    {
        $args = func_get_args();
        if (!call_user_func_array(array(&$this, '_login'), $args)) {
            return false;
        }

        $this->window_size_server_to_client[self::CHANNEL] = $this->window_size;

        $packet = pack(
            'CNa*N3',
            NET_SSH2_MSG_CHANNEL_OPEN,
            strlen('session'),
            'session',
            self::CHANNEL,
            $this->window_size,
            0x4000
        );

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;

        $response = $this->_get_channel_packet(self::CHANNEL);
        if ($response === false) {
            return false;
        }

        $packet = pack(
            'CNNa*CNa*',
            NET_SSH2_MSG_CHANNEL_REQUEST,
            $this->server_channels[self::CHANNEL],
            strlen('subsystem'),
            'subsystem',
            1,
            strlen('sftp'),
            'sftp'
        );
        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;

        $response = $this->_get_channel_packet(self::CHANNEL);
        if ($response === false) {
            // from PuTTY's psftp.exe
            $command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
                       "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
                       "exec sftp-server";
            // we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
            // is redundant
            $packet = pack(
                'CNNa*CNa*',
                NET_SSH2_MSG_CHANNEL_REQUEST,
                $this->server_channels[self::CHANNEL],
                strlen('exec'),
                'exec',
                1,
                strlen($command),
                $command
            );
            if (!$this->_send_binary_packet($packet)) {
                return false;
            }

            $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;

            $response = $this->_get_channel_packet(self::CHANNEL);
            if ($response === false) {
                return false;
            }
        }

        $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;

        if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_VERSION) {
            user_error('Expected SSH_FXP_VERSION');
            return false;
        }

        extract(unpack('Nversion', $this->_string_shift($response, 4)));
        $this->version = $version;
        while (!empty($response)) {
            extract(unpack('Nlength', $this->_string_shift($response, 4)));
            $key = $this->_string_shift($response, $length);
            extract(unpack('Nlength', $this->_string_shift($response, 4)));
            $value = $this->_string_shift($response, $length);
            $this->extensions[$key] = $value;
        }

        /*
         SFTPv4+ defines a 'newline' extension.  SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
         however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
         not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
         one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
         'newline@vandyke.com' would.
        */
        /*
        if (isset($this->extensions['newline@vandyke.com'])) {
            $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
            unset($this->extensions['newline@vandyke.com']);
        }
        */

        $this->request_id = 1;

        /*
         A Note on SFTPv4/5/6 support:
         <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:

         "If the client wishes to interoperate with servers that support noncontiguous version
          numbers it SHOULD send '3'"

         Given that the server only sends its version number after the client has already done so, the above
         seems to be suggesting that v3 should be the default version.  This makes sense given that v3 is the
         most popular.

         <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;

         "If the server did not send the "versions" extension, or the version-from-list was not included, the
          server MAY send a status response describing the failure, but MUST then close the channel without
          processing any further requests."

         So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
         a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4?  If it only implements
         v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
         in draft-ietf-secsh-filexfer-13 would be quite impossible.  As such, what \phpseclib\Net\SFTP would do is close the
         channel and reopen it with a new and updated SSH_FXP_INIT packet.
        */
        switch ($this->version) {
            case 2:
            case 3:
                break;
            default:
                return false;
        }

        $this->pwd = $this->_realpath('.');

        $this->_update_stat_cache($this->pwd, array());

        return true;
    }

    /**
     * Disable the stat cache
     *
     * @access public
     */
    function disableStatCache()
    {
        $this->use_stat_cache = false;
    }

    /**
     * Enable the stat cache
     *
     * @access public
     */
    function enableStatCache()
    {
        $this->use_stat_cache = true;
    }

    /**
     * Clear the stat cache
     *
     * @access public
     */
    function clearStatCache()
    {
        $this->stat_cache = array();
    }

    /**
     * Returns the current directory name
     *
     * @return mixed
     * @access public
     */
    function pwd()
    {
        return $this->pwd;
    }

    /**
     * Logs errors
     *
     * @param string $response
     * @param int $status
     * @access public
     */
    function _logError($response, $status = -1)
    {
        if ($status == -1) {
            extract(unpack('Nstatus', $this->_string_shift($response, 4)));
        }

        $error = $this->status_codes[$status];

        if ($this->version > 2) {
            extract(unpack('Nlength', $this->_string_shift($response, 4)));
            $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
        } else {
            $this->sftp_errors[] = $error;
        }
    }

    /**
     * Returns canonicalized absolute pathname
     *
     * realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input
     * path and returns the canonicalized absolute pathname.
     *
     * @param string $path
     * @return mixed
     * @access public
     */
    function realpath($path)
    {
        return $this->_realpath($path);
    }

    /**
     * Canonicalize the Server-Side Path Name
     *
     * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it.  Returns
     * the absolute (canonicalized) path.
     *
     * @see self::chdir()
     * @param string $path
     * @return mixed
     * @access private
     */
    function _realpath($path)
    {
        if ($this->pwd === false) {
            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
            if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
                return false;
            }

            $response = $this->_get_sftp_packet();
            switch ($this->packet_type) {
                case NET_SFTP_NAME:
                    // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
                    // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
                    // at is the first part and that part is defined the same in SFTP versions 3 through 6.
                    $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
                    extract(unpack('Nlength', $this->_string_shift($response, 4)));
                    return $this->_string_shift($response, $length);
                case NET_SFTP_STATUS:
                    $this->_logError($response);
                    return false;
                default:
                    user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
                    return false;
            }
        }

        if ($path[0] != '/') {
            $path = $this->pwd . '/' . $path;
        }

        $path = explode('/', $path);
        $new = array();
        foreach ($path as $dir) {
            if (!strlen($dir)) {
                continue;
            }
            switch ($dir) {
                case '..':
                    array_pop($new);
                case '.':
                    break;
                default:
                    $new[] = $dir;
            }
        }

        return '/' . implode('/', $new);
    }

    /**
     * Changes the current directory
     *
     * @param string $dir
     * @return bool
     * @access public
     */
    function chdir($dir)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        // assume current dir if $dir is empty
        if ($dir === '') {
            $dir = './';
        // suffix a slash if needed
        } elseif ($dir[strlen($dir) - 1] != '/') {
            $dir.= '/';
        }

        $dir = $this->_realpath($dir);

        // confirm that $dir is, in fact, a valid directory
        if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
            $this->pwd = $dir;
            return true;
        }

        // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
        // the currently logged in user has the appropriate permissions or not. maybe you could see if
        // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
        // way to get those with SFTP

        if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
            return false;
        }

        // see \phpseclib\Net\SFTP::nlist() for a more thorough explanation of the following
        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_HANDLE:
                $handle = substr($response, 4);
                break;
            case NET_SFTP_STATUS:
                $this->_logError($response);
                return false;
            default:
                user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
                return false;
        }

        if (!$this->_close_handle($handle)) {
            return false;
        }

        $this->_update_stat_cache($dir, array());

        $this->pwd = $dir;
        return true;
    }

    /**
     * Returns a list of files in the given directory
     *
     * @param string $dir
     * @param bool $recursive
     * @return mixed
     * @access public
     */
    function nlist($dir = '.', $recursive = false)
    {
        return $this->_nlist_helper($dir, $recursive, '');
    }

    /**
     * Helper method for nlist
     *
     * @param string $dir
     * @param bool $recursive
     * @param string $relativeDir
     * @return mixed
     * @access private
     */
    function _nlist_helper($dir, $recursive, $relativeDir)
    {
        $files = $this->_list($dir, false);

        if (!$recursive || $files === false) {
            return $files;
        }

        $result = array();
        foreach ($files as $value) {
            if ($value == '.' || $value == '..') {
                if ($relativeDir == '') {
                    $result[] = $value;
                }
                continue;
            }
            if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) {
                $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
                $result = array_merge($result, $temp);
            } else {
                $result[] = $relativeDir . $value;
            }
        }

        return $result;
    }

    /**
     * Returns a detailed list of files in the given directory
     *
     * @param string $dir
     * @param bool $recursive
     * @return mixed
     * @access public
     */
    function rawlist($dir = '.', $recursive = false)
    {
        $files = $this->_list($dir, true);
        if (!$recursive || $files === false) {
            return $files;
        }

        static $depth = 0;

        foreach ($files as $key => $value) {
            if ($depth != 0 && $key == '..') {
                unset($files[$key]);
                continue;
            }
            if ($key != '.' && $key != '..' && is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)))) {
                $depth++;
                $files[$key] = $this->rawlist($dir . '/' . $key, true);
                $depth--;
            } else {
                $files[$key] = (object) $value;
            }
        }

        return $files;
    }

    /**
     * Reads a list, be it detailed or not, of files in the given directory
     *
     * @param string $dir
     * @param bool $raw
     * @return mixed
     * @access private
     */
    function _list($dir, $raw = true)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $dir = $this->_realpath($dir . '/');
        if ($dir === false) {
            return false;
        }

        // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
        if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_HANDLE:
                // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
                // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
                // represent the length of the string and leave it at that
                $handle = substr($response, 4);
                break;
            case NET_SFTP_STATUS:
                // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
                $this->_logError($response);
                return false;
            default:
                user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
                return false;
        }

        $this->_update_stat_cache($dir, array());

        $contents = array();
        while (true) {
            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
            // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
            // SSH_MSG_CHANNEL_DATA messages is not known to me.
            if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
                return false;
            }

            $response = $this->_get_sftp_packet();
            switch ($this->packet_type) {
                case NET_SFTP_NAME:
                    extract(unpack('Ncount', $this->_string_shift($response, 4)));
                    for ($i = 0; $i < $count; $i++) {
                        extract(unpack('Nlength', $this->_string_shift($response, 4)));
                        $shortname = $this->_string_shift($response, $length);
                        extract(unpack('Nlength', $this->_string_shift($response, 4)));
                        $longname = $this->_string_shift($response, $length);
                        $attributes = $this->_parseAttributes($response);
                        if (!isset($attributes['type'])) {
                            $fileType = $this->_parseLongname($longname);
                            if ($fileType) {
                                $attributes['type'] = $fileType;
                            }
                        }
                        $contents[$shortname] = $attributes + array('filename' => $shortname);

                        if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
                            $this->_update_stat_cache($dir . '/' . $shortname, array());
                        } else {
                            if ($shortname == '..') {
                                $temp = $this->_realpath($dir . '/..') . '/.';
                            } else {
                                $temp = $dir . '/' . $shortname;
                            }
                            $this->_update_stat_cache($temp, (object) array('lstat' => $attributes));
                        }
                        // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
                        // final SSH_FXP_STATUS packet should tell us that, already.
                    }
                    break;
                case NET_SFTP_STATUS:
                    extract(unpack('Nstatus', $this->_string_shift($response, 4)));
                    if ($status != NET_SFTP_STATUS_EOF) {
                        $this->_logError($response, $status);
                        return false;
                    }
                    break 2;
                default:
                    user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
                    return false;
            }
        }

        if (!$this->_close_handle($handle)) {
            return false;
        }

        if (count($this->sortOptions)) {
            uasort($contents, array(&$this, '_comparator'));
        }

        return $raw ? $contents : array_keys($contents);
    }

    /**
     * Compares two rawlist entries using parameters set by setListOrder()
     *
     * Intended for use with uasort()
     *
     * @param array $a
     * @param array $b
     * @return int
     * @access private
     */
    function _comparator($a, $b)
    {
        switch (true) {
            case $a['filename'] === '.' || $b['filename'] === '.':
                if ($a['filename'] === $b['filename']) {
                    return 0;
                }
                return $a['filename'] === '.' ? -1 : 1;
            case $a['filename'] === '..' || $b['filename'] === '..':
                if ($a['filename'] === $b['filename']) {
                    return 0;
                }
                return $a['filename'] === '..' ? -1 : 1;
            case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
                if (!isset($b['type'])) {
                    return 1;
                }
                if ($b['type'] !== $a['type']) {
                    return -1;
                }
                break;
            case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
                return 1;
        }
        foreach ($this->sortOptions as $sort => $order) {
            if (!isset($a[$sort]) || !isset($b[$sort])) {
                if (isset($a[$sort])) {
                    return -1;
                }
                if (isset($b[$sort])) {
                    return 1;
                }
                return 0;
            }
            switch ($sort) {
                case 'filename':
                    $result = strcasecmp($a['filename'], $b['filename']);
                    if ($result) {
                        return $order === SORT_DESC ? -$result : $result;
                    }
                    break;
                case 'permissions':
                case 'mode':
                    $a[$sort]&= 07777;
                    $b[$sort]&= 07777;
                default:
                    if ($a[$sort] === $b[$sort]) {
                        break;
                    }
                    return $order === SORT_ASC ? $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
            }
        }
    }

    /**
     * Defines how nlist() and rawlist() will be sorted - if at all.
     *
     * If sorting is enabled directories and files will be sorted independently with
     * directories appearing before files in the resultant array that is returned.
     *
     * Any parameter returned by stat is a valid sort parameter for this function.
     * Filename comparisons are case insensitive.
     *
     * Examples:
     *
     * $sftp->setListOrder('filename', SORT_ASC);
     * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
     * $sftp->setListOrder(true);
     *    Separates directories from files but doesn't do any sorting beyond that
     * $sftp->setListOrder();
     *    Don't do any sort of sorting
     *
     * @access public
     */
    function setListOrder()
    {
        $this->sortOptions = array();
        $args = func_get_args();
        if (empty($args)) {
            return;
        }
        $len = count($args) & 0x7FFFFFFE;
        for ($i = 0; $i < $len; $i+=2) {
            $this->sortOptions[$args[$i]] = $args[$i + 1];
        }
        if (!count($this->sortOptions)) {
            $this->sortOptions = array('bogus' => true);
        }
    }

    /**
     * Returns the file size, in bytes, or false, on failure
     *
     * Files larger than 4GB will show up as being exactly 4GB.
     *
     * @param string $filename
     * @return mixed
     * @access public
     */
    function size($filename)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $result = $this->stat($filename);
        if ($result === false) {
            return false;
        }
        return isset($result['size']) ? $result['size'] : -1;
    }

    /**
     * Save files / directories to cache
     *
     * @param string $path
     * @param mixed $value
     * @access private
     */
    function _update_stat_cache($path, $value)
    {
        if ($this->use_stat_cache === false) {
            return;
        }

        // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));

        $temp = &$this->stat_cache;
        $max = count($dirs) - 1;
        foreach ($dirs as $i => $dir) {
            // if $temp is an object that means one of two things.
            //  1. a file was deleted and changed to a directory behind phpseclib's back
            //  2. it's a symlink. when lstat is done it's unclear what it's a symlink to
            if (is_object($temp)) {
                $temp = array();
            }
            if (!isset($temp[$dir])) {
                $temp[$dir] = array();
            }
            if ($i === $max) {
                if (is_object($temp[$dir])) {
                    if (!isset($value->stat) && isset($temp[$dir]->stat)) {
                        $value->stat = $temp[$dir]->stat;
                    }
                    if (!isset($value->lstat) && isset($temp[$dir]->lstat)) {
                        $value->lstat = $temp[$dir]->lstat;
                    }
                }
                $temp[$dir] = $value;
                break;
            }
            $temp = &$temp[$dir];
        }
    }

    /**
     * Remove files / directories from cache
     *
     * @param string $path
     * @return bool
     * @access private
     */
    function _remove_from_stat_cache($path)
    {
        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));

        $temp = &$this->stat_cache;
        $max = count($dirs) - 1;
        foreach ($dirs as $i => $dir) {
            if ($i === $max) {
                unset($temp[$dir]);
                return true;
            }
            if (!isset($temp[$dir])) {
                return false;
            }
            $temp = &$temp[$dir];
        }
    }

    /**
     * Checks cache for path
     *
     * Mainly used by file_exists
     *
     * @param string $dir
     * @return mixed
     * @access private
     */
    function _query_stat_cache($path)
    {
        $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));

        $temp = &$this->stat_cache;
        foreach ($dirs as $dir) {
            if (!isset($temp[$dir])) {
                return null;
            }
            $temp = &$temp[$dir];
        }
        return $temp;
    }

    /**
     * Returns general information about a file.
     *
     * Returns an array on success and false otherwise.
     *
     * @param string $filename
     * @return mixed
     * @access public
     */
    function stat($filename)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $filename = $this->_realpath($filename);
        if ($filename === false) {
            return false;
        }

        if ($this->use_stat_cache) {
            $result = $this->_query_stat_cache($filename);
            if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
                return $result['.']->stat;
            }
            if (is_object($result) && isset($result->stat)) {
                return $result->stat;
            }
        }

        $stat = $this->_stat($filename, NET_SFTP_STAT);
        if ($stat === false) {
            $this->_remove_from_stat_cache($filename);
            return false;
        }
        if (isset($stat['type'])) {
            if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
                $filename.= '/.';
            }
            $this->_update_stat_cache($filename, (object) array('stat' => $stat));
            return $stat;
        }

        $pwd = $this->pwd;
        $stat['type'] = $this->chdir($filename) ?
            NET_SFTP_TYPE_DIRECTORY :
            NET_SFTP_TYPE_REGULAR;
        $this->pwd = $pwd;

        if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
            $filename.= '/.';
        }
        $this->_update_stat_cache($filename, (object) array('stat' => $stat));

        return $stat;
    }

    /**
     * Returns general information about a file or symbolic link.
     *
     * Returns an array on success and false otherwise.
     *
     * @param string $filename
     * @return mixed
     * @access public
     */
    function lstat($filename)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $filename = $this->_realpath($filename);
        if ($filename === false) {
            return false;
        }

        if ($this->use_stat_cache) {
            $result = $this->_query_stat_cache($filename);
            if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
                return $result['.']->lstat;
            }
            if (is_object($result) && isset($result->lstat)) {
                return $result->lstat;
            }
        }

        $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
        if ($lstat === false) {
            $this->_remove_from_stat_cache($filename);
            return false;
        }
        if (isset($lstat['type'])) {
            if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
                $filename.= '/.';
            }
            $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
            return $lstat;
        }

        $stat = $this->_stat($filename, NET_SFTP_STAT);

        if ($lstat != $stat) {
            $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
            $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
            return $stat;
        }

        $pwd = $this->pwd;
        $lstat['type'] = $this->chdir($filename) ?
            NET_SFTP_TYPE_DIRECTORY :
            NET_SFTP_TYPE_REGULAR;
        $this->pwd = $pwd;

        if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
            $filename.= '/.';
        }
        $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));

        return $lstat;
    }

    /**
     * Returns general information about a file or symbolic link
     *
     * Determines information without calling \phpseclib\Net\SFTP::_realpath().
     * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
     *
     * @param string $filename
     * @param int $type
     * @return mixed
     * @access private
     */
    function _stat($filename, $type)
    {
        // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
        $packet = pack('Na*', strlen($filename), $filename);
        if (!$this->_send_sftp_packet($type, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_ATTRS:
                return $this->_parseAttributes($response);
            case NET_SFTP_STATUS:
                $this->_logError($response);
                return false;
        }

        user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
        return false;
    }

    /**
     * Truncates a file to a given length
     *
     * @param string $filename
     * @param int $new_size
     * @return bool
     * @access public
     */
    function truncate($filename, $new_size)
    {
        $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32

        return $this->_setstat($filename, $attr, false);
    }

    /**
     * Sets access and modification time of file.
     *
     * If the file does not exist, it will be created.
     *
     * @param string $filename
     * @param int $time
     * @param int $atime
     * @return bool
     * @access public
     */
    function touch($filename, $time = null, $atime = null)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $filename = $this->_realpath($filename);
        if ($filename === false) {
            return false;
        }

        if (!isset($time)) {
            $time = time();
        }
        if (!isset($atime)) {
            $atime = $time;
        }

        $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL;
        $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $time, $atime);
        $packet = pack('Na*Na*', strlen($filename), $filename, $flags, $attr);
        if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_HANDLE:
                return $this->_close_handle(substr($response, 4));
            case NET_SFTP_STATUS:
                $this->_logError($response);
                break;
            default:
                user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
                return false;
        }

        return $this->_setstat($filename, $attr, false);
    }

    /**
     * Changes file or directory owner
     *
     * Returns true on success or false on error.
     *
     * @param string $filename
     * @param int $uid
     * @param bool $recursive
     * @return bool
     * @access public
     */
    function chown($filename, $uid, $recursive = false)
    {
        // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
        // "if the owner or group is specified as -1, then that ID is not changed"
        $attr = pack('N3', NET_SFTP_ATTR_UIDGID, $uid, -1);

        return $this->_setstat($filename, $attr, $recursive);
    }

    /**
     * Changes file or directory group
     *
     * Returns true on success or false on error.
     *
     * @param string $filename
     * @param int $gid
     * @param bool $recursive
     * @return bool
     * @access public
     */
    function chgrp($filename, $gid, $recursive = false)
    {
        $attr = pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid);

        return $this->_setstat($filename, $attr, $recursive);
    }

    /**
     * Set permissions on a file.
     *
     * Returns the new file permissions on success or false on error.
     * If $recursive is true than this just returns true or false.
     *
     * @param int $mode
     * @param string $filename
     * @param bool $recursive
     * @return mixed
     * @access public
     */
    function chmod($mode, $filename, $recursive = false)
    {
        if (is_string($mode) && is_int($filename)) {
            $temp = $mode;
            $mode = $filename;
            $filename = $temp;
        }

        $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
        if (!$this->_setstat($filename, $attr, $recursive)) {
            return false;
        }
        if ($recursive) {
            return true;
        }

        $filename = $this->_realPath($filename);
        // rather than return what the permissions *should* be, we'll return what they actually are.  this will also
        // tell us if the file actually exists.
        // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
        $packet = pack('Na*', strlen($filename), $filename);
        if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_ATTRS:
                $attrs = $this->_parseAttributes($response);
                return $attrs['permissions'];
            case NET_SFTP_STATUS:
                $this->_logError($response);
                return false;
        }

        user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
        return false;
    }

    /**
     * Sets information about a file
     *
     * @param string $filename
     * @param string $attr
     * @param bool $recursive
     * @return bool
     * @access private
     */
    function _setstat($filename, $attr, $recursive)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $filename = $this->_realpath($filename);
        if ($filename === false) {
            return false;
        }

        $this->_remove_from_stat_cache($filename);

        if ($recursive) {
            $i = 0;
            $result = $this->_setstat_recursive($filename, $attr, $i);
            $this->_read_put_responses($i);
            return $result;
        }

        // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
        // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
        if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) {
            return false;
        }

        /*
         "Because some systems must use separate system calls to set various attributes, it is possible that a failure
          response will be returned, but yet some of the attributes may be have been successfully modified.  If possible,
          servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."

          -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
        */
        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_STATUS) {
            user_error('Expected SSH_FXP_STATUS');
            return false;
        }

        extract(unpack('Nstatus', $this->_string_shift($response, 4)));
        if ($status != NET_SFTP_STATUS_OK) {
            $this->_logError($response, $status);
            return false;
        }

        return true;
    }

    /**
     * Recursively sets information on directories on the SFTP server
     *
     * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
     *
     * @param string $path
     * @param string $attr
     * @param int $i
     * @return bool
     * @access private
     */
    function _setstat_recursive($path, $attr, &$i)
    {
        if (!$this->_read_put_responses($i)) {
            return false;
        }
        $i = 0;
        $entries = $this->_list($path, true);

        if ($entries === false) {
            return $this->_setstat($path, $attr, false);
        }

        // normally $entries would have at least . and .. but it might not if the directories
        // permissions didn't allow reading
        if (empty($entries)) {
            return false;
        }

        unset($entries['.'], $entries['..']);
        foreach ($entries as $filename => $props) {
            if (!isset($props['type'])) {
                return false;
            }

            $temp = $path . '/' . $filename;
            if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
                if (!$this->_setstat_recursive($temp, $attr, $i)) {
                    return false;
                }
            } else {
                if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) {
                    return false;
                }

                $i++;

                if ($i >= NET_SFTP_QUEUE_SIZE) {
                    if (!$this->_read_put_responses($i)) {
                        return false;
                    }
                    $i = 0;
                }
            }
        }

        if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) {
            return false;
        }

        $i++;

        if ($i >= NET_SFTP_QUEUE_SIZE) {
            if (!$this->_read_put_responses($i)) {
                return false;
            }
            $i = 0;
        }

        return true;
    }

    /**
     * Return the target of a symbolic link
     *
     * @param string $link
     * @return mixed
     * @access public
     */
    function readlink($link)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $link = $this->_realpath($link);

        if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_NAME:
                break;
            case NET_SFTP_STATUS:
                $this->_logError($response);
                return false;
            default:
                user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
                return false;
        }

        extract(unpack('Ncount', $this->_string_shift($response, 4)));
        // the file isn't a symlink
        if (!$count) {
            return false;
        }

        extract(unpack('Nlength', $this->_string_shift($response, 4)));
        return $this->_string_shift($response, $length);
    }

    /**
     * Create a symlink
     *
     * symlink() creates a symbolic link to the existing target with the specified name link.
     *
     * @param string $target
     * @param string $link
     * @return bool
     * @access public
     */
    function symlink($target, $link)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        //$target = $this->_realpath($target);
        $link = $this->_realpath($link);

        $packet = pack('Na*Na*', strlen($target), $target, strlen($link), $link);
        if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_STATUS) {
            user_error('Expected SSH_FXP_STATUS');
            return false;
        }

        extract(unpack('Nstatus', $this->_string_shift($response, 4)));
        if ($status != NET_SFTP_STATUS_OK) {
            $this->_logError($response, $status);
            return false;
        }

        return true;
    }

    /**
     * Creates a directory.
     *
     * @param string $dir
     * @return bool
     * @access public
     */
    function mkdir($dir, $mode = -1, $recursive = false)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $dir = $this->_realpath($dir);
        // by not providing any permissions, hopefully the server will use the logged in users umask - their
        // default permissions.
        $attr = $mode == -1 ? "\0\0\0\0" : pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);

        if ($recursive) {
            $dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
            if (empty($dirs[0])) {
                array_shift($dirs);
                $dirs[0] = '/' . $dirs[0];
            }
            for ($i = 0; $i < count($dirs); $i++) {
                $temp = array_slice($dirs, 0, $i + 1);
                $temp = implode('/', $temp);
                $result = $this->_mkdir_helper($temp, $attr);
            }
            return $result;
        }

        return $this->_mkdir_helper($dir, $attr);
    }

    /**
     * Helper function for directory creation
     *
     * @param string $dir
     * @return bool
     * @access private
     */
    function _mkdir_helper($dir, $attr)
    {
        if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, $attr))) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_STATUS) {
            user_error('Expected SSH_FXP_STATUS');
            return false;
        }

        extract(unpack('Nstatus', $this->_string_shift($response, 4)));
        if ($status != NET_SFTP_STATUS_OK) {
            $this->_logError($response, $status);
            return false;
        }

        return true;
    }

    /**
     * Removes a directory.
     *
     * @param string $dir
     * @return bool
     * @access public
     */
    function rmdir($dir)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $dir = $this->_realpath($dir);
        if ($dir === false) {
            return false;
        }

        if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_STATUS) {
            user_error('Expected SSH_FXP_STATUS');
            return false;
        }

        extract(unpack('Nstatus', $this->_string_shift($response, 4)));
        if ($status != NET_SFTP_STATUS_OK) {
            // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
            $this->_logError($response, $status);
            return false;
        }

        $this->_remove_from_stat_cache($dir);
        // the following will do a soft delete, which would be useful if you deleted a file
        // and then tried to do a stat on the deleted file. the above, in contrast, does
        // a hard delete
        //$this->_update_stat_cache($dir, false);

        return true;
    }

    /**
     * Uploads a file to the SFTP server.
     *
     * By default, \phpseclib\Net\SFTP::put() does not read from the local filesystem.  $data is dumped directly into $remote_file.
     * So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SFTP::get(), you will get a file, twelve bytes
     * long, containing 'filename.ext' as its contents.
     *
     * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior.  With self::SOURCE_LOCAL_FILE, $remote_file will
     * contain as many bytes as filename.ext does on your local filesystem.  If your filename.ext is 1MB then that is how
     * large $remote_file will be, as well.
     *
     * Setting $mode to self::SOURCE_CALLBACK will use $data as callback function, which gets only one parameter -- number of bytes to return, and returns a string if there is some data or null if there is no more data
     *
     * If $data is a resource then it'll be used as a resource instead.
     *
     * Currently, only binary mode is supported.  As such, if the line endings need to be adjusted, you will need to take
     * care of that, yourself.
     *
     * $mode can take an additional two parameters - self::RESUME and self::RESUME_START. These are bitwise AND'd with
     * $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
     *
     * self::SOURCE_LOCAL_FILE | self::RESUME
     *
     * If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
     * self::RESUME with self::RESUME_START.
     *
     * If $mode & (self::RESUME | self::RESUME_START) then self::RESUME_START will be assumed.
     *
     * $start and $local_start give you more fine grained control over this process and take precident over self::RESUME
     * when they're non-negative. ie. $start could let you write at the end of a file (like self::RESUME) or in the middle
     * of one. $local_start could let you start your reading from the end of a file (like self::RESUME_START) or in the
     * middle of one.
     *
     * Setting $local_start to > 0 or $mode | self::RESUME_START doesn't do anything unless $mode | self::SOURCE_LOCAL_FILE.
     *
     * @param string $remote_file
     * @param string|resource $data
     * @param int $mode
     * @param int $start
     * @param int $local_start
     * @param callable|null $progressCallback
     * @return bool
     * @access public
     * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib\Net\SFTP::setMode().
     */
    function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $remote_file = $this->_realpath($remote_file);
        if ($remote_file === false) {
            return false;
        }

        $this->_remove_from_stat_cache($remote_file);

        $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
        // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
        // in practice, it doesn't seem to do that.
        //$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;

        if ($start >= 0) {
            $offset = $start;
        } elseif ($mode & self::RESUME) {
            // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
            $size = $this->size($remote_file);
            $offset = $size !== false ? $size : 0;
        } else {
            $offset = 0;
            $flags|= NET_SFTP_OPEN_TRUNCATE;
        }

        $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0);
        if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_HANDLE:
                $handle = substr($response, 4);
                break;
            case NET_SFTP_STATUS:
                $this->_logError($response);
                return false;
            default:
                user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
                return false;
        }

        // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
        $dataCallback = false;
        switch (true) {
            case $mode & self::SOURCE_CALLBACK:
                if (!is_callable($data)) {
                    user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag");
                }
                $dataCallback = $data;
                // do nothing
                break;
            case is_resource($data):
                $mode = $mode & ~self::SOURCE_LOCAL_FILE;
                $fp = $data;
                break;
            case $mode & self::SOURCE_LOCAL_FILE:
                if (!is_file($data)) {
                    user_error("$data is not a valid file");
                    return false;
                }
                $fp = @fopen($data, 'rb');
                if (!$fp) {
                    return false;
                }
        }

        if (isset($fp)) {
            $stat = fstat($fp);
            $size = $stat['size'];

            if ($local_start >= 0) {
                fseek($fp, $local_start);
                $size-= $local_start;
            }
        } elseif ($dataCallback) {
            $size = 0;
        } else {
            $size = strlen($data);
        }

        $sent = 0;
        $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;

        $sftp_packet_size = 4096; // PuTTY uses 4096
        // make the SFTP packet be exactly 4096 bytes by including the bytes in the NET_SFTP_WRITE packets "header"
        $sftp_packet_size-= strlen($handle) + 25;
        $i = 0;
        while ($dataCallback || ($size === 0 || $sent < $size)) {
            if ($dataCallback) {
                $temp = call_user_func($dataCallback, $sftp_packet_size);
                if (is_null($temp)) {
                    break;
                }
            } else {
                $temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
                if ($temp === false || $temp === '') {
                    break;
                }
            }

            $subtemp = $offset + $sent;
            $packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
            if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
                if ($mode & self::SOURCE_LOCAL_FILE) {
                    fclose($fp);
                }
                return false;
            }
            $sent+= strlen($temp);
            if (is_callable($progressCallback)) {
                call_user_func($progressCallback, $sent);
            }

            $i++;

            if ($i == NET_SFTP_QUEUE_SIZE) {
                if (!$this->_read_put_responses($i)) {
                    $i = 0;
                    break;
                }
                $i = 0;
            }
        }

        if (!$this->_read_put_responses($i)) {
            if ($mode & self::SOURCE_LOCAL_FILE) {
                fclose($fp);
            }
            $this->_close_handle($handle);
            return false;
        }

        if ($mode & self::SOURCE_LOCAL_FILE) {
            fclose($fp);
        }

        return $this->_close_handle($handle);
    }

    /**
     * Reads multiple successive SSH_FXP_WRITE responses
     *
     * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
     * SSH_FXP_WRITEs, in succession, and then reading $i responses.
     *
     * @param int $i
     * @return bool
     * @access private
     */
    function _read_put_responses($i)
    {
        while ($i--) {
            $response = $this->_get_sftp_packet();
            if ($this->packet_type != NET_SFTP_STATUS) {
                user_error('Expected SSH_FXP_STATUS');
                return false;
            }

            extract(unpack('Nstatus', $this->_string_shift($response, 4)));
            if ($status != NET_SFTP_STATUS_OK) {
                $this->_logError($response, $status);
                break;
            }
        }

        return $i < 0;
    }

    /**
     * Close handle
     *
     * @param string $handle
     * @return bool
     * @access private
     */
    function _close_handle($handle)
    {
        if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
            return false;
        }

        // "The client MUST release all resources associated with the handle regardless of the status."
        //  -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_STATUS) {
            user_error('Expected SSH_FXP_STATUS');
            return false;
        }

        extract(unpack('Nstatus', $this->_string_shift($response, 4)));
        if ($status != NET_SFTP_STATUS_OK) {
            $this->_logError($response, $status);
            return false;
        }

        return true;
    }

    /**
     * Downloads a file from the SFTP server.
     *
     * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
     * the operation was unsuccessful.  If $local_file is defined, returns true or false depending on the success of the
     * operation.
     *
     * $offset and $length can be used to download files in chunks.
     *
     * @param string $remote_file
     * @param string $local_file
     * @param int $offset
     * @param int $length
     * @return mixed
     * @access public
     */
    function get($remote_file, $local_file = false, $offset = 0, $length = -1)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $remote_file = $this->_realpath($remote_file);
        if ($remote_file === false) {
            return false;
        }

        $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0);
        if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_HANDLE:
                $handle = substr($response, 4);
                break;
            case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
                $this->_logError($response);
                return false;
            default:
                user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
                return false;
        }

        if (is_resource($local_file)) {
            $fp = $local_file;
            $stat = fstat($fp);
            $res_offset = $stat['size'];
        } else {
            $res_offset = 0;
            if ($local_file !== false) {
                $fp = fopen($local_file, 'wb');
                if (!$fp) {
                    return false;
                }
            } else {
                $content = '';
            }
        }

        $fclose_check = $local_file !== false && !is_resource($local_file);

        $start = $offset;
        $read = 0;
        while (true) {
            $i = 0;

            while ($i < NET_SFTP_QUEUE_SIZE && ($length < 0 || $read < $length)) {
                $tempoffset = $start + $read;

                $packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;

                $packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
                if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
                    if ($fclose_check) {
                        fclose($fp);
                    }
                    return false;
                }
                $packet = null;
                $read+= $packet_size;
                $i++;
            }

            if (!$i) {
                break;
            }

            $clear_responses = false;
            while ($i > 0) {
                $i--;

                if ($clear_responses) {
                    $this->_get_sftp_packet();
                    continue;
                } else {
                    $response = $this->_get_sftp_packet();
                }

                switch ($this->packet_type) {
                    case NET_SFTP_DATA:
                        $temp = substr($response, 4);
                        $offset+= strlen($temp);
                        if ($local_file === false) {
                            $content.= $temp;
                        } else {
                            fputs($fp, $temp);
                        }
                        $temp = null;
                        break;
                    case NET_SFTP_STATUS:
                        // could, in theory, return false if !strlen($content) but we'll hold off for the time being
                        $this->_logError($response);
                        $clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses
                        break;
                    default:
                        if ($fclose_check) {
                            fclose($fp);
                        }
                        user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS');
                }
                $response = null;
            }

            if ($clear_responses) {
                break;
            }
        }

        if ($length > 0 && $length <= $offset - $start) {
            if ($local_file === false) {
                $content = substr($content, 0, $length);
            } else {
                ftruncate($fp, $length + $res_offset);
            }
        }

        if ($fclose_check) {
            fclose($fp);
        }

        if (!$this->_close_handle($handle)) {
            return false;
        }

        // if $content isn't set that means a file was written to
        return isset($content) ? $content : true;
    }

    /**
     * Deletes a file on the SFTP server.
     *
     * @param string $path
     * @param bool $recursive
     * @return bool
     * @access public
     */
    function delete($path, $recursive = true)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $path = $this->_realpath($path);
        if ($path === false) {
            return false;
        }

        // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
        if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_STATUS) {
            user_error('Expected SSH_FXP_STATUS');
            return false;
        }

        // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
        extract(unpack('Nstatus', $this->_string_shift($response, 4)));
        if ($status != NET_SFTP_STATUS_OK) {
            $this->_logError($response, $status);
            if (!$recursive) {
                return false;
            }
            $i = 0;
            $result = $this->_delete_recursive($path, $i);
            $this->_read_put_responses($i);
            return $result;
        }

        $this->_remove_from_stat_cache($path);

        return true;
    }

    /**
     * Recursively deletes directories on the SFTP server
     *
     * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
     *
     * @param string $path
     * @param int $i
     * @return bool
     * @access private
     */
    function _delete_recursive($path, &$i)
    {
        if (!$this->_read_put_responses($i)) {
            return false;
        }
        $i = 0;
        $entries = $this->_list($path, true);

        // normally $entries would have at least . and .. but it might not if the directories
        // permissions didn't allow reading
        if (empty($entries)) {
            return false;
        }

        unset($entries['.'], $entries['..']);
        foreach ($entries as $filename => $props) {
            if (!isset($props['type'])) {
                return false;
            }

            $temp = $path . '/' . $filename;
            if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
                if (!$this->_delete_recursive($temp, $i)) {
                    return false;
                }
            } else {
                if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
                    return false;
                }
                $this->_remove_from_stat_cache($temp);

                $i++;

                if ($i >= NET_SFTP_QUEUE_SIZE) {
                    if (!$this->_read_put_responses($i)) {
                        return false;
                    }
                    $i = 0;
                }
            }
        }

        if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
            return false;
        }
        $this->_remove_from_stat_cache($path);

        $i++;

        if ($i >= NET_SFTP_QUEUE_SIZE) {
            if (!$this->_read_put_responses($i)) {
                return false;
            }
            $i = 0;
        }

        return true;
    }

    /**
     * Checks whether a file or directory exists
     *
     * @param string $path
     * @return bool
     * @access public
     */
    function file_exists($path)
    {
        if ($this->use_stat_cache) {
            $path = $this->_realpath($path);

            $result = $this->_query_stat_cache($path);

            if (isset($result)) {
                // return true if $result is an array or if it's an stdClass object
                return $result !== false;
            }
        }

        return $this->stat($path) !== false;
    }

    /**
     * Tells whether the filename is a directory
     *
     * @param string $path
     * @return bool
     * @access public
     */
    function is_dir($path)
    {
        $result = $this->_get_stat_cache_prop($path, 'type');
        if ($result === false) {
            return false;
        }
        return $result === NET_SFTP_TYPE_DIRECTORY;
    }

    /**
     * Tells whether the filename is a regular file
     *
     * @param string $path
     * @return bool
     * @access public
     */
    function is_file($path)
    {
        $result = $this->_get_stat_cache_prop($path, 'type');
        if ($result === false) {
            return false;
        }
        return $result === NET_SFTP_TYPE_REGULAR;
    }

    /**
     * Tells whether the filename is a symbolic link
     *
     * @param string $path
     * @return bool
     * @access public
     */
    function is_link($path)
    {
        $result = $this->_get_lstat_cache_prop($path, 'type');
        if ($result === false) {
            return false;
        }
        return $result === NET_SFTP_TYPE_SYMLINK;
    }

    /**
     * Tells whether a file exists and is readable
     *
     * @param string $path
     * @return bool
     * @access public
     */
    function is_readable($path)
    {
        $path = $this->_realpath($path);

        $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0);
        if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_HANDLE:
                return true;
            case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
                return false;
            default:
                user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
                return false;
        }
    }

    /**
     * Tells whether the filename is writable
     *
     * @param string $path
     * @return bool
     * @access public
     */
    function is_writable($path)
    {
        $path = $this->_realpath($path);

        $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0);
        if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        switch ($this->packet_type) {
            case NET_SFTP_HANDLE:
                return true;
            case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
                return false;
            default:
                user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
                return false;
        }
    }

    /**
     * Tells whether the filename is writeable
     *
     * Alias of is_writable
     *
     * @param string $path
     * @return bool
     * @access public
     */
    function is_writeable($path)
    {
        return $this->is_writable($path);
    }

    /**
     * Gets last access time of file
     *
     * @param string $path
     * @return mixed
     * @access public
     */
    function fileatime($path)
    {
        return $this->_get_stat_cache_prop($path, 'atime');
    }

    /**
     * Gets file modification time
     *
     * @param string $path
     * @return mixed
     * @access public
     */
    function filemtime($path)
    {
        return $this->_get_stat_cache_prop($path, 'mtime');
    }

    /**
     * Gets file permissions
     *
     * @param string $path
     * @return mixed
     * @access public
     */
    function fileperms($path)
    {
        return $this->_get_stat_cache_prop($path, 'permissions');
    }

    /**
     * Gets file owner
     *
     * @param string $path
     * @return mixed
     * @access public
     */
    function fileowner($path)
    {
        return $this->_get_stat_cache_prop($path, 'uid');
    }

    /**
     * Gets file group
     *
     * @param string $path
     * @return mixed
     * @access public
     */
    function filegroup($path)
    {
        return $this->_get_stat_cache_prop($path, 'gid');
    }

    /**
     * Gets file size
     *
     * @param string $path
     * @return mixed
     * @access public
     */
    function filesize($path)
    {
        return $this->_get_stat_cache_prop($path, 'size');
    }

    /**
     * Gets file type
     *
     * @param string $path
     * @return mixed
     * @access public
     */
    function filetype($path)
    {
        $type = $this->_get_stat_cache_prop($path, 'type');
        if ($type === false) {
            return false;
        }

        switch ($type) {
            case NET_SFTP_TYPE_BLOCK_DEVICE:
                return 'block';
            case NET_SFTP_TYPE_CHAR_DEVICE:
                return 'char';
            case NET_SFTP_TYPE_DIRECTORY:
                return 'dir';
            case NET_SFTP_TYPE_FIFO:
                return 'fifo';
            case NET_SFTP_TYPE_REGULAR:
                return 'file';
            case NET_SFTP_TYPE_SYMLINK:
                return 'link';
            default:
                return false;
        }
    }

    /**
     * Return a stat properity
     *
     * Uses cache if appropriate.
     *
     * @param string $path
     * @param string $prop
     * @return mixed
     * @access private
     */
    function _get_stat_cache_prop($path, $prop)
    {
        return $this->_get_xstat_cache_prop($path, $prop, 'stat');
    }

    /**
     * Return an lstat properity
     *
     * Uses cache if appropriate.
     *
     * @param string $path
     * @param string $prop
     * @return mixed
     * @access private
     */
    function _get_lstat_cache_prop($path, $prop)
    {
        return $this->_get_xstat_cache_prop($path, $prop, 'lstat');
    }

    /**
     * Return a stat or lstat properity
     *
     * Uses cache if appropriate.
     *
     * @param string $path
     * @param string $prop
     * @return mixed
     * @access private
     */
    function _get_xstat_cache_prop($path, $prop, $type)
    {
        if ($this->use_stat_cache) {
            $path = $this->_realpath($path);

            $result = $this->_query_stat_cache($path);

            if (is_object($result) && isset($result->$type)) {
                return $result->{$type}[$prop];
            }
        }

        $result = $this->$type($path);

        if ($result === false || !isset($result[$prop])) {
            return false;
        }

        return $result[$prop];
    }

    /**
     * Renames a file or a directory on the SFTP server
     *
     * @param string $oldname
     * @param string $newname
     * @return bool
     * @access public
     */
    function rename($oldname, $newname)
    {
        if (!($this->bitmap & SSH2::MASK_LOGIN)) {
            return false;
        }

        $oldname = $this->_realpath($oldname);
        $newname = $this->_realpath($newname);
        if ($oldname === false || $newname === false) {
            return false;
        }

        // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
        $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
        if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
            return false;
        }

        $response = $this->_get_sftp_packet();
        if ($this->packet_type != NET_SFTP_STATUS) {
            user_error('Expected SSH_FXP_STATUS');
            return false;
        }

        // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
        extract(unpack('Nstatus', $this->_string_shift($response, 4)));
        if ($status != NET_SFTP_STATUS_OK) {
            $this->_logError($response, $status);
            return false;
        }

        // don't move the stat cache entry over since this operation could very well change the
        // atime and mtime attributes
        //$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname));
        $this->_remove_from_stat_cache($oldname);
        $this->_remove_from_stat_cache($newname);

        return true;
    }

    /**
     * Parse Attributes
     *
     * See '7.  File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
     *
     * @param string $response
     * @return array
     * @access private
     */
    function _parseAttributes(&$response)
    {
        $attr = array();
        extract(unpack('Nflags', $this->_string_shift($response, 4)));
        // SFTPv4+ have a type field (a byte) that follows the above flag field
        foreach ($this->attributes as $key => $value) {
            switch ($flags & $key) {
                case NET_SFTP_ATTR_SIZE: // 0x00000001
                    // The size attribute is defined as an unsigned 64-bit integer.
                    // The following will use floats on 32-bit platforms, if necessary.
                    // As can be seen in the BigInteger class, floats are generally
                    // IEEE 754 binary64 "double precision" on such platforms and
                    // as such can represent integers of at least 2^50 without loss
                    // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
                    $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8)));
                    break;
                case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
                    $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
                    break;
                case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
                    $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
                    // mode == permissions; permissions was the original array key and is retained for bc purposes.
                    // mode was added because that's the more industry standard terminology
                    $attr+= array('mode' => $attr['permissions']);
                    $fileType = $this->_parseMode($attr['permissions']);
                    if ($fileType !== false) {
                        $attr+= array('type' => $fileType);
                    }
                    break;
                case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
                    $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
                    break;
                case NET_SFTP_ATTR_EXTENDED: // 0x80000000
                    extract(unpack('Ncount', $this->_string_shift($response, 4)));
                    for ($i = 0; $i < $count; $i++) {
                        extract(unpack('Nlength', $this->_string_shift($response, 4)));
                        $key = $this->_string_shift($response, $length);
                        extract(unpack('Nlength', $this->_string_shift($response, 4)));
                        $attr[$key] = $this->_string_shift($response, $length);
                    }
            }
        }
        return $attr;
    }

    /**
     * Attempt to identify the file type
     *
     * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
     *
     * @param int $mode
     * @return int
     * @access private
     */
    function _parseMode($mode)
    {
        // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
        // see, also, http://linux.die.net/man/2/stat
        switch ($mode & 0170000) {// ie. 1111 0000 0000 0000
            case 0000000: // no file type specified - figure out the file type using alternative means
                return false;
            case 0040000:
                return NET_SFTP_TYPE_DIRECTORY;
            case 0100000:
                return NET_SFTP_TYPE_REGULAR;
            case 0120000:
                return NET_SFTP_TYPE_SYMLINK;
            // new types introduced in SFTPv5+
            // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
            case 0010000: // named pipe (fifo)
                return NET_SFTP_TYPE_FIFO;
            case 0020000: // character special
                return NET_SFTP_TYPE_CHAR_DEVICE;
            case 0060000: // block special
                return NET_SFTP_TYPE_BLOCK_DEVICE;
            case 0140000: // socket
                return NET_SFTP_TYPE_SOCKET;
            case 0160000: // whiteout
                // "SPECIAL should be used for files that are of
                //  a known type which cannot be expressed in the protocol"
                return NET_SFTP_TYPE_SPECIAL;
            default:
                return NET_SFTP_TYPE_UNKNOWN;
        }
    }

    /**
     * Parse Longname
     *
     * SFTPv3 doesn't provide any easy way of identifying a file type.  You could try to open
     * a file as a directory and see if an error is returned or you could try to parse the
     * SFTPv3-specific longname field of the SSH_FXP_NAME packet.  That's what this function does.
     * The result is returned using the
     * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
     *
     * If the longname is in an unrecognized format bool(false) is returned.
     *
     * @param string $longname
     * @return mixed
     * @access private
     */
    function _parseLongname($longname)
    {
        // http://en.wikipedia.org/wiki/Unix_file_types
        // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
        if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) {
            switch ($longname[0]) {
                case '-':
                    return NET_SFTP_TYPE_REGULAR;
                case 'd':
                    return NET_SFTP_TYPE_DIRECTORY;
                case 'l':
                    return NET_SFTP_TYPE_SYMLINK;
                default:
                    return NET_SFTP_TYPE_SPECIAL;
            }
        }

        return false;
    }

    /**
     * Sends SFTP Packets
     *
     * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
     *
     * @param int $type
     * @param string $data
     * @see self::_get_sftp_packet()
     * @see self::_send_channel_packet()
     * @return bool
     * @access private
     */
    function _send_sftp_packet($type, $data)
    {
        $packet = $this->request_id !== false ?
            pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) :
            pack('NCa*',  strlen($data) + 1, $type, $data);

        $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
        $result = $this->_send_channel_packet(self::CHANNEL, $packet);
        $stop = strtok(microtime(), ' ') + strtok('');

        if (defined('NET_SFTP_LOGGING')) {
            $packet_type = '-> ' . $this->packet_types[$type] .
                           ' (' . round($stop - $start, 4) . 's)';
            if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
                echo "<pre>\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
                flush();
                ob_flush();
            } else {
                $this->packet_type_log[] = $packet_type;
                if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
                    $this->packet_log[] = $data;
                }
            }
        }

        return $result;
    }

    /**
     * Receives SFTP Packets
     *
     * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
     *
     * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
     * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
     * messages containing one SFTP packet.
     *
     * @see self::_send_sftp_packet()
     * @return string
     * @access private
     */
    function _get_sftp_packet()
    {
        $this->curTimeout = false;

        $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838

        // SFTP packet length
        while (strlen($this->packet_buffer) < 4) {
            $temp = $this->_get_channel_packet(self::CHANNEL);
            if (is_bool($temp)) {
                $this->packet_type = false;
                $this->packet_buffer = '';
                return false;
            }
            $this->packet_buffer.= $temp;
        }
        extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
        $tempLength = $length;
        $tempLength-= strlen($this->packet_buffer);

        // SFTP packet type and data payload
        while ($tempLength > 0) {
            $temp = $this->_get_channel_packet(self::CHANNEL);
            if (is_bool($temp)) {
                $this->packet_type = false;
                $this->packet_buffer = '';
                return false;
            }
            $this->packet_buffer.= $temp;
            $tempLength-= strlen($temp);
        }

        $stop = strtok(microtime(), ' ') + strtok('');

        $this->packet_type = ord($this->_string_shift($this->packet_buffer));

        if ($this->request_id !== false) {
            $this->_string_shift($this->packet_buffer, 4); // remove the request id
            $length-= 5; // account for the request id and the packet type
        } else {
            $length-= 1; // account for the packet type
        }

        $packet = $this->_string_shift($this->packet_buffer, $length);

        if (defined('NET_SFTP_LOGGING')) {
            $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
                           ' (' . round($stop - $start, 4) . 's)';
            if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
                echo "<pre>\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
                flush();
                ob_flush();
            } else {
                $this->packet_type_log[] = $packet_type;
                if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
                    $this->packet_log[] = $packet;
                }
            }
        }

        return $packet;
    }

    /**
     * Returns a log of the packets that have been sent and received.
     *
     * Returns a string if NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX, an array if NET_SFTP_LOGGING == NET_SFTP_LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
     *
     * @access public
     * @return string or Array
     */
    function getSFTPLog()
    {
        if (!defined('NET_SFTP_LOGGING')) {
            return false;
        }

        switch (NET_SFTP_LOGGING) {
            case NET_SFTP_LOG_COMPLEX:
                return $this->_format_log($this->packet_log, $this->packet_type_log);
                break;
            //case NET_SFTP_LOG_SIMPLE:
            default:
                return $this->packet_type_log;
        }
    }

    /**
     * Returns all errors
     *
     * @return string
     * @access public
     */
    function getSFTPErrors()
    {
        return $this->sftp_errors;
    }

    /**
     * Returns the last error
     *
     * @return string
     * @access public
     */
    function getLastSFTPError()
    {
        return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
    }

    /**
     * Get supported SFTP versions
     *
     * @return array
     * @access public
     */
    function getSupportedVersions()
    {
        $temp = array('version' => $this->version);
        if (isset($this->extensions['versions'])) {
            $temp['extensions'] = $this->extensions['versions'];
        }
        return $temp;
    }

    /**
     * Disconnect
     *
     * @param int $reason
     * @return bool
     * @access private
     */
    function _disconnect($reason)
    {
        $this->pwd = false;
        parent::_disconnect($reason);
    }
}
<?php

/**
 * Pure-PHP implementation of SSHv1.
 *
 * PHP version 5
 *
 * Here's a short example of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $ssh = new \phpseclib\Net\SSH1('www.domain.tld');
 *    if (!$ssh->login('username', 'password')) {
 *        exit('Login Failed');
 *    }
 *
 *    echo $ssh->exec('ls -la');
 * ?>
 * </code>
 *
 * Here's another short example:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $ssh = new \phpseclib\Net\SSH1('www.domain.tld');
 *    if (!$ssh->login('username', 'password')) {
 *        exit('Login Failed');
 *    }
 *
 *    echo $ssh->read('username@username:~$');
 *    $ssh->write("ls -la\n");
 *    echo $ssh->read('username@username:~$');
 * ?>
 * </code>
 *
 * More information on the SSHv1 specification can be found by reading
 * {@link http://www.snailbook.com/docs/protocol-1.5.txt protocol-1.5.txt}.
 *
 * @category  Net
 * @package   SSH1
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Net;

use phpseclib\Crypt\DES;
use phpseclib\Crypt\Random;
use phpseclib\Crypt\TripleDES;
use phpseclib\Math\BigInteger;

/**
 * Pure-PHP implementation of SSHv1.
 *
 * @package SSH1
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class SSH1
{
    /**#@+
     * Encryption Methods
     *
     * @see \phpseclib\Net\SSH1::getSupportedCiphers()
     * @access public
     */
    /**
     * No encryption
     *
     * Not supported.
     */
    const CIPHER_NONE = 0;
    /**
     * IDEA in CFB mode
     *
     * Not supported.
     */
    const CIPHER_IDEA = 1;
    /**
     * DES in CBC mode
     */
    const CIPHER_DES = 2;
    /**
     * Triple-DES in CBC mode
     *
     * All implementations are required to support this
     */
    const CIPHER_3DES = 3;
    /**
     * TRI's Simple Stream encryption CBC
     *
     * Not supported nor is it defined in the official SSH1 specs.  OpenSSH, however, does define it (see cipher.h),
     * although it doesn't use it (see cipher.c)
     */
    const CIPHER_BROKEN_TSS = 4;
    /**
     * RC4
     *
     * Not supported.
     *
     * @internal According to the SSH1 specs:
     *
     *        "The first 16 bytes of the session key are used as the key for
     *         the server to client direction.  The remaining 16 bytes are used
     *         as the key for the client to server direction.  This gives
     *         independent 128-bit keys for each direction."
     *
     *     This library currently only supports encryption when the same key is being used for both directions.  This is
     *     because there's only one $crypto object.  Two could be added ($encrypt and $decrypt, perhaps).
     */
    const CIPHER_RC4 = 5;
    /**
     * Blowfish
     *
     * Not supported nor is it defined in the official SSH1 specs.  OpenSSH, however, defines it (see cipher.h) and
     * uses it (see cipher.c)
     */
    const CIPHER_BLOWFISH = 6;
    /**#@-*/

    /**#@+
     * Authentication Methods
     *
     * @see \phpseclib\Net\SSH1::getSupportedAuthentications()
     * @access public
    */
    /**
     * .rhosts or /etc/hosts.equiv
     */
    const AUTH_RHOSTS = 1;
    /**
     * pure RSA authentication
     */
    const AUTH_RSA = 2;
    /**
     * password authentication
     *
     * This is the only method that is supported by this library.
     */
    const AUTH_PASSWORD = 3;
    /**
     * .rhosts with RSA host authentication
     */
    const AUTH_RHOSTS_RSA = 4;
    /**#@-*/

    /**#@+
     * Terminal Modes
     *
     * @link http://3sp.com/content/developer/maverick-net/docs/Maverick.SSH.PseudoTerminalModesMembers.html
     * @access private
    */
    const TTY_OP_END = 0;
    /**#@-*/

    /**
     * The Response Type
     *
     * @see \phpseclib\Net\SSH1::_get_binary_packet()
     * @access private
     */
    const RESPONSE_TYPE = 1;

    /**
     * The Response Data
     *
     * @see \phpseclib\Net\SSH1::_get_binary_packet()
     * @access private
     */
    const RESPONSE_DATA = 2;

    /**#@+
     * Execution Bitmap Masks
     *
     * @see \phpseclib\Net\SSH1::bitmap
     * @access private
    */
    const MASK_CONSTRUCTOR = 0x00000001;
    const MASK_CONNECTED   = 0x00000002;
    const MASK_LOGIN       = 0x00000004;
    const MASK_SHELL       = 0x00000008;
    /**#@-*/

    /**#@+
     * @access public
     * @see \phpseclib\Net\SSH1::getLog()
    */
    /**
     * Returns the message numbers
     */
    const LOG_SIMPLE = 1;
    /**
     * Returns the message content
     */
    const LOG_COMPLEX = 2;
    /**
     * Outputs the content real-time
     */
    const LOG_REALTIME = 3;
    /**
     * Dumps the content real-time to a file
     */
    const LOG_REALTIME_FILE = 4;
    /**#@-*/

    /**#@+
     * @access public
     * @see \phpseclib\Net\SSH1::read()
    */
    /**
     * Returns when a string matching $expect exactly is found
     */
    const READ_SIMPLE = 1;
    /**
     * Returns when a string matching the regular expression $expect is found
     */
    const READ_REGEX = 2;
    /**#@-*/

    /**
     * The SSH identifier
     *
     * @var string
     * @access private
     */
    var $identifier = 'SSH-1.5-phpseclib';

    /**
     * The Socket Object
     *
     * @var object
     * @access private
     */
    var $fsock;

    /**
     * The cryptography object
     *
     * @var object
     * @access private
     */
    var $crypto = false;

    /**
     * Execution Bitmap
     *
     * The bits that are set represent functions that have been called already.  This is used to determine
     * if a requisite function has been successfully executed.  If not, an error should be thrown.
     *
     * @var int
     * @access private
     */
    var $bitmap = 0;

    /**
     * The Server Key Public Exponent
     *
     * Logged for debug purposes
     *
     * @see self::getServerKeyPublicExponent()
     * @var string
     * @access private
     */
    var $server_key_public_exponent;

    /**
     * The Server Key Public Modulus
     *
     * Logged for debug purposes
     *
     * @see self::getServerKeyPublicModulus()
     * @var string
     * @access private
     */
    var $server_key_public_modulus;

    /**
     * The Host Key Public Exponent
     *
     * Logged for debug purposes
     *
     * @see self::getHostKeyPublicExponent()
     * @var string
     * @access private
     */
    var $host_key_public_exponent;

    /**
     * The Host Key Public Modulus
     *
     * Logged for debug purposes
     *
     * @see self::getHostKeyPublicModulus()
     * @var string
     * @access private
     */
    var $host_key_public_modulus;

    /**
     * Supported Ciphers
     *
     * Logged for debug purposes
     *
     * @see self::getSupportedCiphers()
     * @var array
     * @access private
     */
    var $supported_ciphers = array(
        self::CIPHER_NONE       => 'No encryption',
        self::CIPHER_IDEA       => 'IDEA in CFB mode',
        self::CIPHER_DES        => 'DES in CBC mode',
        self::CIPHER_3DES       => 'Triple-DES in CBC mode',
        self::CIPHER_BROKEN_TSS => 'TRI\'s Simple Stream encryption CBC',
        self::CIPHER_RC4        => 'RC4',
        self::CIPHER_BLOWFISH   => 'Blowfish'
    );

    /**
     * Supported Authentications
     *
     * Logged for debug purposes
     *
     * @see self::getSupportedAuthentications()
     * @var array
     * @access private
     */
    var $supported_authentications = array(
        self::AUTH_RHOSTS     => '.rhosts or /etc/hosts.equiv',
        self::AUTH_RSA        => 'pure RSA authentication',
        self::AUTH_PASSWORD   => 'password authentication',
        self::AUTH_RHOSTS_RSA => '.rhosts with RSA host authentication'
    );

    /**
     * Server Identification
     *
     * @see self::getServerIdentification()
     * @var string
     * @access private
     */
    var $server_identification = '';

    /**
     * Protocol Flags
     *
     * @see self::__construct()
     * @var array
     * @access private
     */
    var $protocol_flags = array();

    /**
     * Protocol Flag Log
     *
     * @see self::getLog()
     * @var array
     * @access private
     */
    var $protocol_flag_log = array();

    /**
     * Message Log
     *
     * @see self::getLog()
     * @var array
     * @access private
     */
    var $message_log = array();

    /**
     * Real-time log file pointer
     *
     * @see self::_append_log()
     * @var resource
     * @access private
     */
    var $realtime_log_file;

    /**
     * Real-time log file size
     *
     * @see self::_append_log()
     * @var int
     * @access private
     */
    var $realtime_log_size;

    /**
     * Real-time log file wrap boolean
     *
     * @see self::_append_log()
     * @var bool
     * @access private
     */
    var $realtime_log_wrap;

    /**
     * Interactive Buffer
     *
     * @see self::read()
     * @var array
     * @access private
     */
    var $interactiveBuffer = '';

    /**
     * Timeout
     *
     * @see self::setTimeout()
     * @access private
     */
    var $timeout;

    /**
     * Current Timeout
     *
     * @see self::_get_channel_packet()
     * @access private
     */
    var $curTimeout;

    /**
     * Log Boundary
     *
     * @see self::_format_log()
     * @access private
     */
    var $log_boundary = ':';

    /**
     * Log Long Width
     *
     * @see self::_format_log()
     * @access private
     */
    var $log_long_width = 65;

    /**
     * Log Short Width
     *
     * @see self::_format_log()
     * @access private
     */
    var $log_short_width = 16;

    /**
     * Hostname
     *
     * @see self::__construct()
     * @see self::_connect()
     * @var string
     * @access private
     */
    var $host;

    /**
     * Port Number
     *
     * @see self::__construct()
     * @see self::_connect()
     * @var int
     * @access private
     */
    var $port;

    /**
     * Timeout for initial connection
     *
     * Set by the constructor call. Calling setTimeout() is optional. If it's not called functions like
     * exec() won't timeout unless some PHP setting forces it too. The timeout specified in the constructor,
     * however, is non-optional. There will be a timeout, whether or not you set it. If you don't it'll be
     * 10 seconds. It is used by fsockopen() in that function.
     *
     * @see self::__construct()
     * @see self::_connect()
     * @var int
     * @access private
     */
    var $connectionTimeout;

    /**
     * Default cipher
     *
     * @see self::__construct()
     * @see self::_connect()
     * @var int
     * @access private
     */
    var $cipher;

    /**
     * Default Constructor.
     *
     * Connects to an SSHv1 server
     *
     * @param string $host
     * @param int $port
     * @param int $timeout
     * @param int $cipher
     * @return \phpseclib\Net\SSH1
     * @access public
     */
    function __construct($host, $port = 22, $timeout = 10, $cipher = self::CIPHER_3DES)
    {
        $this->protocol_flags = array(
            1  => 'NET_SSH1_MSG_DISCONNECT',
            2  => 'NET_SSH1_SMSG_PUBLIC_KEY',
            3  => 'NET_SSH1_CMSG_SESSION_KEY',
            4  => 'NET_SSH1_CMSG_USER',
            9  => 'NET_SSH1_CMSG_AUTH_PASSWORD',
            10 => 'NET_SSH1_CMSG_REQUEST_PTY',
            12 => 'NET_SSH1_CMSG_EXEC_SHELL',
            13 => 'NET_SSH1_CMSG_EXEC_CMD',
            14 => 'NET_SSH1_SMSG_SUCCESS',
            15 => 'NET_SSH1_SMSG_FAILURE',
            16 => 'NET_SSH1_CMSG_STDIN_DATA',
            17 => 'NET_SSH1_SMSG_STDOUT_DATA',
            18 => 'NET_SSH1_SMSG_STDERR_DATA',
            19 => 'NET_SSH1_CMSG_EOF',
            20 => 'NET_SSH1_SMSG_EXITSTATUS',
            33 => 'NET_SSH1_CMSG_EXIT_CONFIRMATION'
        );

        $this->_define_array($this->protocol_flags);

        $this->host = $host;
        $this->port = $port;
        $this->connectionTimeout = $timeout;
        $this->cipher = $cipher;
    }

    /**
     * Connect to an SSHv1 server
     *
     * @return bool
     * @access private
     */
    function _connect()
    {
        $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->connectionTimeout);
        if (!$this->fsock) {
            user_error(rtrim("Cannot connect to {$this->host}:{$this->port}. Error $errno. $errstr"));
            return false;
        }

        $this->server_identification = $init_line = fgets($this->fsock, 255);

        if (defined('NET_SSH1_LOGGING')) {
            $this->_append_log('<-', $this->server_identification);
            $this->_append_log('->', $this->identifier . "\r\n");
        }

        if (!preg_match('#SSH-([0-9\.]+)-(.+)#', $init_line, $parts)) {
            user_error('Can only connect to SSH servers');
            return false;
        }
        if ($parts[1][0] != 1) {
            user_error("Cannot connect to SSH $parts[1] servers");
            return false;
        }

        fputs($this->fsock, $this->identifier."\r\n");

        $response = $this->_get_binary_packet();
        if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_PUBLIC_KEY) {
            user_error('Expected SSH_SMSG_PUBLIC_KEY');
            return false;
        }

        $anti_spoofing_cookie = $this->_string_shift($response[self::RESPONSE_DATA], 8);

        $this->_string_shift($response[self::RESPONSE_DATA], 4);

        $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2));
        $server_key_public_exponent = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256);
        $this->server_key_public_exponent = $server_key_public_exponent;

        $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2));
        $server_key_public_modulus = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256);
        $this->server_key_public_modulus = $server_key_public_modulus;

        $this->_string_shift($response[self::RESPONSE_DATA], 4);

        $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2));
        $host_key_public_exponent = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256);
        $this->host_key_public_exponent = $host_key_public_exponent;

        $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2));
        $host_key_public_modulus = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256);
        $this->host_key_public_modulus = $host_key_public_modulus;

        $this->_string_shift($response[self::RESPONSE_DATA], 4);

        // get a list of the supported ciphers
        extract(unpack('Nsupported_ciphers_mask', $this->_string_shift($response[self::RESPONSE_DATA], 4)));
        foreach ($this->supported_ciphers as $mask => $name) {
            if (($supported_ciphers_mask & (1 << $mask)) == 0) {
                unset($this->supported_ciphers[$mask]);
            }
        }

        // get a list of the supported authentications
        extract(unpack('Nsupported_authentications_mask', $this->_string_shift($response[self::RESPONSE_DATA], 4)));
        foreach ($this->supported_authentications as $mask => $name) {
            if (($supported_authentications_mask & (1 << $mask)) == 0) {
                unset($this->supported_authentications[$mask]);
            }
        }

        $session_id = pack('H*', md5($host_key_public_modulus->toBytes() . $server_key_public_modulus->toBytes() . $anti_spoofing_cookie));

        $session_key = Random::string(32);
        $double_encrypted_session_key = $session_key ^ str_pad($session_id, 32, chr(0));

        if ($server_key_public_modulus->compare($host_key_public_modulus) < 0) {
            $double_encrypted_session_key = $this->_rsa_crypt(
                $double_encrypted_session_key,
                array(
                    $server_key_public_exponent,
                    $server_key_public_modulus
                )
            );
            $double_encrypted_session_key = $this->_rsa_crypt(
                $double_encrypted_session_key,
                array(
                    $host_key_public_exponent,
                    $host_key_public_modulus
                )
            );
        } else {
            $double_encrypted_session_key = $this->_rsa_crypt(
                $double_encrypted_session_key,
                array(
                    $host_key_public_exponent,
                    $host_key_public_modulus
                )
            );
            $double_encrypted_session_key = $this->_rsa_crypt(
                $double_encrypted_session_key,
                array(
                    $server_key_public_exponent,
                    $server_key_public_modulus
                )
            );
        }

        $cipher = isset($this->supported_ciphers[$this->cipher]) ? $this->cipher : self::CIPHER_3DES;
        $data = pack('C2a*na*N', NET_SSH1_CMSG_SESSION_KEY, $cipher, $anti_spoofing_cookie, 8 * strlen($double_encrypted_session_key), $double_encrypted_session_key, 0);

        if (!$this->_send_binary_packet($data)) {
            user_error('Error sending SSH_CMSG_SESSION_KEY');
            return false;
        }

        switch ($cipher) {
            //case self::CIPHER_NONE:
            //    $this->crypto = new \phpseclib\Crypt\Null();
            //    break;
            case self::CIPHER_DES:
                $this->crypto = new DES();
                $this->crypto->disablePadding();
                $this->crypto->enableContinuousBuffer();
                $this->crypto->setKey(substr($session_key, 0,  8));
                break;
            case self::CIPHER_3DES:
                $this->crypto = new TripleDES(TripleDES::MODE_3CBC);
                $this->crypto->disablePadding();
                $this->crypto->enableContinuousBuffer();
                $this->crypto->setKey(substr($session_key, 0, 24));
                break;
            //case self::CIPHER_RC4:
            //    $this->crypto = new RC4();
            //    $this->crypto->enableContinuousBuffer();
            //    $this->crypto->setKey(substr($session_key, 0,  16));
            //    break;
        }

        $response = $this->_get_binary_packet();

        if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) {
            user_error('Expected SSH_SMSG_SUCCESS');
            return false;
        }

        $this->bitmap = self::MASK_CONNECTED;

        return true;
    }

    /**
     * Login
     *
     * @param string $username
     * @param string $password
     * @return bool
     * @access public
     */
    function login($username, $password = '')
    {
        if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
            $this->bitmap |= self::MASK_CONSTRUCTOR;
            if (!$this->_connect()) {
                return false;
            }
        }

        if (!($this->bitmap & self::MASK_CONNECTED)) {
            return false;
        }

        $data = pack('CNa*', NET_SSH1_CMSG_USER, strlen($username), $username);

        if (!$this->_send_binary_packet($data)) {
            user_error('Error sending SSH_CMSG_USER');
            return false;
        }

        $response = $this->_get_binary_packet();

        if ($response === true) {
            return false;
        }
        if ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) {
            $this->bitmap |= self::MASK_LOGIN;
            return true;
        } elseif ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_FAILURE) {
            user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE');
            return false;
        }

        $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen($password), $password);

        if (!$this->_send_binary_packet($data)) {
            user_error('Error sending SSH_CMSG_AUTH_PASSWORD');
            return false;
        }

        // remove the username and password from the last logged packet
        if (defined('NET_SSH1_LOGGING') && NET_SSH1_LOGGING == self::LOG_COMPLEX) {
            $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen('password'), 'password');
            $this->message_log[count($this->message_log) - 1] = $data;
        }

        $response = $this->_get_binary_packet();

        if ($response === true) {
            return false;
        }
        if ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) {
            $this->bitmap |= self::MASK_LOGIN;
            return true;
        } elseif ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_FAILURE) {
            return false;
        } else {
            user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE');
            return false;
        }
    }

    /**
     * Set Timeout
     *
     * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely.  setTimeout() makes it so it'll timeout.
     * Setting $timeout to false or 0 will mean there is no timeout.
     *
     * @param mixed $timeout
     */
    function setTimeout($timeout)
    {
        $this->timeout = $this->curTimeout = $timeout;
    }

    /**
     * Executes a command on a non-interactive shell, returns the output, and quits.
     *
     * An SSH1 server will close the connection after a command has been executed on a non-interactive shell.  SSH2
     * servers don't, however, this isn't an SSH2 client.  The way this works, on the server, is by initiating a
     * shell with the -s option, as discussed in the following links:
     *
     * {@link http://www.faqs.org/docs/bashman/bashref_65.html http://www.faqs.org/docs/bashman/bashref_65.html}
     * {@link http://www.faqs.org/docs/bashman/bashref_62.html http://www.faqs.org/docs/bashman/bashref_62.html}
     *
     * To execute further commands, a new \phpseclib\Net\SSH1 object will need to be created.
     *
     * Returns false on failure and the output, otherwise.
     *
     * @see self::interactiveRead()
     * @see self::interactiveWrite()
     * @param string $cmd
     * @return mixed
     * @access public
     */
    function exec($cmd, $block = true)
    {
        if (!($this->bitmap & self::MASK_LOGIN)) {
            user_error('Operation disallowed prior to login()');
            return false;
        }

        $data = pack('CNa*', NET_SSH1_CMSG_EXEC_CMD, strlen($cmd), $cmd);

        if (!$this->_send_binary_packet($data)) {
            user_error('Error sending SSH_CMSG_EXEC_CMD');
            return false;
        }

        if (!$block) {
            return true;
        }

        $output = '';
        $response = $this->_get_binary_packet();

        if ($response !== false) {
            do {
                $output.= substr($response[self::RESPONSE_DATA], 4);
                $response = $this->_get_binary_packet();
            } while (is_array($response) && $response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_EXITSTATUS);
        }

        $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION);

        // i don't think it's really all that important if this packet gets sent or not.
        $this->_send_binary_packet($data);

        fclose($this->fsock);

        // reset the execution bitmap - a new \phpseclib\Net\SSH1 object needs to be created.
        $this->bitmap = 0;

        return $output;
    }

    /**
     * Creates an interactive shell
     *
     * @see self::interactiveRead()
     * @see self::interactiveWrite()
     * @return bool
     * @access private
     */
    function _initShell()
    {
        // connect using the sample parameters in protocol-1.5.txt.
        // according to wikipedia.org's entry on text terminals, "the fundamental type of application running on a text
        // terminal is a command line interpreter or shell".  thus, opening a terminal session to run the shell.
        $data = pack('CNa*N4C', NET_SSH1_CMSG_REQUEST_PTY, strlen('vt100'), 'vt100', 24, 80, 0, 0, self::TTY_OP_END);

        if (!$this->_send_binary_packet($data)) {
            user_error('Error sending SSH_CMSG_REQUEST_PTY');
            return false;
        }

        $response = $this->_get_binary_packet();

        if ($response === true) {
            return false;
        }
        if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) {
            user_error('Expected SSH_SMSG_SUCCESS');
            return false;
        }

        $data = pack('C', NET_SSH1_CMSG_EXEC_SHELL);

        if (!$this->_send_binary_packet($data)) {
            user_error('Error sending SSH_CMSG_EXEC_SHELL');
            return false;
        }

        $this->bitmap |= self::MASK_SHELL;

        //stream_set_blocking($this->fsock, 0);

        return true;
    }

    /**
     * Inputs a command into an interactive shell.
     *
     * @see self::interactiveWrite()
     * @param string $cmd
     * @return bool
     * @access public
     */
    function write($cmd)
    {
        return $this->interactiveWrite($cmd);
    }

    /**
     * Returns the output of an interactive shell when there's a match for $expect
     *
     * $expect can take the form of a string literal or, if $mode == self::READ__REGEX,
     * a regular expression.
     *
     * @see self::write()
     * @param string $expect
     * @param int $mode
     * @return bool
     * @access public
     */
    function read($expect, $mode = self::READ__SIMPLE)
    {
        if (!($this->bitmap & self::MASK_LOGIN)) {
            user_error('Operation disallowed prior to login()');
            return false;
        }

        if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
            user_error('Unable to initiate an interactive shell session');
            return false;
        }

        $match = $expect;
        while (true) {
            if ($mode == self::READ__REGEX) {
                preg_match($expect, $this->interactiveBuffer, $matches);
                $match = isset($matches[0]) ? $matches[0] : '';
            }
            $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false;
            if ($pos !== false) {
                return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match));
            }
            $response = $this->_get_binary_packet();

            if ($response === true) {
                return $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
            }
            $this->interactiveBuffer.= substr($response[self::RESPONSE_DATA], 4);
        }
    }

    /**
     * Inputs a command into an interactive shell.
     *
     * @see self::interactiveRead()
     * @param string $cmd
     * @return bool
     * @access public
     */
    function interactiveWrite($cmd)
    {
        if (!($this->bitmap & self::MASK_LOGIN)) {
            user_error('Operation disallowed prior to login()');
            return false;
        }

        if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
            user_error('Unable to initiate an interactive shell session');
            return false;
        }

        $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($cmd), $cmd);

        if (!$this->_send_binary_packet($data)) {
            user_error('Error sending SSH_CMSG_STDIN');
            return false;
        }

        return true;
    }

    /**
     * Returns the output of an interactive shell when no more output is available.
     *
     * Requires PHP 4.3.0 or later due to the use of the stream_select() function.  If you see stuff like
     * "^[[00m", you're seeing ANSI escape codes.  According to
     * {@link http://support.microsoft.com/kb/101875 How to Enable ANSI.SYS in a Command Window}, "Windows NT
     * does not support ANSI escape sequences in Win32 Console applications", so if you're a Windows user,
     * there's not going to be much recourse.
     *
     * @see self::interactiveRead()
     * @return string
     * @access public
     */
    function interactiveRead()
    {
        if (!($this->bitmap & self::MASK_LOGIN)) {
            user_error('Operation disallowed prior to login()');
            return false;
        }

        if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
            user_error('Unable to initiate an interactive shell session');
            return false;
        }

        $read = array($this->fsock);
        $write = $except = null;
        if (stream_select($read, $write, $except, 0)) {
            $response = $this->_get_binary_packet();
            return substr($response[self::RESPONSE_DATA], 4);
        } else {
            return '';
        }
    }

    /**
     * Disconnect
     *
     * @access public
     */
    function disconnect()
    {
        $this->_disconnect();
    }

    /**
     * Destructor.
     *
     * Will be called, automatically, if you're supporting just PHP5.  If you're supporting PHP4, you'll need to call
     * disconnect().
     *
     * @access public
     */
    function __destruct()
    {
        $this->_disconnect();
    }

    /**
     * Disconnect
     *
     * @param string $msg
     * @access private
     */
    function _disconnect($msg = 'Client Quit')
    {
        if ($this->bitmap) {
            $data = pack('C', NET_SSH1_CMSG_EOF);
            $this->_send_binary_packet($data);
            /*
            $response = $this->_get_binary_packet();
            if ($response === true) {
                $response = array(self::RESPONSE_TYPE => -1);
            }
            switch ($response[self::RESPONSE_TYPE]) {
                case NET_SSH1_SMSG_EXITSTATUS:
                    $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION);
                    break;
                default:
                    $data = pack('CNa*', NET_SSH1_MSG_DISCONNECT, strlen($msg), $msg);
            }
            */
            $data = pack('CNa*', NET_SSH1_MSG_DISCONNECT, strlen($msg), $msg);

            $this->_send_binary_packet($data);
            fclose($this->fsock);
            $this->bitmap = 0;
        }
    }

    /**
     * Gets Binary Packets
     *
     * See 'The Binary Packet Protocol' of protocol-1.5.txt for more info.
     *
     * Also, this function could be improved upon by adding detection for the following exploit:
     * http://www.securiteam.com/securitynews/5LP042K3FY.html
     *
     * @see self::_send_binary_packet()
     * @return array
     * @access private
     */
    function _get_binary_packet()
    {
        if (feof($this->fsock)) {
            //user_error('connection closed prematurely');
            return false;
        }

        if ($this->curTimeout) {
            $read = array($this->fsock);
            $write = $except = null;

            $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
            $sec = floor($this->curTimeout);
            $usec = 1000000 * ($this->curTimeout - $sec);
            // on windows this returns a "Warning: Invalid CRT parameters detected" error
            if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
                //$this->_disconnect('Timeout');
                return true;
            }
            $elapsed = strtok(microtime(), ' ') + strtok('') - $start;
            $this->curTimeout-= $elapsed;
        }

        $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
        $temp = unpack('Nlength', fread($this->fsock, 4));

        $padding_length = 8 - ($temp['length'] & 7);
        $length = $temp['length'] + $padding_length;
        $raw = '';

        while ($length > 0) {
            $temp = fread($this->fsock, $length);
            $raw.= $temp;
            $length-= strlen($temp);
        }
        $stop = strtok(microtime(), ' ') + strtok('');

        if (strlen($raw) && $this->crypto !== false) {
            $raw = $this->crypto->decrypt($raw);
        }

        $padding = substr($raw, 0, $padding_length);
        $type = $raw[$padding_length];
        $data = substr($raw, $padding_length + 1, -4);

        $temp = unpack('Ncrc', substr($raw, -4));

        //if ( $temp['crc'] != $this->_crc($padding . $type . $data) ) {
        //    user_error('Bad CRC in packet from server');
        //    return false;
        //}

        $type = ord($type);

        if (defined('NET_SSH1_LOGGING')) {
            $temp = isset($this->protocol_flags[$type]) ? $this->protocol_flags[$type] : 'UNKNOWN';
            $temp = '<- ' . $temp .
                    ' (' . round($stop - $start, 4) . 's)';
            $this->_append_log($temp, $data);
        }

        return array(
            self::RESPONSE_TYPE => $type,
            self::RESPONSE_DATA => $data
        );
    }

    /**
     * Sends Binary Packets
     *
     * Returns true on success, false on failure.
     *
     * @see self::_get_binary_packet()
     * @param string $data
     * @return bool
     * @access private
     */
    function _send_binary_packet($data)
    {
        if (feof($this->fsock)) {
            //user_error('connection closed prematurely');
            return false;
        }

        $length = strlen($data) + 4;

        $padding = Random::string(8 - ($length & 7));

        $orig = $data;
        $data = $padding . $data;
        $data.= pack('N', $this->_crc($data));

        if ($this->crypto !== false) {
            $data = $this->crypto->encrypt($data);
        }

        $packet = pack('Na*', $length, $data);

        $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
        $result = strlen($packet) == fputs($this->fsock, $packet);
        $stop = strtok(microtime(), ' ') + strtok('');

        if (defined('NET_SSH1_LOGGING')) {
            $temp = isset($this->protocol_flags[ord($orig[0])]) ? $this->protocol_flags[ord($orig[0])] : 'UNKNOWN';
            $temp = '-> ' . $temp .
                    ' (' . round($stop - $start, 4) . 's)';
            $this->_append_log($temp, $orig);
        }

        return $result;
    }

    /**
     * Cyclic Redundancy Check (CRC)
     *
     * PHP's crc32 function is implemented slightly differently than the one that SSH v1 uses, so
     * we've reimplemented it. A more detailed discussion of the differences can be found after
     * $crc_lookup_table's initialization.
     *
     * @see self::_get_binary_packet()
     * @see self::_send_binary_packet()
     * @param string $data
     * @return int
     * @access private
     */
    function _crc($data)
    {
        static $crc_lookup_table = array(
            0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
            0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
            0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
            0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
            0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
            0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
            0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
            0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
            0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
            0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
            0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
            0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
            0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
            0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
            0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
            0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
            0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
            0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
            0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
            0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
            0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
            0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
            0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
            0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
            0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
            0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
            0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
            0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
            0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
            0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
            0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
            0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
            0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
            0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
            0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
            0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
            0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
            0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
            0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
            0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
            0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
            0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
            0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
            0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
            0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
            0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
            0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
            0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
            0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
            0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
            0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
            0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
            0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
            0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
            0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
            0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
            0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
            0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
            0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
            0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
            0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
            0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
            0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
            0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
        );

        // For this function to yield the same output as PHP's crc32 function, $crc would have to be
        // set to 0xFFFFFFFF, initially - not 0x00000000 as it currently is.
        $crc = 0x00000000;
        $length = strlen($data);

        for ($i=0; $i<$length; $i++) {
            // We AND $crc >> 8 with 0x00FFFFFF because we want the eight newly added bits to all
            // be zero.  PHP, unfortunately, doesn't always do this.  0x80000000 >> 8, as an example,
            // yields 0xFF800000 - not 0x00800000.  The following link elaborates:
            // http://www.php.net/manual/en/language.operators.bitwise.php#57281
            $crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_lookup_table[($crc & 0xFF) ^ ord($data[$i])];
        }

        // In addition to having to set $crc to 0xFFFFFFFF, initially, the return value must be XOR'd with
        // 0xFFFFFFFF for this function to return the same thing that PHP's crc32 function would.
        return $crc;
    }

    /**
     * String Shift
     *
     * Inspired by array_shift
     *
     * @param string $string
     * @param int $index
     * @return string
     * @access private
     */
    function _string_shift(&$string, $index = 1)
    {
        $substr = substr($string, 0, $index);
        $string = substr($string, $index);
        return $substr;
    }

    /**
     * RSA Encrypt
     *
     * Returns mod(pow($m, $e), $n), where $n should be the product of two (large) primes $p and $q and where $e
     * should be a number with the property that gcd($e, ($p - 1) * ($q - 1)) == 1.  Could just make anything that
     * calls this call modexp, instead, but I think this makes things clearer, maybe...
     *
     * @see self::__construct()
     * @param BigInteger $m
     * @param array $key
     * @return BigInteger
     * @access private
     */
    function _rsa_crypt($m, $key)
    {
        /*
        $rsa = new RSA();
        $rsa->loadKey($key, RSA::PUBLIC_FORMAT_RAW);
        $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
        return $rsa->encrypt($m);
        */

        // To quote from protocol-1.5.txt:
        // The most significant byte (which is only partial as the value must be
        // less than the public modulus, which is never a power of two) is zero.
        //
        // The next byte contains the value 2 (which stands for public-key
        // encrypted data in the PKCS standard [PKCS#1]).  Then, there are non-
        // zero random bytes to fill any unused space, a zero byte, and the data
        // to be encrypted in the least significant bytes, the last byte of the
        // data in the least significant byte.

        // Presumably the part of PKCS#1 they're refering to is "Section 7.2.1 Encryption Operation",
        // under "7.2 RSAES-PKCS1-v1.5" and "7 Encryption schemes" of the following URL:
        // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf
        $modulus = $key[1]->toBytes();
        $length = strlen($modulus) - strlen($m) - 3;
        $random = '';
        while (strlen($random) != $length) {
            $block = Random::string($length - strlen($random));
            $block = str_replace("\x00", '', $block);
            $random.= $block;
        }
        $temp = chr(0) . chr(2) . $random . chr(0) . $m;

        $m = new BigInteger($temp, 256);
        $m = $m->modPow($key[0], $key[1]);

        return $m->toBytes();
    }

    /**
     * Define Array
     *
     * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of
     * named constants from it, using the value as the name of the constant and the index as the value of the constant.
     * If any of the constants that would be defined already exists, none of the constants will be defined.
     *
     * @param array $array
     * @access private
     */
    function _define_array()
    {
        $args = func_get_args();
        foreach ($args as $arg) {
            foreach ($arg as $key => $value) {
                if (!defined($value)) {
                    define($value, $key);
                } else {
                    break 2;
                }
            }
        }
    }

    /**
     * Returns a log of the packets that have been sent and received.
     *
     * Returns a string if NET_SSH1_LOGGING == self::LOG_COMPLEX, an array if NET_SSH1_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH1_LOGGING')
     *
     * @access public
     * @return array|false|string
     */
    function getLog()
    {
        if (!defined('NET_SSH1_LOGGING')) {
            return false;
        }

        switch (NET_SSH1_LOGGING) {
            case self::LOG_SIMPLE:
                return $this->message_number_log;
                break;
            case self::LOG_COMPLEX:
                return $this->_format_log($this->message_log, $this->protocol_flags_log);
                break;
            default:
                return false;
        }
    }

    /**
     * Formats a log for printing
     *
     * @param array $message_log
     * @param array $message_number_log
     * @access private
     * @return string
     */
    function _format_log($message_log, $message_number_log)
    {
        $output = '';
        for ($i = 0; $i < count($message_log); $i++) {
            $output.= $message_number_log[$i] . "\r\n";
            $current_log = $message_log[$i];
            $j = 0;
            do {
                if (strlen($current_log)) {
                    $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0  ';
                }
                $fragment = $this->_string_shift($current_log, $this->log_short_width);
                $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary));
                // replace non ASCII printable characters with dots
                // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
                // also replace < with a . since < messes up the output on web browsers
                $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
                $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
                $j++;
            } while (strlen($current_log));
            $output.= "\r\n";
        }

        return $output;
    }

    /**
     * Helper function for _format_log
     *
     * For use with preg_replace_callback()
     *
     * @param array $matches
     * @access private
     * @return string
     */
    function _format_log_helper($matches)
    {
        return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
    }

    /**
     * Return the server key public exponent
     *
     * Returns, by default, the base-10 representation.  If $raw_output is set to true, returns, instead,
     * the raw bytes.  This behavior is similar to PHP's md5() function.
     *
     * @param bool $raw_output
     * @return string
     * @access public
     */
    function getServerKeyPublicExponent($raw_output = false)
    {
        return $raw_output ? $this->server_key_public_exponent->toBytes() : $this->server_key_public_exponent->toString();
    }

    /**
     * Return the server key public modulus
     *
     * Returns, by default, the base-10 representation.  If $raw_output is set to true, returns, instead,
     * the raw bytes.  This behavior is similar to PHP's md5() function.
     *
     * @param bool $raw_output
     * @return string
     * @access public
     */
    function getServerKeyPublicModulus($raw_output = false)
    {
        return $raw_output ? $this->server_key_public_modulus->toBytes() : $this->server_key_public_modulus->toString();
    }

    /**
     * Return the host key public exponent
     *
     * Returns, by default, the base-10 representation.  If $raw_output is set to true, returns, instead,
     * the raw bytes.  This behavior is similar to PHP's md5() function.
     *
     * @param bool $raw_output
     * @return string
     * @access public
     */
    function getHostKeyPublicExponent($raw_output = false)
    {
        return $raw_output ? $this->host_key_public_exponent->toBytes() : $this->host_key_public_exponent->toString();
    }

    /**
     * Return the host key public modulus
     *
     * Returns, by default, the base-10 representation.  If $raw_output is set to true, returns, instead,
     * the raw bytes.  This behavior is similar to PHP's md5() function.
     *
     * @param bool $raw_output
     * @return string
     * @access public
     */
    function getHostKeyPublicModulus($raw_output = false)
    {
        return $raw_output ? $this->host_key_public_modulus->toBytes() : $this->host_key_public_modulus->toString();
    }

    /**
     * Return a list of ciphers supported by SSH1 server.
     *
     * Just because a cipher is supported by an SSH1 server doesn't mean it's supported by this library. If $raw_output
     * is set to true, returns, instead, an array of constants.  ie. instead of array('Triple-DES in CBC mode'), you'll
     * get array(self::CIPHER_3DES).
     *
     * @param bool $raw_output
     * @return array
     * @access public
     */
    function getSupportedCiphers($raw_output = false)
    {
        return $raw_output ? array_keys($this->supported_ciphers) : array_values($this->supported_ciphers);
    }

    /**
     * Return a list of authentications supported by SSH1 server.
     *
     * Just because a cipher is supported by an SSH1 server doesn't mean it's supported by this library. If $raw_output
     * is set to true, returns, instead, an array of constants.  ie. instead of array('password authentication'), you'll
     * get array(self::AUTH_PASSWORD).
     *
     * @param bool $raw_output
     * @return array
     * @access public
     */
    function getSupportedAuthentications($raw_output = false)
    {
        return $raw_output ? array_keys($this->supported_authentications) : array_values($this->supported_authentications);
    }

    /**
     * Return the server identification.
     *
     * @return string
     * @access public
     */
    function getServerIdentification()
    {
        return rtrim($this->server_identification);
    }

    /**
     * Logs data packets
     *
     * Makes sure that only the last 1MB worth of packets will be logged
     *
     * @param string $data
     * @access private
     */
    function _append_log($protocol_flags, $message)
    {
        switch (NET_SSH1_LOGGING) {
            // useful for benchmarks
            case self::LOG_SIMPLE:
                $this->protocol_flags_log[] = $protocol_flags;
                break;
            // the most useful log for SSH1
            case self::LOG_COMPLEX:
                $this->protocol_flags_log[] = $protocol_flags;
                $this->_string_shift($message);
                $this->log_size+= strlen($message);
                $this->message_log[] = $message;
                while ($this->log_size > self::LOG_MAX_SIZE) {
                    $this->log_size-= strlen(array_shift($this->message_log));
                    array_shift($this->protocol_flags_log);
                }
                break;
            // dump the output out realtime; packets may be interspersed with non packets,
            // passwords won't be filtered out and select other packets may not be correctly
            // identified
            case self::LOG_REALTIME:
                echo "<pre>\r\n" . $this->_format_log(array($message), array($protocol_flags)) . "\r\n</pre>\r\n";
                @flush();
                @ob_flush();
                break;
            // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE
            // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
            // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
            // at the beginning of the file
            case self::LOG_REALTIME_FILE:
                if (!isset($this->realtime_log_file)) {
                    // PHP doesn't seem to like using constants in fopen()
                    $filename = self::LOG_REALTIME_FILE;
                    $fp = fopen($filename, 'w');
                    $this->realtime_log_file = $fp;
                }
                if (!is_resource($this->realtime_log_file)) {
                    break;
                }
                $entry = $this->_format_log(array($message), array($protocol_flags));
                if ($this->realtime_log_wrap) {
                    $temp = "<<< START >>>\r\n";
                    $entry.= $temp;
                    fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp));
                }
                $this->realtime_log_size+= strlen($entry);
                if ($this->realtime_log_size > self::LOG_MAX_SIZE) {
                    fseek($this->realtime_log_file, 0);
                    $this->realtime_log_size = strlen($entry);
                    $this->realtime_log_wrap = true;
                }
                fputs($this->realtime_log_file, $entry);
        }
    }
}
<?php

/**
 * Pure-PHP implementation of SSHv2.
 *
 * PHP version 5
 *
 * Here are some examples of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
 *    if (!$ssh->login('username', 'password')) {
 *        exit('Login Failed');
 *    }
 *
 *    echo $ssh->exec('pwd');
 *    echo $ssh->exec('ls -la');
 * ?>
 * </code>
 *
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $key = new \phpseclib\Crypt\RSA();
 *    //$key->setPassword('whatever');
 *    $key->loadKey(file_get_contents('privatekey'));
 *
 *    $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
 *    if (!$ssh->login('username', $key)) {
 *        exit('Login Failed');
 *    }
 *
 *    echo $ssh->read('username@username:~$');
 *    $ssh->write("ls -la\n");
 *    echo $ssh->read('username@username:~$');
 * ?>
 * </code>
 *
 * @category  Net
 * @package   SSH2
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2007 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Net;

use phpseclib\Crypt\Base;
use phpseclib\Crypt\Blowfish;
use phpseclib\Crypt\Hash;
use phpseclib\Crypt\Random;
use phpseclib\Crypt\RC4;
use phpseclib\Crypt\Rijndael;
use phpseclib\Crypt\RSA;
use phpseclib\Crypt\TripleDES;
use phpseclib\Crypt\Twofish;
use phpseclib\Math\BigInteger; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
use phpseclib\System\SSH\Agent;

/**
 * Pure-PHP implementation of SSHv2.
 *
 * @package SSH2
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class SSH2
{
    /**#@+
     * Execution Bitmap Masks
     *
     * @see \phpseclib\Net\SSH2::bitmap
     * @access private
     */
    const MASK_CONSTRUCTOR   = 0x00000001;
    const MASK_CONNECTED     = 0x00000002;
    const MASK_LOGIN_REQ     = 0x00000004;
    const MASK_LOGIN         = 0x00000008;
    const MASK_SHELL         = 0x00000010;
    const MASK_WINDOW_ADJUST = 0x00000020;
    /**#@-*/

    /**#@+
     * Channel constants
     *
     * RFC4254 refers not to client and server channels but rather to sender and recipient channels.  we don't refer
     * to them in that way because RFC4254 toggles the meaning. the client sends a SSH_MSG_CHANNEL_OPEN message with
     * a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response, with a sender and a
     * recepient channel.  at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel
     * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snipet:
     *     The 'recipient channel' is the channel number given in the original
     *     open request, and 'sender channel' is the channel number allocated by
     *     the other side.
     *
     * @see \phpseclib\Net\SSH2::_send_channel_packet()
     * @see \phpseclib\Net\SSH2::_get_channel_packet()
     * @access private
    */
    const CHANNEL_EXEC          = 0; // PuTTy uses 0x100
    const CHANNEL_SHELL         = 1;
    const CHANNEL_SUBSYSTEM     = 2;
    const CHANNEL_AGENT_FORWARD = 3;
    /**#@-*/

    /**#@+
     * @access public
     * @see \phpseclib\Net\SSH2::getLog()
    */
    /**
     * Returns the message numbers
     */
    const LOG_SIMPLE = 1;
    /**
     * Returns the message content
     */
    const LOG_COMPLEX = 2;
    /**
     * Outputs the content real-time
     */
    const LOG_REALTIME = 3;
    /**
     * Dumps the content real-time to a file
     */
    const LOG_REALTIME_FILE = 4;
    /**#@-*/

    /**#@+
     * @access public
     * @see \phpseclib\Net\SSH2::read()
    */
    /**
     * Returns when a string matching $expect exactly is found
     */
    const READ_SIMPLE = 1;
    /**
     * Returns when a string matching the regular expression $expect is found
     */
    const READ_REGEX = 2;
    /**
     * Make sure that the log never gets larger than this
     */
    const LOG_MAX_SIZE = 1048576; // 1024 * 1024
    /**#@-*/

    /**
     * The SSH identifier
     *
     * @var string
     * @access private
     */
    var $identifier;

    /**
     * The Socket Object
     *
     * @var object
     * @access private
     */
    var $fsock;

    /**
     * Execution Bitmap
     *
     * The bits that are set represent functions that have been called already.  This is used to determine
     * if a requisite function has been successfully executed.  If not, an error should be thrown.
     *
     * @var int
     * @access private
     */
    var $bitmap = 0;

    /**
     * Error information
     *
     * @see self::getErrors()
     * @see self::getLastError()
     * @var string
     * @access private
     */
    var $errors = array();

    /**
     * Server Identifier
     *
     * @see self::getServerIdentification()
     * @var array|false
     * @access private
     */
    var $server_identifier = false;

    /**
     * Key Exchange Algorithms
     *
     * @see self::getKexAlgorithims()
     * @var array|false
     * @access private
     */
    var $kex_algorithms = false;

    /**
     * Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
     *
     * @see self::_key_exchange()
     * @var int
     * @access private
     */
    var $kex_dh_group_size_min = 1536;

    /**
     * Preferred Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
     *
     * @see self::_key_exchange()
     * @var int
     * @access private
     */
    var $kex_dh_group_size_preferred = 2048;

    /**
     * Maximum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
     *
     * @see self::_key_exchange()
     * @var int
     * @access private
     */
    var $kex_dh_group_size_max = 4096;

    /**
     * Server Host Key Algorithms
     *
     * @see self::getServerHostKeyAlgorithms()
     * @var array|false
     * @access private
     */
    var $server_host_key_algorithms = false;

    /**
     * Encryption Algorithms: Client to Server
     *
     * @see self::getEncryptionAlgorithmsClient2Server()
     * @var array|false
     * @access private
     */
    var $encryption_algorithms_client_to_server = false;

    /**
     * Encryption Algorithms: Server to Client
     *
     * @see self::getEncryptionAlgorithmsServer2Client()
     * @var array|false
     * @access private
     */
    var $encryption_algorithms_server_to_client = false;

    /**
     * MAC Algorithms: Client to Server
     *
     * @see self::getMACAlgorithmsClient2Server()
     * @var array|false
     * @access private
     */
    var $mac_algorithms_client_to_server = false;

    /**
     * MAC Algorithms: Server to Client
     *
     * @see self::getMACAlgorithmsServer2Client()
     * @var array|false
     * @access private
     */
    var $mac_algorithms_server_to_client = false;

    /**
     * Compression Algorithms: Client to Server
     *
     * @see self::getCompressionAlgorithmsClient2Server()
     * @var array|false
     * @access private
     */
    var $compression_algorithms_client_to_server = false;

    /**
     * Compression Algorithms: Server to Client
     *
     * @see self::getCompressionAlgorithmsServer2Client()
     * @var array|false
     * @access private
     */
    var $compression_algorithms_server_to_client = false;

    /**
     * Languages: Server to Client
     *
     * @see self::getLanguagesServer2Client()
     * @var array|false
     * @access private
     */
    var $languages_server_to_client = false;

    /**
     * Languages: Client to Server
     *
     * @see self::getLanguagesClient2Server()
     * @var array|false
     * @access private
     */
    var $languages_client_to_server = false;

    /**
     * Block Size for Server to Client Encryption
     *
     * "Note that the length of the concatenation of 'packet_length',
     *  'padding_length', 'payload', and 'random padding' MUST be a multiple
     *  of the cipher block size or 8, whichever is larger.  This constraint
     *  MUST be enforced, even when using stream ciphers."
     *
     *  -- http://tools.ietf.org/html/rfc4253#section-6
     *
     * @see self::__construct()
     * @see self::_send_binary_packet()
     * @var int
     * @access private
     */
    var $encrypt_block_size = 8;

    /**
     * Block Size for Client to Server Encryption
     *
     * @see self::__construct()
     * @see self::_get_binary_packet()
     * @var int
     * @access private
     */
    var $decrypt_block_size = 8;

    /**
     * Server to Client Encryption Object
     *
     * @see self::_get_binary_packet()
     * @var object
     * @access private
     */
    var $decrypt = false;

    /**
     * Client to Server Encryption Object
     *
     * @see self::_send_binary_packet()
     * @var object
     * @access private
     */
    var $encrypt = false;

    /**
     * Client to Server HMAC Object
     *
     * @see self::_send_binary_packet()
     * @var object
     * @access private
     */
    var $hmac_create = false;

    /**
     * Server to Client HMAC Object
     *
     * @see self::_get_binary_packet()
     * @var object
     * @access private
     */
    var $hmac_check = false;

    /**
     * Size of server to client HMAC
     *
     * We need to know how big the HMAC will be for the server to client direction so that we know how many bytes to read.
     * For the client to server side, the HMAC object will make the HMAC as long as it needs to be.  All we need to do is
     * append it.
     *
     * @see self::_get_binary_packet()
     * @var int
     * @access private
     */
    var $hmac_size = false;

    /**
     * Server Public Host Key
     *
     * @see self::getServerPublicHostKey()
     * @var string
     * @access private
     */
    var $server_public_host_key;

    /**
     * Session identifier
     *
     * "The exchange hash H from the first key exchange is additionally
     *  used as the session identifier, which is a unique identifier for
     *  this connection."
     *
     *  -- http://tools.ietf.org/html/rfc4253#section-7.2
     *
     * @see self::_key_exchange()
     * @var string
     * @access private
     */
    var $session_id = false;

    /**
     * Exchange hash
     *
     * The current exchange hash
     *
     * @see self::_key_exchange()
     * @var string
     * @access private
     */
    var $exchange_hash = false;

    /**
     * Message Numbers
     *
     * @see self::__construct()
     * @var array
     * @access private
     */
    var $message_numbers = array();

    /**
     * Disconnection Message 'reason codes' defined in RFC4253
     *
     * @see self::__construct()
     * @var array
     * @access private
     */
    var $disconnect_reasons = array();

    /**
     * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254
     *
     * @see self::__construct()
     * @var array
     * @access private
     */
    var $channel_open_failure_reasons = array();

    /**
     * Terminal Modes
     *
     * @link http://tools.ietf.org/html/rfc4254#section-8
     * @see self::__construct()
     * @var array
     * @access private
     */
    var $terminal_modes = array();

    /**
     * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes
     *
     * @link http://tools.ietf.org/html/rfc4254#section-5.2
     * @see self::__construct()
     * @var array
     * @access private
     */
    var $channel_extended_data_type_codes = array();

    /**
     * Send Sequence Number
     *
     * See 'Section 6.4.  Data Integrity' of rfc4253 for more info.
     *
     * @see self::_send_binary_packet()
     * @var int
     * @access private
     */
    var $send_seq_no = 0;

    /**
     * Get Sequence Number
     *
     * See 'Section 6.4.  Data Integrity' of rfc4253 for more info.
     *
     * @see self::_get_binary_packet()
     * @var int
     * @access private
     */
    var $get_seq_no = 0;

    /**
     * Server Channels
     *
     * Maps client channels to server channels
     *
     * @see self::_get_channel_packet()
     * @see self::exec()
     * @var array
     * @access private
     */
    var $server_channels = array();

    /**
     * Channel Buffers
     *
     * If a client requests a packet from one channel but receives two packets from another those packets should
     * be placed in a buffer
     *
     * @see self::_get_channel_packet()
     * @see self::exec()
     * @var array
     * @access private
     */
    var $channel_buffers = array();

    /**
     * Channel Status
     *
     * Contains the type of the last sent message
     *
     * @see self::_get_channel_packet()
     * @var array
     * @access private
     */
    var $channel_status = array();

    /**
     * Packet Size
     *
     * Maximum packet size indexed by channel
     *
     * @see self::_send_channel_packet()
     * @var array
     * @access private
     */
    var $packet_size_client_to_server = array();

    /**
     * Message Number Log
     *
     * @see self::getLog()
     * @var array
     * @access private
     */
    var $message_number_log = array();

    /**
     * Message Log
     *
     * @see self::getLog()
     * @var array
     * @access private
     */
    var $message_log = array();

    /**
     * The Window Size
     *
     * Bytes the other party can send before it must wait for the window to be adjusted (0x7FFFFFFF = 2GB)
     *
     * @var int
     * @see self::_send_channel_packet()
     * @see self::exec()
     * @access private
     */
    var $window_size = 0x7FFFFFFF;

    /**
     * Window size, server to client
     *
     * Window size indexed by channel
     *
     * @see self::_send_channel_packet()
     * @var array
     * @access private
     */
    var $window_size_server_to_client = array();

    /**
     * Window size, client to server
     *
     * Window size indexed by channel
     *
     * @see self::_get_channel_packet()
     * @var array
     * @access private
     */
    var $window_size_client_to_server = array();

    /**
     * Server signature
     *
     * Verified against $this->session_id
     *
     * @see self::getServerPublicHostKey()
     * @var string
     * @access private
     */
    var $signature = '';

    /**
     * Server signature format
     *
     * ssh-rsa or ssh-dss.
     *
     * @see self::getServerPublicHostKey()
     * @var string
     * @access private
     */
    var $signature_format = '';

    /**
     * Interactive Buffer
     *
     * @see self::read()
     * @var array
     * @access private
     */
    var $interactiveBuffer = '';

    /**
     * Current log size
     *
     * Should never exceed self::LOG_MAX_SIZE
     *
     * @see self::_send_binary_packet()
     * @see self::_get_binary_packet()
     * @var int
     * @access private
     */
    var $log_size;

    /**
     * Timeout
     *
     * @see self::setTimeout()
     * @access private
     */
    var $timeout;

    /**
     * Current Timeout
     *
     * @see self::_get_channel_packet()
     * @access private
     */
    var $curTimeout;

    /**
     * Real-time log file pointer
     *
     * @see self::_append_log()
     * @var resource
     * @access private
     */
    var $realtime_log_file;

    /**
     * Real-time log file size
     *
     * @see self::_append_log()
     * @var int
     * @access private
     */
    var $realtime_log_size;

    /**
     * Has the signature been validated?
     *
     * @see self::getServerPublicHostKey()
     * @var bool
     * @access private
     */
    var $signature_validated = false;

    /**
     * Real-time log file wrap boolean
     *
     * @see self::_append_log()
     * @access private
     */
    var $realtime_log_wrap;

    /**
     * Flag to suppress stderr from output
     *
     * @see self::enableQuietMode()
     * @access private
     */
    var $quiet_mode = false;

    /**
     * Time of first network activity
     *
     * @var int
     * @access private
     */
    var $last_packet;

    /**
     * Exit status returned from ssh if any
     *
     * @var int
     * @access private
     */
    var $exit_status;

    /**
     * Flag to request a PTY when using exec()
     *
     * @var bool
     * @see self::enablePTY()
     * @access private
     */
    var $request_pty = false;

    /**
     * Flag set while exec() is running when using enablePTY()
     *
     * @var bool
     * @access private
     */
    var $in_request_pty_exec = false;

    /**
     * Flag set after startSubsystem() is called
     *
     * @var bool
     * @access private
     */
    var $in_subsystem;

    /**
     * Contents of stdError
     *
     * @var string
     * @access private
     */
    var $stdErrorLog;

    /**
     * The Last Interactive Response
     *
     * @see self::_keyboard_interactive_process()
     * @var string
     * @access private
     */
    var $last_interactive_response = '';

    /**
     * Keyboard Interactive Request / Responses
     *
     * @see self::_keyboard_interactive_process()
     * @var array
     * @access private
     */
    var $keyboard_requests_responses = array();

    /**
     * Banner Message
     *
     * Quoting from the RFC, "in some jurisdictions, sending a warning message before
     * authentication may be relevant for getting legal protection."
     *
     * @see self::_filter()
     * @see self::getBannerMessage()
     * @var string
     * @access private
     */
    var $banner_message = '';

    /**
     * Did read() timeout or return normally?
     *
     * @see self::isTimeout()
     * @var bool
     * @access private
     */
    var $is_timeout = false;

    /**
     * Log Boundary
     *
     * @see self::_format_log()
     * @var string
     * @access private
     */
    var $log_boundary = ':';

    /**
     * Log Long Width
     *
     * @see self::_format_log()
     * @var int
     * @access private
     */
    var $log_long_width = 65;

    /**
     * Log Short Width
     *
     * @see self::_format_log()
     * @var int
     * @access private
     */
    var $log_short_width = 16;

    /**
     * Hostname
     *
     * @see self::__construct()
     * @see self::_connect()
     * @var string
     * @access private
     */
    var $host;

    /**
     * Port Number
     *
     * @see self::__construct()
     * @see self::_connect()
     * @var int
     * @access private
     */
    var $port;

    /**
     * Number of columns for terminal window size
     *
     * @see self::getWindowColumns()
     * @see self::setWindowColumns()
     * @see self::setWindowSize()
     * @var int
     * @access private
     */
    var $windowColumns = 80;

    /**
     * Number of columns for terminal window size
     *
     * @see self::getWindowRows()
     * @see self::setWindowRows()
     * @see self::setWindowSize()
     * @var int
     * @access private
     */
    var $windowRows = 24;

    /**
     * Crypto Engine
     *
     * @see self::setCryptoEngine()
     * @see self::_key_exchange()
     * @var int
     * @access private
     */
    var $crypto_engine = false;

    /**
     * A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario
     *
     * @var System_SSH_Agent
     * @access private
     */
    var $agent;

    /**
     * Default Constructor.
     *
     * $host can either be a string, representing the host, or a stream resource.
     *
     * @param mixed $host
     * @param int $port
     * @param int $timeout
     * @see self::login()
     * @return \phpseclib\Net\SSH2
     * @access public
     */
    function __construct($host, $port = 22, $timeout = 10)
    {
        $this->message_numbers = array(
            1 => 'NET_SSH2_MSG_DISCONNECT',
            2 => 'NET_SSH2_MSG_IGNORE',
            3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
            4 => 'NET_SSH2_MSG_DEBUG',
            5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
            6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
            20 => 'NET_SSH2_MSG_KEXINIT',
            21 => 'NET_SSH2_MSG_NEWKEYS',
            30 => 'NET_SSH2_MSG_KEXDH_INIT',
            31 => 'NET_SSH2_MSG_KEXDH_REPLY',
            50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
            51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
            52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
            53 => 'NET_SSH2_MSG_USERAUTH_BANNER',

            80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
            81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
            82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
            90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
            91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
            92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
            93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
            94 => 'NET_SSH2_MSG_CHANNEL_DATA',
            95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
            96 => 'NET_SSH2_MSG_CHANNEL_EOF',
            97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
            98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
            99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
            100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
        );
        $this->disconnect_reasons = array(
            1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
            2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
            3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
            4 => 'NET_SSH2_DISCONNECT_RESERVED',
            5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
            6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
            7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
            8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
            9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
            10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
            11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
            12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
            13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
            14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
            15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
        );
        $this->channel_open_failure_reasons = array(
            1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
        );
        $this->terminal_modes = array(
            0 => 'NET_SSH2_TTY_OP_END'
        );
        $this->channel_extended_data_type_codes = array(
            1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
        );

        $this->_define_array(
            $this->message_numbers,
            $this->disconnect_reasons,
            $this->channel_open_failure_reasons,
            $this->terminal_modes,
            $this->channel_extended_data_type_codes,
            array(60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'),
            array(60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'),
            array(60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
                  61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'),
            // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
            array(30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
                  31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
                  32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
                  33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
                  34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'),
            // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
            array(30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
                  31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY')
        );

        if (is_resource($host)) {
            $this->fsock = $host;
            return;
        }

        if (is_string($host)) {
            $this->host = $host;
            $this->port = $port;
            $this->timeout = $timeout;
        }
    }

    /**
     * Set Crypto Engine Mode
     *
     * Possible $engine values:
     * CRYPT_MODE_INTERNAL, CRYPT_MODE_MCRYPT
     *
     * @param int $engine
     * @access private
     */
    function setCryptoEngine($engine)
    {
        $this->crypto_engine = $engine;
    }

    /**
     * Connect to an SSHv2 server
     *
     * @return bool
     * @access private
     */
    function _connect()
    {
        if ($this->bitmap & self::MASK_CONSTRUCTOR) {
            return false;
        }

        $this->bitmap |= self::MASK_CONSTRUCTOR;

        $this->curTimeout = $this->timeout;

        $this->last_packet = microtime(true);

        if (!is_resource($this->fsock)) {
            $start = microtime(true);
            $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout);
            if (!$this->fsock) {
                $host = $this->host . ':' . $this->port;
                user_error(rtrim("Cannot connect to $host. Error $errno. $errstr"));
                return false;
            }
            $elapsed = microtime(true) - $start;

            $this->curTimeout-= $elapsed;

            if ($this->curTimeout <= 0) {
                $this->is_timeout = true;
                return false;
            }
        }

        /* According to the SSH2 specs,

          "The server MAY send other lines of data before sending the version
           string.  Each line SHOULD be terminated by a Carriage Return and Line
           Feed.  Such lines MUST NOT begin with "SSH-", and SHOULD be encoded
           in ISO-10646 UTF-8 [RFC3629] (language is not specified).  Clients
           MUST be able to process such lines." */
        $data = '';
        while (!feof($this->fsock) && !preg_match('#(.*)^(SSH-(\d\.\d+).*)#ms', $data, $matches)) {
            $line = '';
            while (true) {
                if ($this->curTimeout) {
                    if ($this->curTimeout < 0) {
                        $this->is_timeout = true;
                        return false;
                    }
                    $read = array($this->fsock);
                    $write = $except = null;
                    $start = microtime(true);
                    $sec = floor($this->curTimeout);
                    $usec = 1000000 * ($this->curTimeout - $sec);
                    // on windows this returns a "Warning: Invalid CRT parameters detected" error
                    // the !count() is done as a workaround for <https://bugs.php.net/42682>
                    if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
                        $this->is_timeout = true;
                        return false;
                    }
                    $elapsed = microtime(true) - $start;
                    $this->curTimeout-= $elapsed;
                }

                $temp = stream_get_line($this->fsock, 255, "\n");
                if (strlen($temp) == 255) {
                    continue;
                }

                $line.= "$temp\n";

                // quoting RFC4253, "Implementers who wish to maintain
                // compatibility with older, undocumented versions of this protocol may
                // want to process the identification string without expecting the
                // presence of the carriage return character for reasons described in
                // Section 5 of this document."

                //if (substr($line, -2) == "\r\n") {
                //    break;
                //}

                break;
            }

            $data.= $line;
        }

        if (feof($this->fsock)) {
            user_error('Connection closed by server');
            return false;
        }

        $extra = $matches[1];

        $this->identifier = $this->_generate_identifier();

        if (defined('NET_SSH2_LOGGING')) {
            $this->_append_log('<-', $matches[0]);
            $this->_append_log('->', $this->identifier . "\r\n");
        }

        $this->server_identifier = trim($temp, "\r\n");
        if (strlen($extra)) {
            $this->errors[] = utf8_decode($data);
        }

        if ($matches[3] != '1.99' && $matches[3] != '2.0') {
            user_error("Cannot connect to SSH $matches[3] servers");
            return false;
        }

        fputs($this->fsock, $this->identifier . "\r\n");

        $response = $this->_get_binary_packet();
        if ($response === false) {
            user_error('Connection closed by server');
            return false;
        }

        if (ord($response[0]) != NET_SSH2_MSG_KEXINIT) {
            user_error('Expected SSH_MSG_KEXINIT');
            return false;
        }

        if (!$this->_key_exchange($response)) {
            return false;
        }

        $this->bitmap|= self::MASK_CONNECTED;

        return true;
    }

    /**
     * Generates the SSH identifier
     *
     * You should overwrite this method in your own class if you want to use another identifier
     *
     * @access protected
     * @return string
     */
    function _generate_identifier()
    {
        $identifier = 'SSH-2.0-phpseclib_2.0';

        $ext = array();
        if (extension_loaded('libsodium')) {
            $ext[] = 'libsodium';
        }

        if (extension_loaded('openssl')) {
            $ext[] = 'openssl';
        } elseif (extension_loaded('mcrypt')) {
            $ext[] = 'mcrypt';
        }

        if (extension_loaded('gmp')) {
            $ext[] = 'gmp';
        } elseif (extension_loaded('bcmath')) {
            $ext[] = 'bcmath';
        }

        if (!empty($ext)) {
            $identifier .= ' (' . implode(', ', $ext) . ')';
        }

        return $identifier;
    }

    /**
     * Key Exchange
     *
     * @param string $kexinit_payload_server
     * @access private
     */
    function _key_exchange($kexinit_payload_server)
    {
        $kex_algorithms = array(
            // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using
            // Curve25519. See doc/curve25519-sha256@libssh.org.txt in the
            // libssh repository for more information.
            'curve25519-sha256@libssh.org',

            // Diffie-Hellman Key Agreement (DH) using integer modulo prime
            // groups.
            'diffie-hellman-group1-sha1', // REQUIRED
            'diffie-hellman-group14-sha1', // REQUIRED
            'diffie-hellman-group-exchange-sha1', // RFC 4419
            'diffie-hellman-group-exchange-sha256', // RFC 4419
        );
        if (!function_exists('\\Sodium\\library_version_major')) {
            $kex_algorithms = array_diff(
                $kex_algorithms,
                array('curve25519-sha256@libssh.org')
            );
        }

        $server_host_key_algorithms = array(
            'ssh-rsa', // RECOMMENDED  sign   Raw RSA Key
            'ssh-dss'  // REQUIRED     sign   Raw DSS Key
        );

        $encryption_algorithms = array(
            // from <http://tools.ietf.org/html/rfc4345#section-4>:
            'arcfour256',
            'arcfour128',

            //'arcfour',      // OPTIONAL          the ARCFOUR stream cipher with a 128-bit key

            // CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
            'aes128-ctr',     // RECOMMENDED       AES (Rijndael) in SDCTR mode, with 128-bit key
            'aes192-ctr',     // RECOMMENDED       AES with 192-bit key
            'aes256-ctr',     // RECOMMENDED       AES with 256-bit key

            'twofish128-ctr', // OPTIONAL          Twofish in SDCTR mode, with 128-bit key
            'twofish192-ctr', // OPTIONAL          Twofish with 192-bit key
            'twofish256-ctr', // OPTIONAL          Twofish with 256-bit key

            'aes128-cbc',     // RECOMMENDED       AES with a 128-bit key
            'aes192-cbc',     // OPTIONAL          AES with a 192-bit key
            'aes256-cbc',     // OPTIONAL          AES in CBC mode, with a 256-bit key

            'twofish128-cbc', // OPTIONAL          Twofish with a 128-bit key
            'twofish192-cbc', // OPTIONAL          Twofish with a 192-bit key
            'twofish256-cbc',
            'twofish-cbc',    // OPTIONAL          alias for "twofish256-cbc"
                              //                   (this is being retained for historical reasons)

            'blowfish-ctr',   // OPTIONAL          Blowfish in SDCTR mode

            'blowfish-cbc',   // OPTIONAL          Blowfish in CBC mode

            '3des-ctr',       // RECOMMENDED       Three-key 3DES in SDCTR mode

            '3des-cbc',       // REQUIRED          three-key 3DES in CBC mode
                //'none'         // OPTIONAL          no encryption; NOT RECOMMENDED
        );

        if (extension_loaded('openssl') && !extension_loaded('mcrypt')) {
            // OpenSSL does not support arcfour256 in any capacity and arcfour128 / arcfour support is limited to
            // instances that do not use continuous buffers
            $encryption_algorithms = array_diff(
                $encryption_algorithms,
                array('arcfour256', 'arcfour128', 'arcfour')
            );
        }

        if (class_exists('\phpseclib\Crypt\RC4') === false) {
            $encryption_algorithms = array_diff(
                $encryption_algorithms,
                array('arcfour256', 'arcfour128', 'arcfour')
            );
        }
        if (class_exists('\phpseclib\Crypt\Rijndael') === false) {
            $encryption_algorithms = array_diff(
                $encryption_algorithms,
                array('aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc')
            );
        }
        if (class_exists('\phpseclib\Crypt\Twofish') === false) {
            $encryption_algorithms = array_diff(
                $encryption_algorithms,
                array('twofish128-ctr', 'twofish192-ctr', 'twofish256-ctr', 'twofish128-cbc', 'twofish192-cbc', 'twofish256-cbc', 'twofish-cbc')
            );
        }
        if (class_exists('\phpseclib\Crypt\Blowfish') === false) {
            $encryption_algorithms = array_diff(
                $encryption_algorithms,
                array('blowfish-ctr', 'blowfish-cbc')
            );
        }
        if (class_exists('\phpseclib\Crypt\TripleDES') === false) {
            $encryption_algorithms = array_diff(
                $encryption_algorithms,
                array('3des-ctr', '3des-cbc')
            );
        }
        $encryption_algorithms = array_values($encryption_algorithms);

        $mac_algorithms = array(
            // from <http://www.ietf.org/rfc/rfc6668.txt>:
            'hmac-sha2-256',// RECOMMENDED     HMAC-SHA256 (digest length = key length = 32)

            'hmac-sha1-96', // RECOMMENDED     first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
            'hmac-sha1',    // REQUIRED        HMAC-SHA1 (digest length = key length = 20)
            'hmac-md5-96',  // OPTIONAL        first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
            'hmac-md5',     // OPTIONAL        HMAC-MD5 (digest length = key length = 16)
            //'none'          // OPTIONAL        no MAC; NOT RECOMMENDED
        );

        $compression_algorithms = array(
            'none'   // REQUIRED        no compression
            //'zlib' // OPTIONAL        ZLIB (LZ77) compression
        );

        // some SSH servers have buggy implementations of some of the above algorithms
        switch ($this->server_identifier) {
            case 'SSH-2.0-SSHD':
                $mac_algorithms = array_values(array_diff(
                    $mac_algorithms,
                    array('hmac-sha1-96', 'hmac-md5-96')
                ));
        }

        $str_kex_algorithms = implode(',', $kex_algorithms);
        $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms);
        $encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(',', $encryption_algorithms);
        $mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(',', $mac_algorithms);
        $compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms);

        $client_cookie = Random::string(16);

        $response = $kexinit_payload_server;
        $this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)
        $server_cookie = $this->_string_shift($response, 16);

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));

        extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1)));
        $first_kex_packet_follows = $first_kex_packet_follows != 0;

        // the sending of SSH2_MSG_KEXINIT could go in one of two places.  this is the second place.
        $kexinit_payload_client = pack(
            'Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN',
            NET_SSH2_MSG_KEXINIT,
            $client_cookie,
            strlen($str_kex_algorithms),
            $str_kex_algorithms,
            strlen($str_server_host_key_algorithms),
            $str_server_host_key_algorithms,
            strlen($encryption_algorithms_client_to_server),
            $encryption_algorithms_client_to_server,
            strlen($encryption_algorithms_server_to_client),
            $encryption_algorithms_server_to_client,
            strlen($mac_algorithms_client_to_server),
            $mac_algorithms_client_to_server,
            strlen($mac_algorithms_server_to_client),
            $mac_algorithms_server_to_client,
            strlen($compression_algorithms_client_to_server),
            $compression_algorithms_client_to_server,
            strlen($compression_algorithms_server_to_client),
            $compression_algorithms_server_to_client,
            0,
            '',
            0,
            '',
            0,
            0
        );

        if (!$this->_send_binary_packet($kexinit_payload_client)) {
            return false;
        }
        // here ends the second place.

        // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
        // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
        // diffie-hellman key exchange as fast as possible
        $decrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_server_to_client);
        $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt);
        if ($decryptKeyLength === null) {
            user_error('No compatible server to client encryption algorithms found');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }

        $encrypt = $this->_array_intersect_first($encryption_algorithms, $this->encryption_algorithms_client_to_server);
        $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt);
        if ($encryptKeyLength === null) {
            user_error('No compatible client to server encryption algorithms found');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }

        // through diffie-hellman key exchange a symmetric key is obtained
        $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms);
        if ($kex_algorithm === false) {
            user_error('No compatible key exchange algorithms found');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }

        // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
        $exchange_hash_rfc4419 = '';

        if ($kex_algorithm === 'curve25519-sha256@libssh.org') {
            $x = Random::string(32);
            $eBytes = \Sodium\crypto_box_publickey_from_secretkey($x);
            $clientKexInitMessage = NET_SSH2_MSG_KEX_ECDH_INIT;
            $serverKexReplyMessage = NET_SSH2_MSG_KEX_ECDH_REPLY;
            $kexHash = new Hash('sha256');
        } else {
            if (strpos($kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
                $dh_group_sizes_packed = pack(
                    'NNN',
                    $this->kex_dh_group_size_min,
                    $this->kex_dh_group_size_preferred,
                    $this->kex_dh_group_size_max
                );
                $packet = pack(
                    'Ca*',
                    NET_SSH2_MSG_KEXDH_GEX_REQUEST,
                    $dh_group_sizes_packed
                );
                if (!$this->_send_binary_packet($packet)) {
                    return false;
                }

                $response = $this->_get_binary_packet();
                if ($response === false) {
                    user_error('Connection closed by server');
                    return false;
                }
                extract(unpack('Ctype', $this->_string_shift($response, 1)));
                if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) {
                    user_error('Expected SSH_MSG_KEX_DH_GEX_GROUP');
                    return false;
                }

                extract(unpack('NprimeLength', $this->_string_shift($response, 4)));
                $primeBytes = $this->_string_shift($response, $primeLength);
                $prime = new BigInteger($primeBytes, -256);

                extract(unpack('NgLength', $this->_string_shift($response, 4)));
                $gBytes = $this->_string_shift($response, $gLength);
                $g = new BigInteger($gBytes, -256);

                $exchange_hash_rfc4419 = pack(
                    'a*Na*Na*',
                    $dh_group_sizes_packed,
                    $primeLength,
                    $primeBytes,
                    $gLength,
                    $gBytes
                );

                $clientKexInitMessage = NET_SSH2_MSG_KEXDH_GEX_INIT;
                $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_GEX_REPLY;
            } else {
                switch ($kex_algorithm) {
                    // see http://tools.ietf.org/html/rfc2409#section-6.2 and
                    // http://tools.ietf.org/html/rfc2412, appendex E
                    case 'diffie-hellman-group1-sha1':
                        $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
                                '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
                                '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
                                'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
                        break;
                    // see http://tools.ietf.org/html/rfc3526#section-3
                    case 'diffie-hellman-group14-sha1':
                        $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
                                '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
                                '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
                                'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
                                '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
                                '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
                                'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
                                '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
                        break;
                }
                // For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1
                // the generator field element is 2 (decimal) and the hash function is sha1.
                $g = new BigInteger(2);
                $prime = new BigInteger($prime, 16);
                $clientKexInitMessage = NET_SSH2_MSG_KEXDH_INIT;
                $serverKexReplyMessage = NET_SSH2_MSG_KEXDH_REPLY;
            }

            switch ($kex_algorithm) {
                case 'diffie-hellman-group-exchange-sha256':
                    $kexHash = new Hash('sha256');
                    break;
                default:
                    $kexHash = new Hash('sha1');
            }

            /* To increase the speed of the key exchange, both client and server may
            reduce the size of their private exponents.  It should be at least
            twice as long as the key material that is generated from the shared
            secret.  For more details, see the paper by van Oorschot and Wiener
            [VAN-OORSCHOT].

            -- http://tools.ietf.org/html/rfc4419#section-6.2 */
            $one = new BigInteger(1);
            $keyLength = min($kexHash->getLength(), max($encryptKeyLength, $decryptKeyLength));
            $max = $one->bitwise_leftShift(16 * $keyLength); // 2 * 8 * $keyLength
            $max = $max->subtract($one);

            $x = $one->random($one, $max);
            $e = $g->modPow($x, $prime);

            $eBytes = $e->toBytes(true);
        }
        $data = pack('CNa*', $clientKexInitMessage, strlen($eBytes), $eBytes);

        if (!$this->_send_binary_packet($data)) {
            user_error('Connection closed by server');
            return false;
        }

        $response = $this->_get_binary_packet();
        if ($response === false) {
            user_error('Connection closed by server');
            return false;
        }
        extract(unpack('Ctype', $this->_string_shift($response, 1)));

        if ($type != $serverKexReplyMessage) {
            user_error('Expected SSH_MSG_KEXDH_REPLY');
            return false;
        }

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']);

        $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
        $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']);

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $fBytes = $this->_string_shift($response, $temp['length']);

        $temp = unpack('Nlength', $this->_string_shift($response, 4));
        $this->signature = $this->_string_shift($response, $temp['length']);

        $temp = unpack('Nlength', $this->_string_shift($this->signature, 4));
        $this->signature_format = $this->_string_shift($this->signature, $temp['length']);

        if ($kex_algorithm === 'curve25519-sha256@libssh.org') {
            if (strlen($fBytes) !== 32) {
                user_error('Received curve25519 public key of invalid length.');
                return false;
            }
            $key = new BigInteger(\Sodium\crypto_scalarmult($x, $fBytes), 256);
            \Sodium\memzero($x);
        } else {
            $f = new BigInteger($fBytes, -256);
            $key = $f->modPow($x, $prime);
        }
        $keyBytes = $key->toBytes(true);

        $this->exchange_hash = pack(
            'Na*Na*Na*Na*Na*a*Na*Na*Na*',
            strlen($this->identifier),
            $this->identifier,
            strlen($this->server_identifier),
            $this->server_identifier,
            strlen($kexinit_payload_client),
            $kexinit_payload_client,
            strlen($kexinit_payload_server),
            $kexinit_payload_server,
            strlen($this->server_public_host_key),
            $this->server_public_host_key,
            $exchange_hash_rfc4419,
            strlen($eBytes),
            $eBytes,
            strlen($fBytes),
            $fBytes,
            strlen($keyBytes),
            $keyBytes
        );

        $this->exchange_hash = $kexHash->hash($this->exchange_hash);

        if ($this->session_id === false) {
            $this->session_id = $this->exchange_hash;
        }

        $server_host_key_algorithm = $this->_array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
        if ($server_host_key_algorithm === false) {
            user_error('No compatible server host key algorithms found');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }

        if ($public_key_format != $server_host_key_algorithm || $this->signature_format != $server_host_key_algorithm) {
            user_error('Server Host Key Algorithm Mismatch');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }

        $packet = pack(
            'C',
            NET_SSH2_MSG_NEWKEYS
        );

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $response = $this->_get_binary_packet();

        if ($response === false) {
            user_error('Connection closed by server');
            return false;
        }

        extract(unpack('Ctype', $this->_string_shift($response, 1)));

        if ($type != NET_SSH2_MSG_NEWKEYS) {
            user_error('Expected SSH_MSG_NEWKEYS');
            return false;
        }

        $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);

        $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt);
        if ($this->encrypt) {
            if ($this->crypto_engine) {
                $this->encrypt->setEngine($this->crypto_engine);
            }
            if ($this->encrypt->block_size) {
                $this->encrypt_block_size = $this->encrypt->block_size;
            }
            $this->encrypt->enableContinuousBuffer();
            $this->encrypt->disablePadding();

            $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
            while ($this->encrypt_block_size > strlen($iv)) {
                $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
            }
            $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));

            $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
            while ($encryptKeyLength > strlen($key)) {
                $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
            }
            $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
        }

        $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt);
        if ($this->decrypt) {
            if ($this->crypto_engine) {
                $this->decrypt->setEngine($this->crypto_engine);
            }
            if ($this->decrypt->block_size) {
                $this->decrypt_block_size = $this->decrypt->block_size;
            }
            $this->decrypt->enableContinuousBuffer();
            $this->decrypt->disablePadding();

            $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
            while ($this->decrypt_block_size > strlen($iv)) {
                $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
            }
            $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));

            $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
            while ($decryptKeyLength > strlen($key)) {
                $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
            }
            $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
        }

        /* The "arcfour128" algorithm is the RC4 cipher, as described in
           [SCHNEIER], using a 128-bit key.  The first 1536 bytes of keystream
           generated by the cipher MUST be discarded, and the first byte of the
           first encrypted packet MUST be encrypted using the 1537th byte of
           keystream.

           -- http://tools.ietf.org/html/rfc4345#section-4 */
        if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') {
            $this->encrypt->encrypt(str_repeat("\0", 1536));
        }
        if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') {
            $this->decrypt->decrypt(str_repeat("\0", 1536));
        }

        $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_client_to_server);
        if ($mac_algorithm === false) {
            user_error('No compatible client to server message authentication algorithms found');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }

        $createKeyLength = 0; // ie. $mac_algorithm == 'none'
        switch ($mac_algorithm) {
            case 'hmac-sha2-256':
                $this->hmac_create = new Hash('sha256');
                $createKeyLength = 32;
                break;
            case 'hmac-sha1':
                $this->hmac_create = new Hash('sha1');
                $createKeyLength = 20;
                break;
            case 'hmac-sha1-96':
                $this->hmac_create = new Hash('sha1-96');
                $createKeyLength = 20;
                break;
            case 'hmac-md5':
                $this->hmac_create = new Hash('md5');
                $createKeyLength = 16;
                break;
            case 'hmac-md5-96':
                $this->hmac_create = new Hash('md5-96');
                $createKeyLength = 16;
        }

        $mac_algorithm = $this->_array_intersect_first($mac_algorithms, $this->mac_algorithms_server_to_client);
        if ($mac_algorithm === false) {
            user_error('No compatible server to client message authentication algorithms found');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }

        $checkKeyLength = 0;
        $this->hmac_size = 0;
        switch ($mac_algorithm) {
            case 'hmac-sha2-256':
                $this->hmac_check = new Hash('sha256');
                $checkKeyLength = 32;
                $this->hmac_size = 32;
                break;
            case 'hmac-sha1':
                $this->hmac_check = new Hash('sha1');
                $checkKeyLength = 20;
                $this->hmac_size = 20;
                break;
            case 'hmac-sha1-96':
                $this->hmac_check = new Hash('sha1-96');
                $checkKeyLength = 20;
                $this->hmac_size = 12;
                break;
            case 'hmac-md5':
                $this->hmac_check = new Hash('md5');
                $checkKeyLength = 16;
                $this->hmac_size = 16;
                break;
            case 'hmac-md5-96':
                $this->hmac_check = new Hash('md5-96');
                $checkKeyLength = 16;
                $this->hmac_size = 12;
        }

        $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
        while ($createKeyLength > strlen($key)) {
            $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
        }
        $this->hmac_create->setKey(substr($key, 0, $createKeyLength));

        $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
        while ($checkKeyLength > strlen($key)) {
            $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
        }
        $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));

        $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_server_to_client);
        if ($compression_algorithm === false) {
            user_error('No compatible server to client compression algorithms found');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }
        $this->decompress = $compression_algorithm == 'zlib';

        $compression_algorithm = $this->_array_intersect_first($compression_algorithms, $this->compression_algorithms_client_to_server);
        if ($compression_algorithm === false) {
            user_error('No compatible client to server compression algorithms found');
            return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
        }
        $this->compress = $compression_algorithm == 'zlib';

        return true;
    }

    /**
     * Maps an encryption algorithm name to the number of key bytes.
     *
     * @param string $algorithm Name of the encryption algorithm
     * @return int|null Number of bytes as an integer or null for unknown
     * @access private
     */
    function _encryption_algorithm_to_key_size($algorithm)
    {
        switch ($algorithm) {
            case 'none':
                return 0;
            case 'aes128-cbc':
            case 'aes128-ctr':
            case 'arcfour':
            case 'arcfour128':
            case 'blowfish-cbc':
            case 'blowfish-ctr':
            case 'twofish128-cbc':
            case 'twofish128-ctr':
                return 16;
            case '3des-cbc':
            case '3des-ctr':
            case 'aes192-cbc':
            case 'aes192-ctr':
            case 'twofish192-cbc':
            case 'twofish192-ctr':
                return 24;
            case 'aes256-cbc':
            case 'aes256-ctr':
            case 'arcfour256':
            case 'twofish-cbc':
            case 'twofish256-cbc':
            case 'twofish256-ctr':
                return 32;
        }
        return null;
    }

    /**
     * Maps an encryption algorithm name to an instance of a subclass of
     * \phpseclib\Crypt\Base.
     *
     * @param string $algorithm Name of the encryption algorithm
     * @return mixed Instance of \phpseclib\Crypt\Base or null for unknown
     * @access private
     */
    function _encryption_algorithm_to_crypt_instance($algorithm)
    {
        switch ($algorithm) {
            case '3des-cbc':
                return new TripleDES();
            case '3des-ctr':
                return new TripleDES(Base::MODE_CTR);
            case 'aes256-cbc':
            case 'aes192-cbc':
            case 'aes128-cbc':
                return new Rijndael();
            case 'aes256-ctr':
            case 'aes192-ctr':
            case 'aes128-ctr':
                return new Rijndael(Base::MODE_CTR);
            case 'blowfish-cbc':
                return new Blowfish();
            case 'blowfish-ctr':
                return new Blowfish(Base::MODE_CTR);
            case 'twofish128-cbc':
            case 'twofish192-cbc':
            case 'twofish256-cbc':
            case 'twofish-cbc':
                return new Twofish();
            case 'twofish128-ctr':
            case 'twofish192-ctr':
            case 'twofish256-ctr':
                return new Twofish(Base::MODE_CTR);
            case 'arcfour':
            case 'arcfour128':
            case 'arcfour256':
                return new RC4();
        }
        return null;
    }

    /**
     * Login
     *
     * The $password parameter can be a plaintext password, a \phpseclib\Crypt\RSA object or an array
     *
     * @param string $username
     * @param mixed $password
     * @param mixed $...
     * @return bool
     * @see self::_login()
     * @access public
     */
    function login($username)
    {
        $args = func_get_args();
        return call_user_func_array(array(&$this, '_login'), $args);
    }

    /**
     * Login Helper
     *
     * @param string $username
     * @param mixed $password
     * @param mixed $...
     * @return bool
     * @see self::_login_helper()
     * @access private
     */
    function _login($username)
    {
        if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
            if (!$this->_connect()) {
                return false;
            }
        }

        $args = array_slice(func_get_args(), 1);
        if (empty($args)) {
            return $this->_login_helper($username);
        }

        foreach ($args as $arg) {
            if ($this->_login_helper($username, $arg)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Login Helper
     *
     * @param string $username
     * @param string $password
     * @return bool
     * @access private
     * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
     *           by sending dummy SSH_MSG_IGNORE messages.
     */
    function _login_helper($username, $password = null)
    {
        if (!($this->bitmap & self::MASK_CONNECTED)) {
            return false;
        }

        if (!($this->bitmap & self::MASK_LOGIN_REQ)) {
            $packet = pack(
                'CNa*',
                NET_SSH2_MSG_SERVICE_REQUEST,
                strlen('ssh-userauth'),
                'ssh-userauth'
            );

            if (!$this->_send_binary_packet($packet)) {
                return false;
            }

            $response = $this->_get_binary_packet();
            if ($response === false) {
                user_error('Connection closed by server');
                return false;
            }

            extract(unpack('Ctype', $this->_string_shift($response, 1)));

            if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) {
                user_error('Expected SSH_MSG_SERVICE_ACCEPT');
                return false;
            }
            $this->bitmap |= self::MASK_LOGIN_REQ;
        }

        if (strlen($this->last_interactive_response)) {
            return !is_string($password) && !is_array($password) ? false : $this->_keyboard_interactive_process($password);
        }

        if ($password instanceof RSA) {
            return $this->_privatekey_login($username, $password);
        } elseif ($password instanceof Agent) {
            return $this->_ssh_agent_login($username, $password);
        }

        if (is_array($password)) {
            if ($this->_keyboard_interactive_login($username, $password)) {
                $this->bitmap |= self::MASK_LOGIN;
                return true;
            }
            return false;
        }

        if (!isset($password)) {
            $packet = pack(
                'CNa*Na*Na*',
                NET_SSH2_MSG_USERAUTH_REQUEST,
                strlen($username),
                $username,
                strlen('ssh-connection'),
                'ssh-connection',
                strlen('none'),
                'none'
            );

            if (!$this->_send_binary_packet($packet)) {
                return false;
            }

            $response = $this->_get_binary_packet();
            if ($response === false) {
                user_error('Connection closed by server');
                return false;
            }

            extract(unpack('Ctype', $this->_string_shift($response, 1)));

            switch ($type) {
                case NET_SSH2_MSG_USERAUTH_SUCCESS:
                    $this->bitmap |= self::MASK_LOGIN;
                    return true;
                //case NET_SSH2_MSG_USERAUTH_FAILURE:
                default:
                    return false;
            }
        }

        $packet = pack(
            'CNa*Na*Na*CNa*',
            NET_SSH2_MSG_USERAUTH_REQUEST,
            strlen($username),
            $username,
            strlen('ssh-connection'),
            'ssh-connection',
            strlen('password'),
            'password',
            0,
            strlen($password),
            $password
        );

        // remove the username and password from the logged packet
        if (!defined('NET_SSH2_LOGGING')) {
            $logged = null;
        } else {
            $logged = pack(
                'CNa*Na*Na*CNa*',
                NET_SSH2_MSG_USERAUTH_REQUEST,
                strlen('username'),
                'username',
                strlen('ssh-connection'),
                'ssh-connection',
                strlen('password'),
                'password',
                0,
                strlen('password'),
                'password'
            );
        }

        if (!$this->_send_binary_packet($packet, $logged)) {
            return false;
        }

        $response = $this->_get_binary_packet();
        if ($response === false) {
            user_error('Connection closed by server');
            return false;
        }

        extract(unpack('Ctype', $this->_string_shift($response, 1)));

        switch ($type) {
            case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
                if (defined('NET_SSH2_LOGGING')) {
                    $this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ';
                }
                extract(unpack('Nlength', $this->_string_shift($response, 4)));
                $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . utf8_decode($this->_string_shift($response, $length));
                return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
            case NET_SSH2_MSG_USERAUTH_FAILURE:
                // can we use keyboard-interactive authentication?  if not then either the login is bad or the server employees
                // multi-factor authentication
                extract(unpack('Nlength', $this->_string_shift($response, 4)));
                $auth_methods = explode(',', $this->_string_shift($response, $length));
                extract(unpack('Cpartial_success', $this->_string_shift($response, 1)));
                $partial_success = $partial_success != 0;

                if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) {
                    if ($this->_keyboard_interactive_login($username, $password)) {
                        $this->bitmap |= self::MASK_LOGIN;
                        return true;
                    }
                    return false;
                }
                return false;
            case NET_SSH2_MSG_USERAUTH_SUCCESS:
                $this->bitmap |= self::MASK_LOGIN;
                return true;
        }

        return false;
    }

    /**
     * Login via keyboard-interactive authentication
     *
     * See {@link http://tools.ietf.org/html/rfc4256 RFC4256} for details.  This is not a full-featured keyboard-interactive authenticator.
     *
     * @param string $username
     * @param string $password
     * @return bool
     * @access private
     */
    function _keyboard_interactive_login($username, $password)
    {
        $packet = pack(
            'CNa*Na*Na*Na*Na*',
            NET_SSH2_MSG_USERAUTH_REQUEST,
            strlen($username),
            $username,
            strlen('ssh-connection'),
            'ssh-connection',
            strlen('keyboard-interactive'),
            'keyboard-interactive',
            0,
            '',
            0,
            ''
        );

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        return $this->_keyboard_interactive_process($password);
    }

    /**
     * Handle the keyboard-interactive requests / responses.
     *
     * @param string $responses...
     * @return bool
     * @access private
     */
    function _keyboard_interactive_process()
    {
        $responses = func_get_args();

        if (strlen($this->last_interactive_response)) {
            $response = $this->last_interactive_response;
        } else {
            $orig = $response = $this->_get_binary_packet();
            if ($response === false) {
                user_error('Connection closed by server');
                return false;
            }
        }

        extract(unpack('Ctype', $this->_string_shift($response, 1)));

        switch ($type) {
            case NET_SSH2_MSG_USERAUTH_INFO_REQUEST:
                extract(unpack('Nlength', $this->_string_shift($response, 4)));
                $this->_string_shift($response, $length); // name; may be empty
                extract(unpack('Nlength', $this->_string_shift($response, 4)));
                $this->_string_shift($response, $length); // instruction; may be empty
                extract(unpack('Nlength', $this->_string_shift($response, 4)));
                $this->_string_shift($response, $length); // language tag; may be empty
                extract(unpack('Nnum_prompts', $this->_string_shift($response, 4)));

                for ($i = 0; $i < count($responses); $i++) {
                    if (is_array($responses[$i])) {
                        foreach ($responses[$i] as $key => $value) {
                            $this->keyboard_requests_responses[$key] = $value;
                        }
                        unset($responses[$i]);
                    }
                }
                $responses = array_values($responses);

                if (isset($this->keyboard_requests_responses)) {
                    for ($i = 0; $i < $num_prompts; $i++) {
                        extract(unpack('Nlength', $this->_string_shift($response, 4)));
                        // prompt - ie. "Password: "; must not be empty
                        $prompt = $this->_string_shift($response, $length);
                        //$echo = $this->_string_shift($response) != chr(0);
                        foreach ($this->keyboard_requests_responses as $key => $value) {
                            if (substr($prompt, 0, strlen($key)) == $key) {
                                $responses[] = $value;
                                break;
                            }
                        }
                    }
                }

                // see http://tools.ietf.org/html/rfc4256#section-3.2
                if (strlen($this->last_interactive_response)) {
                    $this->last_interactive_response = '';
                } elseif (defined('NET_SSH2_LOGGING')) {
                    $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
                        'UNKNOWN',
                        'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
                        $this->message_number_log[count($this->message_number_log) - 1]
                    );
                }

                if (!count($responses) && $num_prompts) {
                    $this->last_interactive_response = $orig;
                    return false;
                }

                /*
                   After obtaining the requested information from the user, the client
                   MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message.
                */
                // see http://tools.ietf.org/html/rfc4256#section-3.4
                $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses));
                for ($i = 0; $i < count($responses); $i++) {
                    $packet.= pack('Na*', strlen($responses[$i]), $responses[$i]);
                    $logged.= pack('Na*', strlen('dummy-answer'), 'dummy-answer');
                }

                if (!$this->_send_binary_packet($packet, $logged)) {
                    return false;
                }

                if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) {
                    $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
                        'UNKNOWN',
                        'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE',
                        $this->message_number_log[count($this->message_number_log) - 1]
                    );
                }

                /*
                   After receiving the response, the server MUST send either an
                   SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, or another
                   SSH_MSG_USERAUTH_INFO_REQUEST message.
                */
                // maybe phpseclib should force close the connection after x request / responses?  unless something like that is done
                // there could be an infinite loop of request / responses.
                return $this->_keyboard_interactive_process();
            case NET_SSH2_MSG_USERAUTH_SUCCESS:
                return true;
            case NET_SSH2_MSG_USERAUTH_FAILURE:
                return false;
        }

        return false;
    }

    /**
     * Login with an ssh-agent provided key
     *
     * @param string $username
     * @param \phpseclib\System\SSH\Agent $agent
     * @return bool
     * @access private
     */
    function _ssh_agent_login($username, $agent)
    {
        $this->agent = $agent;
        $keys = $agent->requestIdentities();
        foreach ($keys as $key) {
            if ($this->_privatekey_login($username, $key)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Login with an RSA private key
     *
     * @param string $username
     * @param \phpseclib\Crypt\RSA $password
     * @return bool
     * @access private
     * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
     *           by sending dummy SSH_MSG_IGNORE messages.
     */
    function _privatekey_login($username, $privatekey)
    {
        // see http://tools.ietf.org/html/rfc4253#page-15
        $publickey = $privatekey->getPublicKey(RSA::PUBLIC_FORMAT_RAW);
        if ($publickey === false) {
            return false;
        }

        $publickey = array(
            'e' => $publickey['e']->toBytes(true),
            'n' => $publickey['n']->toBytes(true)
        );
        $publickey = pack(
            'Na*Na*Na*',
            strlen('ssh-rsa'),
            'ssh-rsa',
            strlen($publickey['e']),
            $publickey['e'],
            strlen($publickey['n']),
            $publickey['n']
        );

        $part1 = pack(
            'CNa*Na*Na*',
            NET_SSH2_MSG_USERAUTH_REQUEST,
            strlen($username),
            $username,
            strlen('ssh-connection'),
            'ssh-connection',
            strlen('publickey'),
            'publickey'
        );
        $part2 = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey), $publickey);

        $packet = $part1 . chr(0) . $part2;
        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $response = $this->_get_binary_packet();
        if ($response === false) {
            user_error('Connection closed by server');
            return false;
        }

        extract(unpack('Ctype', $this->_string_shift($response, 1)));

        switch ($type) {
            case NET_SSH2_MSG_USERAUTH_FAILURE:
                extract(unpack('Nlength', $this->_string_shift($response, 4)));
                $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE: ' . $this->_string_shift($response, $length);
                return false;
            case NET_SSH2_MSG_USERAUTH_PK_OK:
                // we'll just take it on faith that the public key blob and the public key algorithm name are as
                // they should be
                if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) {
                    $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
                        'UNKNOWN',
                        'NET_SSH2_MSG_USERAUTH_PK_OK',
                        $this->message_number_log[count($this->message_number_log) - 1]
                    );
                }
        }

        $packet = $part1 . chr(1) . $part2;
        $privatekey->setSignatureMode(RSA::SIGNATURE_PKCS1);
        $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet));
        $signature = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($signature), $signature);
        $packet.= pack('Na*', strlen($signature), $signature);

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $response = $this->_get_binary_packet();
        if ($response === false) {
            user_error('Connection closed by server');
            return false;
        }

        extract(unpack('Ctype', $this->_string_shift($response, 1)));

        switch ($type) {
            case NET_SSH2_MSG_USERAUTH_FAILURE:
                // either the login is bad or the server employs multi-factor authentication
                return false;
            case NET_SSH2_MSG_USERAUTH_SUCCESS:
                $this->bitmap |= self::MASK_LOGIN;
                return true;
        }

        return false;
    }

    /**
     * Set Timeout
     *
     * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely.  setTimeout() makes it so it'll timeout.
     * Setting $timeout to false or 0 will mean there is no timeout.
     *
     * @param mixed $timeout
     * @access public
     */
    function setTimeout($timeout)
    {
        $this->timeout = $this->curTimeout = $timeout;
    }

    /**
     * Get the output from stdError
     *
     * @access public
     */
    function getStdError()
    {
        return $this->stdErrorLog;
    }

    /**
     * Execute Command
     *
     * If $callback is set to false then \phpseclib\Net\SSH2::_get_channel_packet(self::CHANNEL_EXEC) will need to be called manually.
     * In all likelihood, this is not a feature you want to be taking advantage of.
     *
     * @param string $command
     * @param Callback $callback
     * @return string
     * @access public
     */
    function exec($command, $callback = null)
    {
        $this->curTimeout = $this->timeout;
        $this->is_timeout = false;
        $this->stdErrorLog = '';

        if (!($this->bitmap & self::MASK_LOGIN)) {
            return false;
        }

        // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
        // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
        // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
        // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
        $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size;
        // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
        // uses 0x4000, that's what will be used here, as well.
        $packet_size = 0x4000;

        $packet = pack(
            'CNa*N3',
            NET_SSH2_MSG_CHANNEL_OPEN,
            strlen('session'),
            'session',
            self::CHANNEL_EXEC,
            $this->window_size_server_to_client[self::CHANNEL_EXEC],
            $packet_size
        );

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;

        $response = $this->_get_channel_packet(self::CHANNEL_EXEC);
        if ($response === false) {
            return false;
        }

        if ($this->request_pty === true) {
            $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
            $packet = pack(
                'CNNa*CNa*N5a*',
                NET_SSH2_MSG_CHANNEL_REQUEST,
                $this->server_channels[self::CHANNEL_EXEC],
                strlen('pty-req'),
                'pty-req',
                1,
                strlen('vt100'),
                'vt100',
                $this->windowColumns,
                $this->windowRows,
                0,
                0,
                strlen($terminal_modes),
                $terminal_modes
            );

            if (!$this->_send_binary_packet($packet)) {
                return false;
            }

            $response = $this->_get_binary_packet();
            if ($response === false) {
                user_error('Connection closed by server');
                return false;
            }

            list(, $type) = unpack('C', $this->_string_shift($response, 1));

            switch ($type) {
                case NET_SSH2_MSG_CHANNEL_SUCCESS:
                    break;
                case NET_SSH2_MSG_CHANNEL_FAILURE:
                default:
                    user_error('Unable to request pseudo-terminal');
                    return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
            }
            $this->in_request_pty_exec = true;
        }

        // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
        // down.  the one place where it might be desirable is if you're doing something like \phpseclib\Net\SSH2::exec('ping localhost &').
        // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
        // then immediately terminate.  without such a request exec() will loop indefinitely.  the ping process won't end but
        // neither will your script.

        // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
        // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
        // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA.  RFC4254#section-5.2 corroborates.
        $packet = pack(
            'CNNa*CNa*',
            NET_SSH2_MSG_CHANNEL_REQUEST,
            $this->server_channels[self::CHANNEL_EXEC],
            strlen('exec'),
            'exec',
            1,
            strlen($command),
            $command
        );
        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;

        $response = $this->_get_channel_packet(self::CHANNEL_EXEC);
        if ($response === false) {
            return false;
        }

        $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;

        if ($callback === false || $this->in_request_pty_exec) {
            return true;
        }

        $output = '';
        while (true) {
            $temp = $this->_get_channel_packet(self::CHANNEL_EXEC);
            switch (true) {
                case $temp === true:
                    return is_callable($callback) ? true : $output;
                case $temp === false:
                    return false;
                default:
                    if (is_callable($callback)) {
                        if (call_user_func($callback, $temp) === true) {
                            $this->_close_channel(self::CHANNEL_EXEC);
                            return true;
                        }
                    } else {
                        $output.= $temp;
                    }
            }
        }
    }

    /**
     * Creates an interactive shell
     *
     * @see self::read()
     * @see self::write()
     * @return bool
     * @access private
     */
    function _initShell()
    {
        if ($this->in_request_pty_exec === true) {
            return true;
        }

        $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size;
        $packet_size = 0x4000;

        $packet = pack(
            'CNa*N3',
            NET_SSH2_MSG_CHANNEL_OPEN,
            strlen('session'),
            'session',
            self::CHANNEL_SHELL,
            $this->window_size_server_to_client[self::CHANNEL_SHELL],
            $packet_size
        );

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;

        $response = $this->_get_channel_packet(self::CHANNEL_SHELL);
        if ($response === false) {
            return false;
        }

        $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
        $packet = pack(
            'CNNa*CNa*N5a*',
            NET_SSH2_MSG_CHANNEL_REQUEST,
            $this->server_channels[self::CHANNEL_SHELL],
            strlen('pty-req'),
            'pty-req',
            1,
            strlen('vt100'),
            'vt100',
            $this->windowColumns,
            $this->windowRows,
            0,
            0,
            strlen($terminal_modes),
            $terminal_modes
        );

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $response = $this->_get_binary_packet();
        if ($response === false) {
            user_error('Connection closed by server');
            return false;
        }

        list(, $type) = unpack('C', $this->_string_shift($response, 1));

        switch ($type) {
            case NET_SSH2_MSG_CHANNEL_SUCCESS:
            // if a pty can't be opened maybe commands can still be executed
            case NET_SSH2_MSG_CHANNEL_FAILURE:
                break;
            default:
                user_error('Unable to request pseudo-terminal');
                return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
        }

        $packet = pack(
            'CNNa*C',
            NET_SSH2_MSG_CHANNEL_REQUEST,
            $this->server_channels[self::CHANNEL_SHELL],
            strlen('shell'),
            'shell',
            1
        );
        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST;

        $response = $this->_get_channel_packet(self::CHANNEL_SHELL);
        if ($response === false) {
            return false;
        }

        $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;

        $this->bitmap |= self::MASK_SHELL;

        return true;
    }

    /**
     * Return the channel to be used with read() / write()
     *
     * @see self::read()
     * @see self::write()
     * @return int
     * @access public
     */
    function _get_interactive_channel()
    {
        switch (true) {
            case $this->in_subsystem:
                return self::CHANNEL_SUBSYSTEM;
            case $this->in_request_pty_exec:
                return self::CHANNEL_EXEC;
            default:
                return self::CHANNEL_SHELL;
        }
    }

    /**
     * Return an available open channel
     *
     * @return int
     * @access public
     */
    function _get_open_channel()
    {
        $channel = self::CHANNEL_EXEC;
        do {
            if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) {
                return $channel;
            }
        } while ($channel++ < self::CHANNEL_SUBSYSTEM);

        return false;
    }

    /**
     * Returns the output of an interactive shell
     *
     * Returns when there's a match for $expect, which can take the form of a string literal or,
     * if $mode == self::READ_REGEX, a regular expression.
     *
     * @see self::write()
     * @param string $expect
     * @param int $mode
     * @return string
     * @access public
     */
    function read($expect = '', $mode = self::READ_SIMPLE)
    {
        $this->curTimeout = $this->timeout;
        $this->is_timeout = false;

        if (!($this->bitmap & self::MASK_LOGIN)) {
            user_error('Operation disallowed prior to login()');
            return false;
        }

        if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
            user_error('Unable to initiate an interactive shell session');
            return false;
        }

        $channel = $this->_get_interactive_channel();

        $match = $expect;
        while (true) {
            if ($mode == self::READ_REGEX) {
                preg_match($expect, substr($this->interactiveBuffer, -1024), $matches);
                $match = isset($matches[0]) ? $matches[0] : '';
            }
            $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false;
            if ($pos !== false) {
                return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match));
            }
            $response = $this->_get_channel_packet($channel);
            if (is_bool($response)) {
                $this->in_request_pty_exec = false;
                return $response ? $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)) : false;
            }

            $this->interactiveBuffer.= $response;
        }
    }

    /**
     * Inputs a command into an interactive shell.
     *
     * @see self::read()
     * @param string $cmd
     * @return bool
     * @access public
     */
    function write($cmd)
    {
        if (!($this->bitmap & self::MASK_LOGIN)) {
            user_error('Operation disallowed prior to login()');
            return false;
        }

        if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
            user_error('Unable to initiate an interactive shell session');
            return false;
        }

        return $this->_send_channel_packet($this->_get_interactive_channel(), $cmd);
    }

    /**
     * Start a subsystem.
     *
     * Right now only one subsystem at a time is supported. To support multiple subsystem's stopSubsystem() could accept
     * a string that contained the name of the subsystem, but at that point, only one subsystem of each type could be opened.
     * To support multiple subsystem's of the same name maybe it'd be best if startSubsystem() generated a new channel id and
     * returns that and then that that was passed into stopSubsystem() but that'll be saved for a future date and implemented
     * if there's sufficient demand for such a feature.
     *
     * @see self::stopSubsystem()
     * @param string $subsystem
     * @return bool
     * @access public
     */
    function startSubsystem($subsystem)
    {
        $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size;

        $packet = pack(
            'CNa*N3',
            NET_SSH2_MSG_CHANNEL_OPEN,
            strlen('session'),
            'session',
            self::CHANNEL_SUBSYSTEM,
            $this->window_size,
            0x4000
        );

        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;

        $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM);
        if ($response === false) {
            return false;
        }

        $packet = pack(
            'CNNa*CNa*',
            NET_SSH2_MSG_CHANNEL_REQUEST,
            $this->server_channels[self::CHANNEL_SUBSYSTEM],
            strlen('subsystem'),
            'subsystem',
            1,
            strlen($subsystem),
            $subsystem
        );
        if (!$this->_send_binary_packet($packet)) {
            return false;
        }

        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;

        $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM);

        if ($response === false) {
            return false;
        }

        $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;

        $this->bitmap |= self::MASK_SHELL;
        $this->in_subsystem = true;

        return true;
    }

    /**
     * Stops a subsystem.
     *
     * @see self::startSubsystem()
     * @return bool
     * @access public
     */
    function stopSubsystem()
    {
        $this->in_subsystem = false;
        $this->_close_channel(self::CHANNEL_SUBSYSTEM);
        return true;
    }

    /**
     * Closes a channel
     *
     * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
     *
     * @access public
     */
    function reset()
    {
        $this->_close_channel($this->_get_interactive_channel());
    }

    /**
     * Is timeout?
     *
     * Did exec() or read() return because they timed out or because they encountered the end?
     *
     * @access public
     */
    function isTimeout()
    {
        return $this->is_timeout;
    }

    /**
     * Disconnect
     *
     * @access public
     */
    function disconnect()
    {
        $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
        if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) {
            fclose($this->realtime_log_file);
        }
    }

    /**
     * Destructor.
     *
     * Will be called, automatically, if you're supporting just PHP5.  If you're supporting PHP4, you'll need to call
     * disconnect().
     *
     * @access public
     */
    function __destruct()
    {
        $this->disconnect();
    }

    /**
     * Is the connection still active?
     *
     * @return bool
     * @access public
     */
    function isConnected()
    {
        return (bool) ($this->bitmap & self::MASK_CONNECTED);
    }

    /**
     * Have you successfully been logged in?
     *
     * @return bool
     * @access public
     */
    function isAuthenticated()
    {
        return (bool) ($this->bitmap & self::MASK_LOGIN);
    }

    /**
     * Gets Binary Packets
     *
     * See '6. Binary Packet Protocol' of rfc4253 for more info.
     *
     * @see self::_send_binary_packet()
     * @return string
     * @access private
     */
    function _get_binary_packet()
    {
        if (!is_resource($this->fsock) || feof($this->fsock)) {
            user_error('Connection closed prematurely');
            $this->bitmap = 0;
            return false;
        }

        $start = microtime(true);
        $raw = stream_get_contents($this->fsock, $this->decrypt_block_size);

        if (!strlen($raw)) {
            return '';
        }

        if ($this->decrypt !== false) {
            $raw = $this->decrypt->decrypt($raw);
        }
        if ($raw === false) {
            user_error('Unable to decrypt content');
            return false;
        }

        extract(unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5)));

        $remaining_length = $packet_length + 4 - $this->decrypt_block_size;

        // quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
        // "implementations SHOULD check that the packet length is reasonable"
        // PuTTY uses 0x9000 as the actual max packet size and so to shall we
        if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
            user_error('Invalid size');
            return false;
        }

        $buffer = '';
        while ($remaining_length > 0) {
            $temp = stream_get_contents($this->fsock, $remaining_length);
            if ($temp === false || feof($this->fsock)) {
                user_error('Error reading from socket');
                $this->bitmap = 0;
                return false;
            }
            $buffer.= $temp;
            $remaining_length-= strlen($temp);
        }
        $stop = microtime(true);
        if (strlen($buffer)) {
            $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer;
        }

        $payload = $this->_string_shift($raw, $packet_length - $padding_length - 1);
        $padding = $this->_string_shift($raw, $padding_length); // should leave $raw empty

        if ($this->hmac_check !== false) {
            $hmac = stream_get_contents($this->fsock, $this->hmac_size);
            if ($hmac === false || strlen($hmac) != $this->hmac_size) {
                user_error('Error reading socket');
                $this->bitmap = 0;
                return false;
            } elseif ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) {
                user_error('Invalid HMAC');
                return false;
            }
        }

        //if ($this->decompress) {
        //    $payload = gzinflate(substr($payload, 2));
        //}

        $this->get_seq_no++;

        if (defined('NET_SSH2_LOGGING')) {
            $current = microtime(true);
            $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
            $message_number = '<- ' . $message_number .
                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
            $this->_append_log($message_number, $payload);
            $this->last_packet = $current;
        }

        return $this->_filter($payload);
    }

    /**
     * Filter Binary Packets
     *
     * Because some binary packets need to be ignored...
     *
     * @see self::_get_binary_packet()
     * @return string
     * @access private
     */
    function _filter($payload)
    {
        switch (ord($payload[0])) {
            case NET_SSH2_MSG_DISCONNECT:
                $this->_string_shift($payload, 1);
                extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8)));
                $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode($this->_string_shift($payload, $length));
                $this->bitmap = 0;
                return false;
            case NET_SSH2_MSG_IGNORE:
                $payload = $this->_get_binary_packet();
                break;
            case NET_SSH2_MSG_DEBUG:
                $this->_string_shift($payload, 2);
                extract(unpack('Nlength', $this->_string_shift($payload, 4)));
                $this->errors[] = 'SSH_MSG_DEBUG: ' . utf8_decode($this->_string_shift($payload, $length));
                $payload = $this->_get_binary_packet();
                break;
            case NET_SSH2_MSG_UNIMPLEMENTED:
                return false;
            case NET_SSH2_MSG_KEXINIT:
                if ($this->session_id !== false) {
                    if (!$this->_key_exchange($payload)) {
                        $this->bitmap = 0;
                        return false;
                    }
                    $payload = $this->_get_binary_packet();
                }
        }

        // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in
        if (($this->bitmap & self::MASK_CONNECTED) && !($this->bitmap & self::MASK_LOGIN) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
            $this->_string_shift($payload, 1);
            extract(unpack('Nlength', $this->_string_shift($payload, 4)));
            $this->banner_message = utf8_decode($this->_string_shift($payload, $length));
            $payload = $this->_get_binary_packet();
        }

        // only called when we've already logged in
        if (($this->bitmap & self::MASK_CONNECTED) && ($this->bitmap & self::MASK_LOGIN)) {
            switch (ord($payload[0])) {
                case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
                    extract(unpack('Nlength', $this->_string_shift($payload, 4)));
                    $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . $this->_string_shift($payload, $length);

                    if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) {
                        return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
                    }

                    $payload = $this->_get_binary_packet();
                    break;
                case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
                    $this->_string_shift($payload, 1);
                    extract(unpack('Nlength', $this->_string_shift($payload, 4)));
                    $data = $this->_string_shift($payload, $length);
                    extract(unpack('Nserver_channel', $this->_string_shift($payload, 4)));
                    switch ($data) {
                        case 'auth-agent':
                        case 'auth-agent@openssh.com':
                            if (isset($this->agent)) {
                                $new_channel = self::CHANNEL_AGENT_FORWARD;

                                extract(unpack('Nremote_window_size', $this->_string_shift($payload, 4)));
                                extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($payload, 4)));

                                $this->packet_size_client_to_server[$new_channel] = $remote_window_size;
                                $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size;
                                $this->window_size_client_to_server[$new_channel] = $this->window_size;

                                $packet_size = 0x4000;

                                $packet = pack(
                                    'CN4',
                                    NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,
                                    $server_channel,
                                    $new_channel,
                                    $packet_size,
                                    $packet_size
                                );

                                $this->server_channels[$new_channel] = $server_channel;
                                $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
                                if (!$this->_send_binary_packet($packet)) {
                                    return false;
                                }
                            }
                            break;
                        default:
                            $packet = pack(
                                'CN3a*Na*',
                                NET_SSH2_MSG_REQUEST_FAILURE,
                                $server_channel,
                                NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
                                0,
                                '',
                                0,
                                ''
                            );

                            if (!$this->_send_binary_packet($packet)) {
                                return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
                            }
                    }
                    $payload = $this->_get_binary_packet();
                    break;
                case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
                    $this->_string_shift($payload, 1);
                    extract(unpack('Nchannel', $this->_string_shift($payload, 4)));
                    extract(unpack('Nwindow_size', $this->_string_shift($payload, 4)));
                    $this->window_size_client_to_server[$channel]+= $window_size;

                    $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->_get_binary_packet();
            }
        }

        return $payload;
    }

    /**
     * Enable Quiet Mode
     *
     * Suppress stderr from output
     *
     * @access public
     */
    function enableQuietMode()
    {
        $this->quiet_mode = true;
    }

    /**
     * Disable Quiet Mode
     *
     * Show stderr in output
     *
     * @access public
     */
    function disableQuietMode()
    {
        $this->quiet_mode = false;
    }

    /**
     * Returns whether Quiet Mode is enabled or not
     *
     * @see self::enableQuietMode()
     * @see self::disableQuietMode()
     * @access public
     * @return bool
     */
    function isQuietModeEnabled()
    {
        return $this->quiet_mode;
    }

    /**
     * Enable request-pty when using exec()
     *
     * @access public
     */
    function enablePTY()
    {
        $this->request_pty = true;
    }

    /**
     * Disable request-pty when using exec()
     *
     * @access public
     */
    function disablePTY()
    {
        $this->request_pty = false;
    }

    /**
     * Returns whether request-pty is enabled or not
     *
     * @see self::enablePTY()
     * @see self::disablePTY()
     * @access public
     * @return bool
     */
    function isPTYEnabled()
    {
        return $this->request_pty;
    }

    /**
     * Gets channel data
     *
     * Returns the data as a string if it's available and false if not.
     *
     * @param $client_channel
     * @return mixed
     * @access private
     */
    function _get_channel_packet($client_channel, $skip_extended = false)
    {
        if (!empty($this->channel_buffers[$client_channel])) {
            return array_shift($this->channel_buffers[$client_channel]);
        }

        while (true) {
            if ($this->curTimeout) {
                if ($this->curTimeout < 0) {
                    $this->is_timeout = true;
                    return true;
                }

                $read = array($this->fsock);
                $write = $except = null;

                $start = microtime(true);
                $sec = floor($this->curTimeout);
                $usec = 1000000 * ($this->curTimeout - $sec);
                // on windows this returns a "Warning: Invalid CRT parameters detected" error
                if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
                    $this->is_timeout = true;
                    return true;
                }
                $elapsed = microtime(true) - $start;
                $this->curTimeout-= $elapsed;
            }

            $response = $this->_get_binary_packet();
            if ($response === false) {
                user_error('Connection closed by server');
                return false;
            }
            if ($client_channel == -1 && $response === true) {
                return true;
            }
            if (!strlen($response)) {
                return '';
            }

            extract(unpack('Ctype', $this->_string_shift($response, 1)));

            if ($type == NET_SSH2_MSG_CHANNEL_OPEN) {
                extract(unpack('Nlength', $this->_string_shift($response, 4)));
            } else {
                extract(unpack('Nchannel', $this->_string_shift($response, 4)));
            }

            // will not be setup yet on incoming channel open request
            if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) {
                $this->window_size_server_to_client[$channel]-= strlen($response);

                // resize the window, if appropriate
                if ($this->window_size_server_to_client[$channel] < 0) {
                    $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_size);
                    if (!$this->_send_binary_packet($packet)) {
                        return false;
                    }
                    $this->window_size_server_to_client[$channel]+= $this->window_size;
                }

                switch ($this->channel_status[$channel]) {
                    case NET_SSH2_MSG_CHANNEL_OPEN:
                        switch ($type) {
                            case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
                                extract(unpack('Nserver_channel', $this->_string_shift($response, 4)));
                                $this->server_channels[$channel] = $server_channel;
                                extract(unpack('Nwindow_size', $this->_string_shift($response, 4)));
                                if ($window_size < 0) {
                                    $window_size&= 0x7FFFFFFF;
                                    $window_size+= 0x80000000;
                                }
                                $this->window_size_client_to_server[$channel] = $window_size;
                                $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4));
                                $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server'];
                                $result = $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended);
                                $this->_on_channel_open();
                                return $result;
                            //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
                            default:
                                user_error('Unable to open channel');
                                return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
                        }
                        break;
                    case NET_SSH2_MSG_CHANNEL_REQUEST:
                        switch ($type) {
                            case NET_SSH2_MSG_CHANNEL_SUCCESS:
                                return true;
                            case NET_SSH2_MSG_CHANNEL_FAILURE:
                                return false;
                            default:
                                user_error('Unable to fulfill channel request');
                                return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
                        }
                    case NET_SSH2_MSG_CHANNEL_CLOSE:
                        return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended);
                }
            }

            // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA

            switch ($type) {
                case NET_SSH2_MSG_CHANNEL_DATA:
                    /*
                    if ($channel == self::CHANNEL_EXEC) {
                        // SCP requires null packets, such as this, be sent.  further, in the case of the ssh.com SSH server
                        // this actually seems to make things twice as fast.  more to the point, the message right after
                        // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise.
                        // in OpenSSH it slows things down but only by a couple thousandths of a second.
                        $this->_send_channel_packet($channel, chr(0));
                    }
                    */
                    extract(unpack('Nlength', $this->_string_shift($response, 4)));
                    $data = $this->_string_shift($response, $length);

                    if ($channel == self::CHANNEL_AGENT_FORWARD) {
                        $agent_response = $this->agent->_forward_data($data);
                        if (!is_bool($agent_response)) {
                            $this->_send_channel_packet($channel, $agent_response);
                        }
                        break;
                    }

                    if ($client_channel == $channel) {
                        return $data;
                    }
                    if (!isset($this->channel_buffers[$channel])) {
                        $this->channel_buffers[$channel] = array();
                    }
                    $this->channel_buffers[$channel][] = $data;
                    break;
                case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
                    /*
                    if ($client_channel == self::CHANNEL_EXEC) {
                        $this->_send_channel_packet($client_channel, chr(0));
                    }
                    */
                    // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
                    extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8)));
                    $data = $this->_string_shift($response, $length);
                    $this->stdErrorLog.= $data;
                    if ($skip_extended || $this->quiet_mode) {
                        break;
                    }
                    if ($client_channel == $channel) {
                        return $data;
                    }
                    if (!isset($this->channel_buffers[$channel])) {
                        $this->channel_buffers[$channel] = array();
                    }
                    $this->channel_buffers[$channel][] = $data;
                    break;
                case NET_SSH2_MSG_CHANNEL_REQUEST:
                    extract(unpack('Nlength', $this->_string_shift($response, 4)));
                    $value = $this->_string_shift($response, $length);
                    switch ($value) {
                        case 'exit-signal':
                            $this->_string_shift($response, 1);
                            extract(unpack('Nlength', $this->_string_shift($response, 4)));
                            $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length);
                            $this->_string_shift($response, 1);
                            extract(unpack('Nlength', $this->_string_shift($response, 4)));
                            if ($length) {
                                $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length);
                            }

                            $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
                            $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));

                            $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF;

                            break;
                        case 'exit-status':
                            extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5)));
                            $this->exit_status = $exit_status;

                            // "The client MAY ignore these messages."
                            // -- http://tools.ietf.org/html/rfc4254#section-6.10

                            break;
                        default:
                            // "Some systems may not implement signals, in which case they SHOULD ignore this message."
                            //  -- http://tools.ietf.org/html/rfc4254#section-6.9
                            break;
                    }
                    break;
                case NET_SSH2_MSG_CHANNEL_CLOSE:
                    $this->curTimeout = 0;

                    if ($this->bitmap & self::MASK_SHELL) {
                        $this->bitmap&= ~self::MASK_SHELL;
                    }
                    if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
                        $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
                    }

                    $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
                    if ($client_channel == $channel) {
                        return true;
                    }
                case NET_SSH2_MSG_CHANNEL_EOF:
                    break;
                default:
                    user_error('Error reading channel data');
                    return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
            }
        }
    }

    /**
     * Sends Binary Packets
     *
     * See '6. Binary Packet Protocol' of rfc4253 for more info.
     *
     * @param string $data
     * @param string $logged
     * @see self::_get_binary_packet()
     * @return bool
     * @access private
     */
    function _send_binary_packet($data, $logged = null)
    {
        if (!is_resource($this->fsock) || feof($this->fsock)) {
            user_error('Connection closed prematurely');
            $this->bitmap = 0;
            return false;
        }

        //if ($this->compress) {
        //    // the -4 removes the checksum:
        //    // http://php.net/function.gzcompress#57710
        //    $data = substr(gzcompress($data), 0, -4);
        //}

        // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
        $packet_length = strlen($data) + 9;
        // round up to the nearest $this->encrypt_block_size
        $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
        // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
        $padding_length = $packet_length - strlen($data) - 5;
        $padding = Random::string($padding_length);

        // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
        $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);

        $hmac = $this->hmac_create !== false ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : '';
        $this->send_seq_no++;

        if ($this->encrypt !== false) {
            $packet = $this->encrypt->encrypt($packet);
        }

        $packet.= $hmac;

        $start = microtime(true);
        $result = strlen($packet) == fputs($this->fsock, $packet);
        $stop = microtime(true);

        if (defined('NET_SSH2_LOGGING')) {
            $current = microtime(true);
            $message_number = isset($this->message_numbers[ord($data[0])]) ? $this->message_numbers[ord($data[0])] : 'UNKNOWN (' . ord($data[0]) . ')';
            $message_number = '-> ' . $message_number .
                              ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
            $this->_append_log($message_number, isset($logged) ? $logged : $data);
            $this->last_packet = $current;
        }

        return $result;
    }

    /**
     * Logs data packets
     *
     * Makes sure that only the last 1MB worth of packets will be logged
     *
     * @param string $data
     * @access private
     */
    function _append_log($message_number, $message)
    {
        // remove the byte identifying the message type from all but the first two messages (ie. the identification strings)
        if (strlen($message_number) > 2) {
            $this->_string_shift($message);
        }

        switch (NET_SSH2_LOGGING) {
            // useful for benchmarks
            case self::LOG_SIMPLE:
                $this->message_number_log[] = $message_number;
                break;
            // the most useful log for SSH2
            case self::LOG_COMPLEX:
                $this->message_number_log[] = $message_number;
                $this->log_size+= strlen($message);
                $this->message_log[] = $message;
                while ($this->log_size > self::LOG_MAX_SIZE) {
                    $this->log_size-= strlen(array_shift($this->message_log));
                    array_shift($this->message_number_log);
                }
                break;
            // dump the output out realtime; packets may be interspersed with non packets,
            // passwords won't be filtered out and select other packets may not be correctly
            // identified
            case self::LOG_REALTIME:
                switch (PHP_SAPI) {
                    case 'cli':
                        $start = $stop = "\r\n";
                        break;
                    default:
                        $start = '<pre>';
                        $stop = '</pre>';
                }
                echo $start . $this->_format_log(array($message), array($message_number)) . $stop;
                @flush();
                @ob_flush();
                break;
            // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE
            // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
            // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
            // at the beginning of the file
            case self::LOG_REALTIME_FILE:
                if (!isset($this->realtime_log_file)) {
                    // PHP doesn't seem to like using constants in fopen()
                    $filename = self::LOG_REALTIME_FILENAME;
                    $fp = fopen($filename, 'w');
                    $this->realtime_log_file = $fp;
                }
                if (!is_resource($this->realtime_log_file)) {
                    break;
                }
                $entry = $this->_format_log(array($message), array($message_number));
                if ($this->realtime_log_wrap) {
                    $temp = "<<< START >>>\r\n";
                    $entry.= $temp;
                    fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp));
                }
                $this->realtime_log_size+= strlen($entry);
                if ($this->realtime_log_size > self::LOG_MAX_SIZE) {
                    fseek($this->realtime_log_file, 0);
                    $this->realtime_log_size = strlen($entry);
                    $this->realtime_log_wrap = true;
                }
                fputs($this->realtime_log_file, $entry);
        }
    }

    /**
     * Sends channel data
     *
     * Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate
     *
     * @param int $client_channel
     * @param string $data
     * @return bool
     * @access private
     */
    function _send_channel_packet($client_channel, $data)
    {
        while (strlen($data)) {
            if (!$this->window_size_client_to_server[$client_channel]) {
                $this->bitmap^= self::MASK_WINDOW_ADJUST;
                // using an invalid channel will let the buffers be built up for the valid channels
                $this->_get_channel_packet(-1);
                $this->bitmap^= self::MASK_WINDOW_ADJUST;
            }

            /* The maximum amount of data allowed is determined by the maximum
               packet size for the channel, and the current window size, whichever
               is smaller.
                 -- http://tools.ietf.org/html/rfc4254#section-5.2 */
            $max_size = min(
                $this->packet_size_client_to_server[$client_channel],
                $this->window_size_client_to_server[$client_channel]
            );

            $temp = $this->_string_shift($data, $max_size);
            $packet = pack(
                'CN2a*',
                NET_SSH2_MSG_CHANNEL_DATA,
                $this->server_channels[$client_channel],
                strlen($temp),
                $temp
            );
            $this->window_size_client_to_server[$client_channel]-= strlen($temp);
            if (!$this->_send_binary_packet($packet)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Closes and flushes a channel
     *
     * \phpseclib\Net\SSH2 doesn't properly close most channels.  For exec() channels are normally closed by the server
     * and for SFTP channels are presumably closed when the client disconnects.  This functions is intended
     * for SCP more than anything.
     *
     * @param int $client_channel
     * @param bool $want_reply
     * @return bool
     * @access private
     */
    function _close_channel($client_channel, $want_reply = false)
    {
        // see http://tools.ietf.org/html/rfc4254#section-5.3

        $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));

        if (!$want_reply) {
            $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
        }

        $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;

        $this->curTimeout = 0;

        while (!is_bool($this->_get_channel_packet($client_channel))) {
        }

        if ($want_reply) {
            $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
        }

        if ($this->bitmap & self::MASK_SHELL) {
            $this->bitmap&= ~self::MASK_SHELL;
        }
    }

    /**
     * Disconnect
     *
     * @param int $reason
     * @return bool
     * @access private
     */
    function _disconnect($reason)
    {
        if ($this->bitmap & self::MASK_CONNECTED) {
            $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, '');
            $this->_send_binary_packet($data);
            $this->bitmap = 0;
            fclose($this->fsock);
            return false;
        }
    }

    /**
     * String Shift
     *
     * Inspired by array_shift
     *
     * @param string $string
     * @param int $index
     * @return string
     * @access private
     */
    function _string_shift(&$string, $index = 1)
    {
        $substr = substr($string, 0, $index);
        $string = substr($string, $index);
        return $substr;
    }

    /**
     * Define Array
     *
     * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of
     * named constants from it, using the value as the name of the constant and the index as the value of the constant.
     * If any of the constants that would be defined already exists, none of the constants will be defined.
     *
     * @param array $array
     * @access private
     */
    function _define_array()
    {
        $args = func_get_args();
        foreach ($args as $arg) {
            foreach ($arg as $key => $value) {
                if (!defined($value)) {
                    define($value, $key);
                } else {
                    break 2;
                }
            }
        }
    }

    /**
     * Returns a log of the packets that have been sent and received.
     *
     * Returns a string if NET_SSH2_LOGGING == self::LOG_COMPLEX, an array if NET_SSH2_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING')
     *
     * @access public
     * @return array|false|string
     */
    function getLog()
    {
        if (!defined('NET_SSH2_LOGGING')) {
            return false;
        }

        switch (NET_SSH2_LOGGING) {
            case self::LOG_SIMPLE:
                return $this->message_number_log;
                break;
            case self::LOG_COMPLEX:
                return $this->_format_log($this->message_log, $this->message_number_log);
                break;
            default:
                return false;
        }
    }

    /**
     * Formats a log for printing
     *
     * @param array $message_log
     * @param array $message_number_log
     * @access private
     * @return string
     */
    function _format_log($message_log, $message_number_log)
    {
        $output = '';
        for ($i = 0; $i < count($message_log); $i++) {
            $output.= $message_number_log[$i] . "\r\n";
            $current_log = $message_log[$i];
            $j = 0;
            do {
                if (strlen($current_log)) {
                    $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0  ';
                }
                $fragment = $this->_string_shift($current_log, $this->log_short_width);
                $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary));
                // replace non ASCII printable characters with dots
                // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
                // also replace < with a . since < messes up the output on web browsers
                $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
                $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
                $j++;
            } while (strlen($current_log));
            $output.= "\r\n";
        }

        return $output;
    }

    /**
     * Helper function for _format_log
     *
     * For use with preg_replace_callback()
     *
     * @param array $matches
     * @access private
     * @return string
     */
    function _format_log_helper($matches)
    {
        return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
    }

    /**
     * Helper function for agent->_on_channel_open()
     *
     * Used when channels are created to inform agent
     * of said channel opening. Must be called after
     * channel open confirmation received
     *
     * @access private
     */
    function _on_channel_open()
    {
        if (isset($this->agent)) {
            $this->agent->_on_channel_open($this);
        }
    }

    /**
     * Returns the first value of the intersection of two arrays or false if
     * the intersection is empty. The order is defined by the first parameter.
     *
     * @param array $array1
     * @param array $array2
     * @return mixed False if intersection is empty, else intersected value.
     * @access private
     */
    function _array_intersect_first($array1, $array2)
    {
        foreach ($array1 as $value) {
            if (in_array($value, $array2)) {
                return $value;
            }
        }
        return false;
    }

    /**
     * Returns all errors
     *
     * @return string[]
     * @access public
     */
    function getErrors()
    {
        return $this->errors;
    }

    /**
     * Returns the last error
     *
     * @return string
     * @access public
     */
    function getLastError()
    {
        $count = count($this->errors);

        if ($count > 0) {
            return $this->errors[$count - 1];
        }
    }

    /**
     * Return the server identification.
     *
     * @return string
     * @access public
     */
    function getServerIdentification()
    {
        $this->_connect();

        return $this->server_identifier;
    }

    /**
     * Return a list of the key exchange algorithms the server supports.
     *
     * @return array
     * @access public
     */
    function getKexAlgorithms()
    {
        $this->_connect();

        return $this->kex_algorithms;
    }

    /**
     * Return a list of the host key (public key) algorithms the server supports.
     *
     * @return array
     * @access public
     */
    function getServerHostKeyAlgorithms()
    {
        $this->_connect();

        return $this->server_host_key_algorithms;
    }

    /**
     * Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff from the client.
     *
     * @return array
     * @access public
     */
    function getEncryptionAlgorithmsClient2Server()
    {
        $this->_connect();

        return $this->encryption_algorithms_client_to_server;
    }

    /**
     * Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to the client.
     *
     * @return array
     * @access public
     */
    function getEncryptionAlgorithmsServer2Client()
    {
        $this->_connect();

        return $this->encryption_algorithms_server_to_client;
    }

    /**
     * Return a list of the MAC algorithms the server supports, when receiving stuff from the client.
     *
     * @return array
     * @access public
     */
    function getMACAlgorithmsClient2Server()
    {
        $this->_connect();

        return $this->mac_algorithms_client_to_server;
    }

    /**
     * Return a list of the MAC algorithms the server supports, when sending stuff to the client.
     *
     * @return array
     * @access public
     */
    function getMACAlgorithmsServer2Client()
    {
        $this->_connect();

        return $this->mac_algorithms_server_to_client;
    }

    /**
     * Return a list of the compression algorithms the server supports, when receiving stuff from the client.
     *
     * @return array
     * @access public
     */
    function getCompressionAlgorithmsClient2Server()
    {
        $this->_connect();

        return $this->compression_algorithms_client_to_server;
    }

    /**
     * Return a list of the compression algorithms the server supports, when sending stuff to the client.
     *
     * @return array
     * @access public
     */
    function getCompressionAlgorithmsServer2Client()
    {
        $this->_connect();

        return $this->compression_algorithms_server_to_client;
    }

    /**
     * Return a list of the languages the server supports, when sending stuff to the client.
     *
     * @return array
     * @access public
     */
    function getLanguagesServer2Client()
    {
        $this->_connect();

        return $this->languages_server_to_client;
    }

    /**
     * Return a list of the languages the server supports, when receiving stuff from the client.
     *
     * @return array
     * @access public
     */
    function getLanguagesClient2Server()
    {
        $this->_connect();

        return $this->languages_client_to_server;
    }

    /**
     * Returns the banner message.
     *
     * Quoting from the RFC, "in some jurisdictions, sending a warning message before
     * authentication may be relevant for getting legal protection."
     *
     * @return string
     * @access public
     */
    function getBannerMessage()
    {
        return $this->banner_message;
    }

    /**
     * Returns the server public host key.
     *
     * Caching this the first time you connect to a server and checking the result on subsequent connections
     * is recommended.  Returns false if the server signature is not signed correctly with the public host key.
     *
     * @return mixed
     * @access public
     */
    function getServerPublicHostKey()
    {
        if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
            if (!$this->_connect()) {
                return false;
            }
        }

        $signature = $this->signature;
        $server_public_host_key = $this->server_public_host_key;

        extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4)));
        $this->_string_shift($server_public_host_key, $length);

        if ($this->signature_validated) {
            return $this->bitmap ?
                $this->signature_format . ' ' . base64_encode($this->server_public_host_key) :
                false;
        }

        $this->signature_validated = true;

        switch ($this->signature_format) {
            case 'ssh-dss':
                $zero = new BigInteger();

                $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
                $p = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);

                $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
                $q = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);

                $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
                $g = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);

                $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
                $y = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);

                /* The value for 'dss_signature_blob' is encoded as a string containing
                   r, followed by s (which are 160-bit integers, without lengths or
                   padding, unsigned, and in network byte order). */
                $temp = unpack('Nlength', $this->_string_shift($signature, 4));
                if ($temp['length'] != 40) {
                    user_error('Invalid signature');
                    return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
                }

                $r = new BigInteger($this->_string_shift($signature, 20), 256);
                $s = new BigInteger($this->_string_shift($signature, 20), 256);

                switch (true) {
                    case $r->equals($zero):
                    case $r->compare($q) >= 0:
                    case $s->equals($zero):
                    case $s->compare($q) >= 0:
                        user_error('Invalid signature');
                        return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
                }

                $w = $s->modInverse($q);

                $u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 16));
                list(, $u1) = $u1->divide($q);

                $u2 = $w->multiply($r);
                list(, $u2) = $u2->divide($q);

                $g = $g->modPow($u1, $p);
                $y = $y->modPow($u2, $p);

                $v = $g->multiply($y);
                list(, $v) = $v->divide($p);
                list(, $v) = $v->divide($q);

                if (!$v->equals($r)) {
                    user_error('Bad server signature');
                    return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
                }

                break;
            case 'ssh-rsa':
                $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
                $e = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);

                $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
                $rawN = $this->_string_shift($server_public_host_key, $temp['length']);
                $n = new BigInteger($rawN, -256);
                $nLength = strlen(ltrim($rawN, "\0"));

                /*
                $temp = unpack('Nlength', $this->_string_shift($signature, 4));
                $signature = $this->_string_shift($signature, $temp['length']);

                $rsa = new RSA();
                $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
                $rsa->loadKey(array('e' => $e, 'n' => $n), RSA::PUBLIC_FORMAT_RAW);
                if (!$rsa->verify($this->exchange_hash, $signature)) {
                    user_error('Bad server signature');
                    return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
                }
                */

                $temp = unpack('Nlength', $this->_string_shift($signature, 4));
                $s = new BigInteger($this->_string_shift($signature, $temp['length']), 256);

                // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the
                // following URL:
                // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf

                // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source.

                if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) {
                    user_error('Invalid signature');
                    return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
                }

                $s = $s->modPow($e, $n);
                $s = $s->toBytes();

                $h = pack('N4H*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, sha1($this->exchange_hash));
                $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h;

                if ($s != $h) {
                    user_error('Bad server signature');
                    return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
                }
                break;
            default:
                user_error('Unsupported signature format');
                return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
        }

        return $this->signature_format . ' ' . base64_encode($this->server_public_host_key);
    }

    /**
     * Returns the exit status of an SSH command or false.
     *
     * @return false|int
     * @access public
     */
    function getExitStatus()
    {
        if (is_null($this->exit_status)) {
            return false;
        }
        return $this->exit_status;
    }

    /**
     * Returns the number of columns for the terminal window size.
     *
     * @return int
     * @access public
     */
    function getWindowColumns()
    {
        return $this->windowColumns;
    }

    /**
     * Returns the number of rows for the terminal window size.
     *
     * @return int
     * @access public
     */
    function getWindowRows()
    {
        return $this->windowRows;
    }

    /**
     * Sets the number of columns for the terminal window size.
     *
     * @param int $value
     * @access public
     */
    function setWindowColumns($value)
    {
        $this->windowColumns = $value;
    }

    /**
     * Sets the number of rows for the terminal window size.
     *
     * @param int $value
     * @access public
     */
    function setWindowRows($value)
    {
        $this->windowRows = $value;
    }

    /**
     * Sets the number of columns and rows for the terminal window size.
     *
     * @param int $columns
     * @param int $rows
     * @access public
     */
    function setWindowSize($columns = 80, $rows = 24)
    {
        $this->windowColumns = $columns;
        $this->windowRows = $rows;
    }
}
# minimalist openssl.cnf file for use with phpseclib

HOME			= .
RANDFILE		= $ENV::HOME/.rnd

[ v3_ca ]
<?php
/**
 * Pure-PHP ssh-agent client.
 *
 * PHP version 5
 *
 * @category  System
 * @package   SSH\Agent
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2009 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 * @internal  See http://api.libssh.org/rfc/PROTOCOL.agent
 */

namespace phpseclib\System\SSH\Agent;

use phpseclib\System\SSH\Agent;

/**
 * Pure-PHP ssh-agent client identity object
 *
 * Instantiation should only be performed by \phpseclib\System\SSH\Agent class.
 * This could be thought of as implementing an interface that phpseclib\Crypt\RSA
 * implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something.
 * The methods in this interface would be getPublicKey and sign since those are the
 * methods phpseclib looks for to perform public key authentication.
 *
 * @package SSH\Agent
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  internal
 */
class Identity
{
    /**
     * Key Object
     *
     * @var \phpseclib\Crypt\RSA
     * @access private
     * @see self::getPublicKey()
     */
    var $key;

    /**
     * Key Blob
     *
     * @var string
     * @access private
     * @see self::sign()
     */
    var $key_blob;

    /**
     * Socket Resource
     *
     * @var resource
     * @access private
     * @see self::sign()
     */
    var $fsock;

    /**
     * Default Constructor.
     *
     * @param resource $fsock
     * @return \phpseclib\System\SSH\Agent\Identity
     * @access private
     */
    function __construct($fsock)
    {
        $this->fsock = $fsock;
    }

    /**
     * Set Public Key
     *
     * Called by \phpseclib\System\SSH\Agent::requestIdentities()
     *
     * @param \phpseclib\Crypt\RSA $key
     * @access private
     */
    function setPublicKey($key)
    {
        $this->key = $key;
        $this->key->setPublicKey();
    }

    /**
     * Set Public Key
     *
     * Called by \phpseclib\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key
     * but this saves a small amount of computation.
     *
     * @param string $key_blob
     * @access private
     */
    function setPublicKeyBlob($key_blob)
    {
        $this->key_blob = $key_blob;
    }

    /**
     * Get Public Key
     *
     * Wrapper for $this->key->getPublicKey()
     *
     * @param int $format optional
     * @return mixed
     * @access public
     */
    function getPublicKey($format = null)
    {
        return !isset($format) ? $this->key->getPublicKey() : $this->key->getPublicKey($format);
    }

    /**
     * Set Signature Mode
     *
     * Doesn't do anything as ssh-agent doesn't let you pick and choose the signature mode. ie.
     * ssh-agent's only supported mode is \phpseclib\Crypt\RSA::SIGNATURE_PKCS1
     *
     * @param int $mode
     * @access public
     */
    function setSignatureMode($mode)
    {
    }

    /**
     * Create a signature
     *
     * See "2.6.2 Protocol 2 private key signature request"
     *
     * @param string $message
     * @return string
     * @access public
     */
    function sign($message)
    {
        // the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE
        $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, 0);
        $packet = pack('Na*', strlen($packet), $packet);
        if (strlen($packet) != fputs($this->fsock, $packet)) {
            user_error('Connection closed during signing');
        }

        $length = current(unpack('N', fread($this->fsock, 4)));
        $type = ord(fread($this->fsock, 1));
        if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) {
            user_error('Unable to retrieve signature');
        }

        $signature_blob = fread($this->fsock, $length - 1);
        // the only other signature format defined - ssh-dss - is the same length as ssh-rsa
        // the + 12 is for the other various SSH added length fields
        return substr($signature_blob, strlen('ssh-rsa') + 12);
    }
}
<?php

/**
 * Pure-PHP ssh-agent client.
 *
 * PHP version 5
 *
 * Here are some examples of how to use this library:
 * <code>
 * <?php
 *    include 'vendor/autoload.php';
 *
 *    $agent = new \phpseclib\System\SSH\Agent();
 *
 *    $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
 *    if (!$ssh->login('username', $agent)) {
 *        exit('Login Failed');
 *    }
 *
 *    echo $ssh->exec('pwd');
 *    echo $ssh->exec('ls -la');
 * ?>
 * </code>
 *
 * @category  System
 * @package   SSH\Agent
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2014 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 * @internal  See http://api.libssh.org/rfc/PROTOCOL.agent
 */

namespace phpseclib\System\SSH;

use phpseclib\Crypt\RSA;
use phpseclib\System\SSH\Agent\Identity;

/**
 * Pure-PHP ssh-agent client identity factory
 *
 * requestIdentities() method pumps out \phpseclib\System\SSH\Agent\Identity objects
 *
 * @package SSH\Agent
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  internal
 */
class Agent
{
    /**#@+
     * Message numbers
     *
     * @access private
     */
    // to request SSH1 keys you have to use SSH_AGENTC_REQUEST_RSA_IDENTITIES (1)
    const SSH_AGENTC_REQUEST_IDENTITIES = 11;
    // this is the SSH2 response; the SSH1 response is SSH_AGENT_RSA_IDENTITIES_ANSWER (2).
    const SSH_AGENT_IDENTITIES_ANSWER = 12;
    // the SSH1 request is SSH_AGENTC_RSA_CHALLENGE (3)
    const SSH_AGENTC_SIGN_REQUEST = 13;
    // the SSH1 response is SSH_AGENT_RSA_RESPONSE (4)
    const SSH_AGENT_SIGN_RESPONSE = 14;
    /**#@-*/

    /**@+
     * Agent forwarding status
     *
     * @access private
     */
    // no forwarding requested and not active
    const FORWARD_NONE = 0;
    // request agent forwarding when opportune
    const FORWARD_REQUEST = 1;
    // forwarding has been request and is active
    const FORWARD_ACTIVE = 2;
    /**#@-*/

    /**
     * Unused
     */
    const SSH_AGENT_FAILURE = 5;

    /**
     * Socket Resource
     *
     * @var resource
     * @access private
     */
    var $fsock;

    /**
     * Agent forwarding status
     *
     * @access private
     */
    var $forward_status = self::FORWARD_NONE;

    /**
     * Buffer for accumulating forwarded authentication
     * agent data arriving on SSH data channel destined
     * for agent unix socket
     *
     * @access private
     */
    var $socket_buffer = '';

    /**
     * Tracking the number of bytes we are expecting
     * to arrive for the agent socket on the SSH data
     * channel
     */
    var $expected_bytes = 0;

    /**
     * Default Constructor
     *
     * @return \phpseclib\System\SSH\Agent
     * @access public
     */
    function __construct()
    {
        switch (true) {
            case isset($_SERVER['SSH_AUTH_SOCK']):
                $address = $_SERVER['SSH_AUTH_SOCK'];
                break;
            case isset($_ENV['SSH_AUTH_SOCK']):
                $address = $_ENV['SSH_AUTH_SOCK'];
                break;
            default:
                user_error('SSH_AUTH_SOCK not found');
                return false;
        }

        $this->fsock = fsockopen('unix://' . $address, 0, $errno, $errstr);
        if (!$this->fsock) {
            user_error("Unable to connect to ssh-agent (Error $errno: $errstr)");
        }
    }

    /**
     * Request Identities
     *
     * See "2.5.2 Requesting a list of protocol 2 keys"
     * Returns an array containing zero or more \phpseclib\System\SSH\Agent\Identity objects
     *
     * @return array
     * @access public
     */
    function requestIdentities()
    {
        if (!$this->fsock) {
            return array();
        }

        $packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES);
        if (strlen($packet) != fputs($this->fsock, $packet)) {
            user_error('Connection closed while requesting identities');
        }

        $length = current(unpack('N', fread($this->fsock, 4)));
        $type = ord(fread($this->fsock, 1));
        if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) {
            user_error('Unable to request identities');
        }

        $identities = array();
        $keyCount = current(unpack('N', fread($this->fsock, 4)));
        for ($i = 0; $i < $keyCount; $i++) {
            $length = current(unpack('N', fread($this->fsock, 4)));
            $key_blob = fread($this->fsock, $length);
            $key_str = 'ssh-rsa ' . base64_encode($key_blob);
            $length = current(unpack('N', fread($this->fsock, 4)));
            if ($length) {
                $key_str.= ' ' . fread($this->fsock, $length);
            }
            $length = current(unpack('N', substr($key_blob, 0, 4)));
            $key_type = substr($key_blob, 4, $length);
            switch ($key_type) {
                case 'ssh-rsa':
                    $key = new RSA();
                    $key->loadKey($key_str);
                    break;
                case 'ssh-dss':
                    // not currently supported
                    break;
            }
            // resources are passed by reference by default
            if (isset($key)) {
                $identity = new Identity($this->fsock);
                $identity->setPublicKey($key);
                $identity->setPublicKeyBlob($key_blob);
                $identities[] = $identity;
                unset($key);
            }
        }

        return $identities;
    }

    /**
     * Signal that agent forwarding should
     * be requested when a channel is opened
     *
     * @param Net_SSH2 $ssh
     * @return bool
     * @access public
     */
    function startSSHForwarding($ssh)
    {
        if ($this->forward_status == self::FORWARD_NONE) {
            $this->forward_status = self::FORWARD_REQUEST;
        }
    }

    /**
     * Request agent forwarding of remote server
     *
     * @param Net_SSH2 $ssh
     * @return bool
     * @access private
     */
    function _request_forwarding($ssh)
    {
        $request_channel = $ssh->_get_open_channel();
        if ($request_channel === false) {
            return false;
        }

        $packet = pack(
            'CNNa*C',
            NET_SSH2_MSG_CHANNEL_REQUEST,
            $ssh->server_channels[$request_channel],
            strlen('auth-agent-req@openssh.com'),
            'auth-agent-req@openssh.com',
            1
        );

        $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST;

        if (!$ssh->_send_binary_packet($packet)) {
            return false;
        }

        $response = $ssh->_get_channel_packet($request_channel);
        if ($response === false) {
            return false;
        }

        $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN;
        $this->forward_status = self::FORWARD_ACTIVE;

        return true;
    }

    /**
     * On successful channel open
     *
     * This method is called upon successful channel
     * open to give the SSH Agent an opportunity
     * to take further action. i.e. request agent forwarding
     *
     * @param Net_SSH2 $ssh
     * @access private
     */
    function _on_channel_open($ssh)
    {
        if ($this->forward_status == self::FORWARD_REQUEST) {
            $this->_request_forwarding($ssh);
        }
    }

    /**
     * Forward data to SSH Agent and return data reply
     *
     * @param string $data
     * @return data from SSH Agent
     * @access private
     */
    function _forward_data($data)
    {
        if ($this->expected_bytes > 0) {
            $this->socket_buffer.= $data;
            $this->expected_bytes -= strlen($data);
        } else {
            $agent_data_bytes = current(unpack('N', $data));
            $current_data_bytes = strlen($data);
            $this->socket_buffer = $data;
            if ($current_data_bytes != $agent_data_bytes + 4) {
                $this->expected_bytes = ($agent_data_bytes + 4) - $current_data_bytes;
                return false;
            }
        }

        if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) {
            user_error('Connection closed attempting to forward data to SSH agent');
        }

        $this->socket_buffer = '';
        $this->expected_bytes = 0;

        $agent_reply_bytes = current(unpack('N', fread($this->fsock, 4)));

        $agent_reply_data = fread($this->fsock, $agent_reply_bytes);
        $agent_reply_data = current(unpack('a*', $agent_reply_data));

        return pack('Na*', $agent_reply_bytes, $agent_reply_data);
    }
}
# phpseclib - PHP Secure Communications Library

[![Build Status](https://travis-ci.org/phpseclib/phpseclib.svg?branch=2.0)](https://travis-ci.org/phpseclib/phpseclib)

MIT-licensed pure-PHP implementations of an arbitrary-precision integer
arithmetic library, fully PKCS#1 (v2.1) compliant RSA, DES, 3DES, RC4, Rijndael,
AES, Blowfish, Twofish, SSH-1, SSH-2, SFTP, and X.509

* [Download (1.0.4)](http://sourceforge.net/projects/phpseclib/files/phpseclib1.0.4.zip/download)
* [Browse Git](https://github.com/phpseclib/phpseclib)
* [Code Coverage Report](http://phpseclib.bantux.org/code_coverage/2.0/latest/)

<img src="http://phpseclib.sourceforge.net/pear-icon.png" alt="PEAR Channel" width="16" height="16">
PEAR Channel: [phpseclib.sourceforge.net](http://phpseclib.sourceforge.net/pear.htm)

## Documentation

* [Documentation / Manual](http://phpseclib.sourceforge.net/)
* [API Documentation](http://phpseclib.bantux.org/api/2.0/) (generated by Sami)

## Support

Need Support?

* [Checkout Questions and Answers on Stack Overflow](http://stackoverflow.com/questions/tagged/phpseclib)
* [Create a Support Ticket on GitHub](https://github.com/phpseclib/phpseclib/issues/new)
* [Browse the Support Forum](http://www.frostjedi.com/phpbb/viewforum.php?f=46) (no longer in use)

## Installing Development Dependencies

Dependencies are managed via Composer.

1. Download the [`composer.phar`](https://getcomposer.org/composer.phar) executable as per the
   [Composer Download Instructions](https://getcomposer.org/download/), e.g. by running

    ``` sh
    curl -sS https://getcomposer.org/installer | php
    ```

2. Install Dependencies

    ``` sh
    php composer.phar install
    ```

## Contributing

1. Fork the Project

2. Install Development Dependencies

3. Create a Feature Branch

4. (Recommended) Run the Test Suite

    ``` sh
    vendor/bin/phpunit
    ```
5. (Recommended) Check whether your code conforms to our Coding Standards by running

    ``` sh
    vendor/bin/phing -f build/build.xml sniff
    ```

6. Send us a Pull Request
# Changelog

All notable changes to this project will be documented in this file, in reverse chronological order by release.

## 1.0.1 - 2016-08-06

### Added

- Nothing.

### Deprecated

- Nothing.

### Removed

- Nothing.

### Fixed

- Updated all `@return self` annotation references in interfaces to use
  `@return static`, which more closelly follows the semantics of the
  specification.
- Updated the `MessageInterface::getHeaders()` return annotation to use the
  value `string[][]`, indicating the format is a nested array of strings.
- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
  to point to the correct section of RFC 7230.
- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
  to add the parameter name (`$uploadedFiles`).
- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
  method to correctly reference the method parameter (it was referencing an
  incorrect parameter name previously).

## 1.0.0 - 2016-05-18

Initial stable release; reflects accepted PSR-7 specification.
{
    "name": "psr/http-message",
    "description": "Common interface for HTTP messages",
    "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
    "homepage": "https://github.com/php-fig/http-message",
    "license": "MIT",
    "authors": [
        {
            "name": "PHP-FIG",
            "homepage": "http://www.php-fig.org/"
        }
    ],
    "require": {
        "php": ">=5.3.0"
    },
    "autoload": {
        "psr-4": {
            "Psr\\Http\\Message\\": "src/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}
Copyright (c) 2014 PHP Framework Interoperability Group

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.
PSR Http Message
================

This repository holds all interfaces/classes/traits related to
[PSR-7](http://www.php-fig.org/psr/psr-7/).

Note that this is not a HTTP message implementation of its own. It is merely an
interface that describes a HTTP message. See the specification for more details.

Usage
-----

We'll certainly need some stuff in here.<?php

namespace Psr\Http\Message;

/**
 * HTTP messages consist of requests from a client to a server and responses
 * from a server to a client. This interface defines the methods common to
 * each.
 *
 * Messages are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 *
 * @link http://www.ietf.org/rfc/rfc7230.txt
 * @link http://www.ietf.org/rfc/rfc7231.txt
 */
interface MessageInterface
{
    /**
     * Retrieves the HTTP protocol version as a string.
     *
     * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
     *
     * @return string HTTP protocol version.
     */
    public function getProtocolVersion();

    /**
     * Return an instance with the specified HTTP protocol version.
     *
     * The version string MUST contain only the HTTP version number (e.g.,
     * "1.1", "1.0").
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new protocol version.
     *
     * @param string $version HTTP protocol version
     * @return static
     */
    public function withProtocolVersion($version);

    /**
     * Retrieves all message header values.
     *
     * The keys represent the header name as it will be sent over the wire, and
     * each value is an array of strings associated with the header.
     *
     *     // Represent the headers as a string
     *     foreach ($message->getHeaders() as $name => $values) {
     *         echo $name . ": " . implode(", ", $values);
     *     }
     *
     *     // Emit headers iteratively:
     *     foreach ($message->getHeaders() as $name => $values) {
     *         foreach ($values as $value) {
     *             header(sprintf('%s: %s', $name, $value), false);
     *         }
     *     }
     *
     * While header names are not case-sensitive, getHeaders() will preserve the
     * exact case in which headers were originally specified.
     *
     * @return string[][] Returns an associative array of the message's headers. Each
     *     key MUST be a header name, and each value MUST be an array of strings
     *     for that header.
     */
    public function getHeaders();

    /**
     * Checks if a header exists by the given case-insensitive name.
     *
     * @param string $name Case-insensitive header field name.
     * @return bool Returns true if any header names match the given header
     *     name using a case-insensitive string comparison. Returns false if
     *     no matching header name is found in the message.
     */
    public function hasHeader($name);

    /**
     * Retrieves a message header value by the given case-insensitive name.
     *
     * This method returns an array of all the header values of the given
     * case-insensitive header name.
     *
     * If the header does not appear in the message, this method MUST return an
     * empty array.
     *
     * @param string $name Case-insensitive header field name.
     * @return string[] An array of string values as provided for the given
     *    header. If the header does not appear in the message, this method MUST
     *    return an empty array.
     */
    public function getHeader($name);

    /**
     * Retrieves a comma-separated string of the values for a single header.
     *
     * This method returns all of the header values of the given
     * case-insensitive header name as a string concatenated together using
     * a comma.
     *
     * NOTE: Not all header values may be appropriately represented using
     * comma concatenation. For such headers, use getHeader() instead
     * and supply your own delimiter when concatenating.
     *
     * If the header does not appear in the message, this method MUST return
     * an empty string.
     *
     * @param string $name Case-insensitive header field name.
     * @return string A string of values as provided for the given header
     *    concatenated together using a comma. If the header does not appear in
     *    the message, this method MUST return an empty string.
     */
    public function getHeaderLine($name);

    /**
     * Return an instance with the provided value replacing the specified header.
     *
     * While header names are case-insensitive, the casing of the header will
     * be preserved by this function, and returned from getHeaders().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new and/or updated header and value.
     *
     * @param string $name Case-insensitive header field name.
     * @param string|string[] $value Header value(s).
     * @return static
     * @throws \InvalidArgumentException for invalid header names or values.
     */
    public function withHeader($name, $value);

    /**
     * Return an instance with the specified header appended with the given value.
     *
     * Existing values for the specified header will be maintained. The new
     * value(s) will be appended to the existing list. If the header did not
     * exist previously, it will be added.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new header and/or value.
     *
     * @param string $name Case-insensitive header field name to add.
     * @param string|string[] $value Header value(s).
     * @return static
     * @throws \InvalidArgumentException for invalid header names or values.
     */
    public function withAddedHeader($name, $value);

    /**
     * Return an instance without the specified header.
     *
     * Header resolution MUST be done without case-sensitivity.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that removes
     * the named header.
     *
     * @param string $name Case-insensitive header field name to remove.
     * @return static
     */
    public function withoutHeader($name);

    /**
     * Gets the body of the message.
     *
     * @return StreamInterface Returns the body as a stream.
     */
    public function getBody();

    /**
     * Return an instance with the specified message body.
     *
     * The body MUST be a StreamInterface object.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return a new instance that has the
     * new body stream.
     *
     * @param StreamInterface $body Body.
     * @return static
     * @throws \InvalidArgumentException When the body is not valid.
     */
    public function withBody(StreamInterface $body);
}
<?php

namespace Psr\Http\Message;

/**
 * Representation of an outgoing, client-side request.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - HTTP method
 * - URI
 * - Headers
 * - Message body
 *
 * During construction, implementations MUST attempt to set the Host header from
 * a provided URI if no Host header is provided.
 *
 * Requests are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface RequestInterface extends MessageInterface
{
    /**
     * Retrieves the message's request target.
     *
     * Retrieves the message's request-target either as it will appear (for
     * clients), as it appeared at request (for servers), or as it was
     * specified for the instance (see withRequestTarget()).
     *
     * In most cases, this will be the origin-form of the composed URI,
     * unless a value was provided to the concrete implementation (see
     * withRequestTarget() below).
     *
     * If no URI is available, and no request-target has been specifically
     * provided, this method MUST return the string "/".
     *
     * @return string
     */
    public function getRequestTarget();

    /**
     * Return an instance with the specific request-target.
     *
     * If the request needs a non-origin-form request-target — e.g., for
     * specifying an absolute-form, authority-form, or asterisk-form —
     * this method may be used to create an instance with the specified
     * request-target, verbatim.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * changed request target.
     *
     * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
     *     request-target forms allowed in request messages)
     * @param mixed $requestTarget
     * @return static
     */
    public function withRequestTarget($requestTarget);

    /**
     * Retrieves the HTTP method of the request.
     *
     * @return string Returns the request method.
     */
    public function getMethod();

    /**
     * Return an instance with the provided HTTP method.
     *
     * While HTTP method names are typically all uppercase characters, HTTP
     * method names are case-sensitive and thus implementations SHOULD NOT
     * modify the given string.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * changed request method.
     *
     * @param string $method Case-sensitive method.
     * @return static
     * @throws \InvalidArgumentException for invalid HTTP methods.
     */
    public function withMethod($method);

    /**
     * Retrieves the URI instance.
     *
     * This method MUST return a UriInterface instance.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
     * @return UriInterface Returns a UriInterface instance
     *     representing the URI of the request.
     */
    public function getUri();

    /**
     * Returns an instance with the provided URI.
     *
     * This method MUST update the Host header of the returned request by
     * default if the URI contains a host component. If the URI does not
     * contain a host component, any pre-existing Host header MUST be carried
     * over to the returned request.
     *
     * You can opt-in to preserving the original state of the Host header by
     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
     * `true`, this method interacts with the Host header in the following ways:
     *
     * - If the Host header is missing or empty, and the new URI contains
     *   a host component, this method MUST update the Host header in the returned
     *   request.
     * - If the Host header is missing or empty, and the new URI does not contain a
     *   host component, this method MUST NOT update the Host header in the returned
     *   request.
     * - If a Host header is present and non-empty, this method MUST NOT update
     *   the Host header in the returned request.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * new UriInterface instance.
     *
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
     * @param UriInterface $uri New request URI to use.
     * @param bool $preserveHost Preserve the original state of the Host header.
     * @return static
     */
    public function withUri(UriInterface $uri, $preserveHost = false);
}
<?php

namespace Psr\Http\Message;

/**
 * Representation of an outgoing, server-side response.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - Status code and reason phrase
 * - Headers
 * - Message body
 *
 * Responses are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface ResponseInterface extends MessageInterface
{
    /**
     * Gets the response status code.
     *
     * The status code is a 3-digit integer result code of the server's attempt
     * to understand and satisfy the request.
     *
     * @return int Status code.
     */
    public function getStatusCode();

    /**
     * Return an instance with the specified status code and, optionally, reason phrase.
     *
     * If no reason phrase is specified, implementations MAY choose to default
     * to the RFC 7231 or IANA recommended reason phrase for the response's
     * status code.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated status and reason phrase.
     *
     * @link http://tools.ietf.org/html/rfc7231#section-6
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
     * @param int $code The 3-digit integer result code to set.
     * @param string $reasonPhrase The reason phrase to use with the
     *     provided status code; if none is provided, implementations MAY
     *     use the defaults as suggested in the HTTP specification.
     * @return static
     * @throws \InvalidArgumentException For invalid status code arguments.
     */
    public function withStatus($code, $reasonPhrase = '');

    /**
     * Gets the response reason phrase associated with the status code.
     *
     * Because a reason phrase is not a required element in a response
     * status line, the reason phrase value MAY be null. Implementations MAY
     * choose to return the default RFC 7231 recommended reason phrase (or those
     * listed in the IANA HTTP Status Code Registry) for the response's
     * status code.
     *
     * @link http://tools.ietf.org/html/rfc7231#section-6
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
     * @return string Reason phrase; must return an empty string if none present.
     */
    public function getReasonPhrase();
}
<?php

namespace Psr\Http\Message;

/**
 * Representation of an incoming, server-side HTTP request.
 *
 * Per the HTTP specification, this interface includes properties for
 * each of the following:
 *
 * - Protocol version
 * - HTTP method
 * - URI
 * - Headers
 * - Message body
 *
 * Additionally, it encapsulates all data as it has arrived to the
 * application from the CGI and/or PHP environment, including:
 *
 * - The values represented in $_SERVER.
 * - Any cookies provided (generally via $_COOKIE)
 * - Query string arguments (generally via $_GET, or as parsed via parse_str())
 * - Upload files, if any (as represented by $_FILES)
 * - Deserialized body parameters (generally from $_POST)
 *
 * $_SERVER values MUST be treated as immutable, as they represent application
 * state at the time of request; as such, no methods are provided to allow
 * modification of those values. The other values provide such methods, as they
 * can be restored from $_SERVER or the request body, and may need treatment
 * during the application (e.g., body parameters may be deserialized based on
 * content type).
 *
 * Additionally, this interface recognizes the utility of introspecting a
 * request to derive and match additional parameters (e.g., via URI path
 * matching, decrypting cookie values, deserializing non-form-encoded body
 * content, matching authorization headers to users, etc). These parameters
 * are stored in an "attributes" property.
 *
 * Requests are considered immutable; all methods that might change state MUST
 * be implemented such that they retain the internal state of the current
 * message and return an instance that contains the changed state.
 */
interface ServerRequestInterface extends RequestInterface
{
    /**
     * Retrieve server parameters.
     *
     * Retrieves data related to the incoming request environment,
     * typically derived from PHP's $_SERVER superglobal. The data IS NOT
     * REQUIRED to originate from $_SERVER.
     *
     * @return array
     */
    public function getServerParams();

    /**
     * Retrieve cookies.
     *
     * Retrieves cookies sent by the client to the server.
     *
     * The data MUST be compatible with the structure of the $_COOKIE
     * superglobal.
     *
     * @return array
     */
    public function getCookieParams();

    /**
     * Return an instance with the specified cookies.
     *
     * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
     * be compatible with the structure of $_COOKIE. Typically, this data will
     * be injected at instantiation.
     *
     * This method MUST NOT update the related Cookie header of the request
     * instance, nor related values in the server params.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated cookie values.
     *
     * @param array $cookies Array of key/value pairs representing cookies.
     * @return static
     */
    public function withCookieParams(array $cookies);

    /**
     * Retrieve query string arguments.
     *
     * Retrieves the deserialized query string arguments, if any.
     *
     * Note: the query params might not be in sync with the URI or server
     * params. If you need to ensure you are only getting the original
     * values, you may need to parse the query string from `getUri()->getQuery()`
     * or from the `QUERY_STRING` server param.
     *
     * @return array
     */
    public function getQueryParams();

    /**
     * Return an instance with the specified query string arguments.
     *
     * These values SHOULD remain immutable over the course of the incoming
     * request. They MAY be injected during instantiation, such as from PHP's
     * $_GET superglobal, or MAY be derived from some other value such as the
     * URI. In cases where the arguments are parsed from the URI, the data
     * MUST be compatible with what PHP's parse_str() would return for
     * purposes of how duplicate query parameters are handled, and how nested
     * sets are handled.
     *
     * Setting query string arguments MUST NOT change the URI stored by the
     * request, nor the values in the server params.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated query string arguments.
     *
     * @param array $query Array of query string arguments, typically from
     *     $_GET.
     * @return static
     */
    public function withQueryParams(array $query);

    /**
     * Retrieve normalized file upload data.
     *
     * This method returns upload metadata in a normalized tree, with each leaf
     * an instance of Psr\Http\Message\UploadedFileInterface.
     *
     * These values MAY be prepared from $_FILES or the message body during
     * instantiation, or MAY be injected via withUploadedFiles().
     *
     * @return array An array tree of UploadedFileInterface instances; an empty
     *     array MUST be returned if no data is present.
     */
    public function getUploadedFiles();

    /**
     * Create a new instance with the specified uploaded files.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated body parameters.
     *
     * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
     * @return static
     * @throws \InvalidArgumentException if an invalid structure is provided.
     */
    public function withUploadedFiles(array $uploadedFiles);

    /**
     * Retrieve any parameters provided in the request body.
     *
     * If the request Content-Type is either application/x-www-form-urlencoded
     * or multipart/form-data, and the request method is POST, this method MUST
     * return the contents of $_POST.
     *
     * Otherwise, this method may return any results of deserializing
     * the request body content; as parsing returns structured content, the
     * potential types MUST be arrays or objects only. A null value indicates
     * the absence of body content.
     *
     * @return null|array|object The deserialized body parameters, if any.
     *     These will typically be an array or object.
     */
    public function getParsedBody();

    /**
     * Return an instance with the specified body parameters.
     *
     * These MAY be injected during instantiation.
     *
     * If the request Content-Type is either application/x-www-form-urlencoded
     * or multipart/form-data, and the request method is POST, use this method
     * ONLY to inject the contents of $_POST.
     *
     * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
     * deserializing the request body content. Deserialization/parsing returns
     * structured data, and, as such, this method ONLY accepts arrays or objects,
     * or a null value if nothing was available to parse.
     *
     * As an example, if content negotiation determines that the request data
     * is a JSON payload, this method could be used to create a request
     * instance with the deserialized parameters.
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated body parameters.
     *
     * @param null|array|object $data The deserialized body data. This will
     *     typically be in an array or object.
     * @return static
     * @throws \InvalidArgumentException if an unsupported argument type is
     *     provided.
     */
    public function withParsedBody($data);

    /**
     * Retrieve attributes derived from the request.
     *
     * The request "attributes" may be used to allow injection of any
     * parameters derived from the request: e.g., the results of path
     * match operations; the results of decrypting cookies; the results of
     * deserializing non-form-encoded message bodies; etc. Attributes
     * will be application and request specific, and CAN be mutable.
     *
     * @return array Attributes derived from the request.
     */
    public function getAttributes();

    /**
     * Retrieve a single derived request attribute.
     *
     * Retrieves a single derived request attribute as described in
     * getAttributes(). If the attribute has not been previously set, returns
     * the default value as provided.
     *
     * This method obviates the need for a hasAttribute() method, as it allows
     * specifying a default value to return if the attribute is not found.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @param mixed $default Default value to return if the attribute does not exist.
     * @return mixed
     */
    public function getAttribute($name, $default = null);

    /**
     * Return an instance with the specified derived request attribute.
     *
     * This method allows setting a single derived request attribute as
     * described in getAttributes().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that has the
     * updated attribute.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @param mixed $value The value of the attribute.
     * @return static
     */
    public function withAttribute($name, $value);

    /**
     * Return an instance that removes the specified derived request attribute.
     *
     * This method allows removing a single derived request attribute as
     * described in getAttributes().
     *
     * This method MUST be implemented in such a way as to retain the
     * immutability of the message, and MUST return an instance that removes
     * the attribute.
     *
     * @see getAttributes()
     * @param string $name The attribute name.
     * @return static
     */
    public function withoutAttribute($name);
}
<?php

namespace Psr\Http\Message;

/**
 * Describes a data stream.
 *
 * Typically, an instance will wrap a PHP stream; this interface provides
 * a wrapper around the most common operations, including serialization of
 * the entire stream to a string.
 */
interface StreamInterface
{
    /**
     * Reads all data from the stream into a string, from the beginning to end.
     *
     * This method MUST attempt to seek to the beginning of the stream before
     * reading data and read the stream until the end is reached.
     *
     * Warning: This could attempt to load a large amount of data into memory.
     *
     * This method MUST NOT raise an exception in order to conform with PHP's
     * string casting operations.
     *
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
     * @return string
     */
    public function __toString();

    /**
     * Closes the stream and any underlying resources.
     *
     * @return void
     */
    public function close();

    /**
     * Separates any underlying resources from the stream.
     *
     * After the stream has been detached, the stream is in an unusable state.
     *
     * @return resource|null Underlying PHP stream, if any
     */
    public function detach();

    /**
     * Get the size of the stream if known.
     *
     * @return int|null Returns the size in bytes if known, or null if unknown.
     */
    public function getSize();

    /**
     * Returns the current position of the file read/write pointer
     *
     * @return int Position of the file pointer
     * @throws \RuntimeException on error.
     */
    public function tell();

    /**
     * Returns true if the stream is at the end of the stream.
     *
     * @return bool
     */
    public function eof();

    /**
     * Returns whether or not the stream is seekable.
     *
     * @return bool
     */
    public function isSeekable();

    /**
     * Seek to a position in the stream.
     *
     * @link http://www.php.net/manual/en/function.fseek.php
     * @param int $offset Stream offset
     * @param int $whence Specifies how the cursor position will be calculated
     *     based on the seek offset. Valid values are identical to the built-in
     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
     *     offset bytes SEEK_CUR: Set position to current location plus offset
     *     SEEK_END: Set position to end-of-stream plus offset.
     * @throws \RuntimeException on failure.
     */
    public function seek($offset, $whence = SEEK_SET);

    /**
     * Seek to the beginning of the stream.
     *
     * If the stream is not seekable, this method will raise an exception;
     * otherwise, it will perform a seek(0).
     *
     * @see seek()
     * @link http://www.php.net/manual/en/function.fseek.php
     * @throws \RuntimeException on failure.
     */
    public function rewind();

    /**
     * Returns whether or not the stream is writable.
     *
     * @return bool
     */
    public function isWritable();

    /**
     * Write data to the stream.
     *
     * @param string $string The string that is to be written.
     * @return int Returns the number of bytes written to the stream.
     * @throws \RuntimeException on failure.
     */
    public function write($string);

    /**
     * Returns whether or not the stream is readable.
     *
     * @return bool
     */
    public function isReadable();

    /**
     * Read data from the stream.
     *
     * @param int $length Read up to $length bytes from the object and return
     *     them. Fewer than $length bytes may be returned if underlying stream
     *     call returns fewer bytes.
     * @return string Returns the data read from the stream, or an empty string
     *     if no bytes are available.
     * @throws \RuntimeException if an error occurs.
     */
    public function read($length);

    /**
     * Returns the remaining contents in a string
     *
     * @return string
     * @throws \RuntimeException if unable to read or an error occurs while
     *     reading.
     */
    public function getContents();

    /**
     * Get stream metadata as an associative array or retrieve a specific key.
     *
     * The keys returned are identical to the keys returned from PHP's
     * stream_get_meta_data() function.
     *
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
     * @param string $key Specific metadata to retrieve.
     * @return array|mixed|null Returns an associative array if no key is
     *     provided. Returns a specific key value if a key is provided and the
     *     value is found, or null if the key is not found.
     */
    public function getMetadata($key = null);
}
<?php

namespace Psr\Http\Message;

/**
 * Value object representing a file uploaded through an HTTP request.
 *
 * Instances of this interface are considered immutable; all methods that
 * might change state MUST be implemented such that they retain the internal
 * state of the current instance and return an instance that contains the
 * changed state.
 */
interface UploadedFileInterface
{
    /**
     * Retrieve a stream representing the uploaded file.
     *
     * This method MUST return a StreamInterface instance, representing the
     * uploaded file. The purpose of this method is to allow utilizing native PHP
     * stream functionality to manipulate the file upload, such as
     * stream_copy_to_stream() (though the result will need to be decorated in a
     * native PHP stream wrapper to work with such functions).
     *
     * If the moveTo() method has been called previously, this method MUST raise
     * an exception.
     *
     * @return StreamInterface Stream representation of the uploaded file.
     * @throws \RuntimeException in cases when no stream is available or can be
     *     created.
     */
    public function getStream();

    /**
     * Move the uploaded file to a new location.
     *
     * Use this method as an alternative to move_uploaded_file(). This method is
     * guaranteed to work in both SAPI and non-SAPI environments.
     * Implementations must determine which environment they are in, and use the
     * appropriate method (move_uploaded_file(), rename(), or a stream
     * operation) to perform the operation.
     *
     * $targetPath may be an absolute path, or a relative path. If it is a
     * relative path, resolution should be the same as used by PHP's rename()
     * function.
     *
     * The original file or stream MUST be removed on completion.
     *
     * If this method is called more than once, any subsequent calls MUST raise
     * an exception.
     *
     * When used in an SAPI environment where $_FILES is populated, when writing
     * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be
     * used to ensure permissions and upload status are verified correctly.
     *
     * If you wish to move to a stream, use getStream(), as SAPI operations
     * cannot guarantee writing to stream destinations.
     *
     * @see http://php.net/is_uploaded_file
     * @see http://php.net/move_uploaded_file
     * @param string $targetPath Path to which to move the uploaded file.
     * @throws \InvalidArgumentException if the $targetPath specified is invalid.
     * @throws \RuntimeException on any error during the move operation, or on
     *     the second or subsequent call to the method.
     */
    public function moveTo($targetPath);
    
    /**
     * Retrieve the file size.
     *
     * Implementations SHOULD return the value stored in the "size" key of
     * the file in the $_FILES array if available, as PHP calculates this based
     * on the actual size transmitted.
     *
     * @return int|null The file size in bytes or null if unknown.
     */
    public function getSize();
    
    /**
     * Retrieve the error associated with the uploaded file.
     *
     * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants.
     *
     * If the file was uploaded successfully, this method MUST return
     * UPLOAD_ERR_OK.
     *
     * Implementations SHOULD return the value stored in the "error" key of
     * the file in the $_FILES array.
     *
     * @see http://php.net/manual/en/features.file-upload.errors.php
     * @return int One of PHP's UPLOAD_ERR_XXX constants.
     */
    public function getError();
    
    /**
     * Retrieve the filename sent by the client.
     *
     * Do not trust the value returned by this method. A client could send
     * a malicious filename with the intention to corrupt or hack your
     * application.
     *
     * Implementations SHOULD return the value stored in the "name" key of
     * the file in the $_FILES array.
     *
     * @return string|null The filename sent by the client or null if none
     *     was provided.
     */
    public function getClientFilename();
    
    /**
     * Retrieve the media type sent by the client.
     *
     * Do not trust the value returned by this method. A client could send
     * a malicious media type with the intention to corrupt or hack your
     * application.
     *
     * Implementations SHOULD return the value stored in the "type" key of
     * the file in the $_FILES array.
     *
     * @return string|null The media type sent by the client or null if none
     *     was provided.
     */
    public function getClientMediaType();
}
<?php
namespace Psr\Http\Message;

/**
 * Value object representing a URI.
 *
 * This interface is meant to represent URIs according to RFC 3986 and to
 * provide methods for most common operations. Additional functionality for
 * working with URIs can be provided on top of the interface or externally.
 * Its primary use is for HTTP requests, but may also be used in other
 * contexts.
 *
 * Instances of this interface are considered immutable; all methods that
 * might change state MUST be implemented such that they retain the internal
 * state of the current instance and return an instance that contains the
 * changed state.
 *
 * Typically the Host header will be also be present in the request message.
 * For server-side requests, the scheme will typically be discoverable in the
 * server parameters.
 *
 * @link http://tools.ietf.org/html/rfc3986 (the URI specification)
 */
interface UriInterface
{
    /**
     * Retrieve the scheme component of the URI.
     *
     * If no scheme is present, this method MUST return an empty string.
     *
     * The value returned MUST be normalized to lowercase, per RFC 3986
     * Section 3.1.
     *
     * The trailing ":" character is not part of the scheme and MUST NOT be
     * added.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
     * @return string The URI scheme.
     */
    public function getScheme();

    /**
     * Retrieve the authority component of the URI.
     *
     * If no authority information is present, this method MUST return an empty
     * string.
     *
     * The authority syntax of the URI is:
     *
     * <pre>
     * [user-info@]host[:port]
     * </pre>
     *
     * If the port component is not set or is the standard port for the current
     * scheme, it SHOULD NOT be included.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
     * @return string The URI authority, in "[user-info@]host[:port]" format.
     */
    public function getAuthority();

    /**
     * Retrieve the user information component of the URI.
     *
     * If no user information is present, this method MUST return an empty
     * string.
     *
     * If a user is present in the URI, this will return that value;
     * additionally, if the password is also present, it will be appended to the
     * user value, with a colon (":") separating the values.
     *
     * The trailing "@" character is not part of the user information and MUST
     * NOT be added.
     *
     * @return string The URI user information, in "username[:password]" format.
     */
    public function getUserInfo();

    /**
     * Retrieve the host component of the URI.
     *
     * If no host is present, this method MUST return an empty string.
     *
     * The value returned MUST be normalized to lowercase, per RFC 3986
     * Section 3.2.2.
     *
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
     * @return string The URI host.
     */
    public function getHost();

    /**
     * Retrieve the port component of the URI.
     *
     * If a port is present, and it is non-standard for the current scheme,
     * this method MUST return it as an integer. If the port is the standard port
     * used with the current scheme, this method SHOULD return null.
     *
     * If no port is present, and no scheme is present, this method MUST return
     * a null value.
     *
     * If no port is present, but a scheme is present, this method MAY return
     * the standard port for that scheme, but SHOULD return null.
     *
     * @return null|int The URI port.
     */
    public function getPort();

    /**
     * Retrieve the path component of the URI.
     *
     * The path can either be empty or absolute (starting with a slash) or
     * rootless (not starting with a slash). Implementations MUST support all
     * three syntaxes.
     *
     * Normally, the empty path "" and absolute path "/" are considered equal as
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
     * do this normalization because in contexts with a trimmed base path, e.g.
     * the front controller, this difference becomes significant. It's the task
     * of the user to handle both "" and "/".
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.3.
     *
     * As an example, if the value should include a slash ("/") not intended as
     * delimiter between path segments, that value MUST be passed in encoded
     * form (e.g., "%2F") to the instance.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
     * @return string The URI path.
     */
    public function getPath();

    /**
     * Retrieve the query string of the URI.
     *
     * If no query string is present, this method MUST return an empty string.
     *
     * The leading "?" character is not part of the query and MUST NOT be
     * added.
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.4.
     *
     * As an example, if a value in a key/value pair of the query string should
     * include an ampersand ("&") not intended as a delimiter between values,
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
     * @return string The URI query string.
     */
    public function getQuery();

    /**
     * Retrieve the fragment component of the URI.
     *
     * If no fragment is present, this method MUST return an empty string.
     *
     * The leading "#" character is not part of the fragment and MUST NOT be
     * added.
     *
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
     * any characters. To determine what characters to encode, please refer to
     * RFC 3986, Sections 2 and 3.5.
     *
     * @see https://tools.ietf.org/html/rfc3986#section-2
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
     * @return string The URI fragment.
     */
    public function getFragment();

    /**
     * Return an instance with the specified scheme.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified scheme.
     *
     * Implementations MUST support the schemes "http" and "https" case
     * insensitively, and MAY accommodate other schemes if required.
     *
     * An empty scheme is equivalent to removing the scheme.
     *
     * @param string $scheme The scheme to use with the new instance.
     * @return static A new instance with the specified scheme.
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
     */
    public function withScheme($scheme);

    /**
     * Return an instance with the specified user information.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified user information.
     *
     * Password is optional, but the user information MUST include the
     * user; an empty string for the user is equivalent to removing user
     * information.
     *
     * @param string $user The user name to use for authority.
     * @param null|string $password The password associated with $user.
     * @return static A new instance with the specified user information.
     */
    public function withUserInfo($user, $password = null);

    /**
     * Return an instance with the specified host.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified host.
     *
     * An empty host value is equivalent to removing the host.
     *
     * @param string $host The hostname to use with the new instance.
     * @return static A new instance with the specified host.
     * @throws \InvalidArgumentException for invalid hostnames.
     */
    public function withHost($host);

    /**
     * Return an instance with the specified port.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified port.
     *
     * Implementations MUST raise an exception for ports outside the
     * established TCP and UDP port ranges.
     *
     * A null value provided for the port is equivalent to removing the port
     * information.
     *
     * @param null|int $port The port to use with the new instance; a null value
     *     removes the port information.
     * @return static A new instance with the specified port.
     * @throws \InvalidArgumentException for invalid ports.
     */
    public function withPort($port);

    /**
     * Return an instance with the specified path.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified path.
     *
     * The path can either be empty or absolute (starting with a slash) or
     * rootless (not starting with a slash). Implementations MUST support all
     * three syntaxes.
     *
     * If the path is intended to be domain-relative rather than path relative then
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
     * are assumed to be relative to some base path known to the application or
     * consumer.
     *
     * Users can provide both encoded and decoded path characters.
     * Implementations ensure the correct encoding as outlined in getPath().
     *
     * @param string $path The path to use with the new instance.
     * @return static A new instance with the specified path.
     * @throws \InvalidArgumentException for invalid paths.
     */
    public function withPath($path);

    /**
     * Return an instance with the specified query string.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified query string.
     *
     * Users can provide both encoded and decoded query characters.
     * Implementations ensure the correct encoding as outlined in getQuery().
     *
     * An empty query string value is equivalent to removing the query string.
     *
     * @param string $query The query string to use with the new instance.
     * @return static A new instance with the specified query string.
     * @throws \InvalidArgumentException for invalid query strings.
     */
    public function withQuery($query);

    /**
     * Return an instance with the specified URI fragment.
     *
     * This method MUST retain the state of the current instance, and return
     * an instance that contains the specified URI fragment.
     *
     * Users can provide both encoded and decoded fragment characters.
     * Implementations ensure the correct encoding as outlined in getFragment().
     *
     * An empty fragment value is equivalent to removing the fragment.
     *
     * @param string $fragment The fragment to use with the new instance.
     * @return static A new instance with the specified fragment.
     */
    public function withFragment($fragment);

    /**
     * Return the string representation as a URI reference.
     *
     * Depending on which components of the URI are present, the resulting
     * string is either a full URI or relative reference according to RFC 3986,
     * Section 4.1. The method concatenates the various components of the URI,
     * using the appropriate delimiters:
     *
     * - If a scheme is present, it MUST be suffixed by ":".
     * - If an authority is present, it MUST be prefixed by "//".
     * - The path can be concatenated without delimiters. But there are two
     *   cases where the path has to be adjusted to make the URI reference
     *   valid as PHP does not allow to throw an exception in __toString():
     *     - If the path is rootless and an authority is present, the path MUST
     *       be prefixed by "/".
     *     - If the path is starting with more than one "/" and no authority is
     *       present, the starting slashes MUST be reduced to one.
     * - If a query is present, it MUST be prefixed by "?".
     * - If a fragment is present, it MUST be prefixed by "#".
     *
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
     * @return string
     */
    public function __toString();
}
vendor
{
    "name": "psr/log",
    "description": "Common interface for logging libraries",
    "keywords": ["psr", "psr-3", "log"],
    "homepage": "https://github.com/php-fig/log",
    "license": "MIT",
    "authors": [
        {
            "name": "PHP-FIG",
            "homepage": "http://www.php-fig.org/"
        }
    ],
    "require": {
        "php": ">=5.3.0"
    },
    "autoload": {
        "psr-4": {
            "Psr\\Log\\": "Psr/Log/"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0.x-dev"
        }
    }
}
Copyright (c) 2012 PHP Framework Interoperability Group

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.
<?php

namespace Psr\Log;

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
abstract class AbstractLogger implements LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
}
<?php

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance.
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object.
     *
     * @param LoggerInterface $logger
     *
     * @return void
     */
    public function setLogger(LoggerInterface $logger);
}
<?php

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     *
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * Sets a logger.
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}
<?php

namespace Psr\Log;

/**
 * Describes a logger instance.
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data. The only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array());

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array());

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array());

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array());

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array());

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array());

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array());

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array());
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger trait that classes unable to extend AbstractLogger
 * (because they extend another class, etc) can include.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
trait LoggerTrait
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    abstract public function log($level, $message, array $context = array());
}
<?php

namespace Psr\Log;

/**
 * Describes log levels.
 */
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
<?php

namespace Psr\Log;

/**
 * This Logger can be used to avoid conditional log calls.
 *
 * Logging should always be optional, and if no logger is provided to your
 * library creating a NullLogger instance to have something to throw logs at
 * is a good way to avoid littering your code with `if ($this->logger) { }`
 * blocks.
 */
class NullLogger extends AbstractLogger
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array())
    {
        // noop
    }
}
<?php

namespace Psr\Log\Test;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * Provides a base test class for ensuring compliance with the LoggerInterface.
 *
 * Implementors can extend the class and implement abstract methods to run this
 * as part of their test suite.
 */
abstract class LoggerInterfaceTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @return LoggerInterface
     */
    abstract public function getLogger();

    /**
     * This must return the log messages in order.
     *
     * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>".
     *
     * Example ->error('Foo') would yield "error Foo".
     *
     * @return string[]
     */
    abstract public function getLogs();

    public function testImplements()
    {
        $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
    }

    /**
     * @dataProvider provideLevelsAndMessages
     */
    public function testLogsAtAllLevels($level, $message)
    {
        $logger = $this->getLogger();
        $logger->{$level}($message, array('user' => 'Bob'));
        $logger->log($level, $message, array('user' => 'Bob'));

        $expected = array(
            $level.' message of level '.$level.' with context: Bob',
            $level.' message of level '.$level.' with context: Bob',
        );
        $this->assertEquals($expected, $this->getLogs());
    }

    public function provideLevelsAndMessages()
    {
        return array(
            LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
            LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
            LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
            LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
            LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
            LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
            LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
            LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
        );
    }

    /**
     * @expectedException \Psr\Log\InvalidArgumentException
     */
    public function testThrowsOnInvalidLevel()
    {
        $logger = $this->getLogger();
        $logger->log('invalid level', 'Foo');
    }

    public function testContextReplacement()
    {
        $logger = $this->getLogger();
        $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));

        $expected = array('info {Message {nothing} Bob Bar a}');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testObjectCastToString()
    {
        if (method_exists($this, 'createPartialMock')) {
            $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString'));
        } else {
            $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
        }
        $dummy->expects($this->once())
            ->method('__toString')
            ->will($this->returnValue('DUMMY'));

        $this->getLogger()->warning($dummy);

        $expected = array('warning DUMMY');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testContextCanContainAnything()
    {
        $context = array(
            'bool' => true,
            'null' => null,
            'string' => 'Foo',
            'int' => 0,
            'float' => 0.5,
            'nested' => array('with object' => new DummyTest),
            'object' => new \DateTime,
            'resource' => fopen('php://memory', 'r'),
        );

        $this->getLogger()->warning('Crazy context data', $context);

        $expected = array('warning Crazy context data');
        $this->assertEquals($expected, $this->getLogs());
    }

    public function testContextExceptionKeyCanBeExceptionOrOtherValues()
    {
        $logger = $this->getLogger();
        $logger->warning('Random message', array('exception' => 'oops'));
        $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));

        $expected = array(
            'warning Random message',
            'critical Uncaught Exception!'
        );
        $this->assertEquals($expected, $this->getLogs());
    }
}

class DummyTest
{
    public function __toString()
    {
    }
}
PSR Log
=======

This repository holds all interfaces/classes/traits related to
[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).

Note that this is not a logger of its own. It is merely an interface that
describes a logger. See the specification for more details.

Usage
-----

If you need a logger, you can use the interface like this:

```php
<?php

use Psr\Log\LoggerInterface;

class Foo
{
    private $logger;

    public function __construct(LoggerInterface $logger = null)
    {
        $this->logger = $logger;
    }

    public function doSomething()
    {
        if ($this->logger) {
            $this->logger->info('Doing work');
        }

        // do something useful
    }
}
```

You can then pick one of the implementations of the interface to get a logger.

If you want to implement the interface, you can require this package and
implement `Psr\Log\LoggerInterface` in your code. Please read the
[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
for details.
# Unit tests
tests/temp
tests/.sabredav
tests/cov

# Custom settings for tests
tests/config.user.php

# ViM
*.swp

# Composer
composer.lock
vendor

# Composer binaries
bin/phing
bin/phpunit
bin/vobject
bin/generate_vcards
bin/phpdocmd
bin/phpunit
bin/php-cs-fixer
bin/sabre-cs-fixer

# Assuming every .php file in the root is for testing
/*.php

# Other testing stuff
/tmpdata
/data
/public

# Build
build
build.properties

# Docs
docs/api
docs/wikidocs

# Mac
.DS_Store
language: php
php:
  - 5.5
  - 5.6
  - 7.0
  - 7.1


env:
  matrix:
    - LOWEST_DEPS="" TEST_DEPS=""
    - LOWEST_DEPS="--prefer-lowest" TEST_DEPS="tests/Sabre/"

services:
  - mysql
  - postgresql

sudo: false

before_script:
  - mysql -e 'create database sabredav_test'
  - psql -c "create database sabredav_test" -U postgres
  - psql -c "create user sabredav with PASSWORD 'sabredav';GRANT ALL PRIVILEGES ON DATABASE sabredav_test TO sabredav" -U postgres
  #  - composer self-update
  - composer update --prefer-dist $LOWEST_DEPS

# addons:
#    postgresql: "9.5"

script:
  - ./bin/phpunit --configuration tests/phpunit.xml.dist $TEST_DEPS
  - ./bin/sabre-cs-fixer fix . --dry-run --diff

cache:
  directories:
    - $HOME/.composer/cache
#!/usr/bin/env php
<?php

$tasks = [

    'buildzip' => [
        'init', 'test', 'clean',
    ],
    'markrelease' => [
        'init', 'test', 'clean',
    ],
    'clean' => [],
    'test'  => [
        'composerupdate',
    ],
    'init'           => [],
    'composerupdate' => [],
 ];

$default = 'buildzip';

$baseDir = __DIR__ . '/../';
chdir($baseDir);

$currentTask = $default;
if ($argc > 1) $currentTask = $argv[1];
$version = null;
if ($argc > 2) $version = $argv[2];

if (!isset($tasks[$currentTask])) {
    echo "Task not found: ",  $currentTask, "\n";
    die(1);
}

// Creating the dependency graph
$newTaskList = [];
$oldTaskList = [$currentTask => true];

while (count($oldTaskList) > 0) {

    foreach ($oldTaskList as $task => $foo) {

        if (!isset($tasks[$task])) {
            echo "Dependency not found: " . $task, "\n";
            die(1);
        }
        $dependencies = $tasks[$task];

        $fullFilled = true;
        foreach ($dependencies as $dependency) {
            if (isset($newTaskList[$dependency])) {
                // Already in the fulfilled task list.
                continue;
            } else {
                $oldTaskList[$dependency] = true;
                $fullFilled = false;
            }
           
        }
        if ($fullFilled) {
            unset($oldTaskList[$task]);
            $newTaskList[$task] = 1;
        }

    }

}

foreach (array_keys($newTaskList) as $task) {

    echo "task: " . $task, "\n";
    call_user_func($task);
    echo "\n";

}

function init() {

    global $version;
    if (!$version) {
        include __DIR__ . '/../vendor/autoload.php';
        $version = Sabre\DAV\Version::VERSION;
    }

    echo "  Building sabre/dav " . $version, "\n";

}

function clean() {

    global $baseDir;
    echo "  Removing build files\n";
    $outputDir = $baseDir . '/build/SabreDAV';
    if (is_dir($outputDir)) {
        system('rm -r ' . $baseDir . '/build/SabreDAV');
    }

}

function composerupdate() {

    global $baseDir;
    echo "  Updating composer packages to latest version\n\n";
    system('cd ' . $baseDir . '; composer update');
}

function test() {

    global $baseDir;

    echo "  Running all unittests.\n";
    echo "  This may take a while.\n\n";
    system(__DIR__ . '/phpunit --configuration ' . $baseDir . '/tests/phpunit.xml.dist --stop-on-failure', $code);
    if ($code != 0) {
        echo "PHPUnit reported error code $code\n";
        die(1);
    }
   
}

function buildzip() {

    global $baseDir, $version;
    echo "  Generating composer.json\n";

    $input = json_decode(file_get_contents(__DIR__ . '/../composer.json'), true);
    $newComposer = [
        "require" => $input['require'],
        "config"  => [
            "bin-dir" => "./bin",
        ],
        "prefer-stable"     => true,
        "minimum-stability" => "alpha",
    ];
    unset(
        $newComposer['require']['sabre/vobject'],
        $newComposer['require']['sabre/http'],
        $newComposer['require']['sabre/uri'],
        $newComposer['require']['sabre/event']
    );
    $newComposer['require']['sabre/dav'] = $version;
    mkdir('build/SabreDAV');
    file_put_contents('build/SabreDAV/composer.json', json_encode($newComposer, JSON_PRETTY_PRINT));

    echo "  Downloading dependencies\n";
    system("cd build/SabreDAV; composer install -n", $code);
    if ($code !== 0) {
        echo "Composer reported error code $code\n";
        die(1);
    }

    echo "  Removing pointless files\n";
    unlink('build/SabreDAV/composer.json');
    unlink('build/SabreDAV/composer.lock');

    echo "  Moving important files to the root of the project\n";

    $fileNames = [
        'CHANGELOG.md',
        'LICENSE',
        'README.md',
        'examples',
    ];
    foreach ($fileNames as $fileName) {
        echo "    $fileName\n";
        rename('build/SabreDAV/vendor/sabre/dav/' . $fileName, 'build/SabreDAV/' . $fileName);
    }

    // <zip destfile="build/SabreDAV-${sabredav.version}.zip" basedir="build/SabreDAV" prefix="SabreDAV/" />

    echo "\n";
    echo "Zipping the sabredav distribution\n\n";
    system('cd build; zip -qr sabredav-' . $version . '.zip SabreDAV');

    echo "Done.";

}
#!/usr/bin/env python
#
# Copyright 2006, 2007 Google Inc. All Rights Reserved.
# Author: danderson@google.com (David Anderson)
#
# Script for uploading files to a Google Code project.
#
# This is intended to be both a useful script for people who want to
# streamline project uploads and a reference implementation for
# uploading files to Google Code projects.
#
# To upload a file to Google Code, you need to provide a path to the
# file on your local machine, a small summary of what the file is, a
# project name, and a valid account that is a member or owner of that
# project.  You can optionally provide a list of labels that apply to
# the file.  The file will be uploaded under the same name that it has
# in your local filesystem (that is, the "basename" or last path
# component).  Run the script with '--help' to get the exact syntax
# and available options.
#
# Note that the upload script requests that you enter your
# googlecode.com password.  This is NOT your Gmail account password!
# This is the password you use on googlecode.com for committing to
# Subversion and uploading files.  You can find your password by going
# to http://code.google.com/hosting/settings when logged in with your
# Gmail account. If you have already committed to your project's
# Subversion repository, the script will automatically retrieve your
# credentials from there (unless disabled, see the output of '--help'
# for details).
#
# If you are looking at this script as a reference for implementing
# your own Google Code file uploader, then you should take a look at
# the upload() function, which is the meat of the uploader.  You
# basically need to build a multipart/form-data POST request with the
# right fields and send it to https://PROJECT.googlecode.com/files .
# Authenticate the request using HTTP Basic authentication, as is
# shown below.
#
# Licensed under the terms of the Apache Software License 2.0:
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Questions, comments, feature requests and patches are most welcome.
# Please direct all of these to the Google Code users group:
#  http://groups.google.com/group/google-code-hosting

"""Google Code file uploader script.
"""

__author__ = 'danderson@google.com (David Anderson)'

import httplib
import os.path
import optparse
import getpass
import base64
import sys


def upload(file, project_name, user_name, password, summary, labels=None):
  """Upload a file to a Google Code project's file server.

  Args:
    file: The local path to the file.
    project_name: The name of your project on Google Code.
    user_name: Your Google account name.
    password: The googlecode.com password for your account.
              Note that this is NOT your global Google Account password!
    summary: A small description for the file.
    labels: an optional list of label strings with which to tag the file.

  Returns: a tuple:
    http_status: 201 if the upload succeeded, something else if an
                 error occurred.
    http_reason: The human-readable string associated with http_status
    file_url: If the upload succeeded, the URL of the file on Google
              Code, None otherwise.
  """
  # The login is the user part of user@gmail.com. If the login provided
  # is in the full user@domain form, strip it down.
  if user_name.endswith('@gmail.com'):
    user_name = user_name[:user_name.index('@gmail.com')]

  form_fields = [('summary', summary)]
  if labels is not None:
    form_fields.extend([('label', l.strip()) for l in labels])

  content_type, body = encode_upload_request(form_fields, file)

  upload_host = '%s.googlecode.com' % project_name
  upload_uri = '/files'
  auth_token = base64.b64encode('%s:%s'% (user_name, password))
  headers = {
    'Authorization': 'Basic %s' % auth_token,
    'User-Agent': 'Googlecode.com uploader v0.9.4',
    'Content-Type': content_type,
    }

  server = httplib.HTTPSConnection(upload_host)
  server.request('POST', upload_uri, body, headers)
  resp = server.getresponse()
  server.close()

  if resp.status == 201:
    location = resp.getheader('Location', None)
  else:
    location = None
  return resp.status, resp.reason, location


def encode_upload_request(fields, file_path):
  """Encode the given fields and file into a multipart form body.

  fields is a sequence of (name, value) pairs. file is the path of
  the file to upload. The file will be uploaded to Google Code with
  the same file name.

  Returns: (content_type, body) ready for httplib.HTTP instance
  """
  BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla'
  CRLF = '\r\n'

  body = []

  # Add the metadata about the upload first
  for key, value in fields:
    body.extend(
      ['--' + BOUNDARY,
       'Content-Disposition: form-data; name="%s"' % key,
       '',
       value,
       ])

  # Now add the file itself
  file_name = os.path.basename(file_path)
  f = open(file_path, 'rb')
  file_content = f.read()
  f.close()

  body.extend(
    ['--' + BOUNDARY,
     'Content-Disposition: form-data; name="filename"; filename="%s"'
     % file_name,
     # The upload server determines the mime-type, no need to set it.
     'Content-Type: application/octet-stream',
     '',
     file_content,
     ])

  # Finalize the form body
  body.extend(['--' + BOUNDARY + '--', ''])

  return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body)


def upload_find_auth(file_path, project_name, summary, labels=None,
                     user_name=None, password=None, tries=3):
  """Find credentials and upload a file to a Google Code project's file server.

  file_path, project_name, summary, and labels are passed as-is to upload.

  Args:
    file_path: The local path to the file.
    project_name: The name of your project on Google Code.
    summary: A small description for the file.
    labels: an optional list of label strings with which to tag the file.
    config_dir: Path to Subversion configuration directory, 'none', or None.
    user_name: Your Google account name.
    tries: How many attempts to make.
  """

  while tries > 0:
    if user_name is None:
      # Read username if not specified or loaded from svn config, or on
      # subsequent tries.
      sys.stdout.write('Please enter your googlecode.com username: ')
      sys.stdout.flush()
      user_name = sys.stdin.readline().rstrip()
    if password is None:
      # Read password if not loaded from svn config, or on subsequent tries.
      print 'Please enter your googlecode.com password.'
      print '** Note that this is NOT your Gmail account password! **'
      print 'It is the password you use to access Subversion repositories,'
      print 'and can be found here: http://code.google.com/hosting/settings'
      password = getpass.getpass()

    status, reason, url = upload(file_path, project_name, user_name, password,
                                 summary, labels)
    # Returns 403 Forbidden instead of 401 Unauthorized for bad
    # credentials as of 2007-07-17.
    if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]:
      # Rest for another try.
      user_name = password = None
      tries = tries - 1
    else:
      # We're done.
      break

  return status, reason, url


def main():
  parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY '
                                 '-p PROJECT [options] FILE')
  parser.add_option('-s', '--summary', dest='summary',
                    help='Short description of the file')
  parser.add_option('-p', '--project', dest='project',
                    help='Google Code project name')
  parser.add_option('-u', '--user', dest='user',
                    help='Your Google Code username')
  parser.add_option('-w', '--password', dest='password',
                    help='Your Google Code password')
  parser.add_option('-l', '--labels', dest='labels',
                    help='An optional list of comma-separated labels to attach '
                    'to the file')

  options, args = parser.parse_args()

  if not options.summary:
    parser.error('File summary is missing.')
  elif not options.project:
    parser.error('Project name is missing.')
  elif len(args) < 1:
    parser.error('File to upload not provided.')
  elif len(args) > 1:
    parser.error('Only one file may be specified.')

  file_path = args[0]

  if options.labels:
    labels = options.labels.split(',')
  else:
    labels = None

  status, reason, url = upload_find_auth(file_path, options.project,
                                         options.summary, labels,
                                         options.user, options.password)
  if url:
    print 'The file was uploaded successfully.'
    print 'URL: %s' % url
    return 0
  else:
    print 'An error occurred. Your file was not uploaded.'
    print 'Google Code upload server said: %s (%s)' % (reason, status)
    return 1


if __name__ == '__main__':
  sys.exit(main())
#!/usr/bin/env php
<?php

echo "SabreDAV migrate script for version 2.0\n";

if ($argc < 2) {

    echo <<<HELLO

This script help you migrate from a pre-2.0 database to 2.0 and later

The 'calendars', 'addressbooks' and 'cards' tables will be upgraded, and new
tables (calendarchanges, addressbookchanges, propertystorage) will be added.

If you don't use the default PDO CalDAV or CardDAV backend, it's pointless to
run this script.

Keep in mind that ALTER TABLE commands will be executed. If you have a large
dataset this may mean that this process takes a while.

Lastly: Make a back-up first. This script has been tested, but the amount of
potential variants are extremely high, so it's impossible to deal with every
possible situation.

In the worst case, you will lose all your data. This is not an overstatement.

Usage:

php {$argv[0]} [pdo-dsn] [username] [password]

For example:

php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
php {$argv[0]} sqlite:data/sabredav.db

HELLO;

    exit();

}

// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

$dsn = $argv[1];
$user = isset($argv[2]) ? $argv[2] : null;
$pass = isset($argv[3]) ? $argv[3] : null;

echo "Connecting to database: " . $dsn . "\n";

$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);

switch ($driver) {

    case 'mysql' :
        echo "Detected MySQL.\n";
        break;
    case 'sqlite' :
        echo "Detected SQLite.\n";
        break;
    default :
        echo "Error: unsupported driver: " . $driver . "\n";
        die(-1);
}

foreach (['calendar', 'addressbook'] as $itemType) {

    $tableName = $itemType . 's';
    $tableNameOld = $tableName . '_old';
    $changesTable = $itemType . 'changes';

    echo "Upgrading '$tableName'\n";

    // The only cross-db way to do this, is to just fetch a single record.
    $row = $pdo->query("SELECT * FROM $tableName LIMIT 1")->fetch();

    if (!$row) {

        echo "No records were found in the '$tableName' table.\n";
        echo "\n";
        echo "We're going to rename the old table to $tableNameOld (just in case).\n";
        echo "and re-create the new table.\n";

        switch ($driver) {

            case 'mysql' :
                $pdo->exec("RENAME TABLE $tableName TO $tableNameOld");
                switch ($itemType) {
                    case 'calendar' :
                        $pdo->exec("
            CREATE TABLE calendars (
                id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
                principaluri VARCHAR(100),
                displayname VARCHAR(100),
                uri VARCHAR(200),
                synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
                description TEXT,
                calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
                calendarcolor VARCHAR(10),
                timezone TEXT,
                components VARCHAR(20),
                transparent TINYINT(1) NOT NULL DEFAULT '0',
                UNIQUE(principaluri, uri)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
                        ");
                        break;
                    case 'addressbook' :
                        $pdo->exec("
            CREATE TABLE addressbooks (
                id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
                principaluri VARCHAR(255),
                displayname VARCHAR(255),
                uri VARCHAR(200),
                description TEXT,
                synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1',
                UNIQUE(principaluri, uri)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
                        ");
                        break;
                }
                break;

            case 'sqlite' :

                $pdo->exec("ALTER TABLE $tableName RENAME TO $tableNameOld");

                switch ($itemType) {
                    case 'calendar' :
                        $pdo->exec("
            CREATE TABLE calendars (
                id integer primary key asc,
                principaluri text,
                displayname text,
                uri text,
                synctoken integer,
                description text,
                calendarorder integer,
                calendarcolor text,
                timezone text,
                components text,
                transparent bool
            );
                        ");
                        break;
                    case 'addressbook' :
                        $pdo->exec("
            CREATE TABLE addressbooks (
                id integer primary key asc,
                principaluri text,
                displayname text,
                uri text,
                description text,
                synctoken integer
            );
                        ");

                        break;
                }
                break;

        }
        echo "Creation of 2.0 $tableName table is complete\n";

    } else {

        // Checking if there's a synctoken field already.
        if (array_key_exists('synctoken', $row)) {
            echo "The 'synctoken' field already exists in the $tableName table.\n";
            echo "It's likely you already upgraded, so we're simply leaving\n";
            echo "the $tableName table alone\n";
        } else {

            echo "1.8 table schema detected\n";
            switch ($driver) {

                case 'mysql' :
                    $pdo->exec("ALTER TABLE $tableName ADD synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1'");
                    $pdo->exec("ALTER TABLE $tableName DROP ctag");
                    $pdo->exec("UPDATE $tableName SET synctoken = '1'");
                    break;
                case 'sqlite' :
                    $pdo->exec("ALTER TABLE $tableName ADD synctoken integer");
                    $pdo->exec("UPDATE $tableName SET synctoken = '1'");
                    echo "Note: there's no easy way to remove fields in sqlite.\n";
                    echo "The ctag field is no longer used, but it's kept in place\n";
                    break;

            }

            echo "Upgraded '$tableName' to 2.0 schema.\n";

        }

    }

    try {
        $pdo->query("SELECT * FROM $changesTable LIMIT 1");

        echo "'$changesTable' already exists. Assuming that this part of the\n";
        echo "upgrade was already completed.\n";

    } catch (Exception $e) {
        echo "Creating '$changesTable' table.\n";

        switch ($driver) {

            case 'mysql' :
                $pdo->exec("
    CREATE TABLE $changesTable (
        id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
        uri VARCHAR(200) NOT NULL,
        synctoken INT(11) UNSIGNED NOT NULL,
        {$itemType}id INT(11) UNSIGNED NOT NULL,
        operation TINYINT(1) NOT NULL,
        INDEX {$itemType}id_synctoken ({$itemType}id, synctoken)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

                ");
                break;
            case 'sqlite' :
                $pdo->exec("

    CREATE TABLE $changesTable (
        id integer primary key asc,
        uri text,
        synctoken integer,
        {$itemType}id integer,
        operation bool
    );

                ");
                $pdo->exec("CREATE INDEX {$itemType}id_synctoken ON $changesTable ({$itemType}id, synctoken);");
                break;

        }

    }

}

try {
    $pdo->query("SELECT * FROM calendarsubscriptions LIMIT 1");

    echo "'calendarsubscriptions' already exists. Assuming that this part of the\n";
    echo "upgrade was already completed.\n";

} catch (Exception $e) {
    echo "Creating calendarsubscriptions table.\n";

    switch ($driver) {

        case 'mysql' :
            $pdo->exec("
CREATE TABLE calendarsubscriptions (
    id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    uri VARCHAR(200) NOT NULL,
    principaluri VARCHAR(100) NOT NULL,
    source TEXT,
    displayname VARCHAR(100),
    refreshrate VARCHAR(10),
    calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
    calendarcolor VARCHAR(10),
    striptodos TINYINT(1) NULL,
    stripalarms TINYINT(1) NULL,
    stripattachments TINYINT(1) NULL,
    lastmodified INT(11) UNSIGNED,
    UNIQUE(principaluri, uri)
);
            ");
            break;
        case 'sqlite' :
            $pdo->exec("

CREATE TABLE calendarsubscriptions (
    id integer primary key asc,
    uri text,
    principaluri text,
    source text,
    displayname text,
    refreshrate text,
    calendarorder integer,
    calendarcolor text,
    striptodos bool,
    stripalarms bool,
    stripattachments bool,
    lastmodified int
);
            ");

            $pdo->exec("CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);");
            break;

    }

}

try {
    $pdo->query("SELECT * FROM propertystorage LIMIT 1");

    echo "'propertystorage' already exists. Assuming that this part of the\n";
    echo "upgrade was already completed.\n";

} catch (Exception $e) {
    echo "Creating propertystorage table.\n";

    switch ($driver) {

        case 'mysql' :
            $pdo->exec("
CREATE TABLE propertystorage (
    id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    path VARBINARY(1024) NOT NULL,
    name VARBINARY(100) NOT NULL,
    value MEDIUMBLOB
);
            ");
            $pdo->exec("
CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100));
            ");
            break;
        case 'sqlite' :
            $pdo->exec("
CREATE TABLE propertystorage (
    id integer primary key asc,
    path TEXT,
    name TEXT,
    value TEXT
);
            ");
            $pdo->exec("
CREATE UNIQUE INDEX path_property ON propertystorage (path, name);
            ");

            break;

    }

}

echo "Upgrading cards table to 2.0 schema\n";

try {

    $create = false;
    $row = $pdo->query("SELECT * FROM cards LIMIT 1")->fetch();
    if (!$row) {
        $random = mt_rand(1000, 9999);
        echo "There was no data in the cards table, so we're re-creating it\n";
        echo "The old table will be renamed to cards_old$random, just in case.\n";

        $create = true;

        switch ($driver) {
            case 'mysql' :
                $pdo->exec("RENAME TABLE cards TO cards_old$random");
                break;
            case 'sqlite' :
                $pdo->exec("ALTER TABLE cards RENAME TO cards_old$random");
                break;

        }
    }

} catch (Exception $e) {

    echo "Exception while checking cards table. Assuming that the table does not yet exist.\n";
    echo "Debug: ", $e->getMessage(), "\n";
    $create = true;

}

if ($create) {
    switch ($driver) {
        case 'mysql' :
            $pdo->exec("
CREATE TABLE cards (
    id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    addressbookid INT(11) UNSIGNED NOT NULL,
    carddata MEDIUMBLOB,
    uri VARCHAR(200),
    lastmodified INT(11) UNSIGNED,
    etag VARBINARY(32),
    size INT(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

            ");
            break;

        case 'sqlite' :

            $pdo->exec("
CREATE TABLE cards (
    id integer primary key asc,
    addressbookid integer,
    carddata blob,
    uri text,
    lastmodified integer,
    etag text,
    size integer
);
            ");
            break;

    }
} else {
    switch ($driver) {
        case 'mysql' :
            $pdo->exec("
                ALTER TABLE cards
                ADD etag VARBINARY(32),
                ADD size INT(11) UNSIGNED NOT NULL;
            ");
            break;

        case 'sqlite' :

            $pdo->exec("
                ALTER TABLE cards ADD etag text;
                ALTER TABLE cards ADD size integer;
            ");
            break;

    }
    echo "Reading all old vcards and populating etag and size fields.\n";
    $result = $pdo->query('SELECT id, carddata FROM cards');
    $stmt = $pdo->prepare('UPDATE cards SET etag = ?, size = ? WHERE id = ?');
    while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {
        $stmt->execute([
            md5($row['carddata']),
            strlen($row['carddata']),
            $row['id']
        ]);
    }


}

echo "Upgrade to 2.0 schema completed.\n";
#!/usr/bin/env php
<?php

echo "SabreDAV migrate script for version 2.1\n";

if ($argc < 2) {

    echo <<<HELLO

This script help you migrate from a pre-2.1 database to 2.1.

Changes:
  The 'calendarobjects' table will be upgraded.
  'schedulingobjects' will be created.

If you don't use the default PDO CalDAV or CardDAV backend, it's pointless to
run this script.

Keep in mind that ALTER TABLE commands will be executed. If you have a large
dataset this may mean that this process takes a while.

Lastly: Make a back-up first. This script has been tested, but the amount of
potential variants are extremely high, so it's impossible to deal with every
possible situation.

In the worst case, you will lose all your data. This is not an overstatement.

Usage:

php {$argv[0]} [pdo-dsn] [username] [password]

For example:

php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
php {$argv[0]} sqlite:data/sabredav.db

HELLO;

    exit();

}

// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

$dsn = $argv[1];
$user = isset($argv[2]) ? $argv[2] : null;
$pass = isset($argv[3]) ? $argv[3] : null;

echo "Connecting to database: " . $dsn . "\n";

$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);

switch ($driver) {

    case 'mysql' :
        echo "Detected MySQL.\n";
        break;
    case 'sqlite' :
        echo "Detected SQLite.\n";
        break;
    default :
        echo "Error: unsupported driver: " . $driver . "\n";
        die(-1);
}

echo "Upgrading 'calendarobjects'\n";
$addUid = false;
try {
    $result = $pdo->query('SELECT * FROM calendarobjects LIMIT 1');
    $row = $result->fetch(\PDO::FETCH_ASSOC);

    if (!$row) {
        echo "No data in table. Going to try to add the uid field anyway.\n";
        $addUid = true;
    } elseif (array_key_exists('uid', $row)) {
        echo "uid field exists. Assuming that this part of the migration has\n";
        echo "Already been completed.\n";
    } else {
        echo "2.0 schema detected.\n";
        $addUid = true;
    }

} catch (Exception $e) {
    echo "Could not find a calendarobjects table. Skipping this part of the\n";
    echo "upgrade.\n";
}

if ($addUid) {

    switch ($driver) {
        case 'mysql' :
            $pdo->exec('ALTER TABLE calendarobjects ADD uid VARCHAR(200)');
            break;
        case 'sqlite' :
            $pdo->exec('ALTER TABLE calendarobjects ADD uid TEXT');
            break;
    }

    $result = $pdo->query('SELECT id, calendardata FROM calendarobjects');
    $stmt = $pdo->prepare('UPDATE calendarobjects SET uid = ? WHERE id = ?');
    $counter = 0;

    while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {

        try {
            $vobj = \Sabre\VObject\Reader::read($row['calendardata']);
        } catch (\Exception $e) {
            echo "Warning! Item with id $row[id] could not be parsed!\n";
            continue;
        }
        $uid = null;
        $item = $vobj->getBaseComponent();
        if (!isset($item->UID)) {
            echo "Warning! Item with id $item[id] does NOT have a UID property and this is required.\n";
            continue;
        }
        $uid = (string)$item->UID;
        $stmt->execute([$uid, $row['id']]);
        $counter++;

    }

}

echo "Creating 'schedulingobjects'\n";

switch ($driver) {

    case 'mysql' :
        $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects
(
    id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    principaluri VARCHAR(255),
    calendardata MEDIUMBLOB,
    uri VARCHAR(200),
    lastmodified INT(11) UNSIGNED,
    etag VARCHAR(32),
    size INT(11) UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
        ');
        break;


    case 'sqlite' :
        $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects (
    id integer primary key asc,
    principaluri text,
    calendardata blob,
    uri text,
    lastmodified integer,
    etag text,
    size integer
)
');
        break;
}

echo "Done.\n";

echo "Upgrade to 2.1 schema completed.\n";
#!/usr/bin/env php
<?php

echo "SabreDAV migrate script for version 3.0\n";

if ($argc < 2) {

    echo <<<HELLO

This script help you migrate from a pre-3.0 database to 3.0 and later

Changes:
  * The propertystorage table has changed to allow storage of complex
    properties.
  * the vcardurl field in the principals table is no more. This was moved to
    the propertystorage table.

Keep in mind that ALTER TABLE commands will be executed. If you have a large
dataset this may mean that this process takes a while.

Lastly: Make a back-up first. This script has been tested, but the amount of
potential variants are extremely high, so it's impossible to deal with every
possible situation.

In the worst case, you will lose all your data. This is not an overstatement.

Usage:

php {$argv[0]} [pdo-dsn] [username] [password]

For example:

php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
php {$argv[0]} sqlite:data/sabredav.db

HELLO;

    exit();

}

// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

$dsn = $argv[1];
$user = isset($argv[2]) ? $argv[2] : null;
$pass = isset($argv[3]) ? $argv[3] : null;

echo "Connecting to database: " . $dsn . "\n";

$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);

switch ($driver) {

    case 'mysql' :
        echo "Detected MySQL.\n";
        break;
    case 'sqlite' :
        echo "Detected SQLite.\n";
        break;
    default :
        echo "Error: unsupported driver: " . $driver . "\n";
        die(-1);
}

echo "Upgrading 'propertystorage'\n";
$addValueType = false;
try {
    $result = $pdo->query('SELECT * FROM propertystorage LIMIT 1');
    $row = $result->fetch(\PDO::FETCH_ASSOC);

    if (!$row) {
        echo "No data in table. Going to re-create the table.\n";
        $random = mt_rand(1000, 9999);
        echo "Renaming propertystorage -> propertystorage_old$random and creating new table.\n";

        switch ($driver) {

            case 'mysql' :
                $pdo->exec('RENAME TABLE propertystorage TO propertystorage_old' . $random);
                $pdo->exec('
    CREATE TABLE propertystorage (
        id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
        path VARBINARY(1024) NOT NULL,
        name VARBINARY(100) NOT NULL,
        valuetype INT UNSIGNED,
        value MEDIUMBLOB
    );
                ');
                $pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . '  ON propertystorage (path(600), name(100));');
                break;
            case 'sqlite' :
                $pdo->exec('ALTER TABLE propertystorage RENAME TO propertystorage_old' . $random);
                $pdo->exec('
CREATE TABLE propertystorage (
    id integer primary key asc,
    path text,
    name text,
    valuetype integer,
    value blob
);');

                $pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path, name);');
                break;

        }
    } elseif (array_key_exists('valuetype', $row)) {
        echo "valuetype field exists. Assuming that this part of the migration has\n";
        echo "Already been completed.\n";
    } else {
        echo "2.1 schema detected. Going to perform upgrade.\n";
        $addValueType = true;
    }

} catch (Exception $e) {
    echo "Could not find a propertystorage table. Skipping this part of the\n";
    echo "upgrade.\n";
    echo $e->getMessage(), "\n";
}

if ($addValueType) {

    switch ($driver) {
        case 'mysql' :
            $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT UNSIGNED');
            break;
        case 'sqlite' :
            $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT');

            break;
    }

    $pdo->exec('UPDATE propertystorage SET valuetype = 1 WHERE valuetype IS NULL ');

}

echo "Migrating vcardurl\n";

$result = $pdo->query('SELECT id, uri, vcardurl FROM principals WHERE vcardurl IS NOT NULL');
$stmt1 = $pdo->prepare('INSERT INTO propertystorage (path, name, valuetype, value) VALUES (?, ?, 3, ?)');

while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {

    // Inserting the new record
    $stmt1->execute([
        'addressbooks/' . basename($row['uri']),
        '{http://calendarserver.org/ns/}me-card',
        serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl']))
    ]);

    echo serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl']));

}

echo "Done.\n";
echo "Upgrade to 3.0 schema completed.\n";
#!/usr/bin/env php
<?php

echo "SabreDAV migrate script for version 3.2\n";

if ($argc < 2) {

    echo <<<HELLO

This script help you migrate from a 3.1 database to 3.2 and later

Changes:
* Created a new calendarinstances table to support calendar sharing.
* Remove a lot of columns from calendars.

Keep in mind that ALTER TABLE commands will be executed. If you have a large
dataset this may mean that this process takes a while.

Make a back-up first. This script has been tested, but the amount of
potential variants are extremely high, so it's impossible to deal with every
possible situation.

In the worst case, you will lose all your data. This is not an overstatement.

Lastly, if you are upgrading from an older version than 3.1, make sure you run
the earlier migration script first. Migration scripts must be ran in order.

Usage:

php {$argv[0]} [pdo-dsn] [username] [password]

For example:

php {$argv[0]} "mysql:host=localhost;dbname=sabredav" root password
php {$argv[0]} sqlite:data/sabredav.db

HELLO;

    exit();

}

// There's a bunch of places where the autoloader could be, so we'll try all of
// them.
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

$dsn = $argv[1];
$user = isset($argv[2]) ? $argv[2] : null;
$pass = isset($argv[3]) ? $argv[3] : null;

$backupPostfix = time();

echo "Connecting to database: " . $dsn . "\n";

$pdo = new PDO($dsn, $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);

switch ($driver) {

    case 'mysql' :
        echo "Detected MySQL.\n";
        break;
    case 'sqlite' :
        echo "Detected SQLite.\n";
        break;
    default :
        echo "Error: unsupported driver: " . $driver . "\n";
        die(-1);
}

echo "Creating 'calendarinstances'\n";
$addValueType = false;
try {
    $result = $pdo->query('SELECT * FROM calendarinstances LIMIT 1');
    $result->fetch(\PDO::FETCH_ASSOC);
    echo "calendarinstances exists. Assuming this part of the migration has already been done.\n";
} catch (Exception $e) {
    echo "calendarinstances does not yet exist. Creating table and migrating data.\n";

    switch ($driver) {
        case 'mysql' :
            $pdo->exec(<<<SQL
CREATE TABLE calendarinstances (
    id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    calendarid INTEGER UNSIGNED NOT NULL,
    principaluri VARBINARY(100),
    access TINYINT(1) NOT NULL DEFAULT '1' COMMENT '1 = owner, 2 = read, 3 = readwrite',
    displayname VARCHAR(100),
    uri VARBINARY(200),
    description TEXT,
    calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0',
    calendarcolor VARBINARY(10),
    timezone TEXT,
    transparent TINYINT(1) NOT NULL DEFAULT '0',
    share_href VARBINARY(100),
    share_displayname VARCHAR(100),
    share_invitestatus TINYINT(1) NOT NULL DEFAULT '2' COMMENT '1 = noresponse, 2 = accepted, 3 = declined, 4 = invalid',
    UNIQUE(principaluri, uri),
    UNIQUE(calendarid, principaluri),
    UNIQUE(calendarid, share_href)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SQL
        );
            $pdo->exec("
INSERT INTO calendarinstances
    (
        calendarid,
        principaluri,
        access,
        displayname,
        uri,
        description,
        calendarorder,
        calendarcolor,
        transparent
    )
SELECT
    id,
    principaluri,
    1,
    displayname,
    uri,
    description,
    calendarorder,
    calendarcolor,
    transparent
FROM calendars
");
            break;
        case 'sqlite' :
            $pdo->exec(<<<SQL
CREATE TABLE calendarinstances (
    id integer primary key asc NOT NULL,
    calendarid integer,
    principaluri text,
    access integer COMMENT '1 = owner, 2 = read, 3 = readwrite' NOT NULL DEFAULT '1',
    displayname text,
    uri text NOT NULL,
    description text,
    calendarorder integer,
    calendarcolor text,
    timezone text,
    transparent bool,
    share_href text,
    share_displayname text,
    share_invitestatus integer DEFAULT '2',
    UNIQUE (principaluri, uri),
    UNIQUE (calendarid, principaluri),
    UNIQUE (calendarid, share_href)
);
SQL
        );
            $pdo->exec("
INSERT INTO calendarinstances
    (
        calendarid,
        principaluri,
        access,
        displayname,
        uri,
        description,
        calendarorder,
        calendarcolor,
        transparent
    )
SELECT
    id,
    principaluri,
    1,
    displayname,
    uri,
    description,
    calendarorder,
    calendarcolor,
    transparent
FROM calendars
");
            break;
    }

}
try {
    $result = $pdo->query('SELECT * FROM calendars LIMIT 1');
    $row = $result->fetch(\PDO::FETCH_ASSOC);

    if (!$row) {
        echo "Source table is empty.\n";
        $migrateCalendars = true;
    }

    $columnCount = count($row);
    if ($columnCount === 3) {
        echo "The calendars table has 3 columns already. Assuming this part of the migration was already done.\n";
        $migrateCalendars = false;
    } else {
        echo "The calendars table has " . $columnCount . " columns.\n";
        $migrateCalendars = true;
    }

} catch (Exception $e) {
    echo "calendars table does not exist. This is a major problem. Exiting.\n";
    exit(-1);
}

if ($migrateCalendars) {

    $calendarBackup = 'calendars_3_1_' . $backupPostfix;
    echo "Backing up 'calendars' to '", $calendarBackup, "'\n";

    switch ($driver) {
        case 'mysql' :
            $pdo->exec('RENAME TABLE calendars TO ' . $calendarBackup);
            break;
        case 'sqlite' :
            $pdo->exec('ALTER TABLE calendars RENAME TO ' . $calendarBackup);
            break;

    }

    echo "Creating new calendars table.\n";
    switch ($driver) {
        case 'mysql' :
            $pdo->exec(<<<SQL
CREATE TABLE calendars (
    id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1',
    components VARBINARY(21)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
SQL
);
            break;
        case 'sqlite' :
            $pdo->exec(<<<SQL
CREATE TABLE calendars (
    id integer primary key asc NOT NULL,
    synctoken integer DEFAULT 1 NOT NULL,
    components text NOT NULL
);
SQL
        );
            break;

    }

    echo "Migrating data from old to new table\n";

    $pdo->exec(<<<SQL
INSERT INTO calendars (id, synctoken, components) SELECT id, synctoken, COALESCE(components,"VEVENT,VTODO,VJOURNAL") as components FROM $calendarBackup
SQL
    );

}


echo "Upgrade to 3.2 schema completed.\n";
<?php

// SabreDAV test server.

class CliLog {

    protected $stream;

    function __construct() {

        $this->stream = fopen('php://stdout', 'w');

    }

    function log($msg) {
        fwrite($this->stream, $msg . "\n");
    }

}

$log = new CliLog();

if (php_sapi_name() !== 'cli-server') {
    die("This script is intended to run on the built-in php webserver");
}

// Finding composer


$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

use Sabre\DAV;

// Root 
$root = new DAV\FS\Directory(getcwd());

// Setting up server.
$server = new DAV\Server($root);

// Browser plugin
$server->addPlugin(new DAV\Browser\Plugin());

$server->exec();
ChangeLog
=========

3.2.2 (2017-02-14)
------------------

* #943: Fix CardDAV XML reporting bug, which was affecting several CardDAV
  clients. Bug was introduced in 3.2.1.
* The zip release ships with [sabre/vobject 4.1.2][vobj],
  [sabre/http 4.2.2][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.2.0][uri] and [sabre/xml 1.5.0][xml].


3.2.1 (2017-01-28)
------------------

* #877: Fix for syncing large calendars when using the Sqlite PDO backend.
  (@theseer).
* #889 Added support for filtering vCard properties in the addressbook-query
  REPORT (@DeepDiver1975).
* The zip release ships with [sabre/vobject 4.1.2][vobj],
  [sabre/http 4.2.2][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.2.0][uri] and [sabre/xml 1.5.0][xml].


3.2.0 (2016-06-27)
------------------

* The default ACL rules allow an unauthenticated user to read information
  about nodes that don't have their own ACL defined. This was a security
  problem.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
  [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml].


3.2.0-beta1 (2016-05-20)
------------------------

* #833: Calendars throw exceptions when the sharing plugin is not enabled.
* #834: Return vCards exactly as they were stored if we don't need to convert
  in between versions.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
  [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].


3.2.0-alpha1 (2016-05-09)
-------------------------

* Database changes for CalDAV. If you are using the CalDAV PDO backends, you
  must migrate. Run `./bin/migrateto32.php` for more info.
* Support for WebDAV Resource Sharing, an upcoming standard.
* Added support for sharing in the CalDAV PDO backend! Users can now invite
  others to their calendar and give them read/read-write access!
* #397: Support for PSR-3. You can now log exceptions with your favourite
  psr3-compatible logging tool.
* #825: Actual proper, tested support for PostgreSQL. We require version 9.5.
* Removed database migration script for sabre/dav 1.7. To update from that
  version you now first need to update to sabre/dav 3.1.
* Removed deprecated function: `Sabre\DAV\Auth\Plugin::getCurrentUser()`.
* #774: Fixes for getting free disk space on Windows.
* #803: Major changes in the sharing API. If you were using an old sabre/dav
  sharing api, head to the website for more detailed migration notes.
* #657: Support for optional auth using `{DAV:}unauthorized` and `{DAV:}all`
  privileges. This allows you to assign a privilege to a resource, allowing
  non-authenticated users to access it. For instance, this could allow you
  to create a public read-only collection.
* #812 #814: ICS/VCF exporter now includes a more useful filename in its
  `Content-Disposition` header. (@Xenopathic).
* #801: BC break: If you were using the `Href` object before, it's behavior
  now changed a bit, and `LocalHref` was added to replace the old, default
  behavior of `Href`. See the migration doc for more info.
* Removed `Sabre\DAVACL\Plugin::$allowAccessToNodesWithoutACL` setting.
  Instead, you can provide a set of default ACL rules with
  `Sabre\DAVACL\Plugin::setDefaultAcl()`.
* Introduced `Sabre\DAVACL\ACLTrait` which contains a default implementation
  of `Sabre\DAV\IACL` with some sane defaults. We're using this trait all over
  the place now, reducing the amount of boilerplate.
* Plugins can now control the "Supported Privilege Set".
* Added Sharing, ICSExport and VCFExport plugins to `groupwareserver.php`
  example.
* The `{DAV:}all` privilege is now no longer abstract, so it can be assigned
  directly. We're using the `{DAV:}all` privilege now in a lot of cases where
  we before assigned both `{DAV:}read` and `{DAV:}write`.
* Resources that are not collections no longer support the `{DAV:}bind` and
  `{DAV:}unbind` privileges.
* Corrected the CalDAV-scheduling related privileges.
* Doing an `UNLOCK` no longer requires the `{DAV:}write-content` privilege.
* Added a new `getPrincipalByUri` plugin event. Allowing plugins to request
  quickly where a principal lives on a server.
* Renamed `phpunit.xml` to `phpunit.xml.dist` to make local modifications easy.
* Functionality from `IShareableCalendar` is merged into `ISharedCalendar`.
* #751: Fixed XML responses from failing `MKCOL` requests.
* #600: Support for `principal-match` ACL `REPORT`.
* #599: Support for `acl-principal-prop-set` ACL `REPORT`.
* #798: Added an index on `firstoccurence` field in MySQL CalDAV backend. This
  should speed up common calendar-query requests.
* #759: DAV\Client is now able to actually correctly resolve relative urls.
* #671: We are no longer checking the `read-free-busy` privilege on individual
  calendars during freebusy operations in the scheduling plugin. Instead, we
  check the `schedule-query-freebusy` privilege on the target users' inbox,
  which validates access for the entire account, per the spec.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
  [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].


3.1.5 (????-??-??)
------------------

* Fixed: Creating a new calendar on some MySQL configurations caused an error.
* #889 Added support for filtering vCard properties in the addressbook-query
  REPORT (@DeepDiver1975).



3.1.4 (2016-05-28)
------------------

* #834: Backport from `master`: Return vCards exactly as they were stored if
  we don't need to convert in between versions. This should speed up many
  large addressbook syncs sometimes up to 50%.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
  [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.2][xml].


3.1.3 (2016-04-06)
------------------

* Set minimum libxml version to 2.7.0 in `composer.json`.
* #805: It wasn't possible to create calendars that hold events, journals and
  todos using MySQL, because the `components` column was 1 byte too small.
* The zip release ships with [sabre/vobject 4.1.0][vobj],
  [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].


3.1.2 (2016-03-12)
------------------

* #784: Sync logs for address books were not correctly cleaned up after
  deleting them.
* #787: Cannot use non-seekable stream-wrappers with range requests.
* Faster XML parsing and generating due to sabre/xml update.
* #793: The Sqlite schema is now more strict and more similar to the MySQL
  schema. This solves a problem within Baikal.
* The zip release ships with [sabre/vobject 4.0.3][vobj],
  [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].


3.1.1 (2016-01-25)
------------------

* #755: The brower plugin and some operations would break when scheduling and
  delegation would both be enabled.
* #757: A bunch of unittest improvements (@jakobsack).
* The zip release ships with [sabre/vobject 4.0.2][vobj],
  [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].


3.1.0 (2016-01-06)
------------------

* Better error message when the browser plugin is not enabled.
* Added a super minimal server example.
* #730: Switched all mysql tables to `utf8mb4` character set, allowing you to
  use emoji in some tables where you couldn't before.
* #710: Provide an Auth backend that acts as a helper for people implementing
  OAuth2 Bearer token. (@fkooman).
* #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached.
* #727: Added another workaround to make CalDAV work for Windows 10 clients.
* #742: Fixes to make sure that vobject 4 is correctly supported.
* #726: Better error reporting in `Client::propPatch`. We're now throwing
  exceptions.
* #608: When a HTTP error is triggered during `Client:propFind`, we're now
  throwing `Sabre\HTTP\ClientHttpException` instead of `Sabre\DAV\Exception`.
  This new exception contains a LOT more information about the problem.
* #721: Events are now handled in the correct order for `COPY` requests.
  Before this subtle bugs could appear that could cause data-loss.
* #747: Now throwing exceptions and setting the HTTP status to 500 in subtle
  cases where no other plugin set a correct HTTP status.
* #686: Corrected PDO principal backend's findByURI for email addresses that
  don't match the exact capitalization.
* #512: The client now has it's own `User-Agent`.
* #720: Some browser improvements.
* The zip release ships with [sabre/vobject 4.0.1][vobj],
  [sabre/http 4.2.1][http], [sabre/event 3.0.0][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].


3.1.0-alpha2 (2015-09-05)
-------------------------

* Massive calendars and addressbooks should see a big drop in peak memory
  usage.
* Fixed a privilege bug in the availability system.
* #697: Added a "tableName" member to the PropertyStorage PDO backend. (@Frzk).
* #699: PostgreSQL fix for the Locks PDO backend. (@TCKnet)
* Removed the `simplefsserver.php` example file. It's not simple enough.
* #703: PropPatch in client is not correctly encoded.
* #709: Throw exception when running into empty
  `supported-calendar-component-set`.
* #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This
  fixes issues when using sabre/dav as a client.
* The zip release ships with [sabre/vobject 4.0.0-alpha2][vobj],
  [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].


3.1.0-alpha1 (2015-07-19)
-------------------------

* Now requires PHP 5.5
* Upgraded to vobject 4, which is a lot faster.
* Support for PHP 7.
* #690: Support for `calendar-availability`, draft 05.
  [reference][calendar-availability].
* #691: Workaround for broken Windows Phone client.
* The zip release ships with [sabre/vobject 4.0.0-alpha1][vobj],
  [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].


3.0.10 (2016-??-??)
------------------

* #889 Added support for filtering vCard properties in the addressbook-query
  REPORT (@DeepDiver1975).


3.0.9 (2016-04-06)
------------------

* Set minimum libxml version to 2.7.0 in `composer.json`.
* #727: Added another workaround to make CalDAV work for Windows 10 clients.
* #805: It wasn't possible to create calendars that hold events, journals and
  todos using MySQL, because the `components` column was 1 byte too small.
* The zip release ships with [sabre/vobject 3.5.1][vobj],
  [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].


3.0.8 (2016-03-12)
------------------

* #784: Sync logs for address books were not correctly cleaned up after
  deleting them.
* #787: Cannot use non-seekable stream-wrappers with range requests.
* Faster XML parsing and generating due to sabre/xml update.
* The zip release ships with [sabre/vobject 3.5.0][vobj],
  [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml].


3.0.7 (2016-01-12)
------------------

* #752: PHP 7 support for 3.0 branch. (@DeepDiver1975)
* The zip release ships with [sabre/vobject 3.5.0][vobj],
  [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].


3.0.6 (2016-01-04)
------------------

* #730: Switched all mysql tables to `utf8mb4` character set, allowing you to
  use emoji in some tables where you couldn't before.
* #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached.
* #734: Return `418 I'm a Teapot` when generating a multistatus response that
  has resources with no returned properties.
* #740: Bugs in `migrate20.php` script.
* The zip release ships with [sabre/vobject 3.4.8][vobj],
  [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml].


3.0.5 (2015-09-15)
------------------

* #704: Fixed broken uri encoding in multistatus responses. This affected
  at least CyberDuck, but probably also others.
* The zip release ships with [sabre/vobject 3.4.7][vobj],
* The zip release ships with [sabre/vobject 3.4.7][vobj],
  [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].


3.0.4 (2015-09-06)
------------------

* #703: PropPatch in client is not correctly encoded.
* #709: Throw exception when running into empty
  `supported-calendar-component-set`.
* #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This
  fixes issues when using sabre/dav as a client.
* #705: A `MOVE` request that gets prevented from deleting the source resource
  will still remove the target resource. Now all events are triggered before
  any destructive operations.
* The zip release ships with [sabre/vobject 3.4.7][vobj],
  [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml].


3.0.3 (2015-08-06)
------------------

* #700: Digest Auth fails on `HEAD` requests.
* Fixed example files to no longer use now-deprecated realm argument.
* The zip release ships with [sabre/vobject 3.4.6][vobj],
  [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].


3.0.2 (2015-07-21)
------------------

* #657: Migration script would break when coming a cross an iCalendar object
  with no UID.
* #691: Workaround for broken Windows Phone client.
* Fixed a whole bunch of incorrect php docblocks.
* The zip release ships with [sabre/vobject 3.4.5][vobj],
  [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].


3.0.1 (2015-07-02)
------------------

* #674: Postgres sql file fixes. (@davesouthey)
* #677: Resources with the name '0' would not get retrieved when using
  `Depth: infinity` in a `PROPFIND` request.
* #680: Fix 'autoprefixing' of dead `{DAV:}href` properties.
* #675: NTLM support in DAV\Client. (@k42b3)
* The zip release ships with [sabre/vobject 3.4.5][vobj],
  [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml].


3.0.0 (2015-06-02)
------------------

* No changes since last beta.
* The zip release ships with [sabre/vobject 3.4.5][vobj],
  [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].


3.0.0-beta3 (2015-05-29)
------------------------

* Fixed deserializing href properties with no value.
* Fixed deserializing `{DAV:}propstat` without a `{DAV:}prop`.
* #668: More information about vcf-export-plugin in browser plugin.
* #669: Add export button to browser plugin for address books. (@mgee)
* #670: multiget report hrefs were not decoded.
* The zip release ships with [sabre/vobject 3.4.4][vobj],
  [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].


3.0.0-beta2 (2015-05-27)
------------------------

* A node's properties should not overwrite properties that were already set.
* Some uris were not correctly encoded in notifications.
* The zip release ships with [sabre/vobject 3.4.4][vobj],
  [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].


3.0.0-beta1 (2015-05-25)
------------------------

* `migrate22.php` is now called `migrate30.php`.
* Using php-cs-fixer for automated coding standards enforcement and fixing.
* #660: principals could break html output.
* #662: Fixed several bugs in the `share` request parser.
* #665: Fix a bug in serialization of complex properties in the proppatch
  request in the client.
* #666: expand-property report did not correctly prepend the base uri when
  generating uris, this caused delegation to break.
* #659: Don't throw errors when when etag-related checks are done on
  collections.
* Fully supporting the updated `Prefer` header syntax, as defined in
  [rfc7240][rfc7240].
* The zip release ships with [sabre/vobject 3.4.3][vobj],
  [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml].


3.0.0-alpha1 (2015-05-19)
-------------------------

* It's now possible to get all property information from files using the
  browser plugin.
* Browser plugin will now show a 'calendar export' button when the
  ics-export plugin is enabled.
* Some nodes that by default showed the current time as their last
  modification time, now no longer has a last modification time.
* CardDAV namespace was missing from default namespaceMap.
* #646: Properties can now control their own HTML output in the browser plugin.
* #646: Nicer HTML output for the `{DAV:}acl` property.
* Browser plugin no longer shows a few properties that take up a lot of space,
  but are likely not really interesting for most users.
* #654: Added a collection, `Sabre\DAVACL\FS\HomeCollection` for automatically
  creating a private home collection per-user.
* Changed all MySQL columns from `VARCHAR` to `VARBINARY` where possible.
* Improved older migration scripts a bit to allow easier testing.
* The zip release ships with [sabre/vobject 3.4.3][vobj],
  [sabre/http 4.0.0-alpha3][http], [sabre/event 2.0.2][evnt],
  [sabre/uri 1.0.1][uri] and [sabre/xml 0.4.3][xml].


2.2.0-alpha4 (2015-04-13)
-------------------------

* Complete rewrite of the XML system. We now use our own [sabre/xml][xml],
  which has a much smarter XML Reader and Writer.
* BC Break: It's no longer possible to instantiate the Locks plugin without
  a locks backend. I'm not sure why this ever made sense.
* Simplified the Locking system and fixed a bug related to if tokens checking
  locks unrelated to the current request.
* `FSExt` Directory and File no longer do custom property storage. This
  functionality is already covered pretty well by the `PropertyStorage` plugin,
  so please switch.
* Renamed `Sabre\CardDAV\UserAddressBooks` to `Sabre\CardDAV\AddressBookHome`
  to be more consistent with `CalendarHome` as well as the CardDAV
  specification.
* `Sabre\DAV\IExtendedCollection` now receives a `Sabre\DAV\MkCol` object as
  its second argument, and no longer receives seperate properties and
  resourcetype arguments.
* `MKCOL` now integrates better with propertystorage plugins.
* #623: Remove need of temporary files when working with Range requests.
  (@dratini0)
* The zip release ships with [sabre/vobject 3.4.2][vobj],
  [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt],
  [sabre/uri 1.0.0][uri] and [sabre/xml 0.4.3][xml].


2.2.0-alpha3 (2015-02-25)
-------------------------

* Contains all the changes introduced between 2.1.2 and 2.1.3.
* The zip release ships with [sabre/vobject 3.4.2][vobj],
  [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt] and
  [sabre/uri 1.0.0][uri].


2.2.0-alpha2 (2015-01-09)
-------------------------

* Renamed `Sabre\DAV\Auth\Backend\BackendInterface::requireAuth` to
  `challenge`, which is a more correct and better sounding name.
* The zip release ships with [sabre/vobject 3.3.5][vobj],
  [sabre/http 3.0.4][http], [sabre/event 2.0.1][evnt].


2.2.0-alpha1 (2014-12-10)
-------------------------

* The browser plugin now has a new page with information about your sabredav
  server, and shows information about every plugin that's loaded in the
  system.
* #191: The Authentication system can now support multiple authentication
  backends.
* Removed: all `$tableName` arguments from every PDO backend. This was already
  deprecated, but has now been fully removed. All of these have been replaced
  with public properties.
* Deleted several classes that were already deprecated much earlier:
  * `Sabre\CalDAV\CalendarRootNode`
  * `Sabre\CalDAV\UserCalendars`
  * `Sabre\DAV\Exception\FileNotFound`
  * `Sabre\DAV\Locks\Backend\FS`
  * `Sabre\DAV\PartialUpdate\IFile`
  * `Sabre\DAV\URLUtil`
* Removed: `Sabre\DAV\Client::addTrustedCertificates` and
  `Sabre\DAV\Client::setVerifyPeer`.
* Removed: `Sabre\DAV\Plugin::getPlugin()` can now no longer return plugins
  based on its class name.
* Removed: `Sabre\DAVACL\Plugin::getPrincipalByEmail()`.
* #560: GuessContentType plugin will now set content-type to
  `application/octet-stream` if a better content-type could not be determined.
* #568: Added a `componentType` argument to `ICSExportPlugin`, allowing you to
  specifically fetch `VEVENT`, `VTODO` or `VJOURNAL`.
* #582: Authentication backend interface changed to be stateless. If you
  implemented your own authentication backend, make sure you upgrade your class
  to the latest API!
* #582: `Sabre\DAV\Auth\Plugin::getCurrentUser()` is now deprecated. Use
  `Sabre\DAV\Auth\Plugin::getCurrentPrincipal()` instead.
* #193: Fix `Sabre\DAV\FSExt\Directory::getQuotaInfo()` on windows.


2.1.11 (2016-10-06)
-------------------

* #805: It wasn't possible to create calendars that hold events, journals and
  todos using MySQL, because the `components` column was 1 byte too small.
* The zip release ships with [sabre/vobject 3.5.3][vobj],
  [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].


2.1.10 (2016-03-10)
-------------------

* #784: Sync logs for address books were not correctly cleaned up after
  deleting them.
* The zip release ships with [sabre/vobject 3.5.0][vobj],
  [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].


2.1.9 (2016-01-25)
------------------

* #674: PHP7 support (@DeepDiver1975).
* The zip release ships with [sabre/vobject 3.5.0][vobj],
  [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].


2.1.8 (2016-01-04)
------------------

* #729: Fixed a caching problem in the Tree object.
* #740: Bugs in `migrate20.php` script.
* The zip release ships with [sabre/vobject 3.4.8][vobj],
  [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].


2.1.7 (2015-09-05)
------------------

* #705: A `MOVE` request that gets prevented from deleting the source resource
  will still remove the target resource. Now all events are triggered before
  any destructive operations.
* The zip release ships with [sabre/vobject 3.4.7][vobj],
  [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].


2.1.6 (2015-07-21)
------------------

* #657: Migration script would break when coming a cross an iCalendar object
  with no UID.
* #691: Workaround for broken Windows Phone client.
* The zip release ships with [sabre/vobject 3.4.5][vobj],
  [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].


2.1.5 (2015-07-11)
------------------

* #677: Resources with the name '0' would not get retrieved when using
  `Depth: infinity` in a `PROPFIND` request.
* The zip release ships with [sabre/vobject 3.4.5][vobj],
  [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].


2.1.4 (2015-05-25)
------------------

* #651: Double-encoded path in the browser plugin. Should fix a few broken
  links in some setups.
* #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE).
* #658: Updating `schedule-calendar-default-URL` does not work well, so we're
  disabling it until there's a better fix.
* The zip release ships with [sabre/vobject 3.4.3][vobj],
  [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt].


2.1.3 (2015-02-25)
------------------

* #586: `SCHEDULE-STATUS` should not contain a reason-phrase.
* #539: Fixed a bug related to scheduling in shared calendars.
* #595: Support for calendar-timezone in iCalendar exports.
* #581: findByUri would send empty prefixes to the principal backend (@soydeedo)
* #611: Escaping a bit more HTML output in the browser plugin. (@LukasReschke)
* #610: Don't allow discovery of arbitrary files using `..` in the browser
  plugin (@LukasReschke).
* Browser plugin now shows quota properties.
* #612: PropertyStorage didn't delete properties from nodes when a node's
  parents get deleted.
* #581: Fixed problems related to finding attendee information during
  scheduling.
* The zip release ships with [sabre/vobject 3.4.2][vobj],
  [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt].


2.1.2 (2014-12-10)
------------------

* #566: Another issue related to the migration script, which would cause
  scheduling to not work well for events that were already added before the
  migration.
* #567: Doing freebusy requests on accounts that had 0 calendars would throw
  a `E_NOTICE`.
* #572: `HEAD` requests trigger a PHP warning.
* #579: Browser plugin can throw exception for a few resourcetypes that didn't
  have an icon defined.
* The zip release ships with [sabre/vobject 3.3.4][vobj],
  [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt].


2.1.1 (2014-11-22)
------------------

* #561: IMip Plugin didn't strip mailto: from email addresses.
* #566: Migration process had 2 problems related to adding the `uid` field
  to the `calendarobjects` table.
* The zip release ships with [sabre/vobject 3.3.4][vobj],
  [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].


2.1.0 (2014-11-19)
------------------

* #541: CalDAV PDO backend didn't respect overridden PDO table names.
* #550: Scheduling invites are no longer delivered into shared calendars.
* #554: `calendar-multiget` `REPORT` did not work on inbox items.
* #555: The `calendar-timezone` property is now respected for floating times
  and all-day events in the `calendar-query`, `calendar-multiget` and
  `free-busy-query` REPORTs.
* #555: The `calendar-timezone` property is also respected for scheduling
  free-busy requests.
* #547: CalDAV system too aggressively 'corrects' incoming iCalendar data, and
  as a result doesn't return an etag for common cases.
* The zip release ships with [sabre/vobject 3.3.4][vobj],
  [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].


2.1.0-alpha2 (2014-10-23)
-------------------------

* Added: calendar-user-address-set to default principal search properties
  list. This should fix iOS attendee autocomplete support.
* Changed: Moved all 'notifications' functionality from `Sabre\CalDAV\Plugin`
  to a new plugin: `Sabre\CalDAV\Notifications\Plugin`. If you want to use
  notifications-related functionality, just add this plugin.
* Changed: Accessing the caldav inbox, outbox or notification collection no
  longer triggers getCalendarsForUser() on backends.
* #533: New invites are no longer delivered to taks-only calendars.
* #538: Added `calendarObjectChange` event.
* Scheduling speedups.
* #539: added `afterResponse` event. (@joserobleda)
* Deprecated: All the "tableName" constructor arguments for all the PDO
  backends are now deprecated. They still work, but will be removed in the
  next major sabredav version. Every argument that is now deprecated can now
  be accessed as a public property on the respective backends.
* #529: Added getCalendarObjectByUID to PDO backend, speeding up scheduling
  operations on large calendars.
* The zip release ships with [sabre/vobject 3.3.3][vobj],
  [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt].


2.1.0-alpha1 (2014-09-23)
-------------------------

* Added: Support for [rfc6638][rfc6638], also known as CalDAV Scheduling.
* Added: Automatically converting between vCard 3, 4 and jCard using the
  `Accept:` header, in CardDAV reports, and automatically converting from
  jCard to vCard upon `PUT`. It's important to note that your backends _may_
  now receive both vCard 3.0 and 4.0.
* Added: #444. Collections can now opt-in to support high-speed `MOVE`.
* Changed: PropertyStorage backends now have a `move` method.
* Added: `beforeMove`, and `afterMove` events.
* Changed: A few database changes for the CalDAV PDO backend. Make sure you
  run `bin/migrate21.php` to upgrade your database schema.
* Changed: CalDAV backends have a new method: `getCalendarObjectByUID`. This
  method MUST be implemented by all backends, but the `AbstractBackend` has a
  simple default implementation for this.
* Changed: `Sabre\CalDAV\UserCalendars` has been renamed to
  `Sabre\CalDAV\CalendarHome`.
* Changed: `Sabre\CalDAV\CalendarRootNode` has been renamed to
  `Sabre\CalDAV\CalendarRoot`.
* Changed: The IMipHandler has been completely removed. With CalDAV scheduling
  support, it is no longer needed. It's functionality has been replaced by
  `Sabre\CalDAV\Schedule\IMipPlugin`, which can now send emails for clients
  other than iCal.
* Removed: `Sabre\DAV\ObjectTree` and `Sabre\DAV\Tree\FileSystem`. All this
  functionality has been merged into `Sabre\DAV\Tree`.
* Changed: PrincipalBackend now has a findByUri method.
* Changed: `PrincipalBackend::searchPrincipals` has a new optional `test`
  argument.
* Added: Support for the `{http://calendarserver.org/ns/}email-address-set`
  property.
* #460: PropertyStorage must move properties during `MOVE` requests.
* Changed: Restructured the zip distribution to be a little bit more lean
  and consistent.
* #524: Full support for the `test="anyof"` attribute in principal-search
  `REPORT`.
* #472: Always returning lock tokens in the lockdiscovery property.
* Directory entries in the Browser plugin are sorted by type and name.
  (@aklomp)
* #486: It's now possible to return additional properties when an 'allprop'
  PROPFIND request is being done. (@aklomp)
* Changed: Now return HTTP errors when an addressbook-query REPORT is done
  on a uri that's not a vcard. This should help with debugging this common
  mistake.
* Changed: `PUT` requests with a `Content-Range` header now emit a 400 status
  instead of 501, as per RFC7231.
* Added: Browser plugin can now display the contents of the
  `{DAV:}supported-privilege-set` property.
* Added: Now reporting `CALDAV:max-resource-size`, but we're not actively
  restricting it yet.
* Changed: CalDAV plugin is now responsible for reporting
  `CALDAV:supported-collation-set` and `CALDAV:supported-calendar-data`
  properties.
* Added: Now reporting `CARDDAV:max-resource-size`, but we're not actively
  restricting it yet.
* Added: Support for `CARDDAV:supported-collation-set`.
* Changed: CardDAV plugin is now responsible for reporting
  `CARDDAV:supported-address-data`. This functionality has been removed from
  the CardDAV PDO backend.
* When a REPORT is not supported, we now emit HTTP error 415, instead of 403.
* #348: `HEAD` requests now work wherever `GET` also works.
* Changed: Lower priority for the iMip plugins `schedule` event listener.
* Added: #523 Custom CalDAV backends can now mark any calendar as read-only.
* The zip release ships with [sabre/vobject 3.3.3][vobj],
  [sabre/http 3.0.0][http], and [sabre/event 2.0.0][evnt].


2.0.9 (2015-09-04)
------------------

* #705: A `MOVE` request that gets prevented from deleting the source resource
  will still remove the target resource. Now all events are triggered before
  any destructive operations.
* The zip release ships with [sabre/vobject 3.4.6][vobj],
  [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].



2.0.8 (2015-07-11)
------------------

* #677: Resources with the name '0' would not get retrieved when using
  `Depth: infinity` in a `PROPFIND` request.
* The zip release ships with [sabre/vobject 3.3.5][vobj],
  [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].


2.0.7 (2015-05-25)
------------------

* #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE).
* The zip release ships with [sabre/vobject 3.3.4][vobj],
  [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].


2.0.6 (2014-12-10)
------------------

* Added `Sabre\CalDAV\CalendarRoot` as an alias for
  `Sabre\CalDAV\CalendarRootNode`. The latter is going to be deprecated in 2.1,
  so this makes it slightly easier to write code that works in both branches.
* #497: Making sure we're initializing the sync-token field with a value after
  migration.
* The zip release ships with [sabre/vobject 3.3.4][vobj],
  [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].


2.0.5 (2014-10-14)
------------------

* #514: CalDAV PDO backend didn't work when overriding the 'calendar changes'
  database table name.
* #515: 304 status code was not being sent when checking preconditions.
* The zip release ships with [sabre/vobject 3.3.3][vobj],
  [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].


2.0.4 (2014-08-27)
------------------

* #483: typo in calendars creation for PostgreSQL.
* #487: Locks are now automatically removed after a node has been deleted.
* #496: Improve CalDAV and CardDAV sync when there is no webdav-sync support.
* Added: Automatically mapping internal sync-tokens to getctag.
* The zip release ships with [sabre/vobject 3.3.1][vobj],
  [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].


2.0.3 (2014-07-14)
------------------

* #474: Fixed PropertyStorage `pathFilter()`.
* #476: CSP policy incorrect, causing stylesheets to not load in the browser
  plugin.
* #475: Href properties in the browser plugin sometimes included a backslash.
* #478: `TooMuchMatches` exception never worked. This was fixed, and we also
  took this opportunity to rename it to `TooManyMatches`.
* The zip release ships with [sabre/vobject 3.2.4][vobj],
  [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt].


2.0.2 (2014-06-12)
------------------

* #470: Fixed compatibility with PHP < 5.4.14.
* #467: Fixed a problem in `examples/calendarserver.php`.
* #466: All the postgresql sample files have been updated.
* Fixed: An error would be thrown if a client did a propfind on a node the
  user didn't have access to.
* Removed: Old and broken example code from the `examples/` directory.
* The zip release ships with [sabre/vobject 3.2.3][vobj],
  [sabre/http 2.0.3][http], and [sabre/event 1.0.1][evnt].


2.0.1 (2014-05-28)
------------------

* #459: PROPFIND requests on Files with no Depth header would return a fatal
  error.
* #464: A PROPFIND allprops request should not return properties with status
  404.
* The zip release ships with [sabre/vobject 3.2.2][vobj],
  [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].


2.0.0 (2014-05-22)
------------------

* The zip release ships with [sabre/vobject 3.2.2][vobj],
  [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].
* Fixed: #456: Issue in sqlite migration script.
* Updated: MySQL database schema optimized by using more efficient column types.
* Cleaned up browser design.


2.0.0-beta1 (2014-05-15)
-------------------------

* The zip release ships with [sabre/vobject 3.2.2][vobj],
  [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt].
* BC Break: Property updating and fetching got refactored. Read the [migration
  document][mi20] for more information. This allows for creation of a generic
  property storage, and other property-related functionality that was not
  possible before.
* BC Break: Removed `propertyUpdate`, `beforeGetProperties` and
  `afterGetProperties` events.
* Fixed: #413: Memory optimizations for the CardDAV PDO backend.
* Updated: Brand new browser plugin with more debugging features and a design
  that is slightly less painful.
* Added: Support for the `{DAV:}supported-method-set` property server-wide.
* Making it easier for implementors to override how the CardDAV addressbook
  home is located.
* Fixed: Issue #422 Preconditions were not being set on PUT on non-existent
  files. Not really a chance for data-loss, but incorrect nevertheless.
* Fixed: Issue #428: Etag check with `If:` fails if the target is a collection.
* Fixed: Issues #430, #431, #433: Locks plugin didn't not properly release
  filesystem based locks.
* Fixed: #443. Support for creating new calendar subscriptions for OS X 10.9.2
  and up.
* Removed: `Sabre\DAV\Server::NODE_*` constants.
* Moved all precondition checking into a central place, instead of having to
  think about it on a per-method basis.
* jCal transformation for calendar-query REPORT now works again.
* Switched to PSR-4
* Fixed: #175. Returning ETag header upon a failed `If-Match` or
  `If-None-Match` check.
* Removed: `lib/Sabre/autoload.php`. Use `vendor/autoload.php` instead.
* Removed: all the rfc documentation from the sabre/dav source. This made the
  package needlessly larger.
* Updated: Issue #439. Lots of updates in PATCH support. The
  Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be
  removed in a future version.
* Added: `Sabre\DAV\Exception\LengthRequired`.

1.9.0-alpha2 (2014-01-14)
-------------------------

* The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.1, and
  sabre/event 1.0.0.
* Added: Browser can now inspect any node, if ?sabreaction=browser is appended.
* Fixed: Issue #178. Support for multiple items in the Timeout header.
* Fixed: Issue #382. Stricter checking if calendar-query is allowed to run.
* Added: Depth: Infinity support for PROPFIND request. Thanks Thomas Müller and
  Markus Goetz.


1.9.0-alpha1 (2013-11-07)
-------------------------

* The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.0alpha5, and
  sabre/event 1.0.0.
* BC Break: The CardDAV and CalDAV BackendInterface each have a new method:
  getMultipleCards and getMultipleCalendarObjects. The Abstract and PDO backends
  have default implementations, but if you implement that interface directly,
  this method is now required.
* BC Break: XML property classes now receive an extra argument in their
  unserialize method ($propertyMap). This allows for recursively parsing
  properties, if needed.
* BC Break: Now using sabre/event for event emitting/subscription. For plugin
  authors this means Server::subscribeEvent is now Server::on, and
  Server::broadcastEvent is now Server::emit.
* BC Break: Almost all core functionality moved into a CorePlugin.
* BC Break: Most events triggered by the server got an overhaul.
* Changed: Sabre\HTTP now moved into a dedicated sabre/http package.
* Added: Support for WebDAV-sync (rfc6578).
* Added: Support for caldav-subscriptions, which is an easy way for caldav
  clients to manage a list of subscriptions on the server.
* Added: Support for emitting and receiving jCal instead of iCalendar for
  CalDAV.
* Added: BasicCallback authenticaton backend, for creating simple authentication
  systems without having to define any classes.
* Added: A $transactionType property on the server class. This can be used for
  logging and performance measuring purposes.
* Fixed: If event handlers modify the request body from a PUT request, an ETag
  is no longer sent back.
* Added: Sabre\DAV\IMultiGet to optimize requests that retrieve information
  about lists of resources.
* Added: MultiGet support to default CalDAV and CardDAV backends, speeding up
  the multiget and sync reports quite a bit!
* Added: ICSExportPlugin can now generate jCal, filter on time-ranges and expand
  recurrences.
* Fixed: Read-only access to calendars still allows the sharee to modify basic
  calendar properties, such as the displayname and color.
* Changed: The default supportedPrivilegeSet has changed. Most privileges are no
  longer marked as abstract.
* Changed: More elegant ACL management for CalendarObject and Card nodes.
* Added: Browser plugin now marks a carddav directory as type Directory, and a
  shared calendar as 'Shared'.
* Added: When debugExceptions is turned on, all previous exceptions are also
  traversed.
* Removed: Got rid of the Version classes for CalDAV, CardDAV, HTTP, and DAVACL.
  Now that there's no separate packages anymore, this makes a bit more sense.
* Added: Generalized the multistatus response parser a bit more, for better
  re-use.
* Added: Sabre\DAV\Client now has support for complex properties for PROPPATCH.
  (Issue #299).
* Added: Sabre\DAV\Client has support for gzip and deflate encoding.
* Added: Sabre\DAV\Client now has support for sending objects as streams.
* Added: Deserializer for {DAV:}current-user-privilege-set.
* Added: Addressbooks or backends can now specify custom acl rules when creating
  cards.
* Added: The ability for plugins to validate custom tokens in If: headers.
* Changed: Completely refactored the Lock plugin to deal with the new If: header
  system.
* Added: Checking preconditions for MOVE, COPY, DELETE and PROPPATCH methods.
* Added: has() method on DAV\Property\SupportedReportSet.
* Added: If header now gets checked (with ETag) all the time. Before the dealing
  with the If-header was a responsibility of the Locking plugin.
* Fixed: Outbox access for delegates.
* Added: Issue 333: It's now possible to override the calendar-home in the
  CalDAV plugin.
* Added: A negotiateContentType to HTTP\Request. A convenience method.
* Fixed: Issue 349: Denying copying or moving a resource into it's own subtree.
* Fixed: SabreDAV catches every exception again.
* Added: Issue #358, adding a component=vevent parameter to the content-types
  for calendar objects, if the caldav backend provides this info.


1.8.12-stable (2015-01-21)
--------------------------

* The zip release ships with sabre/vobject 2.1.7.
* #568: Support empty usernames and passwords in basic auth.


1.8.11 (2014-12-10)
-------------------

* The zip release ships with sabre/vobject 2.1.6.
* Updated: MySQL database schema optimized by using more efficient column types.
* #516: The DAV client will now only redirect to HTTP and HTTPS urls.


1.8.10 (2014-05-15)
-------------------

* The zip release ships with sabre/vobject 2.1.4.
* includes changes from version 1.7.12.


1.8.9 (2014-02-26)
------------------

* The zip release ships with sabre/vobject 2.1.3.
* includes changes from version 1.7.11.


1.8.8 (2014-02-09)
------------------

* includes changes from version 1.7.10.
* The zip release ships with sabre/vobject 2.1.3.

1.8.7 (2013-10-02)
------------------

* the zip release ships with sabre/vobject 2.1.3.
* includes changes from version 1.7.9.


1.8.6 (2013-06-18)
------------------

* The zip release ships with sabre/vobject 2.1.0.
* Includes changes from version 1.7.8.


1.8.5 (2013-04-11)
------------------

* The zip release ships with sabre/vobject 2.0.7.
* Includes changes from version 1.7.7.


1.8.4 (2013-04-08)
------------------

* The zip release ships with sabre/vobject 2.0.7.
* Includes changes from version 1.7.6.


1.8.3 (2013-03-01)
------------------

* The zip release ships with sabre/vobject 2.0.6.
* Includes changes from version 1.7.5.
* Fixed: organizer email-address for shared calendars is now prefixed with
  mailto:, as it should.


1.8.2 (2013-01-19)
------------------

* The zip release ships with sabre/vobject 2.0.5.
* Includes changes from version 1.7.4.


1.8.1 (2012-12-01)
------------------

* The zip release ships with sabre/vobject 2.0.5.
* Includes changes from version 1.7.3.
* Fixed: Typo in 1.7 migration script caused it to fail.


1.8.0 (2012-11-08)
------------------

* The zip release ships with sabre/vobject 2.0.5.
* BC Break: Moved the entire codebase to PHP namespaces.
* BC Break: Every backend package (CalDAV, CardDAV, Auth, Locks, Principals) now
  has consistent naming conventions. There's a BackendInterface, and an
  AbstractBackend class.
* BC Break: Changed a bunch of constructor signatures in the CalDAV package, to
  reduce dependencies on the ACL package.
* BC Break: Sabre_CalDAV_ISharedCalendar now also has a getShares method, so
  sharees can figure out who is also on a shared calendar.
* Added: Sabre_DAVACL_IPrincipalCollection interface, to advertise support for
  principal-property-search on any node.
* Added: Simple console script to fire up a fileserver in the current directory
  using PHP 5.4's built-in webserver.
* Added: Sharee's can now also read out the list of invites for a shared
  calendar.
* Added: The Proxy principal classes now both implement an interface, for
  greater flexibility.


1.7.13 (2014-07-28)
-------------------

* The zip release ships with sabre/vobject 2.1.4.
* Changed: Removed phing and went with a custom build script for now.


1.7.12 (2014-05-15)
-------------------

* The zip release ships with sabre/vobject 2.1.4.
* Updated: Issue #439. Lots of updates in PATCH support. The
  Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be removed
  in a future version.
* Fixed: Restoring old setting after changing libxml_disable_entity_loader.
* Fixed: Issue #422: Preconditions were not being set on PUT on non-existent
  files. Not really a chance for data-loss, but incorrect nevertheless.
* Fixed: Issue #427: Now checking preconditions on DELETE requests.
* Fixed: Issue #428: Etag check with If: fails if the target is a collection.
* Fixed: Issue #393: PATCH request with missing end-range was handled
  incorrectly.
* Added: Sabre_DAV_Exception_LengthRequired to omit 411 errors.


1.7.11 (2014-02-26)
-------------------

* The zip release ships with sabre/vobject 2.1.3.
* Fixed: Issue #407: large downloads failed.
* Fixed: Issue #414: XXE security problem on older PHP versions.


1.7.10 (2014-02-09)
-------------------

* Fixed: Issue #374: Don't urlescape colon (:) when it's not required.
* Fixed: Potential security vulnerability in the http client.


1.7.9 (2013-10-02)
------------------

* The zip release ships with sabre/vobject 2.1.3.
* Fixed: Issue #365. Incorrect output when principal urls have spaces in them.
* Added: Issue #367: Automatically adding a UID to vcards that don't have them.


1.7.8 (2013-06-17)
------------------

* The zip release ships with sabre/vobject 2.1.0.
* Changed: Sabre\DAV\Client::verifyPeer is now a protected property (instead of
  private).
* Fixed: Text was incorrectly escaped in the Href and HrefList properties,
  disallowing urls with ampersands (&) in them.
* Added: deserializer for Sabre\DAVACL\Property\CurrentUserPrivilegeSet.
* Fixed: Issue 335: Client only deserializes properties with status 200.
* Fixed: Issue 341: Escaping xml in 423 Locked error responses.
* Added: Issue 339: beforeGetPropertiesForPath event.


1.7.7 (2013-04-11)
------------------

* The zip release ships with sabre/vobject 2.0.7.
* Fixed: Assets in the browser plugins were not being served on windows
  machines.


1.7.6 (2013-04-08)
------------------

* The zip release ships with sabre/vobject 2.0.7.
* Fixed: vcardurl in database schema can now hold 255 characters instead of 80
  (which is often way to small).
* Fixed: The browser plugin potentially allowed people to open any arbitrary
  file on windows servers (CVE-2013-1939).


1.7.5 (2013-03-01)
------------------

* The zip release ships with sabre/vobject 2.0.6.
* Change: No longer advertising support for 4.0 vcards. iOS and OS X address
  book don't handle this well, and just advertising 3.0 support seems like the
  most logical course of action.
* Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it,
  don't use this..).


1.7.4 (2013-01-19)
------------------

* The zip release ships with sabre/vobject 2.0.5.
* Changed: To be compatible with MS Office 2011 for Mac, a workaround was
  removed that was added to support old versions of Windows XP (pre-SP3).
  Indeed! We needed a crazy workaround to work with one MS product in the past,
  and we can't keep that workaround to be compatible with another MS product.
* Fixed: expand-properties REPORT had incorrect values for the href element.
* Fixed: Range requests now work for non-seekable streams. (Thanks Alfred
  Klomp).
* Fixed: Changed serialization of {DAV:}getlastmodified and {DAV:}supportedlock
  to improve compatibility with MS Office 2011 for Mac.
* Changed: reverted the automatic translation of 'DAV:' xml namespaces to
  'urn:DAV' when parsing files. Issues were reported with libxml 2.6.32, on a
  relatively recent debian release, so we'll wait till 2015 to take this one out
  again.
* Added: Sabre_DAV_Exception_ServiceUnavailable, for emitting 503's.


1.7.3 (2012-12-01)
------------------

* The zip release ships with sabre/vobject 2.0.5.
* Fixed: Removing double slashes from getPropertiesForPath.
* Change: Marked a few more properties in the CardDAV as protected, instead of
  private.
* Fixed: SharingPlugin now plays nicer with other plugins with similar
  functionality.
* Fixed: Issue 174. Sending back HTTP/1.0 for requests with this version.


1.7.2 (2012-11-08)
------------------

* The zip release ships with sabre/vobject 2.0.5.
* Added: ACL plugin advertises support for 'calendarserver-principal-
  property-search'.
* Fixed: [#153] Allowing for relative http principals in iMip requests.
* Added: Support for cs:first-name and cs:last-name properties in sharing
  invites.
* Fixed: Made a bunch of properties protected, where they were private before.
* Added: Some non-standard properties for sharing to improve compatibility.
* Fixed: some bugfixes in postgres sql script.
* Fixed: When requesting some properties using PROPFIND, they could show up as
  both '200 Ok' and '403 Forbidden'.
* Fixed: calendar-proxy principals were not checked for deeper principal
  membership than 1 level.
* Fixed: setGroupMemberSet argument now correctly receives relative principal
  urls, instead of the absolute ones.
* Fixed: Server class will filter out any bonus properties if any extra were
  returned. This means the implementor of the IProperty class can be a bit
  lazier when implementing. Note: bug numbers after this line refer to Google
  Code tickets. We're using github now.


1.7.1 (2012-10-07)
------------------

* Fixed: include path problem in the migration script.


1.7.0 (2012-10-06)
------------------

* BC Break: The calendarobjects database table has a bunch of new fields, and a
  migration script is required to ensure everything will keep working. Read the
  wiki for more details.
* BC Break: The ICalendar interface now has a new method: calendarQuery.
* BC Break: In this version a number of classes have been deleted, that have
  been previously deprecated. Namely: - Sabre_DAV_Directory (now:
  Sabre_DAV_Collection) - Sabre_DAV_SimpleDirectory (now:
  Sabre_DAV_SimpleCollection)
* BC Break: Sabre_CalDAV_Schedule_IMip::sendMessage now has an extra argument.
  If you extended this class, you should fix this method. It's only used for
  informational purposes.
* BC Break: The DAV: namespace is no longer converted to urn:DAV. This was a
  workaround for a bug in older PHP versions (pre-5.3).
* Removed: Sabre.includes.php was deprecated, and is now removed.
* Removed: Sabre_CalDAV_Server was deprecated, and is now removed. Please use
  Sabre_DAV_Server and check the examples in the examples/ directory.
* Changed: The Sabre_VObject library now spawned into it's own project! The
  VObject library is still included in the SabreDAV zip package.
* Added: Experimental interfaces to allow implementation of caldav-sharing. Note
  that no implementation is provided yet, just the api hooks.
* Added: Free-busy reporting compliant with the caldav-scheduling standard. This
  allows iCal and other clients to fetch other users' free-busy data.
* Added: Experimental NotificationSupport interface to add caldav notifications.
* Added: VCF Export plugin. If enabled, it can generate an export of an entire
  addressbook.
* Added: Support for PATCH using a SabreDAV format, to live-patch files.
* Added: Support for Prefer: return-minimal and Brief: t headers for PROPFIND
  and PROPPATCH requests.
* Changed: Responsibility for dealing with the calendar-query is now moved from
  the CalDAV plugin to the CalDAV backends. This allows for heavy optimizations.
* Changed: The CalDAV PDO backend is now a lot faster for common calendar
  queries.
* Changed: We are now using the composer autoloader.
* Changed: The CalDAV backend now all implement an interface.
* Changed: Instead of Sabre_DAV_Property, Sabre_DAV_PropertyInterface is now the
  basis of every property class.
* Update: Caching results for principal lookups. This should cut down queries
  and performance for a number of heavy requests.
* Update: ObjectTree caches lookups much more aggresively, which will help
  especially speeding up a bunch of REPORT queries.
* Added: Support for the schedule-calendar-transp property.
* Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 encoded.
* Fixed: Workaround for the SOGO connector, as it doesn't understand receiving
  "text/x-vcard; charset=utf-8" for a contenttype.
* Added: Sabre_DAV_Client now throws more specific exceptions in cases where we
  already has an exception class.
* Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the PATCH method
  to update parts of a file.
* Added: Tons of timezone name mappings for Microsoft Exchange.
* Added: Support for an 'exception' event in the server class.
* Fixed: Uploaded VCards without a UID are now rejected. (thanks Dominik!)
* Fixed: Rejecting calendar objects if they are not in the
  supported-calendar-component list. (thanks Armin!)
* Fixed: Issue 219: serialize() now reorders correctly.
* Fixed: Sabre_DAV_XMLUtil no longer returns empty $dom->childNodes if there is
  whitespace in $dom.
* Fixed: Returning 409 Conflict instead of 500 when an attempt is made to create
  a file as a child of something that's not a collection.
* Fixed: Issue 237: xml-encoding values in SabreDAV error responses.
* Fixed: Returning 403, instead of 501 when an unknown REPORT is requested.
* Fixed: Postfixing slash on {DAV:}owner properties.
* Fixed: Several embarrassing spelling mistakes in docblocks.


1.6.10 (2013-06-17)
-------------------

* Fixed: Text was incorrectly escaped in the Href and HrefList properties,
  disallowing urls with ampersands (&) in them.
* Fixed: Issue 341: Escaping xml in 423 Locked error responses.


1.6.9 (2013-04-11)
------------------

* Fixed: Assets in the browser plugins were not being served on windows
  machines.


1.6.8 (2013-04-08)
------------------

* Fixed: vcardurl in database schema can now hold 255 characters instead of 80
  (which is often way to small).
* Fixed: The browser plugin potentially allowed people to open any arbitrary
  file on windows servers. (CVE-2013-1939).


1.6.7 (2013-03-01)
------------------

* Change: No longer advertising support for 4.0 vcards. iOS and OS X address
  book don't handle this well, and just advertising 3.0 support seems like the
  most logical course of action.
* Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it,
  don't use this..).


1.6.6 (2013-01-19)
------------------

* Fixed: Backported a fix for broken XML serialization in error responses.
  (Thanks @DeepDiver1975!)


1.6.5 (2012-10-04)
------------------

* Fixed: Workaround for line-ending bug OS X 10.8 addressbook has.
* Added: Ability to allow users to set SSL certificates for the Client class.
  (Thanks schiesbn!).
* Fixed: Directory indexes with lots of nodes should be a lot faster.
* Fixed: Issue 235: E_NOTICE thrown when doing a propfind request with
  Sabre_DAV_Client, and no valid properties are returned.
* Fixed: Issue with filtering on alarms in tasks.


1.6.4 (2012-08-02)
------------------

* Fixed: Issue 220: Calendar-query filters may fail when filtering on alarms, if
  an overridden event has it's alarm removed.
* Fixed: Compatibility for OS/X 10.8 iCal in the IMipHandler.
* Fixed: Issue 222: beforeWriteContent shouldn't be called for lock requests.
* Fixed: Problem with POST requests to the outbox if mailto: was not lower
  cased.
* Fixed: Yearly recurrence rule expansion on leap-days no behaves correctly.
* Fixed: Correctly checking if recurring, all-day events with no dtstart fall in
  a timerange if the start of the time-range exceeds the start of the instance
  of an event, but not the end.
* Fixed: All-day recurring events wouldn't match if an occurence ended exactly
  on the start of a time-range.
* Fixed: HTTP basic auth did not correctly deal with passwords containing colons
  on some servers.
* Fixed: Issue 228: DTEND is now non-inclusive for all-day events in the
  calendar-query REPORT and free-busy calculations.


1.6.3 (2012-06-12)
------------------

* Added: It's now possible to specify in Sabre_DAV_Client which type of
  authentication is to be used.
* Fixed: Issue 206: Sabre_DAV_Client PUT requests are fixed.
* Fixed: Issue 205: Parsing an iCalendar 0-second date interval.
* Fixed: Issue 112: Stronger validation of iCalendar objects. Now making sure
  every iCalendar object only contains 1 component, and disallowing vcards,
  forcing every component to have a UID.
* Fixed: Basic validation for vcards in the CardDAV plugin.
* Fixed: Issue 213: Workaround for an Evolution bug, that prevented it from
  updating events.
* Fixed: Issue 211: A time-limit query on a non-relative alarm trigger in a
  recurring event could result in an endless loop.
* Fixed: All uri fields are now a maximum of 200 characters. The Bynari outlook
  plugin used much longer strings so this should improve compatibility.
* Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See
  https://bugs.kde.org/show_bug.cgi?id=300047
* Fixed: Issue 217: Sabre_DAV_Tree_FileSystem was pretty broken.


1.6.2 (2012-04-16)
------------------

* Fixed: Sabre_VObject_Node::$parent should have been public.
* Fixed: Recurrence rules of events are now taken into consideration when doing
  time-range queries on alarms.
* Fixed: Added a workaround for the fact that php's DateInterval cannot parse
  weeks and days at the same time.
* Added: Sabre_DAV_Server::$exposeVersion, allowing you to hide SabreDAV's
  version number from various outputs.
* Fixed: DTSTART values would be incorrect when expanding events.
* Fixed: DTSTART and DTEND would be incorrect for expansion of WEEKLY BYDAY
  recurrences.
* Fixed: Issue 203: A problem with overridden events hitting the exact date and
  time of a subsequent event in the recurrence set.
* Fixed: There was a problem with recurrence rules, for example the 5th tuesday
  of the month, if this day did not exist.
* Added: New HTTP status codes from draft-nottingham-http-new-status-04.


1.6.1 (2012-03-05)
------------------

* Added: createFile and put() can now return an ETag.
* Added: Sending back an ETag on for operations on CardDAV backends. This should
  help with OS X 10.6 Addressbook compatibility.
* Fixed: Fixed a bug where an infinite loop could occur in the recurrence
  iterator if the recurrence was YEARLY, with a BYMONTH rule, and either BYDAY
  or BYMONTHDAY match the first day of the month.
* Fixed: Events that are excluded using EXDATE are still counted in the COUNT=
  parameter in the RRULE property.
* Added: Support for time-range filters on VALARM components.
* Fixed: Correctly filtering all-day events.
* Fixed: Sending back correct mimetypes from the browser plugin (thanks
  Jürgen).
* Fixed: Issue 195: Sabre_CardDAV pear package had an incorrect dependency.
* Fixed: Calendardata would be destroyed when performing a MOVE request.


1.6.0 (2012-02-22)
------------------

* BC Break: Now requires PHP 5.3
* BC Break: Any node that implemented Sabre_DAVACL_IACL must now also implement
  the getSupportedPrivilegeSet method. See website for details.
* BC Break: Moved functions from Sabre_CalDAV_XMLUtil to
  Sabre_VObject_DateTimeParser.
* BC Break: The Sabre_DAVACL_IPrincipalCollection now has two new methods:
  'searchPrincipals' and 'updatePrincipal'.
* BC Break: Sabre_DAV_ILockable is removed and all related per-node locking
  functionality.
* BC Break: Sabre_DAV_Exception_FileNotFound is now deprecated in favor of
  Sabre_DAV_Exception_NotFound. The former will be removed in a later version.
* BC Break: Removed Sabre_CalDAV_ICalendarUtil, use Sabre_VObject instead.
* BC Break: Sabre_CalDAV_Server is now deprecated, check out the documentation
  on how to setup a caldav server with just Sabre_DAV_Server.
* BC Break: Default Principals PDO backend now needs a new field in the
  'principals' table. See the website for details.
* Added: Ability to create new calendars and addressbooks from within the
  browser plugin.
* Added: Browser plugin: icons for various nodes.
* Added: Support for FREEBUSY reports!
* Added: Support for creating principals with admin-level privileges.
* Added: Possibility to let server send out invitation emails on behalf of
  CalDAV client, using Sabre_CalDAV_Schedule_IMip.
* Changed: beforeCreateFile event now passes data argument by reference.
* Changed: The 'propertyMap' property from Sabre_VObject_Reader, must now be
  specified in Sabre_VObject_Property::$classMap.
* Added: Ability for plugins to tell the ACL plugin which principal plugins are
  searchable.
* Added: [DAVACL] Per-node overriding of supported privileges. This allows for
  custom privileges where needed.
* Added: [DAVACL] Public 'principalSearch' method on the DAVACL plugin, which
  allows for easy searching for principals, based on their properties.
* Added: Sabre_VObject_Component::getComponents() to return a list of only
  components and not properties.
* Added: An includes.php file in every sub-package (CalDAV, CardDAV, DAV,
  DAVACL, HTTP, VObject) as an alternative to the autoloader. This often works
  much faster.
* Added: Support for the 'Me card', which allows Addressbook.app users specify
  which vcard is their own.
* Added: Support for updating principal properties in the DAVACL principal
  backends.
* Changed: Major refactoring in the calendar-query REPORT code. Should make
  things more flexible and correct.
* Changed: The calendar-proxy-[read|write] principals will now only appear in
  the tree, if they actually exist in the Principal backend. This should reduce
  some problems people have been having with this.
* Changed: Sabre_VObject_Element_* classes are now renamed to
  Sabre_VObject_Property. Old classes are retained for backwards compatibility,
  but this will be removed in the future.
* Added: Sabre_VObject_FreeBusyGenerator to generate free-busy reports based on
  lists of events.
* Added: Sabre_VObject_RecurrenceIterator to find all the dates and times for
  recurring events.
* Fixed: Issue 97: Correctly handling RRULE for the calendar-query REPORT.
* Fixed: Issue 154: Encoding of VObject parameters with no value was incorrect.
* Added: Support for {DAV:}acl-restrictions property from RFC3744.
* Added: The contentlength for calendar objects can now be supplied by a CalDAV
  backend, allowing for more optimizations.
* Fixed: Much faster implementation of Sabre_DAV_URLUtil::encodePath.
* Fixed: {DAV:}getcontentlength may now be not specified.
* Fixed: Issue 66: Using rawurldecode instead of urldecode to decode paths from
  clients. This means that + will now be treated as a literal rather than a
  space, and this should improve compatibility with the Windows built-in client.
* Added: Sabre_DAV_Exception_PaymentRequired exception, to emit HTTP 402 status
  codes.
* Added: Some mysql unique constraints to example files.
* Fixed: Correctly formatting HTTP dates.
* Fixed: Issue 94: Sending back Last-Modified header for 304 responses.
* Added: Sabre_VObject_Component_VEvent, Sabre_VObject_Component_VJournal,
  Sabre_VObject_Component_VTodo and Sabre_VObject_Component_VCalendar.
* Changed: Properties are now also automatically mapped to their appropriate
  classes, if they are created using the add() or __set() methods.
* Changed: Cloning VObject objects now clones the entire tree, rather than just
  the default shallow copy.
* Added: Support for recurrence expansion in the CALDAV:calendar-multiget and
  CALDAV:calendar-query REPORTS.
* Changed: CalDAV PDO backend now sorts calendars based on the internal
  'calendarorder' field.
* Added: Issue 181: Carddav backends may no optionally not supply the carddata
  in getCards, if etag and size are specified. This may speed up certain
  requests.
* Added: More arguments to beforeWriteContent and beforeCreateFile (see
  WritingPlugins wiki document).
* Added: Hook for iCalendar validation. This allows us to validate iCalendar
  objects when they're uploaded. At the moment we're just validating syntax.
* Added: VObject now support Windows Timezone names correctly (thanks mrpace2).
* Added: If a timezonename could not be detected, we fall back on the default
  PHP timezone.
* Added: Now a Composer package (thanks willdurand).
* Fixed: Support for \N as a newline character in the VObject reader.
* Added: afterWriteContent, afterCreateFile and afterUnbind events.
* Added: Postgresql example files. Not part of the unittests though, so use at
  your own risk.
* Fixed: Issue 182: Removed backticks from sql queries, so it will work with
  Postgres.


1.5.9 (2012-04-16)
------------------

* Fixed: Issue with parsing timezone identifiers that were surrounded by quotes.
  (Fixes emClient compatibility).


1.5.8 (2012-02-22)
------------------

* Fixed: Issue 95: Another timezone parsing issue, this time in calendar-query.


1.5.7 (2012-02-19)
------------------

* Fixed: VObject properties are now always encoded before components.
* Fixed: Sabre_DAVACL had issues with multiple levels of privilege aggregration.
* Changed: Added 'GuessContentType' plugin to fileserver.php example.
* Fixed: The Browser plugin will now trigger the correct events when creating
  files.
* Fixed: The ICSExportPlugin now considers ACL's.
* Added: Made it optional to supply carddata from an Addressbook backend when
  requesting getCards. This can make some operations much faster, and could
  result in much lower memory use.
* Fixed: Issue 187: Sabre_DAV_UUIDUtil was missing from includes file.
* Fixed: Issue 191: beforeUnlock was triggered twice.


1.5.6 (2012-01-07)
------------------

* Fixed: Issue 174: VObject could break UTF-8 characters.
* Fixed: pear package installation issues.


1.5.5 (2011-12-16)
------------------

* Fixed: CalDAV time-range filter workaround for recurring events.
* Fixed: Bug in Sabre_DAV_Locks_Backend_File that didn't allow multiple files to
  be locked at the same time.


1.5.4 (2011-10-28)
------------------

* Fixed: GuessContentType plugin now supports mixed case file extensions.
* Fixed: DATE-TIME encoding was wrong in VObject. (we used 'DATETIME').
* Changed: Sending back HTTP 204 after a PUT request on an existing resource
  instead of HTTP 200. This should fix Evolution CardDAV client compatibility.
* Fixed: Issue 95: Parsing X-LIC-LOCATION if it's available.
* Added: All VObject elements now have a reference to their parent node.


1.5.3 (2011-09-28)
------------------

* Fixed: Sabre_DAV_Collection was missing from the includes file.
* Fixed: Issue 152. iOS 1.4.2 apparantly requires HTTP/1.1 200 OK to be in
  uppercase.
* Fixed: Issue 153: Support for files with mixed newline styles in
  Sabre_VObject.
* Fixed: Issue 159: Automatically converting any vcard and icalendardata to
  UTF-8.
* Added: Sabre_DAV_SimpleFile class for easy static file creation.
* Added: Issue 158: Support for the CARDDAV:supported-address-data property.


1.5.2 (2011-09-21)
------------------

* Fixed: carddata and calendardata MySQL fields are now of type 'mediumblob'.
  'TEXT' was too small sometimes to hold all the data.
* Fixed: {DAV:}supported-report-set is now correctly reporting the reports for
  IAddressBook.
* Added: Sabre_VObject_Property::add() to add duplicate parameters to
  properties.
* Added: Issue 151: Sabre_CalDAV_ICalendar and Sabre_CalDAV_ICalendarObject
  interfaces.
* Fixed: Issue 140: Not returning 201 Created if an event cancelled the creation
  of a file.
* Fixed: Issue 150: Faster URLUtil::encodePath() implementation.
* Fixed: Issue 144: Browser plugin could interfere with
  TemporaryFileFilterPlugin if it was loaded first.
* Added: It's not possible to specify more 'alternate uris' in principal
  backends.


1.5.1 (2011-08-24)
------------------

* Fixed: Issue 137. Hiding action interface in HTML browser for non-collections.
* Fixed: addressbook-query is now correctly returned from the
  {DAV:}supported-report-set property.
* Fixed: Issue 142: Bugs in groupwareserver.php example.
* Fixed: Issue 139: Rejecting PUT requests with Content-Range.


1.5.0 (2011-08-12)
------------------

* Added: CardDAV support.
* Added: An experimental WebDAV client.
* Added: MIME-Directory grouping support in the VObject library. This is very
  useful for people attempting to parse vcards.
* BC Break: Adding parameters with the VObject libraries now overwrites the
  previous parameter, rather than just add it. This makes more sense for 99% of
  the cases.
* BC Break: lib/Sabre.autoload.php is now removed in favor of
  lib/Sabre/autoload.php.
* Deprecated: Sabre_DAV_Directory is now deprecated and will be removed in a
  future version. Use Sabre_DAV_Collection instead.
* Deprecated: Sabre_DAV_SimpleDirectory is now deprecated and will be removed in
  a future version. Use Sabre_DAV_SimpleCollection instead.
* Fixed: Problem with overriding tablenames for the CalDAV backend.
* Added: Clark-notation parser to XML utility.
* Added: unset() support to VObject components.
* Fixed: Refactored CalDAV property fetching to be faster and simpler.
* Added: Central string-matcher for CalDAV and CardDAV plugins.
* Added: i;unicode-casemap support
* Fixed: VObject bug: wouldn't parse parameters if they weren't specified in
  uppercase.
* Fixed: VObject bug: Parameters now behave more like Properties.
* Fixed: VObject bug: Parameters with no value are now correctly parsed.
* Changed: If calendars don't specify which components they allow, 'all'
  components are assumed (e.g.: VEVENT, VTODO, VJOURNAL).
* Changed: Browser plugin now uses POST variable 'sabreAction' instead of
  'action' to reduce the chance of collisions.


1.4.4 (2011-07-07)
------------------

* Fixed: Issue 131: Custom CalDAV backends could break in certain cases.
* Added: The option to override the default tablename all PDO backends use.
  (Issue 60).
* Fixed: Issue 124: 'File' authentication backend now takes realm into
  consideration.
* Fixed: Sabre_DAV_Property_HrefList now properly deserializes. This allows
  users to update the {DAV:}group-member-set property.
* Added: Helper functions for DateTime-values in Sabre_VObject package.
* Added: VObject library can now automatically map iCalendar properties to
  custom classes.


1.4.3 (2011-04-25)
------------------

* Fixed: Issue 123: Added workaround for Windows 7 UNLOCK bug.
* Fixed: datatype of lastmodified field in mysql.calendars.sql. Please change
  the DATETIME field to an INT to ensure this field will work correctly.
* Change: Sabre_DAV_Property_Principal is now renamed to
  Sabre_DAVACL_Property_Principal.
* Added: API level support for ACL HTTP method.
* Fixed: Bug in serializing {DAV:}acl property.
* Added: deserializer for {DAV:}resourcetype property.
* Added: deserializer for {DAV:}acl property.
* Added: deserializer for {DAV:}principal property.


1.4.2-beta (2011-04-01)
-----------------------

* Added: It's not possible to disable listing of nodes that are denied read
  access by ACL.
* Fixed: Changed a few properties in CalDAV classes from private to protected.
* Fixed: Issue 119: Terrible things could happen when relying on guessBaseUri,
  the server was running on the root of the domain and a user tried to access a
  file ending in .php. This is a slight BC break.
* Fixed: Issue 118: Lock tokens in If headers without a uri should be treated as
  the request uri, not 'all relevant uri's.
* Fixed: Issue 120: PDO backend was incorrectly fetching too much locks in cases
  where there were similar named locked files in a directory.


1.4.1-beta (2011-02-26)
-----------------------

* Fixed: Sabre_DAV_Locks_Backend_PDO returned too many locks.
* Fixed: Sabre_HTTP_Request::getHeader didn't return Content-Type when running
  on apache, so a few workarounds were added.
* Change: Slightly changed CalDAV Backend API's, to allow for heavy
  optimizations. This is non-bc breaking.


1.4.0-beta (2011-02-12)
-----------------------

* Added: Partly RFC3744 ACL support.
* Added: Calendar-delegation (caldav-proxy) support.
* BC break: In order to fix Issue 99, a new argument had to be added to
  Sabre_DAV_Locks_Backend_*::getLocks classes. Consult the classes for details.
* Deprecated: Sabre_DAV_Locks_Backend_FS is now deprecated and will be removed
  in a later version. Use PDO or the new File class instead.
* Deprecated: The Sabre_CalDAV_ICalendarUtil class is now marked deprecated, and
  will be removed in a future version. Please use Sabre_VObject instead.
* Removed: All principal-related functionality has been removed from the
  Sabre_DAV_Auth_Plugin, and moved to the Sabre_DAVACL_Plugin.
* Added: VObject library, for easy vcard/icalendar parsing using a natural
  interface.
* Added: Ability to automatically generate full .ics feeds off calendars. To
  use: Add the Sabre_CalDAV_ICSExportPlugin, and add ?export to your calendar
  url.
* Added: Plugins can now specify a pluginname, for easy access using
  Sabre_DAV_Server::getPlugin().
* Added: beforeGetProperties event.
* Added: updateProperties event.
* Added: Principal listings and calendar-access can now be done privately,
  disallowing users from accessing or modifying other users' data.
* Added: You can now pass arrays to the Sabre_DAV_Server constructor. If it's an
  array with node-objects, a Root collection will automatically be created, and
  the nodes are used as top-level children.
* Added: The principal base uri is now customizable. It used to be hardcoded to
  'principals/[user]'.
* Added: getSupportedReportSet method in ServerPlugin class. This allows you to
  easily specify which reports you're implementing.
* Added: A '..' link to the HTML browser.
* Fixed: Issue 99: Locks on child elements were ignored when their parent nodes
  were deleted.
* Fixed: Issue 90: lockdiscovery property and LOCK response now include a
  {DAV}lockroot element.
* Fixed: Issue 96: support for 'default' collation in CalDAV text-match filters.
* Fixed: Issue 102: Ensuring that copy and move with identical source and
  destination uri's fails.
* Fixed: Issue 105: Supporting MKCALENDAR with no body.
* Fixed: Issue 109: Small fixes in Sabre_HTTP_Util.
* Fixed: Issue 111: Properly catching the ownername in a lock (if it's a string)
* Fixed: Sabre_DAV_ObjectTree::nodeExist always returned false for the root
  node.
* Added: Global way to easily supply new resourcetypes for certain node classes.
* Fixed: Issue 59: Allowing the user to override the authentication realm in
  Sabre_CalDAV_Server.
* Update: Issue 97: Looser time-range checking if there's a recurrence rule in
  an event. This fixes 'missing recurring events'.


1.3.0 (2010-10-14)
------------------

* Added: childExists method to Sabre_DAV_ICollection. This is an api break, so
  if you implement Sabre_DAV_ICollection directly, add the method.
* Changed: Almost all HTTP method implementations now take a uri argument,
  including events. This allows for internal rerouting of certain calls. If you
  have custom plugins, make sure they use this argument. If they don't, they
  will likely still work, but it might get in the way of future changes.
* Changed: All getETag methods MUST now surround the etag with double-quotes.
  This was a mistake made in all previous SabreDAV versions. If you don't do
  this, any If-Match, If-None-Match and If: headers using Etags will work
  incorrectly. (Issue 85).
* Added: Sabre_DAV_Auth_Backend_AbstractBasic class, which can be used to easily
  implement basic authentication.
* Removed: Sabre_DAV_PermissionDenied class. Use Sabre_DAV_Forbidden instead.
* Removed: Sabre_DAV_IDirectory interface, use Sabre_DAV_ICollection instead.
* Added: Browser plugin now uses {DAV:}displayname if this property is
  available.
* Added: Cache layer in the ObjectTree.
* Added: Tree classes now have a delete and getChildren method.
* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if the
  date is an exact match.
* Fixed: Support for multiple ETags in If-Match and If-None-Match headers.
* Fixed: Improved baseUrl handling.
* Fixed: Issue 67: Non-seekable stream support in ::put()/::get().
* Fixed: Issue 65: Invalid dates are now ignored.
* Updated: Refactoring in Sabre_CalDAV to make everything a bit more ledgable.
* Fixed: Issue 88, Issue 89: Fixed compatibility for running SabreDAV on
  Windows.
* Fixed: Issue 86: Fixed Content-Range top-boundary from 'file size' to 'file
  size'-1.


1.2.5 (2010-08-18)
------------------

* Fixed: Issue 73: guessBaseUrl fails for some servers.
* Fixed: Issue 67: SabreDAV works better with non-seekable streams.
* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if
  the date is an exact match.


1.2.4 (2010-07-13)
------------------

* Fixed: Issue 62: Guessing baseUrl fails when url contains a query-string.
* Added: Apache configuration sample for CGI/FastCGI setups.
* Fixed: Issue 64: Only returning calendar-data when it was actually requested.


1.2.3 (2010-06-26)
------------------

* Fixed: Issue 57: Supporting quotes around etags in If-Match and If-None-Match


1.2.2 (2010-06-21)
------------------

* Updated: SabreDAV now attempts to guess the BaseURI if it's not set.
* Updated: Better compatibility with BitKinex
* Fixed: Issue 56: Incorrect behaviour for If-None-Match headers and GET
  requests.
* Fixed: Issue with certain encoded paths in Browser Plugin.


1.2.1 (2010-06-07)
------------------

* Fixed: Issue 50, patch by Mattijs Hoitink.
* Fixed: Issue 51, Adding windows 7 lockfiles to TemporaryFileFilter.
* Fixed: Issue 38, Allowing custom filters to be added to TemporaryFileFilter.
* Fixed: Issue 53, ETags in the If: header were always failing. This behaviour
  is now corrected.
* Added: Apache Authentication backend, in case authentication through .htaccess
  is desired.
* Updated: Small improvements to example files.


1.2.0 (2010-05-24)
------------------

* Fixed: Browser plugin now displays international characters.
* Changed: More properties in CalDAV classes are now protected instead of
  private.


1.2.0beta3 (2010-05-14)
-----------------------

* Fixed: Custom properties were not properly sent back for allprops requests.
* Fixed: Issue 49, incorrect parsing of PROPPATCH, affecting Office 2007.
* Changed: Removed CalDAV items from includes.php, and added a few missing ones.


1.2.0beta2 (2010-05-04)
-----------------------

* Fixed: Issue 46: Fatal error for some non-existent nodes.
* Updated: some example sql to include email address.
* Added: 208 and 508 statuscodes from RFC5842.
* Added: Apache2 configuration examples


1.2.0beta1 (2010-04-28)
-----------------------

* Fixed: redundant namespace declaration in resourcetypes.
* Fixed: 2 locking bugs triggered by litmus when no Sabre_DAV_ILockable
  interface is used.
* Changed: using http://sabredav.org/ns for all custom xml properties.
* Added: email address property to principals.
* Updated: CalendarObject validation.


1.2.0alpha4 (2010-04-24)
------------------------

* Added: Support for If-Range, If-Match, If-None-Match, If-Modified-Since,
  If-Unmodified-Since.
* Changed: Brand new build system. Functionality is split up between Sabre,
  Sabre_HTTP, Sabre_DAV and Sabre_CalDAV packages. In addition to that a new
  non-pear package will be created with all this functionality combined.
* Changed: Autoloader moved to Sabre/autoload.php.
* Changed: The Allow: header is now more accurate, with appropriate HTTP methods
  per uri.
* Changed: Now throwing back Sabre_DAV_Exception_MethodNotAllowed on a few
  places where Sabre_DAV_Exception_NotImplemented was used.


1.2.0alpha3 (2010-04-20)
------------------------

* Update: Complete rewrite of property updating. Now easier to use and atomic.
* Fixed: Issue 16, automatically adding trailing / to baseUri.
* Added: text/plain is used for .txt files in GuessContentType plugin.
* Added: support for principal-property-search and principal-search-property-set
  reports.
* Added: Issue 31: Hiding exception information by default. Can be turned on
  with the Sabre_DAV_Server::$debugExceptions property.


1.2.0alpha2 (2010-04-08)
------------------------

* Added: Calendars are now private and can only be read by the owner.
* Fixed: double namespace declaration in multistatus responses.
* Added: MySQL database dumps. MySQL is now also supported next to SQLite.
* Added: expand-properties REPORT from RFC 3253.
* Added: Sabre_DAV_Property_IHref interface for properties exposing urls.
* Added: Issue 25: Throwing error on broken Finder behaviour.
* Changed: Authentication backend is now aware of current user.


1.2.0alpha1 (2010-03-31)
------------------------

* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special
  characters.
* Fixed: Issue 34: Incorrect Lock-Token response header for LOCK. Fixes Office
  2010 compatibility.
* Added: Issue 35: SabreDAV version to header to OPTIONS response to ease
  debugging.
* Fixed: Issue 36: Incorrect variable name, throwing error in some requests.
* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
* Fixed: Issue 39 & Issue 40: Basename fails on non-utf-8 locales.
* Added: More unittests.
* Added: SabreDAV version to all error responses.
* Added: URLUtil class for decoding urls.
* Changed: Now using pear.sabredav.org pear channel.
* Changed: Sabre_DAV_Server::getCopyAndMoveInfo is now a public method.


1.1.2-alpha (2010-03-18)
------------------------

* Added: RFC5397 - current-user-principal support.
* Fixed: Issue 27: encoding entities in property responses.
* Added: naturalselection script now allows the user to specify a 'minimum
  number of bytes' for deletion. This should reduce load due to less crawling
* Added: Full support for the calendar-query report.
* Added: More unittests.
* Added: Support for complex property deserialization through the static
  ::unserialize() method.
* Added: Support for modifying calendar-component-set
* Fixed: Issue 29: Added TIMEOUT_INFINITE constant


1.1.1-alpha (2010-03-11)
------------------------

* Added: RFC5689 - Extended MKCOL support.
* Fixed: Evolution support for CalDAV.
* Fixed: PDO-locks backend was pretty much completely broken. This is 100%
  unittested now.
* Added: support for ctags.
* Fixed: Comma's between HTTP methods in 'Allow' method.
* Changed: default argument for Sabre_DAV_Locks_Backend_FS. This means a
  datadirectory must always be specified from now on.
* Changed: Moved Sabre_DAV_Server::parseProps to
  Sabre_DAV_XMLUtil::parseProperties.
* Changed: Sabre_DAV_IDirectory is now Sabre_DAV_ICollection.
* Changed: Sabre_DAV_Exception_PermissionDenied is now
  Sabre_DAV_Exception_Forbidden.
* Changed: Sabre_CalDAV_ICalendarCollection is removed.
* Added: Sabre_DAV_IExtendedCollection.
* Added: Many more unittests.
* Added: support for calendar-timezone property.


1.1.0-alpha (2010-03-01)
------------------------

* Note: This version is forked from version 1.0.5, so release dates may be out
  of order.
* Added: CalDAV - RFC 4791
* Removed: Sabre_PHP_Exception. PHP has a built-in ErrorException for this.
* Added: PDO authentication backend.
* Added: Example sql for auth, caldav, locks for sqlite.
* Added: Sabre_DAV_Browser_GuessContentType plugin
* Changed: Authentication plugin refactored, making it possible to implement
  non-digest authentication.
* Fixed: Better error display in browser plugin.
* Added: Support for {DAV:}supported-report-set
* Added: XML utility class with helper functions for the WebDAV protocol.
* Added: Tons of unittests
* Added: PrincipalCollection and Principal classes
* Added: Sabre_DAV_Server::getProperties for easy property retrieval
* Changed: {DAV:}resourceType defaults to 0
* Changed: Any non-null resourceType now gets a / appended to the href value.
  Before this was just for {DAV:}collection's, but this is now also the case for
  for example {DAV:}principal.
* Changed: The Href property class can now optionally create non-relative uri's.
* Changed: Sabre_HTTP_Response now returns false if headers are already sent and
  header-methods are called.
* Fixed: Issue 19: HEAD requests on Collections
* Fixed: Issue 21: Typo in Sabre_DAV_Property_Response
* Fixed: Issue 18: Doesn't work with Evolution Contacts


1.0.15 (2010-05-28)
-------------------

* Added: Issue 31: Hiding exception information by default. Can be turned on
  with the Sabre_DAV_Server::$debugExceptions property.
* Added: Moved autoload from lib/ to lib/Sabre/autoload.php. This is also the
  case in the upcoming 1.2.0, so it will improve future compatibility.


1.0.14 (2010-04-15)
-------------------

* Fixed: double namespace declaration in multistatus responses.


1.0.13 (2010-03-30)
-------------------

* Fixed: Issue 40: Last references to basename/dirname


1.0.12 (2010-03-30)
-------------------

* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter.
* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special
  characters.
* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8.
* Fixed: Issue 39: Basename fails on non-utf-8 locales.
* Added: More unittests.
* Added: SabreDAV version to all error responses.
* Added: URLUtil class for decoding urls.
* Updated: Now using pear.sabredav.org pear channel.


1.0.11 (2010-03-23)
-------------------

* Non-public release. This release is identical to 1.0.10, but it is used to
  test releasing packages to pear.sabredav.org.


1.0.10 (2010-03-22)
-------------------

* Fixed: Issue 34: Invalid Lock-Token header response.
* Added: Issue 35: Adding SabreDAV version to HTTP OPTIONS responses.


1.0.9 (2010-03-19)
------------------

* Fixed: Issue 27: Entities not being encoded in PROPFIND responses.
* Fixed: Issue 29: Added missing TIMEOUT_INFINITE constant.


1.0.8 (2010-03-03)
------------------

* Fixed: Issue 21: typos causing errors
* Fixed: Issue 23: Comma's between methods in Allow header.
* Added: Sabre_DAV_ICollection interface, to aid in future compatibility.
* Added: Sabre_DAV_Exception_Forbidden exception. This will replace
  Sabre_DAV_Exception_PermissionDenied in the future, and can already be used to
  ensure future compatibility.


1.0.7 (2010-02-24)
------------------

* Fixed: Issue 19 regression for MS Office


1.0.6 (2010-02-23)
------------------

* Fixed: Issue 19: HEAD requests on Collections


1.0.5 (2010-01-22)
------------------

* Fixed: Fatal error when a malformed url was used for unlocking, in conjuction
  with Sabre.autoload.php due to a incorrect filename.
* Fixed: Improved unittests and build system


1.0.4 (2010-01-11)
------------------

* Fixed: needed 2 different releases. One for googlecode and one for pearfarm.
  This is to retain the old method to install SabreDAV until pearfarm becomes
  the standard installation method.


1.0.3 (2010-01-11)
------------------

* Added: RFC4709 support (davmount)
* Added: 6 unittests
* Added: naturalselection. A tool to keep cache directories below a specified
  theshold.
* Changed: Now using pearfarm.org channel server.


1.0.1 (2009-12-22)
------------------

* Fixed: Issue 15: typos in examples
* Fixed: Minor pear installation issues


1.0.0 (2009-11-02)
------------------

* Added: SimpleDirectory class. This class allows creating static directory
  structures with ease.
* Changed: Custom complex properties and exceptions now get an instance of
  Sabre_DAV_Server as their first argument in serialize()
* Changed: Href complex property now prepends server's baseUri
* Changed: delete before an overwriting copy/move is now handles by server class
  instead of tree classes
* Changed: events must now explicitly return false to stop execution. Before,
  execution would be stopped by anything loosely evaluating to false.
* Changed: the getPropertiesForPath method now takes a different set of
  arguments, and returns a different response. This allows plugin developers to
  return statuses for properties other than 200 and 404. The hrefs are now also
  always calculated relative to the baseUri, and not the uri of the request.
* Changed: generatePropFindResponse is renamed to generateMultiStatus, and now
  takes a list of properties similar to the response of getPropertiesForPath.
  This was also needed to improve flexibility for plugin development.
* Changed: Auth plugins are no longer included. They were not yet stable
  quality, so they will probably be reintroduced in a later version.
* Changed: PROPPATCH also used generateMultiStatus now.
* Removed: unknownProperties event. This is replaced by the afterGetProperties
  event, which should provide more flexibility.
* Fixed: Only calling getSize() on IFile instances in httpHead()
* Added: beforeBind event. This is invoked upon file or directory creation
* Added: beforeWriteContent event, this is invoked by PUT and LOCK on an
  existing resource.
* Added: beforeUnbind event. This is invoked right before deletion of any
  resource.
* Added: afterGetProperties event. This event can be used to make modifications
  to property responses.
* Added: beforeLock and beforeUnlock events.
* Added: afterBind event.
* Fixed: Copy and Move could fail in the root directory. This is now fixed.
* Added: Plugins can now be retrieved by their classname. This is useful for
  inter-plugin communication.
* Added: The Auth backend can now return usernames and user-id's.
* Added: The Auth backend got a getUsers method
* Added: Sabre_DAV_FSExt_Directory now returns quota info


0.12.1-beta (2009-09-11)
------------------------

* Fixed: UNLOCK bug. Unlock didn't work at all


0.12-beta (2009-09-10)
----------------------

* Updated: Browser plugin now shows multiple {DAV:}resourcetype values if
  available.
* Added: Experimental PDO backend for Locks Manager
* Fixed: Sending Content-Length: 0 for every empty response. This improves NGinx
  compatibility.
* Fixed: Last modification time is reported in UTC timezone. This improves
  Finder compatibility.


0.11-beta (2009-08-11)
----------------------

* Updated: Now in Beta
* Updated: Pear package no longer includes docs/ directory. These just contained
  rfc's, which are publicly available. This reduces the package from ~800k to
  ~60k
* Added: generatePropfindResponse now takes a baseUri argument
* Added: ResourceType property can now contain multiple resourcetypes.
* Fixed: Issue 13.


0.10-alpha (2009-08-03)
-----------------------

* Added: Plugin to automatically map GET requests to non-files to PROPFIND
  (Sabre_DAV_Browser_MapGetToPropFind). This should allow easier debugging of
  complicated WebDAV setups.
* Added: Sabre_DAV_Property_Href class. For future use.
* Added: Ability to choose to use auth-int, auth or both for HTTP Digest
  authentication. (Issue 11)
* Changed: Made more methods in Sabre_DAV_Server public.
* Fixed: TemporaryFileFilter plugin now intercepts HTTP LOCK requests to
  non-existent files. (Issue 12)
* Added: Central list of defined xml namespace prefixes. This can reduce
  Bandwidth and legibility for xml bodies with user-defined namespaces.
* Added: now a PEAR-compatible package again, thanks to Michael Gauthier
* Changed: moved default copy and move logic from ObjectTree to Tree class

0.9a-alpha (2009-07-21)
----------------------

* Fixed: Broken release

0.9-alpha (2009-07-21)
----------------------

* Changed: Major refactoring, removed most of the logic from the Tree objects.
  The Server class now directly works with the INode, IFile and IDirectory
  objects. If you created your own Tree objects, this will most likely break in
  this release.
* Changed: Moved all the Locking logic from the Tree and Server classes into a
  separate plugin.
* Changed: TemporaryFileFilter is now a plugin.
* Added: Comes with an autoloader script. This can be used instead of the
  includer script, and is preferred by some people.
* Added: AWS Authentication class.
* Added: simpleserversetup.py script. This will quickly get a fileserver up and
  running.
* Added: When subscribing to events, it is now possible to supply a priority.
  This is for example needed to ensure that the Authentication Plugin is used
  before any other Plugin.
* Added: 22 new tests.
* Added: Users-manager plugin for .htdigest files. Experimental and subject to
  change.
* Added: RFC 2324 HTTP 418 status code
* Fixed: Exclusive locks could in some cases be picked up as shared locks
* Fixed: Digest auth for non-apache servers had a bug (still not actually tested
  this well).


0.8-alpha (2009-05-30)
----------------------

* Changed: Renamed all exceptions! This is a compatibility break. Every
  Exception now follows Sabre_DAV_Exception_FileNotFound convention instead of
  Sabre_DAV_FileNotFoundException.
* Added: Browser plugin now allows uploading and creating directories straight
  from the browser.
* Added: 12 more unittests
* Fixed: Locking bug, which became prevalent on Windows Vista.
* Fixed: Netdrive support
* Fixed: TemporaryFileFilter filtered out too many files. Fixed some of the
  regexes.
* Fixed: Added README and ChangeLog to package


0.7-alpha (2009-03-29)
----------------------

* Added: System to return complex properties from PROPFIND.
* Added: support for {DAV:}supportedlock.
* Added: support for {DAV:}lockdiscovery.
* Added: 6 new tests.
* Added: New plugin system.
* Added: Simple HTML directory plugin, for browser access.
* Added: Server class now sends back standard pre-condition error xml bodies.
  This was new since RFC4918.
* Added: Sabre_DAV_Tree_Aggregate, which can 'host' multiple Tree objects into
  one.
* Added: simple basis for HTTP REPORT method. This method is not used yet, but
  can be used by plugins to add reports.
* Changed: ->getSize is only called for files, no longer for collections. r303
* Changed: Sabre_DAV_FilterTree is now Sabre_DAV_Tree_Filter
* Changed: Sabre_DAV_TemporaryFileFilter is now called
  Sabre_DAV_Tree_TemporaryFileFilter.
* Changed: removed functions (get(/set)HTTPRequest(/Response)) from Server
  class, and using a public property instead.
* Fixed: bug related to parsing proppatch and propfind requests. Didn't show up
  in most clients, but it needed fixing regardless. (r255)
* Fixed: auth-int is now properly supported within HTTP Digest.
* Fixed: Using application/xml for a mimetype vs. text/xml as per RFC4918 sec
  8.2.
* Fixed: TemporaryFileFilter now lets through GET's if they actually exist on
  the backend. (r274)
* Fixed: Some methods didn't get passed through in the FilterTree (r283).
* Fixed: LockManager is now slightly more complex, Tree classes slightly less.
  (r287)


0.6-alpha (2009-02-16)
----------------------

* Added: Now uses streams for files, instead of strings. This means it won't
  require to hold entire files in memory, which can be an issue if you're
  dealing with big files. Note that this breaks compatibility for put() and
  createFile methods.
* Added: HTTP Digest Authentication helper class.
* Added: Support for HTTP Range header
* Added: Support for ETags within If: headers
* Added: The API can now return ETags and override the default Content-Type
* Added: starting with basic framework for unittesting, using PHPUnit.
* Added: 49 unittests.
* Added: Abstraction for the HTTP request.
* Updated: Using Clark Notation for tags in properties. This means tags are
  serialized as {namespace}tagName instead of namespace#tagName
* Fixed: HTTP_BasicAuth class now works as expected.
* Fixed: DAV_Server uses / for a default baseUrl.
* Fixed: Last modification date is no longer ignored in PROPFIND.
* Fixed: PROPFIND now sends back information about the requestUri even when
  "Depth: 1" is specified.


0.5-alpha (2009-01-14)
----------------------

* Added: Added a very simple example for implementing a mapping to PHP file
  streams. This should allow easy implementation of for example a WebDAV to FTP
  proxy.
* Added: HTTP Basic Authentication helper class.
* Added: Sabre_HTTP_Response class. This centralizes HTTP operations and will be
  a start towards the creating of a testing framework.
* Updated: Backwards compatibility break: all require_once() statements are
  removed from all the files. It is now recommended to use autoloading of
  classes, or just including lib/Sabre.includes.php. This fix was made to allow
  easier integration into applications not using this standard inclusion model.
* Updated: Better in-file documentation.
* Updated: Sabre_DAV_Tree can now work with Sabre_DAV_LockManager.
* Updated: Fixes a shared-lock bug.
* Updated: Removed ?> from the bottom of each php file.
* Updated: Split up some operations from Sabre_DAV_Server to
  Sabre_HTTP_Response.
* Fixed: examples are now actually included in the pear package.


0.4-alpha (2008-11-05)
----------------------

* Passes all litmus tests!
* Added: more examples
* Added: Custom property support
* Added: Shared lock support
* Added: Depth support to locks
* Added: Locking on unmapped urls (non-existent nodes)
* Fixed: Advertising as WebDAV class 3 support


0.3-alpha (2008-06-29)
----------------------

* Fully working in MS Windows clients.
* Added: temporary file filter: support for smultron files.
* Added: Phing build scripts
* Added: PEAR package
* Fixed: MOVE bug identified using finder.
* Fixed: Using gzuncompress instead of gzdecode in the temporary file filter.
  This seems more common.


0.2-alpha (2008-05-27)
----------------------

* Somewhat working in Windows clients
* Added: Working PROPPATCH method (doesn't support custom properties yet)
* Added: Temporary filename handling system
* Added: Sabre_DAV_IQuota to return quota information
* Added: PROPFIND now reads the request body and only supplies the requested
  properties


0.1-alpha (2008-04-04)
----------------------

* First release!
* Passes litmus: basic, http and copymove test.
* Fully working in Finder and DavFS2.

Project started: 2007-12-13

[vobj]: http://sabre.io/vobject/
[evnt]: http://sabre.io/event/
[http]: http://sabre.io/http/
[uri]: http://sabre.io/uri/
[xml]: http://sabre.io/xml/
[mi20]: http://sabre.io/dav/upgrade/1.8-to-2.0/
[rfc6638]: http://tools.ietf.org/html/rfc6638 "CalDAV Scheduling"
[rfc7240]: http://tools.ietf.org/html/rfc7240
[calendar-availability]: https://tools.ietf.org/html/draft-daboo-calendar-availability-05
{
    "name": "sabre/dav",
    "type": "library",
    "description": "WebDAV Framework for PHP",
    "keywords": ["Framework", "WebDAV", "CalDAV", "CardDAV", "iCalendar"],
    "homepage": "http://sabre.io/",
    "license" : "BSD-3-Clause",
    "authors": [
        {
            "name": "Evert Pot",
            "email": "me@evertpot.com",
            "homepage" : "http://evertpot.com/",
            "role" : "Developer"
        }
    ],
    "require": {
        "php": ">=5.5.0",
        "sabre/vobject": "^4.1.0",
        "sabre/event" : ">=2.0.0, <4.0.0",
        "sabre/xml"  : "^1.4.0",
        "sabre/http" : "^4.2.1",
        "sabre/uri" : "^1.0.1",
        "ext-dom": "*",
        "ext-pcre": "*",
        "ext-spl": "*",
        "ext-simplexml": "*",
        "ext-mbstring" : "*",
        "ext-ctype" : "*",
        "ext-date" : "*",
        "ext-iconv" : "*",
        "lib-libxml" : ">=2.7.0",
        "psr/log": "^1.0"
    },
    "require-dev" : {
        "phpunit/phpunit" : "> 4.8, <6.0.0",
        "evert/phpdoc-md" : "~0.1.0",
        "sabre/cs"        : "^1.0.0",
        "monolog/monolog": "^1.18"
    },
    "suggest" : {
        "ext-curl" : "*",
        "ext-pdo" : "*"
    },
    "autoload": {
        "psr-4" : {
            "Sabre\\DAV\\"     : "lib/DAV/",
            "Sabre\\DAVACL\\"  : "lib/DAVACL/",
            "Sabre\\CalDAV\\"  : "lib/CalDAV/",
            "Sabre\\CardDAV\\" : "lib/CardDAV/"
        }
    },
    "support" : {
        "forum" : "https://groups.google.com/group/sabredav-discuss",
        "source" : "https://github.com/fruux/sabre-dav"
    },
    "bin" : [
        "bin/sabredav",
        "bin/naturalselection"
    ],
    "config" : {
        "bin-dir" : "./bin"
    },
    "extra" : {
        "branch-alias": {
            "dev-master": "3.1.0-dev"
        }
    }
}
Contributing to sabre projects
==============================

Want to contribute to sabre/dav? Here are some guidelines to ensure your patch
gets accepted.


Building a new feature? Contact us first
----------------------------------------

We may not want to accept every feature that comes our way. Sometimes
features are out of scope for our projects.

We don't want to waste your time, so by having a quick chat with us first,
you may find out quickly if the feature makes sense to us, and we can give
some tips on how to best build the feature.

If we don't accept the feature, it could be for a number of reasons. For
instance, we've rejected features in the past because we felt uncomfortable
assuming responsibility for maintaining the feature.

In those cases, it's often possible to keep the feature separate from the
sabre projects. sabre/dav for instance has a plugin system, and there's no
reason the feature can't live in a project you own.

In that case, definitely let us know about your plugin as well, so we can
feature it on [sabre.io][4].

We are often on [IRC][5], in the #sabredav channel on freenode. If there's
no one there, post a message on the [mailing list][6].


Coding standards
----------------

sabre projects follow:

1. [PSR-1][1]
2. [PSR-4][2]

sabre projects don't follow [PSR-2][3].

In addition to that, here's a list of basic rules:

1. PHP 5.4 array syntax must be used every where. This means you use `[` and
   `]` instead of `array(` and `)`.
2. Use PHP namespaces everywhere.
3. Use 4 spaces for indentation.
4. Try to keep your lines under 80 characters. This is not a hard rule, as
   there are many places in the source where it felt more sensibile to not
   do so. In particular, function declarations are never split over multiple
   lines.
5. Opening braces (`{`) are _always_ on the same line as the `class`, `if`,
   `function`, etc. they belong to.
6. `public` must be omitted from method declarations. It must also be omitted
   for static properties.
7. All files should use unix-line endings (`\n`).
8. Files must omit the closing php tag (`?>`).
9. `true`, `false` and `null` are always lower-case.
10. Constants are always upper-case.
11. Any of the rules stated before may be broken where this is the pragmatic
    thing to do.


Unit test requirements
----------------------

Any new feature or change requires unittests. We use [PHPUnit][7] for all our
tests.

Adding unittests will greatly increase the likelyhood of us quickly accepting
your pull request. If unittests are not included though for whatever reason,
we'd still _love_ your pull request.

We may have to write the tests ourselves, which can increase the time it takes
to accept the patch, but we'd still really like your contribution!

To run the testsuite jump into the directory `cd tests` and trigger `phpunit`.
Make sure you did a `composer install` beforehand.

[1]: http://www.php-fig.org/psr/psr-1/
[2]: http://www.php-fig.org/psr/psr-4/
[3]: http://www.php-fig.org/psr/psr-2/
[4]: http://sabre.io/
[5]: irc://freenode.net/#sabredav
[6]: http://groups.google.com/group/sabredav-discuss
[7]: http://phpunit.de/
<?php

namespace Sabre\CalDAV\Backend;

use Sabre\CalDAV;
use Sabre\VObject;

/**
 * Abstract Calendaring backend. Extend this class to create your own backends.
 *
 * Checkout the BackendInterface for all the methods that must be implemented.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractBackend implements BackendInterface {

    /**
     * Updates properties for a calendar.
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param mixed $calendarId
     * @param \Sabre\DAV\PropPatch $propPatch
     * @return void
     */
    function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {

    }

    /**
     * Returns a list of calendar objects.
     *
     * This method should work identical to getCalendarObject, but instead
     * return all the calendar objects in the list as an array.
     *
     * If the backend supports this, it may allow for some speed-ups.
     *
     * @param mixed $calendarId
     * @param array $uris
     * @return array
     */
    function getMultipleCalendarObjects($calendarId, array $uris) {

        return array_map(function($uri) use ($calendarId) {
            return $this->getCalendarObject($calendarId, $uri);
        }, $uris);

    }

    /**
     * Performs a calendar-query on the contents of this calendar.
     *
     * The calendar-query is defined in RFC4791 : CalDAV. Using the
     * calendar-query it is possible for a client to request a specific set of
     * object, based on contents of iCalendar properties, date-ranges and
     * iCalendar component types (VTODO, VEVENT).
     *
     * This method should just return a list of (relative) urls that match this
     * query.
     *
     * The list of filters are specified as an array. The exact array is
     * documented by \Sabre\CalDAV\CalendarQueryParser.
     *
     * Note that it is extremely likely that getCalendarObject for every path
     * returned from this method will be called almost immediately after. You
     * may want to anticipate this to speed up these requests.
     *
     * This method provides a default implementation, which parses *all* the
     * iCalendar objects in the specified calendar.
     *
     * This default may well be good enough for personal use, and calendars
     * that aren't very large. But if you anticipate high usage, big calendars
     * or high loads, you are strongly adviced to optimize certain paths.
     *
     * The best way to do so is override this method and to optimize
     * specifically for 'common filters'.
     *
     * Requests that are extremely common are:
     *   * requests for just VEVENTS
     *   * requests for just VTODO
     *   * requests with a time-range-filter on either VEVENT or VTODO.
     *
     * ..and combinations of these requests. It may not be worth it to try to
     * handle every possible situation and just rely on the (relatively
     * easy to use) CalendarQueryValidator to handle the rest.
     *
     * Note that especially time-range-filters may be difficult to parse. A
     * time-range filter specified on a VEVENT must for instance also handle
     * recurrence rules correctly.
     * A good example of how to interprete all these filters can also simply
     * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
     * as possible, so it gives you a good idea on what type of stuff you need
     * to think of.
     *
     * @param mixed $calendarId
     * @param array $filters
     * @return array
     */
    function calendarQuery($calendarId, array $filters) {

        $result = [];
        $objects = $this->getCalendarObjects($calendarId);

        foreach ($objects as $object) {

            if ($this->validateFilterForObject($object, $filters)) {
                $result[] = $object['uri'];
            }

        }

        return $result;

    }

    /**
     * This method validates if a filter (as passed to calendarQuery) matches
     * the given object.
     *
     * @param array $object
     * @param array $filters
     * @return bool
     */
    protected function validateFilterForObject(array $object, array $filters) {

        // Unfortunately, setting the 'calendardata' here is optional. If
        // it was excluded, we actually need another call to get this as
        // well.
        if (!isset($object['calendardata'])) {
            $object = $this->getCalendarObject($object['calendarid'], $object['uri']);
        }

        $vObject = VObject\Reader::read($object['calendardata']);

        $validator = new CalDAV\CalendarQueryValidator();
        $result = $validator->validate($vObject, $filters);

        // Destroy circular references so PHP will GC the object.
        $vObject->destroy();

        return $result;

    }

    /**
     * Searches through all of a users calendars and calendar objects to find
     * an object with a specific UID.
     *
     * This method should return the path to this object, relative to the
     * calendar home, so this path usually only contains two parts:
     *
     * calendarpath/objectpath.ics
     *
     * If the uid is not found, return null.
     *
     * This method should only consider * objects that the principal owns, so
     * any calendars owned by other principals that also appear in this
     * collection should be ignored.
     *
     * @param string $principalUri
     * @param string $uid
     * @return string|null
     */
    function getCalendarObjectByUID($principalUri, $uid) {

        // Note: this is a super slow naive implementation of this method. You
        // are highly recommended to optimize it, if your backend allows it.
        foreach ($this->getCalendarsForUser($principalUri) as $calendar) {

            // We must ignore calendars owned by other principals.
            if ($calendar['principaluri'] !== $principalUri) {
                continue;
            }

            // Ignore calendars that are shared.
            if (isset($calendar['{http://sabredav.org/ns}owner-principal']) && $calendar['{http://sabredav.org/ns}owner-principal'] !== $principalUri) {
                continue;
            }

            $results = $this->calendarQuery(
                $calendar['id'],
                [
                    'name'         => 'VCALENDAR',
                    'prop-filters' => [],
                    'comp-filters' => [
                        [
                            'name'           => 'VEVENT',
                            'is-not-defined' => false,
                            'time-range'     => null,
                            'comp-filters'   => [],
                            'prop-filters'   => [
                                [
                                    'name'           => 'UID',
                                    'is-not-defined' => false,
                                    'time-range'     => null,
                                    'text-match'     => [
                                        'value'            => $uid,
                                        'negate-condition' => false,
                                        'collation'        => 'i;octet',
                                    ],
                                    'param-filters' => [],
                                ],
                            ]
                        ]
                    ],
                ]
            );
            if ($results) {
                // We have a match
                return $calendar['uri'] . '/' . $results[0];
            }

        }

    }

}
<?php

namespace Sabre\CalDAV\Backend;

/**
 * Every CalDAV backend must at least implement this interface.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface BackendInterface {

    /**
     * Returns a list of calendars for a principal.
     *
     * Every project is an array with the following keys:
     *  * id, a unique id that will be used by other functions to modify the
     *    calendar. This can be the same as the uri or a database key.
     *  * uri, which is the basename of the uri with which the calendar is
     *    accessed.
     *  * principaluri. The owner of the calendar. Almost always the same as
     *    principalUri passed to this method.
     *
     * Furthermore it can contain webdav properties in clark notation. A very
     * common one is '{DAV:}displayname'.
     *
     * Many clients also require:
     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
     * For this property, you can just return an instance of
     * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
     *
     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
     * ACL will automatically be put in read-only mode.
     *
     * @param string $principalUri
     * @return array
     */
    function getCalendarsForUser($principalUri);

    /**
     * Creates a new calendar for a principal.
     *
     * If the creation was a success, an id must be returned that can be used to
     * reference this calendar in other methods, such as updateCalendar.
     *
     * The id can be any type, including ints, strings, objects or array.
     *
     * @param string $principalUri
     * @param string $calendarUri
     * @param array $properties
     * @return mixed
     */
    function createCalendar($principalUri, $calendarUri, array $properties);

    /**
     * Updates properties for a calendar.
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param mixed $calendarId
     * @param \Sabre\DAV\PropPatch $propPatch
     * @return void
     */
    function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch);

    /**
     * Delete a calendar and all its objects
     *
     * @param mixed $calendarId
     * @return void
     */
    function deleteCalendar($calendarId);

    /**
     * Returns all calendar objects within a calendar.
     *
     * Every item contains an array with the following keys:
     *   * calendardata - The iCalendar-compatible calendar data
     *   * uri - a unique key which will be used to construct the uri. This can
     *     be any arbitrary string, but making sure it ends with '.ics' is a
     *     good idea. This is only the basename, or filename, not the full
     *     path.
     *   * lastmodified - a timestamp of the last modification time
     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
     *   '"abcdef"')
     *   * size - The size of the calendar objects, in bytes.
     *   * component - optional, a string containing the type of object, such
     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
     *     the Content-Type header.
     *
     * Note that the etag is optional, but it's highly encouraged to return for
     * speed reasons.
     *
     * The calendardata is also optional. If it's not returned
     * 'getCalendarObject' will be called later, which *is* expected to return
     * calendardata.
     *
     * If neither etag or size are specified, the calendardata will be
     * used/fetched to determine these numbers. If both are specified the
     * amount of times this is needed is reduced by a great degree.
     *
     * @param mixed $calendarId
     * @return array
     */
    function getCalendarObjects($calendarId);

    /**
     * Returns information from a single calendar object, based on it's object
     * uri.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * The returned array must have the same keys as getCalendarObjects. The
     * 'calendardata' object is required here though, while it's not required
     * for getCalendarObjects.
     *
     * This method must return null if the object did not exist.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @return array|null
     */
    function getCalendarObject($calendarId, $objectUri);

    /**
     * Returns a list of calendar objects.
     *
     * This method should work identical to getCalendarObject, but instead
     * return all the calendar objects in the list as an array.
     *
     * If the backend supports this, it may allow for some speed-ups.
     *
     * @param mixed $calendarId
     * @param array $uris
     * @return array
     */
    function getMultipleCalendarObjects($calendarId, array $uris);

    /**
     * Creates a new calendar object.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * It is possible to return an etag from this function, which will be used
     * in the response to this PUT request. Note that the ETag must be
     * surrounded by double-quotes.
     *
     * However, you should only really return this ETag if you don't mangle the
     * calendar-data. If the result of a subsequent GET to this object is not
     * the exact same as this request body, you should omit the ETag.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @param string $calendarData
     * @return string|null
     */
    function createCalendarObject($calendarId, $objectUri, $calendarData);

    /**
     * Updates an existing calendarobject, based on it's uri.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * It is possible return an etag from this function, which will be used in
     * the response to this PUT request. Note that the ETag must be surrounded
     * by double-quotes.
     *
     * However, you should only really return this ETag if you don't mangle the
     * calendar-data. If the result of a subsequent GET to this object is not
     * the exact same as this request body, you should omit the ETag.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @param string $calendarData
     * @return string|null
     */
    function updateCalendarObject($calendarId, $objectUri, $calendarData);

    /**
     * Deletes an existing calendar object.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @return void
     */
    function deleteCalendarObject($calendarId, $objectUri);

    /**
     * Performs a calendar-query on the contents of this calendar.
     *
     * The calendar-query is defined in RFC4791 : CalDAV. Using the
     * calendar-query it is possible for a client to request a specific set of
     * object, based on contents of iCalendar properties, date-ranges and
     * iCalendar component types (VTODO, VEVENT).
     *
     * This method should just return a list of (relative) urls that match this
     * query.
     *
     * The list of filters are specified as an array. The exact array is
     * documented by Sabre\CalDAV\CalendarQueryParser.
     *
     * Note that it is extremely likely that getCalendarObject for every path
     * returned from this method will be called almost immediately after. You
     * may want to anticipate this to speed up these requests.
     *
     * This method provides a default implementation, which parses *all* the
     * iCalendar objects in the specified calendar.
     *
     * This default may well be good enough for personal use, and calendars
     * that aren't very large. But if you anticipate high usage, big calendars
     * or high loads, you are strongly adviced to optimize certain paths.
     *
     * The best way to do so is override this method and to optimize
     * specifically for 'common filters'.
     *
     * Requests that are extremely common are:
     *   * requests for just VEVENTS
     *   * requests for just VTODO
     *   * requests with a time-range-filter on either VEVENT or VTODO.
     *
     * ..and combinations of these requests. It may not be worth it to try to
     * handle every possible situation and just rely on the (relatively
     * easy to use) CalendarQueryValidator to handle the rest.
     *
     * Note that especially time-range-filters may be difficult to parse. A
     * time-range filter specified on a VEVENT must for instance also handle
     * recurrence rules correctly.
     * A good example of how to interprete all these filters can also simply
     * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
     * as possible, so it gives you a good idea on what type of stuff you need
     * to think of.
     *
     * @param mixed $calendarId
     * @param array $filters
     * @return array
     */
    function calendarQuery($calendarId, array $filters);

    /**
     * Searches through all of a users calendars and calendar objects to find
     * an object with a specific UID.
     *
     * This method should return the path to this object, relative to the
     * calendar home, so this path usually only contains two parts:
     *
     * calendarpath/objectpath.ics
     *
     * If the uid is not found, return null.
     *
     * This method should only consider * objects that the principal owns, so
     * any calendars owned by other principals that also appear in this
     * collection should be ignored.
     *
     * @param string $principalUri
     * @param string $uid
     * @return string|null
     */
    function getCalendarObjectByUID($principalUri, $uid);

}
<?php

namespace Sabre\CalDAV\Backend;

use Sabre\CalDAV\Xml\Notification\NotificationInterface;

/**
 * Adds caldav notification support to a backend.
 *
 * Note: This feature is experimental, and may change in between different
 * SabreDAV versions.
 *
 * Notifications are defined at:
 * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt
 *
 * These notifications are basically a list of server-generated notifications
 * displayed to the user. Users can dismiss notifications by deleting them.
 *
 * The primary usecase is to allow for calendar-sharing.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface NotificationSupport extends BackendInterface {

    /**
     * Returns a list of notifications for a given principal url.
     *
     * @param string $principalUri
     * @return NotificationInterface[]
     */
    function getNotificationsForPrincipal($principalUri);

    /**
     * This deletes a specific notifcation.
     *
     * This may be called by a client once it deems a notification handled.
     *
     * @param string $principalUri
     * @param NotificationInterface $notification
     * @return void
     */
    function deleteNotification($principalUri, NotificationInterface $notification);

    /**
     * This method is called when a user replied to a request to share.
     *
     * If the user chose to accept the share, this method should return the
     * newly created calendar url.
     *
     * @param string $href The sharee who is replying (often a mailto: address)
     * @param int $status One of the SharingPlugin::STATUS_* constants
     * @param string $calendarUri The url to the calendar thats being shared
     * @param string $inReplyTo The unique id this message is a response to
     * @param string $summary A description of the reply
     * @return null|string
     */
    function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null);

}
<?php

namespace Sabre\CalDAV\Backend;

use Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Xml\Element\Sharee;
use Sabre\VObject;

/**
 * PDO CalDAV backend
 *
 * This backend is used to store calendar-data in a PDO database, such as
 * sqlite or MySQL
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PDO extends AbstractBackend
    implements
        SyncSupport,
        SubscriptionSupport,
        SchedulingSupport,
        SharingSupport {

    /**
     * We need to specify a max date, because we need to stop *somewhere*
     *
     * On 32 bit system the maximum for a signed integer is 2147483647, so
     * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
     * in 2038-01-19 to avoid problems when the date is converted
     * to a unix timestamp.
     */
    const MAX_DATE = '2038-01-01';

    /**
     * pdo
     *
     * @var \PDO
     */
    protected $pdo;

    /**
     * The table name that will be used for calendars
     *
     * @var string
     */
    public $calendarTableName = 'calendars';

    /**
     * The table name that will be used for calendars instances.
     *
     * A single calendar can have multiple instances, if the calendar is
     * shared.
     *
     * @var string
     */
    public $calendarInstancesTableName = 'calendarinstances';

    /**
     * The table name that will be used for calendar objects
     *
     * @var string
     */
    public $calendarObjectTableName = 'calendarobjects';

    /**
     * The table name that will be used for tracking changes in calendars.
     *
     * @var string
     */
    public $calendarChangesTableName = 'calendarchanges';

    /**
     * The table name that will be used inbox items.
     *
     * @var string
     */
    public $schedulingObjectTableName = 'schedulingobjects';

    /**
     * The table name that will be used for calendar subscriptions.
     *
     * @var string
     */
    public $calendarSubscriptionsTableName = 'calendarsubscriptions';

    /**
     * List of CalDAV properties, and how they map to database fieldnames
     * Add your own properties by simply adding on to this array.
     *
     * Note that only string-based properties are supported here.
     *
     * @var array
     */
    public $propertyMap = [
        '{DAV:}displayname'                                   => 'displayname',
        '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
        '{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
        '{http://apple.com/ns/ical/}calendar-order'           => 'calendarorder',
        '{http://apple.com/ns/ical/}calendar-color'           => 'calendarcolor',
    ];

    /**
     * List of subscription properties, and how they map to database fieldnames.
     *
     * @var array
     */
    public $subscriptionPropertyMap = [
        '{DAV:}displayname'                                           => 'displayname',
        '{http://apple.com/ns/ical/}refreshrate'                      => 'refreshrate',
        '{http://apple.com/ns/ical/}calendar-order'                   => 'calendarorder',
        '{http://apple.com/ns/ical/}calendar-color'                   => 'calendarcolor',
        '{http://calendarserver.org/ns/}subscribed-strip-todos'       => 'striptodos',
        '{http://calendarserver.org/ns/}subscribed-strip-alarms'      => 'stripalarms',
        '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
    ];

    /**
     * Creates the backend
     *
     * @param \PDO $pdo
     */
    function __construct(\PDO $pdo) {

        $this->pdo = $pdo;

    }

    /**
     * Returns a list of calendars for a principal.
     *
     * Every project is an array with the following keys:
     *  * id, a unique id that will be used by other functions to modify the
     *    calendar. This can be the same as the uri or a database key.
     *  * uri. This is just the 'base uri' or 'filename' of the calendar.
     *  * principaluri. The owner of the calendar. Almost always the same as
     *    principalUri passed to this method.
     *
     * Furthermore it can contain webdav properties in clark notation. A very
     * common one is '{DAV:}displayname'.
     *
     * Many clients also require:
     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
     * For this property, you can just return an instance of
     * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
     *
     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
     * ACL will automatically be put in read-only mode.
     *
     * @param string $principalUri
     * @return array
     */
    function getCalendarsForUser($principalUri) {

        $fields = array_values($this->propertyMap);
        $fields[] = 'calendarid';
        $fields[] = 'uri';
        $fields[] = 'synctoken';
        $fields[] = 'components';
        $fields[] = 'principaluri';
        $fields[] = 'transparent';
        $fields[] = 'access';

        // Making fields a comma-delimited list
        $fields = implode(', ', $fields);
        $stmt = $this->pdo->prepare(<<<SQL
SELECT {$this->calendarInstancesTableName}.id as id, $fields FROM {$this->calendarInstancesTableName}
    LEFT JOIN {$this->calendarTableName} ON
        {$this->calendarInstancesTableName}.calendarid = {$this->calendarTableName}.id
WHERE principaluri = ? ORDER BY calendarorder ASC
SQL
        );
        $stmt->execute([$principalUri]);

        $calendars = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

            $components = [];
            if ($row['components']) {
                $components = explode(',', $row['components']);
            }

            $calendar = [
                'id'                                                                 => [(int)$row['calendarid'], (int)$row['id']],
                'uri'                                                                => $row['uri'],
                'principaluri'                                                       => $row['principaluri'],
                '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}getctag'                  => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ? $row['synctoken'] : '0'),
                '{http://sabredav.org/ns}sync-token'                                 => $row['synctoken'] ? $row['synctoken'] : '0',
                '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components),
                '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp'         => new CalDAV\Xml\Property\ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'),
                'share-resource-uri'                                                 => '/ns/share/' . $row['calendarid'],
            ];

            $calendar['share-access'] = (int)$row['access'];
            // 1 = owner, 2 = readonly, 3 = readwrite
            if ($row['access'] > 1) {
                // We need to find more information about the original owner.
                //$stmt2 = $this->pdo->prepare('SELECT principaluri FROM ' . $this->calendarInstancesTableName . ' WHERE access = 1 AND id = ?');
                //$stmt2->execute([$row['id']]);

                // read-only is for backwards compatbility. Might go away in
                // the future.
                $calendar['read-only'] = (int)$row['access'] === \Sabre\DAV\Sharing\Plugin::ACCESS_READ;
            }

            foreach ($this->propertyMap as $xmlName => $dbName) {
                $calendar[$xmlName] = $row[$dbName];
            }

            $calendars[] = $calendar;

        }

        return $calendars;

    }

    /**
     * Creates a new calendar for a principal.
     *
     * If the creation was a success, an id must be returned that can be used
     * to reference this calendar in other methods, such as updateCalendar.
     *
     * @param string $principalUri
     * @param string $calendarUri
     * @param array $properties
     * @return string
     */
    function createCalendar($principalUri, $calendarUri, array $properties) {

        $fieldNames = [
            'principaluri',
            'uri',
            'transparent',
            'calendarid',
        ];
        $values = [
            ':principaluri' => $principalUri,
            ':uri'          => $calendarUri,
            ':transparent'  => 0,
        ];


        $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
        if (!isset($properties[$sccs])) {
            // Default value
            $components = 'VEVENT,VTODO';
        } else {
            if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) {
                throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet');
            }
            $components = implode(',', $properties[$sccs]->getValue());
        }
        $transp = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';
        if (isset($properties[$transp])) {
            $values[':transparent'] = $properties[$transp]->getValue() === 'transparent' ? 1 : 0;
        }
        $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarTableName . " (synctoken, components) VALUES (1, ?)");
        $stmt->execute([$components]);

        $calendarId = $this->pdo->lastInsertId(
            $this->calendarTableName . '_id_seq'
        );

        $values[':calendarid'] = $calendarId;

        foreach ($this->propertyMap as $xmlName => $dbName) {
            if (isset($properties[$xmlName])) {

                $values[':' . $dbName] = $properties[$xmlName];
                $fieldNames[] = $dbName;
            }
        }

        $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarInstancesTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");

        $stmt->execute($values);

        return [
            $calendarId,
            $this->pdo->lastInsertId($this->calendarInstancesTableName . '_id_seq')
        ];

    }

    /**
     * Updates properties for a calendar.
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param mixed $calendarId
     * @param \Sabre\DAV\PropPatch $propPatch
     * @return void
     */
    function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $supportedProperties = array_keys($this->propertyMap);
        $supportedProperties[] = '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp';

        $propPatch->handle($supportedProperties, function($mutations) use ($calendarId, $instanceId) {
            $newValues = [];
            foreach ($mutations as $propertyName => $propertyValue) {

                switch ($propertyName) {
                    case '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' :
                        $fieldName = 'transparent';
                        $newValues[$fieldName] = $propertyValue->getValue() === 'transparent';
                        break;
                    default :
                        $fieldName = $this->propertyMap[$propertyName];
                        $newValues[$fieldName] = $propertyValue;
                        break;
                }

            }
            $valuesSql = [];
            foreach ($newValues as $fieldName => $value) {
                $valuesSql[] = $fieldName . ' = ?';
            }

            $stmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET " . implode(', ', $valuesSql) . " WHERE id = ?");
            $newValues['id'] = $instanceId;
            $stmt->execute(array_values($newValues));

            $this->addChange($calendarId, "", 2);

            return true;

        });

    }

    /**
     * Delete a calendar and all it's objects
     *
     * @param mixed $calendarId
     * @return void
     */
    function deleteCalendar($calendarId) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $stmt = $this->pdo->prepare('SELECT access FROM ' . $this->calendarInstancesTableName . ' where id = ?');
        $stmt->execute([$instanceId]);
        $access = (int)$stmt->fetchColumn();

        if ($access === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {

            /**
             * If the user is the owner of the calendar, we delete all data and all
             * instances.
             **/
            $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
            $stmt->execute([$calendarId]);

            $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarChangesTableName . ' WHERE calendarid = ?');
            $stmt->execute([$calendarId]);

            $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE calendarid = ?');
            $stmt->execute([$calendarId]);

            $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarTableName . ' WHERE id = ?');
            $stmt->execute([$calendarId]);

        } else {

            /**
             * If it was an instance of a shared calendar, we only delete that
             * instance.
             */
            $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');
            $stmt->execute([$instanceId]);

        }


    }

    /**
     * Returns all calendar objects within a calendar.
     *
     * Every item contains an array with the following keys:
     *   * calendardata - The iCalendar-compatible calendar data
     *   * uri - a unique key which will be used to construct the uri. This can
     *     be any arbitrary string, but making sure it ends with '.ics' is a
     *     good idea. This is only the basename, or filename, not the full
     *     path.
     *   * lastmodified - a timestamp of the last modification time
     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
     *   '  "abcdef"')
     *   * size - The size of the calendar objects, in bytes.
     *   * component - optional, a string containing the type of object, such
     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
     *     the Content-Type header.
     *
     * Note that the etag is optional, but it's highly encouraged to return for
     * speed reasons.
     *
     * The calendardata is also optional. If it's not returned
     * 'getCalendarObject' will be called later, which *is* expected to return
     * calendardata.
     *
     * If neither etag or size are specified, the calendardata will be
     * used/fetched to determine these numbers. If both are specified the
     * amount of times this is needed is reduced by a great degree.
     *
     * @param mixed $calendarId
     * @return array
     */
    function getCalendarObjects($calendarId) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ?');
        $stmt->execute([$calendarId]);

        $result = [];
        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
            $result[] = [
                'id'           => $row['id'],
                'uri'          => $row['uri'],
                'lastmodified' => (int)$row['lastmodified'],
                'etag'         => '"' . $row['etag'] . '"',
                'size'         => (int)$row['size'],
                'component'    => strtolower($row['componenttype']),
            ];
        }

        return $result;

    }

    /**
     * Returns information from a single calendar object, based on it's object
     * uri.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * The returned array must have the same keys as getCalendarObjects. The
     * 'calendardata' object is required here though, while it's not required
     * for getCalendarObjects.
     *
     * This method must return null if the object did not exist.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @return array|null
     */
    function getCalendarObject($calendarId, $objectUri) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
        $stmt->execute([$calendarId, $objectUri]);
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);

        if (!$row) return null;

        return [
            'id'           => $row['id'],
            'uri'          => $row['uri'],
            'lastmodified' => (int)$row['lastmodified'],
            'etag'         => '"' . $row['etag'] . '"',
            'size'         => (int)$row['size'],
            'calendardata' => $row['calendardata'],
            'component'    => strtolower($row['componenttype']),
         ];

    }

    /**
     * Returns a list of calendar objects.
     *
     * This method should work identical to getCalendarObject, but instead
     * return all the calendar objects in the list as an array.
     *
     * If the backend supports this, it may allow for some speed-ups.
     *
     * @param mixed $calendarId
     * @param array $uris
     * @return array
     */
    function getMultipleCalendarObjects($calendarId, array $uris) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $result = [];
        foreach (array_chunk($uris, 900) as $chunk) {
            $query = 'SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri IN (';
            // Inserting a whole bunch of question marks
            $query .= implode(',', array_fill(0, count($chunk), '?'));
            $query .= ')';

            $stmt = $this->pdo->prepare($query);
            $stmt->execute(array_merge([$calendarId], $chunk));

            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

                $result[] = [
                    'id'           => $row['id'],
                    'uri'          => $row['uri'],
                    'lastmodified' => (int)$row['lastmodified'],
                    'etag'         => '"' . $row['etag'] . '"',
                    'size'         => (int)$row['size'],
                    'calendardata' => $row['calendardata'],
                    'component'    => strtolower($row['componenttype']),
                ];

            }
        }
        return $result;

    }


    /**
     * Creates a new calendar object.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * It is possible return an etag from this function, which will be used in
     * the response to this PUT request. Note that the ETag must be surrounded
     * by double-quotes.
     *
     * However, you should only really return this ETag if you don't mangle the
     * calendar-data. If the result of a subsequent GET to this object is not
     * the exact same as this request body, you should omit the ETag.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @param string $calendarData
     * @return string|null
     */
    function createCalendarObject($calendarId, $objectUri, $calendarData) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $extraData = $this->getDenormalizedData($calendarData);

        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarObjectTableName . ' (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)');
        $stmt->execute([
            $calendarId,
            $objectUri,
            $calendarData,
            time(),
            $extraData['etag'],
            $extraData['size'],
            $extraData['componentType'],
            $extraData['firstOccurence'],
            $extraData['lastOccurence'],
            $extraData['uid'],
        ]);
        $this->addChange($calendarId, $objectUri, 1);

        return '"' . $extraData['etag'] . '"';

    }

    /**
     * Updates an existing calendarobject, based on it's uri.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * It is possible return an etag from this function, which will be used in
     * the response to this PUT request. Note that the ETag must be surrounded
     * by double-quotes.
     *
     * However, you should only really return this ETag if you don't mangle the
     * calendar-data. If the result of a subsequent GET to this object is not
     * the exact same as this request body, you should omit the ETag.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @param string $calendarData
     * @return string|null
     */
    function updateCalendarObject($calendarId, $objectUri, $calendarData) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $extraData = $this->getDenormalizedData($calendarData);

        $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarObjectTableName . ' SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?');
        $stmt->execute([$calendarData, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calendarId, $objectUri]);

        $this->addChange($calendarId, $objectUri, 2);

        return '"' . $extraData['etag'] . '"';

    }

    /**
     * Parses some information from calendar objects, used for optimized
     * calendar-queries.
     *
     * Returns an array with the following keys:
     *   * etag - An md5 checksum of the object without the quotes.
     *   * size - Size of the object in bytes
     *   * componentType - VEVENT, VTODO or VJOURNAL
     *   * firstOccurence
     *   * lastOccurence
     *   * uid - value of the UID property
     *
     * @param string $calendarData
     * @return array
     */
    protected function getDenormalizedData($calendarData) {

        $vObject = VObject\Reader::read($calendarData);
        $componentType = null;
        $component = null;
        $firstOccurence = null;
        $lastOccurence = null;
        $uid = null;
        foreach ($vObject->getComponents() as $component) {
            if ($component->name !== 'VTIMEZONE') {
                $componentType = $component->name;
                $uid = (string)$component->UID;
                break;
            }
        }
        if (!$componentType) {
            throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
        }
        if ($componentType === 'VEVENT') {
            $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
            // Finding the last occurence is a bit harder
            if (!isset($component->RRULE)) {
                if (isset($component->DTEND)) {
                    $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
                } elseif (isset($component->DURATION)) {
                    $endDate = clone $component->DTSTART->getDateTime();
                    $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue()));
                    $lastOccurence = $endDate->getTimeStamp();
                } elseif (!$component->DTSTART->hasTime()) {
                    $endDate = clone $component->DTSTART->getDateTime();
                    $endDate = $endDate->modify('+1 day');
                    $lastOccurence = $endDate->getTimeStamp();
                } else {
                    $lastOccurence = $firstOccurence;
                }
            } else {
                $it = new VObject\Recur\EventIterator($vObject, (string)$component->UID);
                $maxDate = new \DateTime(self::MAX_DATE);
                if ($it->isInfinite()) {
                    $lastOccurence = $maxDate->getTimeStamp();
                } else {
                    $end = $it->getDtEnd();
                    while ($it->valid() && $end < $maxDate) {
                        $end = $it->getDtEnd();
                        $it->next();

                    }
                    $lastOccurence = $end->getTimeStamp();
                }

            }

            // Ensure Occurence values are positive
            if ($firstOccurence < 0) $firstOccurence = 0;
            if ($lastOccurence < 0) $lastOccurence = 0;
        }

        // Destroy circular references to PHP will GC the object.
        $vObject->destroy();

        return [
            'etag'           => md5($calendarData),
            'size'           => strlen($calendarData),
            'componentType'  => $componentType,
            'firstOccurence' => $firstOccurence,
            'lastOccurence'  => $lastOccurence,
            'uid'            => $uid,
        ];

    }

    /**
     * Deletes an existing calendar object.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @return void
     */
    function deleteCalendarObject($calendarId, $objectUri) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarObjectTableName . ' WHERE calendarid = ? AND uri = ?');
        $stmt->execute([$calendarId, $objectUri]);

        $this->addChange($calendarId, $objectUri, 3);

    }

    /**
     * Performs a calendar-query on the contents of this calendar.
     *
     * The calendar-query is defined in RFC4791 : CalDAV. Using the
     * calendar-query it is possible for a client to request a specific set of
     * object, based on contents of iCalendar properties, date-ranges and
     * iCalendar component types (VTODO, VEVENT).
     *
     * This method should just return a list of (relative) urls that match this
     * query.
     *
     * The list of filters are specified as an array. The exact array is
     * documented by \Sabre\CalDAV\CalendarQueryParser.
     *
     * Note that it is extremely likely that getCalendarObject for every path
     * returned from this method will be called almost immediately after. You
     * may want to anticipate this to speed up these requests.
     *
     * This method provides a default implementation, which parses *all* the
     * iCalendar objects in the specified calendar.
     *
     * This default may well be good enough for personal use, and calendars
     * that aren't very large. But if you anticipate high usage, big calendars
     * or high loads, you are strongly adviced to optimize certain paths.
     *
     * The best way to do so is override this method and to optimize
     * specifically for 'common filters'.
     *
     * Requests that are extremely common are:
     *   * requests for just VEVENTS
     *   * requests for just VTODO
     *   * requests with a time-range-filter on a VEVENT.
     *
     * ..and combinations of these requests. It may not be worth it to try to
     * handle every possible situation and just rely on the (relatively
     * easy to use) CalendarQueryValidator to handle the rest.
     *
     * Note that especially time-range-filters may be difficult to parse. A
     * time-range filter specified on a VEVENT must for instance also handle
     * recurrence rules correctly.
     * A good example of how to interpret all these filters can also simply
     * be found in \Sabre\CalDAV\CalendarQueryFilter. This class is as correct
     * as possible, so it gives you a good idea on what type of stuff you need
     * to think of.
     *
     * This specific implementation (for the PDO) backend optimizes filters on
     * specific components, and VEVENT time-ranges.
     *
     * @param mixed $calendarId
     * @param array $filters
     * @return array
     */
    function calendarQuery($calendarId, array $filters) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $componentType = null;
        $requirePostFilter = true;
        $timeRange = null;

        // if no filters were specified, we don't need to filter after a query
        if (!$filters['prop-filters'] && !$filters['comp-filters']) {
            $requirePostFilter = false;
        }

        // Figuring out if there's a component filter
        if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
            $componentType = $filters['comp-filters'][0]['name'];

            // Checking if we need post-filters
            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
                $requirePostFilter = false;
            }
            // There was a time-range filter
            if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
                $timeRange = $filters['comp-filters'][0]['time-range'];

                // If start time OR the end time is not specified, we can do a
                // 100% accurate mysql query.
                if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
                    $requirePostFilter = false;
                }
            }

        }

        if ($requirePostFilter) {
            $query = "SELECT uri, calendardata FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
        } else {
            $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = :calendarid";
        }

        $values = [
            'calendarid' => $calendarId,
        ];

        if ($componentType) {
            $query .= " AND componenttype = :componenttype";
            $values['componenttype'] = $componentType;
        }

        if ($timeRange && $timeRange['start']) {
            $query .= " AND lastoccurence > :startdate";
            $values['startdate'] = $timeRange['start']->getTimeStamp();
        }
        if ($timeRange && $timeRange['end']) {
            $query .= " AND firstoccurence < :enddate";
            $values['enddate'] = $timeRange['end']->getTimeStamp();
        }

        $stmt = $this->pdo->prepare($query);
        $stmt->execute($values);

        $result = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            if ($requirePostFilter) {
                if (!$this->validateFilterForObject($row, $filters)) {
                    continue;
                }
            }
            $result[] = $row['uri'];

        }

        return $result;

    }

    /**
     * Searches through all of a users calendars and calendar objects to find
     * an object with a specific UID.
     *
     * This method should return the path to this object, relative to the
     * calendar home, so this path usually only contains two parts:
     *
     * calendarpath/objectpath.ics
     *
     * If the uid is not found, return null.
     *
     * This method should only consider * objects that the principal owns, so
     * any calendars owned by other principals that also appear in this
     * collection should be ignored.
     *
     * @param string $principalUri
     * @param string $uid
     * @return string|null
     */
    function getCalendarObjectByUID($principalUri, $uid) {

        $query = <<<SQL
SELECT
    calendar_instances.uri AS calendaruri, calendarobjects.uri as objecturi
FROM
    $this->calendarObjectTableName AS calendarobjects
LEFT JOIN
    $this->calendarInstancesTableName AS calendar_instances
    ON calendarobjects.calendarid = calendar_instances.calendarid
WHERE
    calendar_instances.principaluri = ?
    AND
    calendarobjects.uid = ?
SQL;

        $stmt = $this->pdo->prepare($query);
        $stmt->execute([$principalUri, $uid]);

        if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            return $row['calendaruri'] . '/' . $row['objecturi'];
        }

    }

    /**
     * The getChanges method returns all the changes that have happened, since
     * the specified syncToken in the specified calendar.
     *
     * This function should return an array, such as the following:
     *
     * [
     *   'syncToken' => 'The current synctoken',
     *   'added'   => [
     *      'new.txt',
     *   ],
     *   'modified'   => [
     *      'modified.txt',
     *   ],
     *   'deleted' => [
     *      'foo.php.bak',
     *      'old.txt'
     *   ]
     * ];
     *
     * The returned syncToken property should reflect the *current* syncToken
     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
     * property this is needed here too, to ensure the operation is atomic.
     *
     * If the $syncToken argument is specified as null, this is an initial
     * sync, and all members should be reported.
     *
     * The modified property is an array of nodenames that have changed since
     * the last token.
     *
     * The deleted property is an array with nodenames, that have been deleted
     * from collection.
     *
     * The $syncLevel argument is basically the 'depth' of the report. If it's
     * 1, you only have to report changes that happened only directly in
     * immediate descendants. If it's 2, it should also include changes from
     * the nodes below the child collections. (grandchildren)
     *
     * The $limit argument allows a client to specify how many results should
     * be returned at most. If the limit is not specified, it should be treated
     * as infinite.
     *
     * If the limit (infinite or not) is higher than you're willing to return,
     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
     *
     * If the syncToken is expired (due to data cleanup) or unknown, you must
     * return null.
     *
     * The limit is 'suggestive'. You are free to ignore it.
     *
     * @param mixed $calendarId
     * @param string $syncToken
     * @param int $syncLevel
     * @param int $limit
     * @return array
     */
    function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        // Current synctoken
        $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->calendarTableName . ' WHERE id = ?');
        $stmt->execute([$calendarId]);
        $currentToken = $stmt->fetchColumn(0);

        if (is_null($currentToken)) return null;

        $result = [
            'syncToken' => $currentToken,
            'added'     => [],
            'modified'  => [],
            'deleted'   => [],
        ];

        if ($syncToken) {

            $query = "SELECT uri, operation FROM " . $this->calendarChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
            if ($limit > 0) $query .= " LIMIT " . (int)$limit;

            // Fetching all changes
            $stmt = $this->pdo->prepare($query);
            $stmt->execute([$syncToken, $currentToken, $calendarId]);

            $changes = [];

            // This loop ensures that any duplicates are overwritten, only the
            // last change on a node is relevant.
            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

                $changes[$row['uri']] = $row['operation'];

            }

            foreach ($changes as $uri => $operation) {

                switch ($operation) {
                    case 1 :
                        $result['added'][] = $uri;
                        break;
                    case 2 :
                        $result['modified'][] = $uri;
                        break;
                    case 3 :
                        $result['deleted'][] = $uri;
                        break;
                }

            }
        } else {
            // No synctoken supplied, this is the initial sync.
            $query = "SELECT uri FROM " . $this->calendarObjectTableName . " WHERE calendarid = ?";
            $stmt = $this->pdo->prepare($query);
            $stmt->execute([$calendarId]);

            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
        }
        return $result;

    }

    /**
     * Adds a change record to the calendarchanges table.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @param int $operation 1 = add, 2 = modify, 3 = delete.
     * @return void
     */
    protected function addChange($calendarId, $objectUri, $operation) {

        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->calendarChangesTableName . ' (uri, synctoken, calendarid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->calendarTableName . ' WHERE id = ?');
        $stmt->execute([
            $objectUri,
            $calendarId,
            $operation,
            $calendarId
        ]);
        $stmt = $this->pdo->prepare('UPDATE ' . $this->calendarTableName . ' SET synctoken = synctoken + 1 WHERE id = ?');
        $stmt->execute([
            $calendarId
        ]);

    }

    /**
     * Returns a list of subscriptions for a principal.
     *
     * Every subscription is an array with the following keys:
     *  * id, a unique id that will be used by other functions to modify the
     *    subscription. This can be the same as the uri or a database key.
     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
     *  * principaluri. The owner of the subscription. Almost always the same as
     *    principalUri passed to this method.
     *  * source. Url to the actual feed
     *
     * Furthermore, all the subscription info must be returned too:
     *
     * 1. {DAV:}displayname
     * 2. {http://apple.com/ns/ical/}refreshrate
     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
     *    should not be stripped).
     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
     *    should not be stripped).
     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
     *    attachments should not be stripped).
     * 7. {http://apple.com/ns/ical/}calendar-color
     * 8. {http://apple.com/ns/ical/}calendar-order
     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
     *    (should just be an instance of
     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
     *    default components).
     *
     * @param string $principalUri
     * @return array
     */
    function getSubscriptionsForUser($principalUri) {

        $fields = array_values($this->subscriptionPropertyMap);
        $fields[] = 'id';
        $fields[] = 'uri';
        $fields[] = 'source';
        $fields[] = 'principaluri';
        $fields[] = 'lastmodified';

        // Making fields a comma-delimited list
        $fields = implode(', ', $fields);
        $stmt = $this->pdo->prepare("SELECT " . $fields . " FROM " . $this->calendarSubscriptionsTableName . " WHERE principaluri = ? ORDER BY calendarorder ASC");
        $stmt->execute([$principalUri]);

        $subscriptions = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

            $subscription = [
                'id'           => $row['id'],
                'uri'          => $row['uri'],
                'principaluri' => $row['principaluri'],
                'source'       => $row['source'],
                'lastmodified' => $row['lastmodified'],

                '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
            ];

            foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
                if (!is_null($row[$dbName])) {
                    $subscription[$xmlName] = $row[$dbName];
                }
            }

            $subscriptions[] = $subscription;

        }

        return $subscriptions;

    }

    /**
     * Creates a new subscription for a principal.
     *
     * If the creation was a success, an id must be returned that can be used to reference
     * this subscription in other methods, such as updateSubscription.
     *
     * @param string $principalUri
     * @param string $uri
     * @param array $properties
     * @return mixed
     */
    function createSubscription($principalUri, $uri, array $properties) {

        $fieldNames = [
            'principaluri',
            'uri',
            'source',
            'lastmodified',
        ];

        if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
            throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
        }

        $values = [
            ':principaluri' => $principalUri,
            ':uri'          => $uri,
            ':source'       => $properties['{http://calendarserver.org/ns/}source']->getHref(),
            ':lastmodified' => time(),
        ];

        foreach ($this->subscriptionPropertyMap as $xmlName => $dbName) {
            if (isset($properties[$xmlName])) {

                $values[':' . $dbName] = $properties[$xmlName];
                $fieldNames[] = $dbName;
            }
        }

        $stmt = $this->pdo->prepare("INSERT INTO " . $this->calendarSubscriptionsTableName . " (" . implode(', ', $fieldNames) . ") VALUES (" . implode(', ', array_keys($values)) . ")");
        $stmt->execute($values);

        return $this->pdo->lastInsertId(
            $this->calendarSubscriptionsTableName . '_id_seq'
        );

    }

    /**
     * Updates a subscription
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param mixed $subscriptionId
     * @param \Sabre\DAV\PropPatch $propPatch
     * @return void
     */
    function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) {

        $supportedProperties = array_keys($this->subscriptionPropertyMap);
        $supportedProperties[] = '{http://calendarserver.org/ns/}source';

        $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {

            $newValues = [];

            foreach ($mutations as $propertyName => $propertyValue) {

                if ($propertyName === '{http://calendarserver.org/ns/}source') {
                    $newValues['source'] = $propertyValue->getHref();
                } else {
                    $fieldName = $this->subscriptionPropertyMap[$propertyName];
                    $newValues[$fieldName] = $propertyValue;
                }

            }

            // Now we're generating the sql query.
            $valuesSql = [];
            foreach ($newValues as $fieldName => $value) {
                $valuesSql[] = $fieldName . ' = ?';
            }

            $stmt = $this->pdo->prepare("UPDATE " . $this->calendarSubscriptionsTableName . " SET " . implode(', ', $valuesSql) . ", lastmodified = ? WHERE id = ?");
            $newValues['lastmodified'] = time();
            $newValues['id'] = $subscriptionId;
            $stmt->execute(array_values($newValues));

            return true;

        });

    }

    /**
     * Deletes a subscription
     *
     * @param mixed $subscriptionId
     * @return void
     */
    function deleteSubscription($subscriptionId) {

        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->calendarSubscriptionsTableName . ' WHERE id = ?');
        $stmt->execute([$subscriptionId]);

    }

    /**
     * Returns a single scheduling object.
     *
     * The returned array should contain the following elements:
     *   * uri - A unique basename for the object. This will be used to
     *           construct a full uri.
     *   * calendardata - The iCalendar object
     *   * lastmodified - The last modification date. Can be an int for a unix
     *                    timestamp, or a PHP DateTime object.
     *   * etag - A unique token that must change if the object changed.
     *   * size - The size of the object, in bytes.
     *
     * @param string $principalUri
     * @param string $objectUri
     * @return array
     */
    function getSchedulingObject($principalUri, $objectUri) {

        $stmt = $this->pdo->prepare('SELECT uri, calendardata, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
        $stmt->execute([$principalUri, $objectUri]);
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);

        if (!$row) return null;

        return [
            'uri'          => $row['uri'],
            'calendardata' => $row['calendardata'],
            'lastmodified' => $row['lastmodified'],
            'etag'         => '"' . $row['etag'] . '"',
            'size'         => (int)$row['size'],
         ];

    }

    /**
     * Returns all scheduling objects for the inbox collection.
     *
     * These objects should be returned as an array. Every item in the array
     * should follow the same structure as returned from getSchedulingObject.
     *
     * The main difference is that 'calendardata' is optional.
     *
     * @param string $principalUri
     * @return array
     */
    function getSchedulingObjects($principalUri) {

        $stmt = $this->pdo->prepare('SELECT id, calendardata, uri, lastmodified, etag, size FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ?');
        $stmt->execute([$principalUri]);

        $result = [];
        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
            $result[] = [
                'calendardata' => $row['calendardata'],
                'uri'          => $row['uri'],
                'lastmodified' => $row['lastmodified'],
                'etag'         => '"' . $row['etag'] . '"',
                'size'         => (int)$row['size'],
            ];
        }

        return $result;

    }

    /**
     * Deletes a scheduling object
     *
     * @param string $principalUri
     * @param string $objectUri
     * @return void
     */
    function deleteSchedulingObject($principalUri, $objectUri) {

        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->schedulingObjectTableName . ' WHERE principaluri = ? AND uri = ?');
        $stmt->execute([$principalUri, $objectUri]);

    }

    /**
     * Creates a new scheduling object. This should land in a users' inbox.
     *
     * @param string $principalUri
     * @param string $objectUri
     * @param string $objectData
     * @return void
     */
    function createSchedulingObject($principalUri, $objectUri, $objectData) {

        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->schedulingObjectTableName . ' (principaluri, calendardata, uri, lastmodified, etag, size) VALUES (?, ?, ?, ?, ?, ?)');
        $stmt->execute([$principalUri, $objectData, $objectUri, time(), md5($objectData), strlen($objectData)]);

    }

    /**
     * Updates the list of shares.
     *
     * @param mixed $calendarId
     * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
     * @return void
     */
    function updateInvites($calendarId, array $sharees) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId');
        }
        $currentInvites = $this->getInvites($calendarId);
        list($calendarId, $instanceId) = $calendarId;

        $removeStmt = $this->pdo->prepare("DELETE FROM " . $this->calendarInstancesTableName . " WHERE calendarid = ? AND share_href = ? AND access IN (2,3)");
        $updateStmt = $this->pdo->prepare("UPDATE " . $this->calendarInstancesTableName . " SET access = ?, share_displayname = ?, share_invitestatus = ? WHERE calendarid = ? AND share_href = ?");

        $insertStmt = $this->pdo->prepare('
INSERT INTO ' . $this->calendarInstancesTableName . '
    (
        calendarid,
        principaluri,
        access,
        displayname,
        uri,
        description,
        calendarorder,
        calendarcolor,
        timezone,
        transparent,
        share_href,
        share_displayname,
        share_invitestatus
    )
    SELECT
        ?,
        ?,
        ?,
        displayname,
        ?,
        description,
        calendarorder,
        calendarcolor,
        timezone,
        1,
        ?,
        ?,
        ?
    FROM ' . $this->calendarInstancesTableName . ' WHERE id = ?');

        foreach ($sharees as $sharee) {

            if ($sharee->access === \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS) {
                // if access was set no NOACCESS, it means access for an
                // existing sharee was removed.
                $removeStmt->execute([$calendarId, $sharee->href]);
                continue;
            }

            if (is_null($sharee->principal)) {
                // If the server could not determine the principal automatically,
                // we will mark the invite status as invalid.
                $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_INVALID;
            } else {
                // Because sabre/dav does not yet have an invitation system,
                // every invite is automatically accepted for now.
                $sharee->inviteStatus = \Sabre\DAV\Sharing\Plugin::INVITE_ACCEPTED;
            }

            foreach ($currentInvites as $oldSharee) {

                if ($oldSharee->href === $sharee->href) {
                    // This is an update
                    $sharee->properties = array_merge(
                        $oldSharee->properties,
                        $sharee->properties
                    );
                    $updateStmt->execute([
                        $sharee->access,
                        isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
                        $sharee->inviteStatus ?: $oldSharee->inviteStatus,
                        $calendarId,
                        $sharee->href
                    ]);
                    continue 2;
                }

            }
            // If we got here, it means it was a new sharee
            $insertStmt->execute([
                $calendarId,
                $sharee->principal,
                $sharee->access,
                \Sabre\DAV\UUIDUtil::getUUID(),
                $sharee->href,
                isset($sharee->properties['{DAV:}displayname']) ? $sharee->properties['{DAV:}displayname'] : null,
                $sharee->inviteStatus ?: \Sabre\DAV\Sharing\Plugin::INVITE_NORESPONSE,
                $instanceId
            ]);

        }

    }

    /**
     * Returns the list of people whom a calendar is shared with.
     *
     * Every item in the returned list must be a Sharee object with at
     * least the following properties set:
     *   $href
     *   $shareAccess
     *   $inviteStatus
     *
     * and optionally:
     *   $properties
     *
     * @param mixed $calendarId
     * @return \Sabre\DAV\Xml\Element\Sharee[]
     */
    function getInvites($calendarId) {

        if (!is_array($calendarId)) {
            throw new \InvalidArgumentException('The value passed to getInvites() is expected to be an array with a calendarId and an instanceId');
        }
        list($calendarId, $instanceId) = $calendarId;

        $query = <<<SQL
SELECT
    principaluri,
    access,
    share_href,
    share_displayname,
    share_invitestatus
FROM {$this->calendarInstancesTableName}
WHERE
    calendarid = ?
SQL;

        $stmt = $this->pdo->prepare($query);
        $stmt->execute([$calendarId]);

        $result = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

            $result[] = new Sharee([
                'href'   => isset($row['share_href']) ? $row['share_href'] : \Sabre\HTTP\encodePath($row['principaluri']),
                'access' => (int)$row['access'],
                /// Everyone is always immediately accepted, for now.
                'inviteStatus' => (int)$row['share_invitestatus'],
                'properties'   =>
                    !empty($row['share_displayname'])
                    ? ['{DAV:}displayname' => $row['share_displayname']]
                    : [],
                'principal' => $row['principaluri'],
            ]);

        }
        return $result;

    }

    /**
     * Publishes a calendar
     *
     * @param mixed $calendarId
     * @param bool $value
     * @return void
     */
    function setPublishStatus($calendarId, $value) {

        throw new DAV\Exception\NotImplemented('Not implemented');

    }

}
<?php

namespace Sabre\CalDAV\Backend;

/**
 * Implementing this interface adds CalDAV Scheduling support to your caldav
 * server, as defined in rfc6638.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface SchedulingSupport extends BackendInterface {

    /**
     * Returns a single scheduling object for the inbox collection.
     *
     * The returned array should contain the following elements:
     *   * uri - A unique basename for the object. This will be used to
     *           construct a full uri.
     *   * calendardata - The iCalendar object
     *   * lastmodified - The last modification date. Can be an int for a unix
     *                    timestamp, or a PHP DateTime object.
     *   * etag - A unique token that must change if the object changed.
     *   * size - The size of the object, in bytes.
     *
     * @param string $principalUri
     * @param string $objectUri
     * @return array
     */
    function getSchedulingObject($principalUri, $objectUri);

    /**
     * Returns all scheduling objects for the inbox collection.
     *
     * These objects should be returned as an array. Every item in the array
     * should follow the same structure as returned from getSchedulingObject.
     *
     * The main difference is that 'calendardata' is optional.
     *
     * @param string $principalUri
     * @return array
     */
    function getSchedulingObjects($principalUri);

    /**
     * Deletes a scheduling object from the inbox collection.
     *
     * @param string $principalUri
     * @param string $objectUri
     * @return void
     */
    function deleteSchedulingObject($principalUri, $objectUri);

    /**
     * Creates a new scheduling object. This should land in a users' inbox.
     *
     * @param string $principalUri
     * @param string $objectUri
     * @param string $objectData
     * @return void
     */
    function createSchedulingObject($principalUri, $objectUri, $objectData);

}
<?php

namespace Sabre\CalDAV\Backend;

/**
 * Adds support for sharing features to a CalDAV server.
 *
 * CalDAV backends that implement this interface, must make the following
 * modifications to getCalendarsForUser:
 *
 * 1. Return shared calendars for users.
 * 2. For every calendar, return calendar-resource-uri. This strings is a URI or
 *    relative URI reference that must be unique for every calendar, but
 *    identical for every instance of the same shared calendar.
 * 3. For every calendar, you must return a share-access element. This element
 *    should contain one of the Sabre\DAV\Sharing\Plugin:ACCESS_* constants and
 *    indicates the access level the user has.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface SharingSupport extends BackendInterface {

    /**
     * Updates the list of shares.
     *
     * @param mixed $calendarId
     * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
     * @return void
     */
    function updateInvites($calendarId, array $sharees);

    /**
     * Returns the list of people whom this calendar is shared with.
     *
     * Every item in the returned list must be a Sharee object with at
     * least the following properties set:
     *   $href
     *   $shareAccess
     *   $inviteStatus
     *
     * and optionally:
     *   $properties
     *
     * @param mixed $calendarId
     * @return \Sabre\DAV\Xml\Element\Sharee[]
     */
    function getInvites($calendarId);

    /**
     * Publishes a calendar
     *
     * @param mixed $calendarId
     * @param bool $value
     * @return void
     */
    function setPublishStatus($calendarId, $value);

}
<?php

namespace Sabre\CalDAV\Backend;

use Sabre\CalDAV;
use Sabre\DAV;

/**
 * Simple PDO CalDAV backend.
 *
 * This class is basically the most minimum example to get a caldav backend up
 * and running. This class uses the following schema (MySQL example):
 *
 * CREATE TABLE simple_calendars (
 *    id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
 *    uri VARBINARY(200) NOT NULL,
 *    principaluri VARBINARY(200) NOT NULL
 * );
 *
 * CREATE TABLE simple_calendarobjects (
 *    id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
 *    calendarid INT UNSIGNED NOT NULL,
 *    uri VARBINARY(200) NOT NULL,
 *    calendardata MEDIUMBLOB
 * )
 *
 * To make this class work, you absolutely need to have the PropertyStorage
 * plugin enabled.
 *
 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SimplePDO extends AbstractBackend {

    /**
     * pdo
     *
     * @var \PDO
     */
    protected $pdo;

    /**
     * Creates the backend
     *
     * @param \PDO $pdo
     */
    function __construct(\PDO $pdo) {

        $this->pdo = $pdo;

    }

    /**
     * Returns a list of calendars for a principal.
     *
     * Every project is an array with the following keys:
     *  * id, a unique id that will be used by other functions to modify the
     *    calendar. This can be the same as the uri or a database key.
     *  * uri. This is just the 'base uri' or 'filename' of the calendar.
     *  * principaluri. The owner of the calendar. Almost always the same as
     *    principalUri passed to this method.
     *
     * Furthermore it can contain webdav properties in clark notation. A very
     * common one is '{DAV:}displayname'.
     *
     * Many clients also require:
     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
     * For this property, you can just return an instance of
     * Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet.
     *
     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
     * ACL will automatically be put in read-only mode.
     *
     * @param string $principalUri
     * @return array
     */
    function getCalendarsForUser($principalUri) {

        // Making fields a comma-delimited list
        $stmt = $this->pdo->prepare("SELECT id, uri FROM simple_calendars WHERE principaluri = ? ORDER BY id ASC");
        $stmt->execute([$principalUri]);

        $calendars = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

            $calendars[] = [
                'id'           => $row['id'],
                'uri'          => $row['uri'],
                'principaluri' => $principalUri,
            ];

        }

        return $calendars;

    }

    /**
     * Creates a new calendar for a principal.
     *
     * If the creation was a success, an id must be returned that can be used
     * to reference this calendar in other methods, such as updateCalendar.
     *
     * @param string $principalUri
     * @param string $calendarUri
     * @param array $properties
     * @return string
     */
    function createCalendar($principalUri, $calendarUri, array $properties) {

        $stmt = $this->pdo->prepare("INSERT INTO simple_calendars (principaluri, uri) VALUES (?, ?)");
        $stmt->execute([$principalUri, $calendarUri]);

        return $this->pdo->lastInsertId();

    }

    /**
     * Delete a calendar and all it's objects
     *
     * @param string $calendarId
     * @return void
     */
    function deleteCalendar($calendarId) {

        $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ?');
        $stmt->execute([$calendarId]);

        $stmt = $this->pdo->prepare('DELETE FROM simple_calendars WHERE id = ?');
        $stmt->execute([$calendarId]);

    }

    /**
     * Returns all calendar objects within a calendar.
     *
     * Every item contains an array with the following keys:
     *   * calendardata - The iCalendar-compatible calendar data
     *   * uri - a unique key which will be used to construct the uri. This can
     *     be any arbitrary string, but making sure it ends with '.ics' is a
     *     good idea. This is only the basename, or filename, not the full
     *     path.
     *   * lastmodified - a timestamp of the last modification time
     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
     *   '  "abcdef"')
     *   * size - The size of the calendar objects, in bytes.
     *   * component - optional, a string containing the type of object, such
     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
     *     the Content-Type header.
     *
     * Note that the etag is optional, but it's highly encouraged to return for
     * speed reasons.
     *
     * The calendardata is also optional. If it's not returned
     * 'getCalendarObject' will be called later, which *is* expected to return
     * calendardata.
     *
     * If neither etag or size are specified, the calendardata will be
     * used/fetched to determine these numbers. If both are specified the
     * amount of times this is needed is reduced by a great degree.
     *
     * @param string $calendarId
     * @return array
     */
    function getCalendarObjects($calendarId) {

        $stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ?');
        $stmt->execute([$calendarId]);

        $result = [];
        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
            $result[] = [
                'id'           => $row['id'],
                'uri'          => $row['uri'],
                'etag'         => '"' . md5($row['calendardata']) . '"',
                'calendarid'   => $calendarId,
                'size'         => strlen($row['calendardata']),
                'calendardata' => $row['calendardata'],
            ];
        }

        return $result;

    }

    /**
     * Returns information from a single calendar object, based on it's object
     * uri.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * The returned array must have the same keys as getCalendarObjects. The
     * 'calendardata' object is required here though, while it's not required
     * for getCalendarObjects.
     *
     * This method must return null if the object did not exist.
     *
     * @param string $calendarId
     * @param string $objectUri
     * @return array|null
     */
    function getCalendarObject($calendarId, $objectUri) {

        $stmt = $this->pdo->prepare('SELECT id, uri, calendardata FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
        $stmt->execute([$calendarId, $objectUri]);
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);

        if (!$row) return null;

        return [
            'id'           => $row['id'],
            'uri'          => $row['uri'],
            'etag'         => '"' . md5($row['calendardata']) . '"',
            'calendarid'   => $calendarId,
            'size'         => strlen($row['calendardata']),
            'calendardata' => $row['calendardata'],
         ];

    }

    /**
     * Creates a new calendar object.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * It is possible return an etag from this function, which will be used in
     * the response to this PUT request. Note that the ETag must be surrounded
     * by double-quotes.
     *
     * However, you should only really return this ETag if you don't mangle the
     * calendar-data. If the result of a subsequent GET to this object is not
     * the exact same as this request body, you should omit the ETag.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @param string $calendarData
     * @return string|null
     */
    function createCalendarObject($calendarId, $objectUri, $calendarData) {

        $stmt = $this->pdo->prepare('INSERT INTO simple_calendarobjects (calendarid, uri, calendardata) VALUES (?,?,?)');
        $stmt->execute([
            $calendarId,
            $objectUri,
            $calendarData
        ]);

        return '"' . md5($calendarData) . '"';

    }

    /**
     * Updates an existing calendarobject, based on it's uri.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * It is possible return an etag from this function, which will be used in
     * the response to this PUT request. Note that the ETag must be surrounded
     * by double-quotes.
     *
     * However, you should only really return this ETag if you don't mangle the
     * calendar-data. If the result of a subsequent GET to this object is not
     * the exact same as this request body, you should omit the ETag.
     *
     * @param mixed $calendarId
     * @param string $objectUri
     * @param string $calendarData
     * @return string|null
     */
    function updateCalendarObject($calendarId, $objectUri, $calendarData) {

        $stmt = $this->pdo->prepare('UPDATE simple_calendarobjects SET calendardata = ? WHERE calendarid = ? AND uri = ?');
        $stmt->execute([$calendarData, $calendarId, $objectUri]);

        return '"' . md5($calendarData) . '"';

    }

    /**
     * Deletes an existing calendar object.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * @param string $calendarId
     * @param string $objectUri
     * @return void
     */
    function deleteCalendarObject($calendarId, $objectUri) {

        $stmt = $this->pdo->prepare('DELETE FROM simple_calendarobjects WHERE calendarid = ? AND uri = ?');
        $stmt->execute([$calendarId, $objectUri]);

    }

}
<?php

namespace Sabre\CalDAV\Backend;

use Sabre\DAV;

/**
 * Every CalDAV backend must at least implement this interface.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface SubscriptionSupport extends BackendInterface {

    /**
     * Returns a list of subscriptions for a principal.
     *
     * Every subscription is an array with the following keys:
     *  * id, a unique id that will be used by other functions to modify the
     *    subscription. This can be the same as the uri or a database key.
     *  * uri. This is just the 'base uri' or 'filename' of the subscription.
     *  * principaluri. The owner of the subscription. Almost always the same as
     *    principalUri passed to this method.
     *
     * Furthermore, all the subscription info must be returned too:
     *
     * 1. {DAV:}displayname
     * 2. {http://apple.com/ns/ical/}refreshrate
     * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
     *    should not be stripped).
     * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
     *    should not be stripped).
     * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
     *    attachments should not be stripped).
     * 6. {http://calendarserver.org/ns/}source (Must be a
     *     Sabre\DAV\Property\Href).
     * 7. {http://apple.com/ns/ical/}calendar-color
     * 8. {http://apple.com/ns/ical/}calendar-order
     * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
     *    (should just be an instance of
     *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
     *    default components).
     *
     * @param string $principalUri
     * @return array
     */
    function getSubscriptionsForUser($principalUri);

    /**
     * Creates a new subscription for a principal.
     *
     * If the creation was a success, an id must be returned that can be used to reference
     * this subscription in other methods, such as updateSubscription.
     *
     * @param string $principalUri
     * @param string $uri
     * @param array $properties
     * @return mixed
     */
    function createSubscription($principalUri, $uri, array $properties);

    /**
     * Updates a subscription
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param mixed $subscriptionId
     * @param \Sabre\DAV\PropPatch $propPatch
     * @return void
     */
    function updateSubscription($subscriptionId, DAV\PropPatch $propPatch);

    /**
     * Deletes a subscription.
     *
     * @param mixed $subscriptionId
     * @return void
     */
    function deleteSubscription($subscriptionId);

}
<?php

namespace Sabre\CalDAV\Backend;

/**
 * WebDAV-sync support for CalDAV backends.
 *
 * In order for backends to advertise support for WebDAV-sync, this interface
 * must be implemented.
 *
 * Implementing this can result in a significant reduction of bandwidth and CPU
 * time.
 *
 * For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
 * property from getCalendarsFromUser.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface SyncSupport extends BackendInterface {

    /**
     * The getChanges method returns all the changes that have happened, since
     * the specified syncToken in the specified calendar.
     *
     * This function should return an array, such as the following:
     *
     * [
     *   'syncToken' => 'The current synctoken',
     *   'added'   => [
     *      'new.txt',
     *   ],
     *   'modified'   => [
     *      'modified.txt',
     *   ],
     *   'deleted' => [
     *      'foo.php.bak',
     *      'old.txt'
     *   ]
     * );
     *
     * The returned syncToken property should reflect the *current* syncToken
     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
     * property This is * needed here too, to ensure the operation is atomic.
     *
     * If the $syncToken argument is specified as null, this is an initial
     * sync, and all members should be reported.
     *
     * The modified property is an array of nodenames that have changed since
     * the last token.
     *
     * The deleted property is an array with nodenames, that have been deleted
     * from collection.
     *
     * The $syncLevel argument is basically the 'depth' of the report. If it's
     * 1, you only have to report changes that happened only directly in
     * immediate descendants. If it's 2, it should also include changes from
     * the nodes below the child collections. (grandchildren)
     *
     * The $limit argument allows a client to specify how many results should
     * be returned at most. If the limit is not specified, it should be treated
     * as infinite.
     *
     * If the limit (infinite or not) is higher than you're willing to return,
     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
     *
     * If the syncToken is expired (due to data cleanup) or unknown, you must
     * return null.
     *
     * The limit is 'suggestive'. You are free to ignore it.
     *
     * @param string $calendarId
     * @param string $syncToken
     * @param int $syncLevel
     * @param int $limit
     * @return array
     */
    function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null);

}
<?php

namespace Sabre\CalDAV;

use Sabre\DAV;
use Sabre\DAV\PropPatch;
use Sabre\DAVACL;

/**
 * This object represents a CalDAV calendar.
 *
 * A calendar can contain multiple TODO and or Events. These are represented
 * as \Sabre\CalDAV\CalendarObject objects.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet {

    use DAVACL\ACLTrait;

    /**
     * This is an array with calendar information
     *
     * @var array
     */
    protected $calendarInfo;

    /**
     * CalDAV backend
     *
     * @var Backend\BackendInterface
     */
    protected $caldavBackend;

    /**
     * Constructor
     *
     * @param Backend\BackendInterface $caldavBackend
     * @param array $calendarInfo
     */
    function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) {

        $this->caldavBackend = $caldavBackend;
        $this->calendarInfo = $calendarInfo;

    }

    /**
     * Returns the name of the calendar
     *
     * @return string
     */
    function getName() {

        return $this->calendarInfo['uri'];

    }

    /**
     * Updates properties on this node.
     *
     * This method received a PropPatch object, which contains all the
     * information about the update.
     *
     * To update specific properties, call the 'handle' method on this object.
     * Read the PropPatch documentation for more information.
     *
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatch(PropPatch $propPatch) {

        return $this->caldavBackend->updateCalendar($this->calendarInfo['id'], $propPatch);

    }

    /**
     * Returns the list of properties
     *
     * @param array $requestedProperties
     * @return array
     */
    function getProperties($requestedProperties) {

        $response = [];

        foreach ($this->calendarInfo as $propName => $propValue) {

            if (!is_null($propValue) && $propName[0] === '{')
                $response[$propName] = $this->calendarInfo[$propName];

        }
        return $response;

    }

    /**
     * Returns a calendar object
     *
     * The contained calendar objects are for example Events or Todo's.
     *
     * @param string $name
     * @return \Sabre\CalDAV\ICalendarObject
     */
    function getChild($name) {

        $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);

        if (!$obj) throw new DAV\Exception\NotFound('Calendar object not found');

        $obj['acl'] = $this->getChildACL();

        return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);

    }

    /**
     * Returns the full list of calendar objects
     *
     * @return array
     */
    function getChildren() {

        $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']);
        $children = [];
        foreach ($objs as $obj) {
            $obj['acl'] = $this->getChildACL();
            $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
        }
        return $children;

    }

    /**
     * This method receives a list of paths in it's first argument.
     * It must return an array with Node objects.
     *
     * If any children are not found, you do not have to return them.
     *
     * @param string[] $paths
     * @return array
     */
    function getMultipleChildren(array $paths) {

        $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths);
        $children = [];
        foreach ($objs as $obj) {
            $obj['acl'] = $this->getChildACL();
            $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj);
        }
        return $children;

    }

    /**
     * Checks if a child-node exists.
     *
     * @param string $name
     * @return bool
     */
    function childExists($name) {

        $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name);
        if (!$obj)
            return false;
        else
            return true;

    }

    /**
     * Creates a new directory
     *
     * We actually block this, as subdirectories are not allowed in calendars.
     *
     * @param string $name
     * @return void
     */
    function createDirectory($name) {

        throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed');

    }

    /**
     * Creates a new file
     *
     * The contents of the new file must be a valid ICalendar string.
     *
     * @param string $name
     * @param resource $calendarData
     * @return string|null
     */
    function createFile($name, $calendarData = null) {

        if (is_resource($calendarData)) {
            $calendarData = stream_get_contents($calendarData);
        }
        return $this->caldavBackend->createCalendarObject($this->calendarInfo['id'], $name, $calendarData);

    }

    /**
     * Deletes the calendar.
     *
     * @return void
     */
    function delete() {

        $this->caldavBackend->deleteCalendar($this->calendarInfo['id']);

    }

    /**
     * Renames the calendar. Note that most calendars use the
     * {DAV:}displayname to display a name to display a name.
     *
     * @param string $newName
     * @return void
     */
    function setName($newName) {

        throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported');

    }

    /**
     * Returns the last modification date as a unix timestamp.
     *
     * @return null
     */
    function getLastModified() {

        return null;

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->calendarInfo['principaluri'];

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        $acl = [
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner(),
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner() . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner() . '/calendar-proxy-read',
                'protected' => true,
            ],
            [
                'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
                'principal' => '{DAV:}authenticated',
                'protected' => true,
            ],

        ];
        if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
            $acl[] = [
                'privilege' => '{DAV:}write',
                'principal' => $this->getOwner(),
                'protected' => true,
            ];
            $acl[] = [
                'privilege' => '{DAV:}write',
                'principal' => $this->getOwner() . '/calendar-proxy-write',
                'protected' => true,
            ];
        }

        return $acl;

    }

    /**
     * This method returns the ACL's for calendar objects in this calendar.
     * The result of this method automatically gets passed to the
     * calendar-object nodes in the calendar.
     *
     * @return array
     */
    function getChildACL() {

        $acl = [
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner(),
                'protected' => true,
            ],

            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner() . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner() . '/calendar-proxy-read',
                'protected' => true,
            ],

        ];
        if (empty($this->calendarInfo['{http://sabredav.org/ns}read-only'])) {
            $acl[] = [
                'privilege' => '{DAV:}write',
                'principal' => $this->getOwner(),
                'protected' => true,
            ];
            $acl[] = [
                'privilege' => '{DAV:}write',
                'principal' => $this->getOwner() . '/calendar-proxy-write',
                'protected' => true,
            ];

        }
        return $acl;

    }


    /**
     * Performs a calendar-query on the contents of this calendar.
     *
     * The calendar-query is defined in RFC4791 : CalDAV. Using the
     * calendar-query it is possible for a client to request a specific set of
     * object, based on contents of iCalendar properties, date-ranges and
     * iCalendar component types (VTODO, VEVENT).
     *
     * This method should just return a list of (relative) urls that match this
     * query.
     *
     * The list of filters are specified as an array. The exact array is
     * documented by Sabre\CalDAV\CalendarQueryParser.
     *
     * @param array $filters
     * @return array
     */
    function calendarQuery(array $filters) {

        return $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters);

    }

    /**
     * This method returns the current sync-token for this collection.
     * This can be any string.
     *
     * If null is returned from this function, the plugin assumes there's no
     * sync information available.
     *
     * @return string|null
     */
    function getSyncToken() {

        if (
            $this->caldavBackend instanceof Backend\SyncSupport &&
            isset($this->calendarInfo['{DAV:}sync-token'])
        ) {
            return $this->calendarInfo['{DAV:}sync-token'];
        }
        if (
            $this->caldavBackend instanceof Backend\SyncSupport &&
            isset($this->calendarInfo['{http://sabredav.org/ns}sync-token'])
        ) {
            return $this->calendarInfo['{http://sabredav.org/ns}sync-token'];
        }

    }

    /**
     * The getChanges method returns all the changes that have happened, since
     * the specified syncToken and the current collection.
     *
     * This function should return an array, such as the following:
     *
     * [
     *   'syncToken' => 'The current synctoken',
     *   'added'   => [
     *      'new.txt',
     *   ],
     *   'modified'   => [
     *      'modified.txt',
     *   ],
     *   'deleted' => [
     *      'foo.php.bak',
     *      'old.txt'
     *   ]
     * ];
     *
     * The syncToken property should reflect the *current* syncToken of the
     * collection, as reported getSyncToken(). This is needed here too, to
     * ensure the operation is atomic.
     *
     * If the syncToken is specified as null, this is an initial sync, and all
     * members should be reported.
     *
     * The modified property is an array of nodenames that have changed since
     * the last token.
     *
     * The deleted property is an array with nodenames, that have been deleted
     * from collection.
     *
     * The second argument is basically the 'depth' of the report. If it's 1,
     * you only have to report changes that happened only directly in immediate
     * descendants. If it's 2, it should also include changes from the nodes
     * below the child collections. (grandchildren)
     *
     * The third (optional) argument allows a client to specify how many
     * results should be returned at most. If the limit is not specified, it
     * should be treated as infinite.
     *
     * If the limit (infinite or not) is higher than you're willing to return,
     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
     *
     * If the syncToken is expired (due to data cleanup) or unknown, you must
     * return null.
     *
     * The limit is 'suggestive'. You are free to ignore it.
     *
     * @param string $syncToken
     * @param int $syncLevel
     * @param int $limit
     * @return array
     */
    function getChanges($syncToken, $syncLevel, $limit = null) {

        if (!$this->caldavBackend instanceof Backend\SyncSupport) {
            return null;
        }

        return $this->caldavBackend->getChangesForCalendar(
            $this->calendarInfo['id'],
            $syncToken,
            $syncLevel,
            $limit
        );

    }

}
<?php

namespace Sabre\CalDAV;

use Sabre\DAV;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\MkCol;
use Sabre\DAVACL;
use Sabre\HTTP\URLUtil;

/**
 * The CalendarHome represents a node that is usually in a users'
 * calendar-homeset.
 *
 * It contains all the users' calendars, and can optionally contain a
 * notifications collection, calendar subscriptions, a users' inbox, and a
 * users' outbox.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL {

    use DAVACL\ACLTrait;

    /**
     * CalDAV backend
     *
     * @var Backend\BackendInterface
     */
    protected $caldavBackend;

    /**
     * Principal information
     *
     * @var array
     */
    protected $principalInfo;

    /**
     * Constructor
     *
     * @param Backend\BackendInterface $caldavBackend
     * @param array $principalInfo
     */
    function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) {

        $this->caldavBackend = $caldavBackend;
        $this->principalInfo = $principalInfo;

    }

    /**
     * Returns the name of this object
     *
     * @return string
     */
    function getName() {

        list(, $name) = URLUtil::splitPath($this->principalInfo['uri']);
        return $name;

    }

    /**
     * Updates the name of this object
     *
     * @param string $name
     * @return void
     */
    function setName($name) {

        throw new DAV\Exception\Forbidden();

    }

    /**
     * Deletes this object
     *
     * @return void
     */
    function delete() {

        throw new DAV\Exception\Forbidden();

    }

    /**
     * Returns the last modification date
     *
     * @return int
     */
    function getLastModified() {

        return null;

    }

    /**
     * Creates a new file under this object.
     *
     * This is currently not allowed
     *
     * @param string $filename
     * @param resource $data
     * @return void
     */
    function createFile($filename, $data = null) {

        throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');

    }

    /**
     * Creates a new directory under this object.
     *
     * This is currently not allowed.
     *
     * @param string $filename
     * @return void
     */
    function createDirectory($filename) {

        throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');

    }

    /**
     * Returns a single calendar, by name
     *
     * @param string $name
     * @return Calendar
     */
    function getChild($name) {

        // Special nodes
        if ($name === 'inbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
            return new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
        }
        if ($name === 'outbox' && $this->caldavBackend instanceof Backend\SchedulingSupport) {
            return new Schedule\Outbox($this->principalInfo['uri']);
        }
        if ($name === 'notifications' && $this->caldavBackend instanceof Backend\NotificationSupport) {
            return new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
        }

        // Calendars
        foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) {
            if ($calendar['uri'] === $name) {
                if ($this->caldavBackend instanceof Backend\SharingSupport) {
                    return new SharedCalendar($this->caldavBackend, $calendar);
                } else {
                    return new Calendar($this->caldavBackend, $calendar);
                }
            }
        }

        if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
            foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
                if ($subscription['uri'] === $name) {
                    return new Subscriptions\Subscription($this->caldavBackend, $subscription);
                }
            }

        }

        throw new NotFound('Node with name \'' . $name . '\' could not be found');

    }

    /**
     * Checks if a calendar exists.
     *
     * @param string $name
     * @return bool
     */
    function childExists($name) {

        try {
            return !!$this->getChild($name);
        } catch (NotFound $e) {
            return false;
        }

    }

    /**
     * Returns a list of calendars
     *
     * @return array
     */
    function getChildren() {

        $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']);
        $objs = [];
        foreach ($calendars as $calendar) {
            if ($this->caldavBackend instanceof Backend\SharingSupport) {
                $objs[] = new SharedCalendar($this->caldavBackend, $calendar);
            } else {
                $objs[] = new Calendar($this->caldavBackend, $calendar);
            }
        }

        if ($this->caldavBackend instanceof Backend\SchedulingSupport) {
            $objs[] = new Schedule\Inbox($this->caldavBackend, $this->principalInfo['uri']);
            $objs[] = new Schedule\Outbox($this->principalInfo['uri']);
        }

        // We're adding a notifications node, if it's supported by the backend.
        if ($this->caldavBackend instanceof Backend\NotificationSupport) {
            $objs[] = new Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']);
        }

        // If the backend supports subscriptions, we'll add those as well,
        if ($this->caldavBackend instanceof Backend\SubscriptionSupport) {
            foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) {
                $objs[] = new Subscriptions\Subscription($this->caldavBackend, $subscription);
            }
        }

        return $objs;

    }

    /**
     * Creates a new calendar or subscription.
     *
     * @param string $name
     * @param MkCol $mkCol
     * @throws DAV\Exception\InvalidResourceType
     * @return void
     */
    function createExtendedCollection($name, MkCol $mkCol) {

        $isCalendar = false;
        $isSubscription = false;
        foreach ($mkCol->getResourceType() as $rt) {
            switch ($rt) {
                case '{DAV:}collection' :
                case '{http://calendarserver.org/ns/}shared-owner' :
                    // ignore
                    break;
                case '{urn:ietf:params:xml:ns:caldav}calendar' :
                    $isCalendar = true;
                    break;
                case '{http://calendarserver.org/ns/}subscribed' :
                    $isSubscription = true;
                    break;
                default :
                    throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt);
            }
        }

        $properties = $mkCol->getRemainingValues();
        $mkCol->setRemainingResultCode(201);

        if ($isSubscription) {
            if (!$this->caldavBackend instanceof Backend\SubscriptionSupport) {
                throw new DAV\Exception\InvalidResourceType('This backend does not support subscriptions');
            }
            $this->caldavBackend->createSubscription($this->principalInfo['uri'], $name, $properties);

        } elseif ($isCalendar) {
            $this->caldavBackend->createCalendar($this->principalInfo['uri'], $name, $properties);

        } else {
            throw new DAV\Exception\InvalidResourceType('You can only create calendars and subscriptions in this collection');

        }

    }

    /**
     * Returns the owner of the calendar home.
     *
     * @return string
     */
    function getOwner() {

        return $this->principalInfo['uri'];

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        return [
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->principalInfo['uri'],
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}write',
                'principal' => $this->principalInfo['uri'],
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}write',
                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->principalInfo['uri'] . '/calendar-proxy-read',
                'protected' => true,
            ],

        ];

    }


    /**
     * This method is called when a user replied to a request to share.
     *
     * This method should return the url of the newly created calendar if the
     * share was accepted.
     *
     * @param string $href The sharee who is replying (often a mailto: address)
     * @param int    $status One of the SharingPlugin::STATUS_* constants
     * @param string $calendarUri The url to the calendar thats being shared
     * @param string $inReplyTo The unique id this message is a response to
     * @param string $summary A description of the reply
     * @return null|string
     */
    function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) {

        if (!$this->caldavBackend instanceof Backend\SharingSupport) {
            throw new DAV\Exception\NotImplemented('Sharing support is not implemented by this backend.');
        }

        return $this->caldavBackend->shareReply($href, $status, $calendarUri, $inReplyTo, $summary);

    }

    /**
     * Searches through all of a users calendars and calendar objects to find
     * an object with a specific UID.
     *
     * This method should return the path to this object, relative to the
     * calendar home, so this path usually only contains two parts:
     *
     * calendarpath/objectpath.ics
     *
     * If the uid is not found, return null.
     *
     * This method should only consider * objects that the principal owns, so
     * any calendars owned by other principals that also appear in this
     * collection should be ignored.
     *
     * @param string $uid
     * @return string|null
     */
    function getCalendarObjectByUID($uid) {

        return $this->caldavBackend->getCalendarObjectByUID($this->principalInfo['uri'], $uid);

    }

}
<?php

namespace Sabre\CalDAV;

/**
 * The CalendarObject represents a single VEVENT or VTODO within a Calendar.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL {

    use \Sabre\DAVACL\ACLTrait;

    /**
     * Sabre\CalDAV\Backend\BackendInterface
     *
     * @var Backend\AbstractBackend
     */
    protected $caldavBackend;

    /**
     * Array with information about this CalendarObject
     *
     * @var array
     */
    protected $objectData;

    /**
     * Array with information about the containing calendar
     *
     * @var array
     */
    protected $calendarInfo;

    /**
     * Constructor
     *
     * The following properties may be passed within $objectData:
     *
     *   * calendarid - This must refer to a calendarid from a caldavBackend
     *   * uri - A unique uri. Only the 'basename' must be passed.
     *   * calendardata (optional) - The iCalendar data
     *   * etag - (optional) The etag for this object, MUST be encloded with
     *            double-quotes.
     *   * size - (optional) The size of the data in bytes.
     *   * lastmodified - (optional) format as a unix timestamp.
     *   * acl - (optional) Use this to override the default ACL for the node.
     *
     * @param Backend\BackendInterface $caldavBackend
     * @param array $calendarInfo
     * @param array $objectData
     */
    function __construct(Backend\BackendInterface $caldavBackend, array $calendarInfo, array $objectData) {

        $this->caldavBackend = $caldavBackend;

        if (!isset($objectData['uri'])) {
            throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
        }

        $this->calendarInfo = $calendarInfo;
        $this->objectData = $objectData;

    }

    /**
     * Returns the uri for this object
     *
     * @return string
     */
    function getName() {

        return $this->objectData['uri'];

    }

    /**
     * Returns the ICalendar-formatted object
     *
     * @return string
     */
    function get() {

        // Pre-populating the 'calendardata' is optional, if we don't have it
        // already we fetch it from the backend.
        if (!isset($this->objectData['calendardata'])) {
            $this->objectData = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);
        }
        return $this->objectData['calendardata'];

    }

    /**
     * Updates the ICalendar-formatted object
     *
     * @param string|resource $calendarData
     * @return string
     */
    function put($calendarData) {

        if (is_resource($calendarData)) {
            $calendarData = stream_get_contents($calendarData);
        }
        $etag = $this->caldavBackend->updateCalendarObject($this->calendarInfo['id'], $this->objectData['uri'], $calendarData);
        $this->objectData['calendardata'] = $calendarData;
        $this->objectData['etag'] = $etag;

        return $etag;

    }

    /**
     * Deletes the calendar object
     *
     * @return void
     */
    function delete() {

        $this->caldavBackend->deleteCalendarObject($this->calendarInfo['id'], $this->objectData['uri']);

    }

    /**
     * Returns the mime content-type
     *
     * @return string
     */
    function getContentType() {

        $mime = 'text/calendar; charset=utf-8';
        if (isset($this->objectData['component']) && $this->objectData['component']) {
            $mime .= '; component=' . $this->objectData['component'];
        }
        return $mime;

    }

    /**
     * Returns an ETag for this object.
     *
     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
     *
     * @return string
     */
    function getETag() {

        if (isset($this->objectData['etag'])) {
            return $this->objectData['etag'];
        } else {
            return '"' . md5($this->get()) . '"';
        }

    }

    /**
     * Returns the last modification date as a unix timestamp
     *
     * @return int
     */
    function getLastModified() {

        return $this->objectData['lastmodified'];

    }

    /**
     * Returns the size of this object in bytes
     *
     * @return int
     */
    function getSize() {

        if (array_key_exists('size', $this->objectData)) {
            return $this->objectData['size'];
        } else {
            return strlen($this->get());
        }

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->calendarInfo['principaluri'];

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        // An alternative acl may be specified in the object data.
        if (isset($this->objectData['acl'])) {
            return $this->objectData['acl'];
        }

        // The default ACL
        return [
            [
                'privilege' => '{DAV:}all',
                'principal' => $this->calendarInfo['principaluri'],
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}all',
                'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
                'protected' => true,
            ],

        ];

    }

}
<?php

namespace Sabre\CalDAV;

use DateTime;
use Sabre\VObject;

/**
 * CalendarQuery Validator
 *
 * This class is responsible for checking if an iCalendar object matches a set
 * of filters. The main function to do this is 'validate'.
 *
 * This is used to determine which icalendar objects should be returned for a
 * calendar-query REPORT request.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CalendarQueryValidator {

    /**
     * Verify if a list of filters applies to the calendar data object
     *
     * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
     *
     * @param VObject\Component\VCalendar $vObject
     * @param array $filters
     * @return bool
     */
    function validate(VObject\Component\VCalendar $vObject, array $filters) {

        // The top level object is always a component filter.
        // We'll parse it manually, as it's pretty simple.
        if ($vObject->name !== $filters['name']) {
            return false;
        }

        return
            $this->validateCompFilters($vObject, $filters['comp-filters']) &&
            $this->validatePropFilters($vObject, $filters['prop-filters']);


    }

    /**
     * This method checks the validity of comp-filters.
     *
     * A list of comp-filters needs to be specified. Also the parent of the
     * component we're checking should be specified, not the component to check
     * itself.
     *
     * @param VObject\Component $parent
     * @param array $filters
     * @return bool
     */
    protected function validateCompFilters(VObject\Component $parent, array $filters) {

        foreach ($filters as $filter) {

            $isDefined = isset($parent->{$filter['name']});

            if ($filter['is-not-defined']) {

                if ($isDefined) {
                    return false;
                } else {
                    continue;
                }

            }
            if (!$isDefined) {
                return false;
            }

            if ($filter['time-range']) {
                foreach ($parent->{$filter['name']} as $subComponent) {
                    if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
                        continue 2;
                    }
                }
                return false;
            }

            if (!$filter['comp-filters'] && !$filter['prop-filters']) {
                continue;
            }

            // If there are sub-filters, we need to find at least one component
            // for which the subfilters hold true.
            foreach ($parent->{$filter['name']} as $subComponent) {

                if (
                    $this->validateCompFilters($subComponent, $filter['comp-filters']) &&
                    $this->validatePropFilters($subComponent, $filter['prop-filters'])) {
                        // We had a match, so this comp-filter succeeds
                        continue 2;
                }

            }

            // If we got here it means there were sub-comp-filters or
            // sub-prop-filters and there was no match. This means this filter
            // needs to return false.
            return false;

        }

        // If we got here it means we got through all comp-filters alive so the
        // filters were all true.
        return true;

    }

    /**
     * This method checks the validity of prop-filters.
     *
     * A list of prop-filters needs to be specified. Also the parent of the
     * property we're checking should be specified, not the property to check
     * itself.
     *
     * @param VObject\Component $parent
     * @param array $filters
     * @return bool
     */
    protected function validatePropFilters(VObject\Component $parent, array $filters) {

        foreach ($filters as $filter) {

            $isDefined = isset($parent->{$filter['name']});

            if ($filter['is-not-defined']) {

                if ($isDefined) {
                    return false;
                } else {
                    continue;
                }

            }
            if (!$isDefined) {
                return false;
            }

            if ($filter['time-range']) {
                foreach ($parent->{$filter['name']} as $subComponent) {
                    if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
                        continue 2;
                    }
                }
                return false;
            }

            if (!$filter['param-filters'] && !$filter['text-match']) {
                continue;
            }

            // If there are sub-filters, we need to find at least one property
            // for which the subfilters hold true.
            foreach ($parent->{$filter['name']} as $subComponent) {

                if (
                    $this->validateParamFilters($subComponent, $filter['param-filters']) &&
                    (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
                ) {
                    // We had a match, so this prop-filter succeeds
                    continue 2;
                }

            }

            // If we got here it means there were sub-param-filters or
            // text-match filters and there was no match. This means the
            // filter needs to return false.
            return false;

        }

        // If we got here it means we got through all prop-filters alive so the
        // filters were all true.
        return true;

    }

    /**
     * This method checks the validity of param-filters.
     *
     * A list of param-filters needs to be specified. Also the parent of the
     * parameter we're checking should be specified, not the parameter to check
     * itself.
     *
     * @param VObject\Property $parent
     * @param array $filters
     * @return bool
     */
    protected function validateParamFilters(VObject\Property $parent, array $filters) {

        foreach ($filters as $filter) {

            $isDefined = isset($parent[$filter['name']]);

            if ($filter['is-not-defined']) {

                if ($isDefined) {
                    return false;
                } else {
                    continue;
                }

            }
            if (!$isDefined) {
                return false;
            }

            if (!$filter['text-match']) {
                continue;
            }

            // If there are sub-filters, we need to find at least one parameter
            // for which the subfilters hold true.
            foreach ($parent[$filter['name']]->getParts() as $paramPart) {

                if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
                    // We had a match, so this param-filter succeeds
                    continue 2;
                }

            }

            // If we got here it means there was a text-match filter and there
            // were no matches. This means the filter needs to return false.
            return false;

        }

        // If we got here it means we got through all param-filters alive so the
        // filters were all true.
        return true;

    }

    /**
     * This method checks the validity of a text-match.
     *
     * A single text-match should be specified as well as the specific property
     * or parameter we need to validate.
     *
     * @param VObject\Node|string $check Value to check against.
     * @param array $textMatch
     * @return bool
     */
    protected function validateTextMatch($check, array $textMatch) {

        if ($check instanceof VObject\Node) {
            $check = $check->getValue();
        }

        $isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);

        return ($textMatch['negate-condition'] xor $isMatching);

    }

    /**
     * Validates if a component matches the given time range.
     *
     * This is all based on the rules specified in rfc4791, which are quite
     * complex.
     *
     * @param VObject\Node $component
     * @param DateTime $start
     * @param DateTime $end
     * @return bool
     */
    protected function validateTimeRange(VObject\Node $component, $start, $end) {

        if (is_null($start)) {
            $start = new DateTime('1900-01-01');
        }
        if (is_null($end)) {
            $end = new DateTime('3000-01-01');
        }

        switch ($component->name) {

            case 'VEVENT' :
            case 'VTODO' :
            case 'VJOURNAL' :

                return $component->isInTimeRange($start, $end);

            case 'VALARM' :

                // If the valarm is wrapped in a recurring event, we need to
                // expand the recursions, and validate each.
                //
                // Our datamodel doesn't easily allow us to do this straight
                // in the VALARM component code, so this is a hack, and an
                // expensive one too.
                if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {

                    // Fire up the iterator!
                    $it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
                    while ($it->valid()) {
                        $expandedEvent = $it->getEventObject();

                        // We need to check from these expanded alarms, which
                        // one is the first to trigger. Based on this, we can
                        // determine if we can 'give up' expanding events.
                        $firstAlarm = null;
                        if ($expandedEvent->VALARM !== null) {
                            foreach ($expandedEvent->VALARM as $expandedAlarm) {

                                $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
                                if ($expandedAlarm->isInTimeRange($start, $end)) {
                                    return true;
                                }

                                if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
                                    // This is an alarm with a non-relative trigger
                                    // time, likely created by a buggy client. The
                                    // implication is that every alarm in this
                                    // recurring event trigger at the exact same
                                    // time. It doesn't make sense to traverse
                                    // further.
                                } else {
                                    // We store the first alarm as a means to
                                    // figure out when we can stop traversing.
                                    if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
                                        $firstAlarm = $effectiveTrigger;
                                    }
                                }
                            }
                        }
                        if (is_null($firstAlarm)) {
                            // No alarm was found.
                            //
                            // Or technically: No alarm that will change for
                            // every instance of the recurrence was found,
                            // which means we can assume there was no match.
                            return false;
                        }
                        if ($firstAlarm > $end) {
                            return false;
                        }
                        $it->next();
                    }
                    return false;
                } else {
                    return $component->isInTimeRange($start, $end);
                }

            case 'VFREEBUSY' :
                throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');

            case 'COMPLETED' :
            case 'CREATED' :
            case 'DTEND' :
            case 'DTSTAMP' :
            case 'DTSTART' :
            case 'DUE' :
            case 'LAST-MODIFIED' :
                return ($start <= $component->getDateTime() && $end >= $component->getDateTime());



            default :
                throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');

        }

    }

}
<?php

namespace Sabre\CalDAV;

use Sabre\DAVACL\PrincipalBackend;

/**
 * Calendars collection
 *
 * This object is responsible for generating a list of calendar-homes for each
 * user.
 *
 * This is the top-most node for the calendars tree. In most servers this class
 * represents the "/calendars" path.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CalendarRoot extends \Sabre\DAVACL\AbstractPrincipalCollection {

    /**
     * CalDAV backend
     *
     * @var Backend\BackendInterface
     */
    protected $caldavBackend;

    /**
     * Constructor
     *
     * This constructor needs both an authentication and a caldav backend.
     *
     * By default this class will show a list of calendar collections for
     * principals in the 'principals' collection. If your main principals are
     * actually located in a different path, use the $principalPrefix argument
     * to override this.
     *
     * @param PrincipalBackend\BackendInterface $principalBackend
     * @param Backend\BackendInterface $caldavBackend
     * @param string $principalPrefix
     */
    function __construct(PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $caldavBackend, $principalPrefix = 'principals') {

        parent::__construct($principalBackend, $principalPrefix);
        $this->caldavBackend = $caldavBackend;

    }

    /**
     * Returns the nodename
     *
     * We're overriding this, because the default will be the 'principalPrefix',
     * and we want it to be Sabre\CalDAV\Plugin::CALENDAR_ROOT
     *
     * @return string
     */
    function getName() {

        return Plugin::CALENDAR_ROOT;

    }

    /**
     * This method returns a node for a principal.
     *
     * The passed array contains principal information, and is guaranteed to
     * at least contain a uri item. Other properties may or may not be
     * supplied by the authentication backend.
     *
     * @param array $principal
     * @return \Sabre\DAV\INode
     */
    function getChildForPrincipal(array $principal) {

        return new CalendarHome($this->caldavBackend, $principal);

    }

}
<?php

namespace Sabre\CalDAV\Exception;

use Sabre\CalDAV;
use Sabre\DAV;

/**
 * InvalidComponentType
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class InvalidComponentType extends DAV\Exception\Forbidden {

    /**
     * Adds in extra information in the xml response.
     *
     * This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $doc = $errorNode->ownerDocument;

        $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV, 'cal:supported-calendar-component');
        $errorNode->appendChild($np);

    }

}
<?php

namespace Sabre\CalDAV;

use Sabre\DAVACL;

/**
 * Calendar interface
 *
 * Implement this interface to allow a node to be recognized as an calendar.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ICalendar extends ICalendarObjectContainer, DAVACL\IACL {

}
<?php

namespace Sabre\CalDAV;

use Sabre\DAV;

/**
 * CalendarObject interface
 *
 * Extend the ICalendarObject interface to allow your custom nodes to be picked up as
 * CalendarObjects.
 *
 * Calendar objects are resources such as Events, Todo's or Journals.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ICalendarObject extends DAV\IFile {

}
<?php

namespace Sabre\CalDAV;

/**
 * This interface represents a node that may contain calendar objects.
 *
 * This is the shared parent for both the Inbox collection and calendars
 * resources.
 *
 * In most cases you will likely want to look at ICalendar instead of this
 * interface.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ICalendarObjectContainer extends \Sabre\DAV\ICollection {

    /**
     * Performs a calendar-query on the contents of this calendar.
     *
     * The calendar-query is defined in RFC4791 : CalDAV. Using the
     * calendar-query it is possible for a client to request a specific set of
     * object, based on contents of iCalendar properties, date-ranges and
     * iCalendar component types (VTODO, VEVENT).
     *
     * This method should just return a list of (relative) urls that match this
     * query.
     *
     * The list of filters are specified as an array. The exact array is
     * documented by \Sabre\CalDAV\CalendarQueryParser.
     *
     * @param array $filters
     * @return array
     */
    function calendarQuery(array $filters);

}
<?php

namespace Sabre\CalDAV;

use DateTime;
use DateTimeZone;
use Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject;

/**
 * ICS Exporter
 *
 * This plugin adds the ability to export entire calendars as .ics files.
 * This is useful for clients that don't support CalDAV yet. They often do
 * support ics files.
 *
 * To use this, point a http client to a caldav calendar, and add ?expand to
 * the url.
 *
 * Further options that can be added to the url:
 *   start=123456789 - Only return events after the given unix timestamp
 *   end=123245679   - Only return events from before the given unix timestamp
 *   expand=1        - Strip timezone information and expand recurring events.
 *                     If you'd like to expand, you _must_ also specify start
 *                     and end.
 *
 * By default this plugin returns data in the text/calendar format (iCalendar
 * 2.0). If you'd like to receive jCal data instead, you can use an Accept
 * header:
 *
 * Accept: application/calendar+json
 *
 * Alternatively, you can also specify this in the url using
 * accept=application/calendar+json, or accept=jcal for short. If the url
 * parameter and Accept header is specified, the url parameter wins.
 *
 * Note that specifying a start or end data implies that only events will be
 * returned. VTODO and VJOURNAL will be stripped.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ICSExportPlugin extends DAV\ServerPlugin {

    /**
     * Reference to Server class
     *
     * @var \Sabre\DAV\Server
     */
    protected $server;

    /**
     * Initializes the plugin and registers event handlers
     *
     * @param \Sabre\DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;
        $server->on('method:GET', [$this, 'httpGet'], 90);
        $server->on('browserButtonActions', function($path, $node, &$actions) {
            if ($node instanceof ICalendar) {
                $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="calendar"></span></a>';
            }
        });

    }

    /**
     * Intercepts GET requests on calendar urls ending with ?export.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpGet(RequestInterface $request, ResponseInterface $response) {

        $queryParams = $request->getQueryParameters();
        if (!array_key_exists('export', $queryParams)) return;

        $path = $request->getPath();

        $node = $this->server->getProperties($path, [
            '{DAV:}resourcetype',
            '{DAV:}displayname',
            '{http://sabredav.org/ns}sync-token',
            '{DAV:}sync-token',
            '{http://apple.com/ns/ical/}calendar-color',
        ]);

        if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
            return;
        }
        // Marking the transactionType, for logging purposes.
        $this->server->transactionType = 'get-calendar-export';

        $properties = $node;

        $start = null;
        $end = null;
        $expand = false;
        $componentType = false;
        if (isset($queryParams['start'])) {
            if (!ctype_digit($queryParams['start'])) {
                throw new BadRequest('The start= parameter must contain a unix timestamp');
            }
            $start = DateTime::createFromFormat('U', $queryParams['start']);
        }
        if (isset($queryParams['end'])) {
            if (!ctype_digit($queryParams['end'])) {
                throw new BadRequest('The end= parameter must contain a unix timestamp');
            }
            $end = DateTime::createFromFormat('U', $queryParams['end']);
        }
        if (isset($queryParams['expand']) && !!$queryParams['expand']) {
            if (!$start || !$end) {
                throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
            }
            $expand = true;
            $componentType = 'VEVENT';
        }
        if (isset($queryParams['componentType'])) {
            if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
                throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
            }
            $componentType = $queryParams['componentType'];
        }

        $format = \Sabre\HTTP\Util::Negotiate(
            $request->getHeader('Accept'),
            [
                'text/calendar',
                'application/calendar+json',
            ]
        );

        if (isset($queryParams['accept'])) {
            if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
                $format = 'application/calendar+json';
            }
        }
        if (!$format) {
            $format = 'text/calendar';
        }

        $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);

        // Returning false to break the event chain
        return false;

    }

    /**
     * This method is responsible for generating the actual, full response.
     *
     * @param string $path
     * @param DateTime|null $start
     * @param DateTime|null $end
     * @param bool $expand
     * @param string $componentType
     * @param string $format
     * @param array $properties
     * @param ResponseInterface $response
     */
    protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {

        $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
        $calendarNode = $this->server->tree->getNodeForPath($path);

        $blobs = [];
        if ($start || $end || $componentType) {

            // If there was a start or end filter, we need to enlist
            // calendarQuery for speed.
            $queryResult = $calendarNode->calendarQuery([
                'name'         => 'VCALENDAR',
                'comp-filters' => [
                    [
                        'name'           => $componentType,
                        'comp-filters'   => [],
                        'prop-filters'   => [],
                        'is-not-defined' => false,
                        'time-range'     => [
                            'start' => $start,
                            'end'   => $end,
                        ],
                    ],
                ],
                'prop-filters'   => [],
                'is-not-defined' => false,
                'time-range'     => null,
            ]);

            // queryResult is just a list of base urls. We need to prefix the
            // calendar path.
            $queryResult = array_map(
                function($item) use ($path) {
                    return $path . '/' . $item;
                },
                $queryResult
            );
            $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
            unset($queryResult);

        } else {
            $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
        }

        // Flattening the arrays
        foreach ($nodes as $node) {
            if (isset($node[200][$calDataProp])) {
                $blobs[$node['href']] = $node[200][$calDataProp];
            }
        }
        unset($nodes);

        $mergedCalendar = $this->mergeObjects(
            $properties,
            $blobs
        );

        if ($expand) {
            $calendarTimeZone = null;
            // We're expanding, and for that we need to figure out the
            // calendar's timezone.
            $tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone';
            $tzResult = $this->server->getProperties($path, [$tzProp]);
            if (isset($tzResult[$tzProp])) {
                // This property contains a VCALENDAR with a single
                // VTIMEZONE.
                $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
                // Destroy circular references to PHP will GC the object.
                $vtimezoneObj->destroy();
                unset($vtimezoneObj);
            } else {
                // Defaulting to UTC.
                $calendarTimeZone = new DateTimeZone('UTC');
            }

            $mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
        }

        $filenameExtension = '.ics';

        switch ($format) {
            case 'text/calendar' :
                $mergedCalendar = $mergedCalendar->serialize();
                $filenameExtension = '.ics';
                break;
            case 'application/calendar+json' :
                $mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
                $filenameExtension = '.json';
                break;
        }

        $filename = preg_replace(
            '/[^a-zA-Z0-9-_ ]/um',
            '',
            $calendarNode->getName()
        );
        $filename .= '-' . date('Y-m-d') . $filenameExtension;

        $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
        $response->setHeader('Content-Type', $format);

        $response->setStatus(200);
        $response->setBody($mergedCalendar);

    }

    /**
     * Merges all calendar objects, and builds one big iCalendar blob.
     *
     * @param array $properties Some CalDAV properties
     * @param array $inputObjects
     * @return VObject\Component\VCalendar
     */
    function mergeObjects(array $properties, array $inputObjects) {

        $calendar = new VObject\Component\VCalendar();
        $calendar->VERSION = '2.0';
        if (DAV\Server::$exposeVersion) {
            $calendar->PRODID = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
        } else {
            $calendar->PRODID = '-//SabreDAV//SabreDAV//EN';
        }
        if (isset($properties['{DAV:}displayname'])) {
            $calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
        }
        if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
            $calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
        }

        $collectedTimezones = [];

        $timezones = [];
        $objects = [];

        foreach ($inputObjects as $href => $inputObject) {

            $nodeComp = VObject\Reader::read($inputObject);

            foreach ($nodeComp->children() as $child) {

                switch ($child->name) {
                    case 'VEVENT' :
                    case 'VTODO' :
                    case 'VJOURNAL' :
                        $objects[] = clone $child;
                        break;

                    // VTIMEZONE is special, because we need to filter out the duplicates
                    case 'VTIMEZONE' :
                        // Naively just checking tzid.
                        if (in_array((string)$child->TZID, $collectedTimezones)) continue;

                        $timezones[] = clone $child;
                        $collectedTimezones[] = $child->TZID;
                        break;

                }

            }
            // Destroy circular references to PHP will GC the object.
            $nodeComp->destroy();
            unset($nodeComp);

        }

        foreach ($timezones as $tz) $calendar->add($tz);
        foreach ($objects as $obj) $calendar->add($obj);

        return $calendar;

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'ics-export';

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
            'link'        => 'http://sabre.io/dav/ics-export-plugin/',
        ];

    }

}
<?php

namespace Sabre\CalDAV;

use Sabre\DAV\Sharing\ISharedNode;

/**
 * This interface represents a Calendar that is shared by a different user.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ISharedCalendar extends ISharedNode {

    /**
     * Marks this calendar as published.
     *
     * Publishing a calendar should automatically create a read-only, public,
     * subscribable calendar.
     *
     * @param bool $value
     * @return void
     */
    function setPublishStatus($value);
}
<?php

namespace Sabre\CalDAV\Notifications;

use Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAVACL;

/**
 * This node represents a list of notifications.
 *
 * It provides no additional functionality, but you must implement this
 * interface to allow the Notifications plugin to mark the collection
 * as a notifications collection.
 *
 * This collection should only return Sabre\CalDAV\Notifications\INode nodes as
 * its children.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Collection extends DAV\Collection implements ICollection, DAVACL\IACL {

    use DAVACL\ACLTrait;

    /**
     * The notification backend
     *
     * @var CalDAV\Backend\NotificationSupport
     */
    protected $caldavBackend;

    /**
     * Principal uri
     *
     * @var string
     */
    protected $principalUri;

    /**
     * Constructor
     *
     * @param CalDAV\Backend\NotificationSupport $caldavBackend
     * @param string $principalUri
     */
    function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) {

        $this->caldavBackend = $caldavBackend;
        $this->principalUri = $principalUri;

    }

    /**
     * Returns all notifications for a principal
     *
     * @return array
     */
    function getChildren() {

        $children = [];
        $notifications = $this->caldavBackend->getNotificationsForPrincipal($this->principalUri);

        foreach ($notifications as $notification) {

            $children[] = new Node(
                $this->caldavBackend,
                $this->principalUri,
                $notification
            );
        }

        return $children;

    }

    /**
     * Returns the name of this object
     *
     * @return string
     */
    function getName() {

        return 'notifications';

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->principalUri;

    }

}
<?php

namespace Sabre\CalDAV\Notifications;

use Sabre\DAV;

/**
 * This node represents a list of notifications.
 *
 * It provides no additional functionality, but you must implement this
 * interface to allow the Notifications plugin to mark the collection
 * as a notifications collection.
 *
 * This collection should only return Sabre\CalDAV\Notifications\INode nodes as
 * its children.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ICollection extends DAV\ICollection {

}
<?php

namespace Sabre\CalDAV\Notifications;

use Sabre\CalDAV\Xml\Notification\NotificationInterface;

/**
 * This node represents a single notification.
 *
 * The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
 * MUST return an xml document that matches the requirements of the
 * 'caldav-notifications.txt' spec.
 *
 * For a complete example, check out the Notification class, which contains
 * some helper functions.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface INode {

    /**
     * This method must return an xml element, using the
     * Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
     *
     * @return NotificationInterface
     */
    function getNotificationType();

    /**
     * Returns the etag for the notification.
     *
     * The etag must be surrounded by literal double-quotes.
     *
     * @return string
     */
    function getETag();

}
<?php

namespace Sabre\CalDAV\Notifications;

use Sabre\CalDAV;
use Sabre\CalDAV\Xml\Notification\NotificationInterface;
use Sabre\DAV;
use Sabre\DAVACL;

/**
 * This node represents a single notification.
 *
 * The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method
 * MUST return an xml document that matches the requirements of the
 * 'caldav-notifications.txt' spec.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Node extends DAV\File implements INode, DAVACL\IACL {

    use DAVACL\ACLTrait;

    /**
     * The notification backend
     *
     * @var CalDAV\Backend\NotificationSupport
     */
    protected $caldavBackend;

    /**
     * The actual notification
     *
     * @var NotificationInterface
     */
    protected $notification;

    /**
     * Owner principal of the notification
     *
     * @var string
     */
    protected $principalUri;

    /**
     * Constructor
     *
     * @param CalDAV\Backend\NotificationSupport $caldavBackend
     * @param string $principalUri
     * @param NotificationInterface $notification
     */
    function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, NotificationInterface $notification) {

        $this->caldavBackend = $caldavBackend;
        $this->principalUri = $principalUri;
        $this->notification = $notification;

    }

    /**
     * Returns the path name for this notification
     *
     * @return string
     */
    function getName() {

        return $this->notification->getId() . '.xml';

    }

    /**
     * Returns the etag for the notification.
     *
     * The etag must be surrounded by litteral double-quotes.
     *
     * @return string
     */
    function getETag() {

        return $this->notification->getETag();

    }

    /**
     * This method must return an xml element, using the
     * Sabre\CalDAV\Xml\Notification\NotificationInterface classes.
     *
     * @return NotificationInterface
     */
    function getNotificationType() {

        return $this->notification;

    }

    /**
     * Deletes this notification
     *
     * @return void
     */
    function delete() {

        $this->caldavBackend->deleteNotification($this->getOwner(), $this->notification);

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->principalUri;

    }

}
<?php

namespace Sabre\CalDAV\Notifications;

use Sabre\DAV;
use Sabre\DAV\INode as BaseINode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAVACL;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * Notifications plugin
 *
 * This plugin implements several features required by the caldav-notification
 * draft specification.
 *
 * Before version 2.1.0 this functionality was part of Sabre\CalDAV\Plugin but
 * this has since been split up.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends ServerPlugin {

    /**
     * This is the namespace for the proprietary calendarserver extensions
     */
    const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';

    /**
     * Reference to the main server object.
     *
     * @var Server
     */
    protected $server;

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'notifications';

    }

    /**
     * This initializes the plugin.
     *
     * This function is called by Sabre\DAV\Server, after
     * addPlugin is called.
     *
     * This method should set up the required event subscriptions.
     *
     * @param Server $server
     * @return void
     */
    function initialize(Server $server) {

        $this->server = $server;
        $server->on('method:GET', [$this, 'httpGet'], 90);
        $server->on('propFind',   [$this, 'propFind']);

        $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';
        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Notifications\\ICollection'] = '{' . self::NS_CALENDARSERVER . '}notification';

        array_push($server->protectedProperties,
            '{' . self::NS_CALENDARSERVER . '}notification-URL',
            '{' . self::NS_CALENDARSERVER . '}notificationtype'
        );

    }

    /**
     * PropFind
     *
     * @param PropFind $propFind
     * @param BaseINode $node
     * @return void
     */
    function propFind(PropFind $propFind, BaseINode $node) {

        $caldavPlugin = $this->server->getPlugin('caldav');

        if ($node instanceof DAVACL\IPrincipal) {

            $principalUrl = $node->getPrincipalUrl();

            // notification-URL property
            $propFind->handle('{' . self::NS_CALENDARSERVER . '}notification-URL', function() use ($principalUrl, $caldavPlugin) {

                $notificationPath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl) . '/notifications/';
                return new DAV\Xml\Property\Href($notificationPath);

            });

        }

        if ($node instanceof INode) {

            $propFind->handle(
                '{' . self::NS_CALENDARSERVER . '}notificationtype',
                [$node, 'getNotificationType']
            );

        }

    }

    /**
     * This event is triggered before the usual GET request handler.
     *
     * We use this to intercept GET calls to notification nodes, and return the
     * proper response.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function httpGet(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        try {
            $node = $this->server->tree->getNodeForPath($path);
        } catch (DAV\Exception\NotFound $e) {
            return;
        }

        if (!$node instanceof INode)
            return;

        $writer = $this->server->xml->getWriter();
        $writer->contextUri = $this->server->getBaseUri();
        $writer->openMemory();
        $writer->startDocument('1.0', 'UTF-8');
        $writer->startElement('{http://calendarserver.org/ns/}notification');
        $node->getNotificationType()->xmlSerializeFull($writer);
        $writer->endElement();

        $response->setHeader('Content-Type', 'application/xml');
        $response->setHeader('ETag', $node->getETag());
        $response->setStatus(200);
        $response->setBody($writer->outputMemory());

        // Return false to break the event chain.
        return false;

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds support for caldav-notifications, which is required to enable caldav-sharing.',
            'link'        => 'http://sabre.io/dav/caldav-sharing/',
        ];

    }

}
<?php

namespace Sabre\CalDAV;

use DateTimeZone;
use Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\INode;
use Sabre\DAV\MkCol;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\Uri;
use Sabre\VObject;

/**
 * CalDAV plugin
 *
 * This plugin provides functionality added by CalDAV (RFC 4791)
 * It implements new reports, and the MKCALENDAR method.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends DAV\ServerPlugin {

    /**
     * This is the official CalDAV namespace
     */
    const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';

    /**
     * This is the namespace for the proprietary calendarserver extensions
     */
    const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';

    /**
     * The hardcoded root for calendar objects. It is unfortunate
     * that we're stuck with it, but it will have to do for now
     */
    const CALENDAR_ROOT = 'calendars';

    /**
     * Reference to server object
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
     * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
     * capping it to 10M here.
     */
    protected $maxResourceSize = 10000000;

    /**
     * Use this method to tell the server this plugin defines additional
     * HTTP methods.
     *
     * This method is passed a uri. It should only return HTTP methods that are
     * available for the specified uri.
     *
     * @param string $uri
     * @return array
     */
    function getHTTPMethods($uri) {

        // The MKCALENDAR is only available on unmapped uri's, whose
        // parents extend IExtendedCollection
        list($parent, $name) = Uri\split($uri);

        $node = $this->server->tree->getNodeForPath($parent);

        if ($node instanceof DAV\IExtendedCollection) {
            try {
                $node->getChild($name);
            } catch (DAV\Exception\NotFound $e) {
                return ['MKCALENDAR'];
            }
        }
        return [];

    }

    /**
     * Returns the path to a principal's calendar home.
     *
     * The return url must not end with a slash.
     * This function should return null in case a principal did not have
     * a calendar home.
     *
     * @param string $principalUrl
     * @return string
     */
    function getCalendarHomeForPrincipal($principalUrl) {

        // The default behavior for most sabre/dav servers is that there is a
        // principals root node, which contains users directly under it.
        //
        // This function assumes that there are two components in a principal
        // path. If there's more, we don't return a calendar home. This
        // excludes things like the calendar-proxy-read principal (which it
        // should).
        $parts = explode('/', trim($principalUrl, '/'));
        if (count($parts) !== 2) return;
        if ($parts[0] !== 'principals') return;

        return self::CALENDAR_ROOT . '/' . $parts[1];

    }

    /**
     * Returns a list of features for the DAV: HTTP header.
     *
     * @return array
     */
    function getFeatures() {

        return ['calendar-access', 'calendar-proxy'];

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'caldav';

    }

    /**
     * Returns a list of reports this plugin supports.
     *
     * This will be used in the {DAV:}supported-report-set property.
     * Note that you still need to subscribe to the 'report' event to actually
     * implement them
     *
     * @param string $uri
     * @return array
     */
    function getSupportedReportSet($uri) {

        $node = $this->server->tree->getNodeForPath($uri);

        $reports = [];
        if ($node instanceof ICalendarObjectContainer || $node instanceof ICalendarObject) {
            $reports[] = '{' . self::NS_CALDAV . '}calendar-multiget';
            $reports[] = '{' . self::NS_CALDAV . '}calendar-query';
        }
        if ($node instanceof ICalendar) {
            $reports[] = '{' . self::NS_CALDAV . '}free-busy-query';
        }
        // iCal has a bug where it assumes that sync support is enabled, only
        // if we say we support it on the calendar-home, even though this is
        // not actually the case.
        if ($node instanceof CalendarHome && $this->server->getPlugin('sync')) {
            $reports[] = '{DAV:}sync-collection';
        }
        return $reports;

    }

    /**
     * Initializes the plugin
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;

        $server->on('method:MKCALENDAR',   [$this, 'httpMkCalendar']);
        $server->on('report',              [$this, 'report']);
        $server->on('propFind',            [$this, 'propFind']);
        $server->on('onHTMLActionsPanel',  [$this, 'htmlActionsPanel']);
        $server->on('beforeCreateFile',    [$this, 'beforeCreateFile']);
        $server->on('beforeWriteContent',  [$this, 'beforeWriteContent']);
        $server->on('afterMethod:GET',     [$this, 'httpAfterGET']);
        $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);

        $server->xml->namespaceMap[self::NS_CALDAV] = 'cal';
        $server->xml->namespaceMap[self::NS_CALENDARSERVER] = 'cs';

        $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-query'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarQueryReport';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}calendar-multiget'] = 'Sabre\\CalDAV\\Xml\\Request\\CalendarMultiGetReport';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}free-busy-query'] = 'Sabre\\CalDAV\\Xml\\Request\\FreeBusyQueryReport';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}mkcalendar'] = 'Sabre\\CalDAV\\Xml\\Request\\MkCalendar';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}schedule-calendar-transp'] = 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp';
        $server->xml->elementMap['{' . self::NS_CALDAV . '}supported-calendar-component-set'] = 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet';

        $server->resourceTypeMapping['\\Sabre\\CalDAV\\ICalendar'] = '{urn:ietf:params:xml:ns:caldav}calendar';

        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyRead'] = '{http://calendarserver.org/ns/}calendar-proxy-read';
        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Principal\\IProxyWrite'] = '{http://calendarserver.org/ns/}calendar-proxy-write';

        array_push($server->protectedProperties,

            '{' . self::NS_CALDAV . '}supported-calendar-component-set',
            '{' . self::NS_CALDAV . '}supported-calendar-data',
            '{' . self::NS_CALDAV . '}max-resource-size',
            '{' . self::NS_CALDAV . '}min-date-time',
            '{' . self::NS_CALDAV . '}max-date-time',
            '{' . self::NS_CALDAV . '}max-instances',
            '{' . self::NS_CALDAV . '}max-attendees-per-instance',
            '{' . self::NS_CALDAV . '}calendar-home-set',
            '{' . self::NS_CALDAV . '}supported-collation-set',
            '{' . self::NS_CALDAV . '}calendar-data',

            // CalendarServer extensions
            '{' . self::NS_CALENDARSERVER . '}getctag',
            '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for',
            '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for'

        );

        if ($aclPlugin = $server->getPlugin('acl')) {
            $aclPlugin->principalSearchPropertySet['{' . self::NS_CALDAV . '}calendar-user-address-set'] = 'Calendar address';
        }
    }

    /**
     * This functions handles REPORT requests specific to CalDAV
     *
     * @param string $reportName
     * @param mixed $report
     * @param mixed $path
     * @return bool
     */
    function report($reportName, $report, $path) {

        switch ($reportName) {
            case '{' . self::NS_CALDAV . '}calendar-multiget' :
                $this->server->transactionType = 'report-calendar-multiget';
                $this->calendarMultiGetReport($report);
                return false;
            case '{' . self::NS_CALDAV . '}calendar-query' :
                $this->server->transactionType = 'report-calendar-query';
                $this->calendarQueryReport($report);
                return false;
            case '{' . self::NS_CALDAV . '}free-busy-query' :
                $this->server->transactionType = 'report-free-busy-query';
                $this->freeBusyQueryReport($report);
                return false;

        }


    }

    /**
     * This function handles the MKCALENDAR HTTP method, which creates
     * a new calendar.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpMkCalendar(RequestInterface $request, ResponseInterface $response) {

        $body = $request->getBodyAsString();
        $path = $request->getPath();

        $properties = [];

        if ($body) {

            try {
                $mkcalendar = $this->server->xml->expect(
                    '{urn:ietf:params:xml:ns:caldav}mkcalendar',
                    $body
                );
            } catch (\Sabre\Xml\ParseException $e) {
                throw new BadRequest($e->getMessage(), null, $e);
            }
            $properties = $mkcalendar->getProperties();

        }

        // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored
        // subscriptions. Before that it used MKCOL which was the correct way
        // to do this.
        //
        // If the body had a {DAV:}resourcetype, it means we stumbled upon this
        // request, and we simply use it instead of the pre-defined list.
        if (isset($properties['{DAV:}resourcetype'])) {
            $resourceType = $properties['{DAV:}resourcetype']->getValue();
        } else {
            $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar'];
        }

        $this->server->createCollection($path, new MkCol($resourceType, $properties));

        $response->setStatus(201);
        $response->setHeader('Content-Length', 0);

        // This breaks the method chain.
        return false;
    }

    /**
     * PropFind
     *
     * This method handler is invoked before any after properties for a
     * resource are fetched. This allows us to add in any CalDAV specific
     * properties.
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @return void
     */
    function propFind(DAV\PropFind $propFind, DAV\INode $node) {

        $ns = '{' . self::NS_CALDAV . '}';

        if ($node instanceof ICalendarObjectContainer) {

            $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
            $propFind->handle($ns . 'supported-calendar-data', function() {
                return new Xml\Property\SupportedCalendarData();
            });
            $propFind->handle($ns . 'supported-collation-set', function() {
                return new Xml\Property\SupportedCollationSet();
            });

        }

        if ($node instanceof DAVACL\IPrincipal) {

            $principalUrl = $node->getPrincipalUrl();

            $propFind->handle('{' . self::NS_CALDAV . '}calendar-home-set', function() use ($principalUrl) {

                $calendarHomePath = $this->getCalendarHomeForPrincipal($principalUrl);
                if (is_null($calendarHomePath)) return null;
                return new LocalHref($calendarHomePath . '/');

            });
            // The calendar-user-address-set property is basically mapped to
            // the {DAV:}alternate-URI-set property.
            $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-address-set', function() use ($node) {
                $addresses = $node->getAlternateUriSet();
                $addresses[] = $this->server->getBaseUri() . $node->getPrincipalUrl() . '/';
                return new LocalHref($addresses);
            });
            // For some reason somebody thought it was a good idea to add
            // another one of these properties. We're supporting it too.
            $propFind->handle('{' . self::NS_CALENDARSERVER . '}email-address-set', function() use ($node) {
                $addresses = $node->getAlternateUriSet();
                $emails = [];
                foreach ($addresses as $address) {
                    if (substr($address, 0, 7) === 'mailto:') {
                        $emails[] = substr($address, 7);
                    }
                }
                return new Xml\Property\EmailAddressSet($emails);
            });

            // These two properties are shortcuts for ical to easily find
            // other principals this principal has access to.
            $propRead = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-read-for';
            $propWrite = '{' . self::NS_CALENDARSERVER . '}calendar-proxy-write-for';

            if ($propFind->getStatus($propRead) === 404 || $propFind->getStatus($propWrite) === 404) {

                $aclPlugin = $this->server->getPlugin('acl');
                $membership = $aclPlugin->getPrincipalMembership($propFind->getPath());
                $readList = [];
                $writeList = [];

                foreach ($membership as $group) {

                    $groupNode = $this->server->tree->getNodeForPath($group);

                    $listItem = Uri\split($group)[0] . '/';

                    // If the node is either ap proxy-read or proxy-write
                    // group, we grab the parent principal and add it to the
                    // list.
                    if ($groupNode instanceof Principal\IProxyRead) {
                        $readList[] = $listItem;
                    }
                    if ($groupNode instanceof Principal\IProxyWrite) {
                        $writeList[] = $listItem;
                    }

                }

                $propFind->set($propRead, new LocalHref($readList));
                $propFind->set($propWrite, new LocalHref($writeList));

            }

        } // instanceof IPrincipal

        if ($node instanceof ICalendarObject) {

            // The calendar-data property is not supposed to be a 'real'
            // property, but in large chunks of the spec it does act as such.
            // Therefore we simply expose it as a property.
            $propFind->handle('{' . self::NS_CALDAV . '}calendar-data', function() use ($node) {
                $val = $node->get();
                if (is_resource($val))
                    $val = stream_get_contents($val);

                // Taking out \r to not screw up the xml output
                return str_replace("\r", "", $val);

            });

        }

    }

    /**
     * This function handles the calendar-multiget REPORT.
     *
     * This report is used by the client to fetch the content of a series
     * of urls. Effectively avoiding a lot of redundant requests.
     *
     * @param CalendarMultiGetReport $report
     * @return void
     */
    function calendarMultiGetReport($report) {

        $needsJson = $report->contentType === 'application/calendar+json';

        $timeZones = [];
        $propertyList = [];

        $paths = array_map(
            [$this->server, 'calculateUri'],
            $report->hrefs
        );

        foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $uri => $objProps) {

            if (($needsJson || $report->expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) {
                $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']);

                if ($report->expand) {
                    // We're expanding, and for that we need to figure out the
                    // calendar's timezone.
                    list($calendarPath) = Uri\split($uri);
                    if (!isset($timeZones[$calendarPath])) {
                        // Checking the calendar-timezone property.
                        $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
                        $tzResult = $this->server->getProperties($calendarPath, [$tzProp]);
                        if (isset($tzResult[$tzProp])) {
                            // This property contains a VCALENDAR with a single
                            // VTIMEZONE.
                            $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
                            $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
                        } else {
                            // Defaulting to UTC.
                            $timeZone = new DateTimeZone('UTC');
                        }
                        $timeZones[$calendarPath] = $timeZone;
                    }

                    $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $timeZones[$calendarPath]);
                }
                if ($needsJson) {
                    $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
                } else {
                    $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
                }
                // Destroy circular references so PHP will garbage collect the
                // object.
                $vObject->destroy();
            }

            $propertyList[] = $objProps;

        }

        $prefer = $this->server->getHTTPPrefer();

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
        $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));

    }

    /**
     * This function handles the calendar-query REPORT
     *
     * This report is used by clients to request calendar objects based on
     * complex conditions.
     *
     * @param Xml\Request\CalendarQueryReport $report
     * @return void
     */
    function calendarQueryReport($report) {

        $path = $this->server->getRequestUri();

        $needsJson = $report->contentType === 'application/calendar+json';

        $node = $this->server->tree->getNodeForPath($this->server->getRequestUri());
        $depth = $this->server->getHTTPDepth(0);

        // The default result is an empty array
        $result = [];

        $calendarTimeZone = null;
        if ($report->expand) {
            // We're expanding, and for that we need to figure out the
            // calendar's timezone.
            $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';
            $tzResult = $this->server->getProperties($path, [$tzProp]);
            if (isset($tzResult[$tzProp])) {
                // This property contains a VCALENDAR with a single
                // VTIMEZONE.
                $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();

                // Destroy circular references so PHP will garbage collect the
                // object.
                $vtimezoneObj->destroy();
            } else {
                // Defaulting to UTC.
                $calendarTimeZone = new DateTimeZone('UTC');
            }
        }

        // The calendarobject was requested directly. In this case we handle
        // this locally.
        if ($depth == 0 && $node instanceof ICalendarObject) {

            $requestedCalendarData = true;
            $requestedProperties = $report->properties;

            if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) {

                // We always retrieve calendar-data, as we need it for filtering.
                $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data';

                // If calendar-data wasn't explicitly requested, we need to remove
                // it after processing.
                $requestedCalendarData = false;
            }

            $properties = $this->server->getPropertiesForPath(
                $path,
                $requestedProperties,
                0
            );

            // This array should have only 1 element, the first calendar
            // object.
            $properties = current($properties);

            // If there wasn't any calendar-data returned somehow, we ignore
            // this.
            if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {

                $validator = new CalendarQueryValidator();

                $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
                if ($validator->validate($vObject, $report->filters)) {

                    // If the client didn't require the calendar-data property,
                    // we won't give it back.
                    if (!$requestedCalendarData) {
                        unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']);
                    } else {


                        if ($report->expand) {
                            $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
                        }
                        if ($needsJson) {
                            $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
                        } elseif ($report->expand) {
                            $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
                        }
                    }

                    $result = [$properties];

                }
                // Destroy circular references so PHP will garbage collect the
                // object.
                $vObject->destroy();

            }

        }

        if ($node instanceof ICalendarObjectContainer && $depth === 0) {

            if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-') === 0) {
                // Microsoft clients incorrectly supplied depth as 0, when it actually
                // should have set depth to 1. We're implementing a workaround here
                // to deal with this.
                //
                // This targets at least the following clients:
                //   Windows 10
                //   Windows Phone 8, 10
                $depth = 1;
            } else {
                throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1');
            }

        }

        // If we're dealing with a calendar, the calendar itself is responsible
        // for the calendar-query.
        if ($node instanceof ICalendarObjectContainer && $depth == 1) {

            $nodePaths = $node->calendarQuery($report->filters);

            foreach ($nodePaths as $path) {

                list($properties) =
                    $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties);

                if (($needsJson || $report->expand)) {
                    $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']);

                    if ($report->expand) {
                        $vObject = $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone);
                    }

                    if ($needsJson) {
                        $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize());
                    } else {
                        $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize();
                    }

                    // Destroy circular references so PHP will garbage collect the
                    // object.
                    $vObject->destroy();
                }
                $result[] = $properties;

            }

        }

        $prefer = $this->server->getHTTPPrefer();

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
        $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));

    }

    /**
     * This method is responsible for parsing the request and generating the
     * response for the CALDAV:free-busy-query REPORT.
     *
     * @param Xml\Request\FreeBusyQueryReport $report
     * @return void
     */
    protected function freeBusyQueryReport(Xml\Request\FreeBusyQueryReport $report) {

        $uri = $this->server->getRequestUri();

        $acl = $this->server->getPlugin('acl');
        if ($acl) {
            $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy');
        }

        $calendar = $this->server->tree->getNodeForPath($uri);
        if (!$calendar instanceof ICalendar) {
            throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars');
        }

        $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone';

        // Figuring out the default timezone for the calendar, for floating
        // times.
        $calendarProps = $this->server->getProperties($uri, [$tzProp]);

        if (isset($calendarProps[$tzProp])) {
            $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]);
            $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
            // Destroy circular references so PHP will garbage collect the object.
            $vtimezoneObj->destroy();
        } else {
            $calendarTimeZone = new DateTimeZone('UTC');
        }

        // Doing a calendar-query first, to make sure we get the most
        // performance.
        $urls = $calendar->calendarQuery([
            'name'         => 'VCALENDAR',
            'comp-filters' => [
                [
                    'name'           => 'VEVENT',
                    'comp-filters'   => [],
                    'prop-filters'   => [],
                    'is-not-defined' => false,
                    'time-range'     => [
                        'start' => $report->start,
                        'end'   => $report->end,
                    ],
                ],
            ],
            'prop-filters'   => [],
            'is-not-defined' => false,
            'time-range'     => null,
        ]);

        $objects = array_map(function($url) use ($calendar) {
            $obj = $calendar->getChild($url)->get();
            return $obj;
        }, $urls);

        $generator = new VObject\FreeBusyGenerator();
        $generator->setObjects($objects);
        $generator->setTimeRange($report->start, $report->end);
        $generator->setTimeZone($calendarTimeZone);
        $result = $generator->getResult();
        $result = $result->serialize();

        $this->server->httpResponse->setStatus(200);
        $this->server->httpResponse->setHeader('Content-Type', 'text/calendar');
        $this->server->httpResponse->setHeader('Content-Length', strlen($result));
        $this->server->httpResponse->setBody($result);

    }

    /**
     * This method is triggered before a file gets updated with new content.
     *
     * This plugin uses this method to ensure that CalDAV objects receive
     * valid calendar data.
     *
     * @param string $path
     * @param DAV\IFile $node
     * @param resource $data
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @return void
     */
    function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {

        if (!$node instanceof ICalendarObject)
            return;

        // We're onyl interested in ICalendarObject nodes that are inside of a
        // real calendar. This is to avoid triggering validation and scheduling
        // for non-calendars (such as an inbox).
        list($parent) = Uri\split($path);
        $parentNode = $this->server->tree->getNodeForPath($parent);

        if (!$parentNode instanceof ICalendar)
            return;

        $this->validateICalendar(
            $data,
            $path,
            $modified,
            $this->server->httpRequest,
            $this->server->httpResponse,
            false
        );

    }

    /**
     * This method is triggered before a new file is created.
     *
     * This plugin uses this method to ensure that newly created calendar
     * objects contain valid calendar data.
     *
     * @param string $path
     * @param resource $data
     * @param DAV\ICollection $parentNode
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @return void
     */
    function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {

        if (!$parentNode instanceof ICalendar)
            return;

        $this->validateICalendar(
            $data,
            $path,
            $modified,
            $this->server->httpRequest,
            $this->server->httpResponse,
            true
        );

    }

    /**
     * Checks if the submitted iCalendar data is in fact, valid.
     *
     * An exception is thrown if it's not.
     *
     * @param resource|string $data
     * @param string $path
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @param RequestInterface $request The http request.
     * @param ResponseInterface $response The http response.
     * @param bool $isNew Is the item a new one, or an update.
     * @return void
     */
    protected function validateICalendar(&$data, $path, &$modified, RequestInterface $request, ResponseInterface $response, $isNew) {

        // If it's a stream, we convert it to a string first.
        if (is_resource($data)) {
            $data = stream_get_contents($data);
        }

        $before = $data;

        try {

            // If the data starts with a [, we can reasonably assume we're dealing
            // with a jCal object.
            if (substr($data, 0, 1) === '[') {
                $vobj = VObject\Reader::readJson($data);

                // Converting $data back to iCalendar, as that's what we
                // technically support everywhere.
                $data = $vobj->serialize();
                $modified = true;
            } else {
                $vobj = VObject\Reader::read($data);
            }

        } catch (VObject\ParseException $e) {

            throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid iCalendar 2.0 data. Parse error: ' . $e->getMessage());

        }

        if ($vobj->name !== 'VCALENDAR') {
            throw new DAV\Exception\UnsupportedMediaType('This collection can only support iCalendar objects.');
        }

        $sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';

        // Get the Supported Components for the target calendar
        list($parentPath) = Uri\split($path);
        $calendarProperties = $this->server->getProperties($parentPath, [$sCCS]);

        if (isset($calendarProperties[$sCCS])) {
            $supportedComponents = $calendarProperties[$sCCS]->getValue();
        } else {
            $supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
        }

        $foundType = null;

        foreach ($vobj->getComponents() as $component) {
            switch ($component->name) {
                case 'VTIMEZONE' :
                    continue 2;
                case 'VEVENT' :
                case 'VTODO' :
                case 'VJOURNAL' :
                    $foundType = $component->name;
                    break;
            }

        }

        if (!$foundType || !in_array($foundType, $supportedComponents)) {
            throw new Exception\InvalidComponentType('iCalendar objects must at least have a component of type ' . implode(', ', $supportedComponents));
        }

        $options = VObject\Node::PROFILE_CALDAV;
        $prefer = $this->server->getHTTPPrefer();

        if ($prefer['handling'] !== 'strict') {
            $options |= VObject\Node::REPAIR;
        }

        $messages = $vobj->validate($options);

        $highestLevel = 0;
        $warningMessage = null;

        // $messages contains a list of problems with the vcard, along with
        // their severity.
        foreach ($messages as $message) {

            if ($message['level'] > $highestLevel) {
                // Recording the highest reported error level.
                $highestLevel = $message['level'];
                $warningMessage = $message['message'];
            }
            switch ($message['level']) {

                case 1 :
                    // Level 1 means that there was a problem, but it was repaired.
                    $modified = true;
                    break;
                case 2 :
                    // Level 2 means a warning, but not critical
                    break;
                case 3 :
                    // Level 3 means a critical error
                    throw new DAV\Exception\UnsupportedMediaType('Validation error in iCalendar: ' . $message['message']);

            }

        }
        if ($warningMessage) {
            $response->setHeader(
                'X-Sabre-Ew-Gross',
                'iCalendar validation warning: ' . $warningMessage
            );
        }

        // We use an extra variable to allow event handles to tell us whether
        // the object was modified or not.
        //
        // This helps us determine if we need to re-serialize the object.
        $subModified = false;

        $this->server->emit(
            'calendarObjectChange',
            [
                $request,
                $response,
                $vobj,
                $parentPath,
                &$subModified,
                $isNew
            ]
        );

        if ($modified || $subModified) {
            // An event handler told us that it modified the object.
            $data = $vobj->serialize();

            // Using md5 to figure out if there was an *actual* change.
            if (!$modified && strcmp($data, $before) !== 0) {
                $modified = true;
            }

        }

        // Destroy circular references so PHP will garbage collect the object.
        $vobj->destroy();

    }

    /**
     * This method is triggered whenever a subsystem reqeuests the privileges
     * that are supported on a particular node.
     *
     * @param INode $node
     * @param array $supportedPrivilegeSet
     */
    function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {

        if ($node instanceof ICalendar) {
            $supportedPrivilegeSet['{DAV:}read']['aggregates']['{' . self::NS_CALDAV . '}read-free-busy'] = [
                'abstract'   => false,
                'aggregates' => [],
            ];
        }
    }

    /**
     * This method is used to generate HTML output for the
     * DAV\Browser\Plugin. This allows us to generate an interface users
     * can use to create new calendars.
     *
     * @param DAV\INode $node
     * @param string $output
     * @return bool
     */
    function htmlActionsPanel(DAV\INode $node, &$output) {

        if (!$node instanceof CalendarHome)
            return;

        $output .= '<tr><td colspan="2"><form method="post" action="">
            <h3>Create new calendar</h3>
            <input type="hidden" name="sabreAction" value="mkcol" />
            <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CALDAV . '}calendar" />
            <label>Name (uri):</label> <input type="text" name="name" /><br />
            <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
            <input type="submit" value="create" />
            </form>
            </td></tr>';

        return false;

    }

    /**
     * This event is triggered after GET requests.
     *
     * This is used to transform data into jCal, if this was requested.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function httpAfterGet(RequestInterface $request, ResponseInterface $response) {

        if (strpos($response->getHeader('Content-Type'), 'text/calendar') === false) {
            return;
        }

        $result = HTTP\Util::negotiate(
            $request->getHeader('Accept'),
            ['text/calendar', 'application/calendar+json']
        );

        if ($result !== 'application/calendar+json') {
            // Do nothing
            return;
        }

        // Transforming.
        $vobj = VObject\Reader::read($response->getBody());

        $jsonBody = json_encode($vobj->jsonSerialize());
        $response->setBody($jsonBody);

        // Destroy circular references so PHP will garbage collect the object.
        $vobj->destroy();

        $response->setHeader('Content-Type', 'application/calendar+json');
        $response->setHeader('Content-Length', strlen($jsonBody));

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds support for CalDAV (rfc4791)',
            'link'        => 'http://sabre.io/dav/caldav/',
        ];

    }

}
<?php

namespace Sabre\CalDAV\Principal;

use Sabre\DAVACL;

/**
 * Principal collection
 *
 * This is an alternative collection to the standard ACL principal collection.
 * This collection adds support for the calendar-proxy-read and
 * calendar-proxy-write sub-principals, as defined by the caldav-proxy
 * specification.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Collection extends DAVACL\PrincipalCollection {

    /**
     * Returns a child object based on principal information
     *
     * @param array $principalInfo
     * @return User
     */
    function getChildForPrincipal(array $principalInfo) {

        return new User($this->principalBackend, $principalInfo);

    }

}
<?php

namespace Sabre\CalDAV\Principal;

use Sabre\DAVACL;

/**
 * ProxyRead principal interface
 *
 * Any principal node implementing this interface will be picked up as a 'proxy
 * principal group'.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IProxyRead extends DAVACL\IPrincipal {

}
<?php

namespace Sabre\CalDAV\Principal;

use Sabre\DAVACL;

/**
 * ProxyWrite principal interface
 *
 * Any principal node implementing this interface will be picked up as a 'proxy
 * principal group'.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IProxyWrite extends DAVACL\IPrincipal {

}
<?php

namespace Sabre\CalDAV\Principal;

use Sabre\DAV;
use Sabre\DAVACL;

/**
 * ProxyRead principal
 *
 * This class represents a principal group, hosted under the main principal.
 * This is needed to implement 'Calendar delegation' support. This class is
 * instantiated by User.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ProxyRead implements IProxyRead {

    /**
     * Principal information from the parent principal.
     *
     * @var array
     */
    protected $principalInfo;

    /**
     * Principal backend
     *
     * @var DAVACL\PrincipalBackend\BackendInterface
     */
    protected $principalBackend;

    /**
     * Creates the object.
     *
     * Note that you MUST supply the parent principal information.
     *
     * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
     * @param array $principalInfo
     */
    function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {

        $this->principalInfo = $principalInfo;
        $this->principalBackend = $principalBackend;

    }

    /**
     * Returns this principals name.
     *
     * @return string
     */
    function getName() {

        return 'calendar-proxy-read';

    }

    /**
     * Returns the last modification time
     *
     * @return null
     */
    function getLastModified() {

        return null;

    }

    /**
     * Deletes the current node
     *
     * @throws DAV\Exception\Forbidden
     * @return void
     */
    function delete() {

        throw new DAV\Exception\Forbidden('Permission denied to delete node');

    }

    /**
     * Renames the node
     *
     * @param string $name The new name
     * @throws DAV\Exception\Forbidden
     * @return void
     */
    function setName($name) {

        throw new DAV\Exception\Forbidden('Permission denied to rename file');

    }


    /**
     * Returns a list of alternative urls for a principal
     *
     * This can for example be an email address, or ldap url.
     *
     * @return array
     */
    function getAlternateUriSet() {

        return [];

    }

    /**
     * Returns the full principal url
     *
     * @return string
     */
    function getPrincipalUrl() {

        return $this->principalInfo['uri'] . '/' . $this->getName();

    }

    /**
     * Returns the list of group members
     *
     * If this principal is a group, this function should return
     * all member principal uri's for the group.
     *
     * @return array
     */
    function getGroupMemberSet() {

        return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());

    }

    /**
     * Returns the list of groups this principal is member of
     *
     * If this principal is a member of a (list of) groups, this function
     * should return a list of principal uri's for it's members.
     *
     * @return array
     */
    function getGroupMembership() {

        return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());

    }

    /**
     * Sets a list of group members
     *
     * If this principal is a group, this method sets all the group members.
     * The list of members is always overwritten, never appended to.
     *
     * This method should throw an exception if the members could not be set.
     *
     * @param array $principals
     * @return void
     */
    function setGroupMemberSet(array $principals) {

        $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);

    }

    /**
     * Returns the displayname
     *
     * This should be a human readable name for the principal.
     * If none is available, return the nodename.
     *
     * @return string
     */
    function getDisplayName() {

        return $this->getName();

    }

}
<?php

namespace Sabre\CalDAV\Principal;

use Sabre\DAV;
use Sabre\DAVACL;

/**
 * ProxyWrite principal
 *
 * This class represents a principal group, hosted under the main principal.
 * This is needed to implement 'Calendar delegation' support. This class is
 * instantiated by User.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ProxyWrite implements IProxyWrite {

    /**
     * Parent principal information
     *
     * @var array
     */
    protected $principalInfo;

    /**
     * Principal Backend
     *
     * @var DAVACL\PrincipalBackend\BackendInterface
     */
    protected $principalBackend;

    /**
     * Creates the object
     *
     * Note that you MUST supply the parent principal information.
     *
     * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
     * @param array $principalInfo
     */
    function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) {

        $this->principalInfo = $principalInfo;
        $this->principalBackend = $principalBackend;

    }

    /**
     * Returns this principals name.
     *
     * @return string
     */
    function getName() {

        return 'calendar-proxy-write';

    }

    /**
     * Returns the last modification time
     *
     * @return null
     */
    function getLastModified() {

        return null;

    }

    /**
     * Deletes the current node
     *
     * @throws DAV\Exception\Forbidden
     * @return void
     */
    function delete() {

        throw new DAV\Exception\Forbidden('Permission denied to delete node');

    }

    /**
     * Renames the node
     *
     * @param string $name The new name
     * @throws DAV\Exception\Forbidden
     * @return void
     */
    function setName($name) {

        throw new DAV\Exception\Forbidden('Permission denied to rename file');

    }


    /**
     * Returns a list of alternative urls for a principal
     *
     * This can for example be an email address, or ldap url.
     *
     * @return array
     */
    function getAlternateUriSet() {

        return [];

    }

    /**
     * Returns the full principal url
     *
     * @return string
     */
    function getPrincipalUrl() {

        return $this->principalInfo['uri'] . '/' . $this->getName();

    }

    /**
     * Returns the list of group members
     *
     * If this principal is a group, this function should return
     * all member principal uri's for the group.
     *
     * @return array
     */
    function getGroupMemberSet() {

        return $this->principalBackend->getGroupMemberSet($this->getPrincipalUrl());

    }

    /**
     * Returns the list of groups this principal is member of
     *
     * If this principal is a member of a (list of) groups, this function
     * should return a list of principal uri's for it's members.
     *
     * @return array
     */
    function getGroupMembership() {

        return $this->principalBackend->getGroupMembership($this->getPrincipalUrl());

    }

    /**
     * Sets a list of group members
     *
     * If this principal is a group, this method sets all the group members.
     * The list of members is always overwritten, never appended to.
     *
     * This method should throw an exception if the members could not be set.
     *
     * @param array $principals
     * @return void
     */
    function setGroupMemberSet(array $principals) {

        $this->principalBackend->setGroupMemberSet($this->getPrincipalUrl(), $principals);

    }

    /**
     * Returns the displayname
     *
     * This should be a human readable name for the principal.
     * If none is available, return the nodename.
     *
     * @return string
     */
    function getDisplayName() {

        return $this->getName();

    }

}
<?php

namespace Sabre\CalDAV\Principal;

use Sabre\DAV;
use Sabre\DAVACL;

/**
 * CalDAV principal
 *
 * This is a standard user-principal for CalDAV. This principal is also a
 * collection and returns the caldav-proxy-read and caldav-proxy-write child
 * principals.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class User extends DAVACL\Principal implements DAV\ICollection {

    /**
     * Creates a new file in the directory
     *
     * @param string $name Name of the file
     * @param resource $data Initial payload, passed as a readable stream resource.
     * @throws DAV\Exception\Forbidden
     * @return void
     */
    function createFile($name, $data = null) {

        throw new DAV\Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');

    }

    /**
     * Creates a new subdirectory
     *
     * @param string $name
     * @throws DAV\Exception\Forbidden
     * @return void
     */
    function createDirectory($name) {

        throw new DAV\Exception\Forbidden('Permission denied to create directory');

    }

    /**
     * Returns a specific child node, referenced by its name
     *
     * @param string $name
     * @return DAV\INode
     */
    function getChild($name) {

        $principal = $this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/' . $name);
        if (!$principal) {
            throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');
        }
        if ($name === 'calendar-proxy-read')
            return new ProxyRead($this->principalBackend, $this->principalProperties);

        if ($name === 'calendar-proxy-write')
            return new ProxyWrite($this->principalBackend, $this->principalProperties);

        throw new DAV\Exception\NotFound('Node with name ' . $name . ' was not found');

    }

    /**
     * Returns an array with all the child nodes
     *
     * @return DAV\INode[]
     */
    function getChildren() {

        $r = [];
        if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-read')) {
            $r[] = new ProxyRead($this->principalBackend, $this->principalProperties);
        }
        if ($this->principalBackend->getPrincipalByPath($this->getPrincipalURL() . '/calendar-proxy-write')) {
            $r[] = new ProxyWrite($this->principalBackend, $this->principalProperties);
        }

        return $r;

    }

    /**
     * Returns whether or not the child node exists
     *
     * @param string $name
     * @return bool
     */
    function childExists($name) {

        try {
            $this->getChild($name);
            return true;
        } catch (DAV\Exception\NotFound $e) {
            return false;
        }

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        $acl = parent::getACL();
        $acl[] = [
            'privilege' => '{DAV:}read',
            'principal' => $this->principalProperties['uri'] . '/calendar-proxy-read',
            'protected' => true,
        ];
        $acl[] = [
            'privilege' => '{DAV:}read',
            'principal' => $this->principalProperties['uri'] . '/calendar-proxy-write',
            'protected' => true,
        ];
        return $acl;

    }

}
<?php

namespace Sabre\CalDAV\Schedule;

/**
 * Implement this interface to have a node be recognized as a CalDAV scheduling
 * inbox.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IInbox extends \Sabre\CalDAV\ICalendarObjectContainer, \Sabre\DAVACL\IACL {

}
<?php

namespace Sabre\CalDAV\Schedule;

use Sabre\DAV;
use Sabre\VObject\ITip;

/**
 * iMIP handler.
 *
 * This class is responsible for sending out iMIP messages. iMIP is the
 * email-based transport for iTIP. iTIP deals with scheduling operations for
 * iCalendar objects.
 *
 * If you want to customize the email that gets sent out, you can do so by
 * extending this class and overriding the sendMessage method.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class IMipPlugin extends DAV\ServerPlugin {

    /**
     * Email address used in From: header.
     *
     * @var string
     */
    protected $senderEmail;

    /**
     * ITipMessage
     *
     * @var ITip\Message
     */
    protected $itipMessage;

    /**
     * Creates the email handler.
     *
     * @param string $senderEmail. The 'senderEmail' is the email that shows up
     *                             in the 'From:' address. This should
     *                             generally be some kind of no-reply email
     *                             address you own.
     */
    function __construct($senderEmail) {

        $this->senderEmail = $senderEmail;

    }

    /*
     * This initializes the plugin.
     *
     * This function is called by Sabre\DAV\Server, after
     * addPlugin is called.
     *
     * This method should set up the required event subscriptions.
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $server->on('schedule', [$this, 'schedule'], 120);

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'imip';

    }

    /**
     * Event handler for the 'schedule' event.
     *
     * @param ITip\Message $iTipMessage
     * @return void
     */
    function schedule(ITip\Message $iTipMessage) {

        // Not sending any emails if the system considers the update
        // insignificant.
        if (!$iTipMessage->significantChange) {
            if (!$iTipMessage->scheduleStatus) {
                $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
            }
            return;
        }

        $summary = $iTipMessage->message->VEVENT->SUMMARY;

        if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto')
            return;

        if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto')
            return;

        $sender = substr($iTipMessage->sender, 7);
        $recipient = substr($iTipMessage->recipient, 7);

        if ($iTipMessage->senderName) {
            $sender = $iTipMessage->senderName . ' <' . $sender . '>';
        }
        if ($iTipMessage->recipientName) {
            $recipient = $iTipMessage->recipientName . ' <' . $recipient . '>';
        }

        $subject = 'SabreDAV iTIP message';
        switch (strtoupper($iTipMessage->method)) {
            case 'REPLY' :
                $subject = 'Re: ' . $summary;
                break;
            case 'REQUEST' :
                $subject = $summary;
                break;
            case 'CANCEL' :
                $subject = 'Cancelled: ' . $summary;
                break;
        }

        $headers = [
            'Reply-To: ' . $sender,
            'From: ' . $this->senderEmail,
            'Content-Type: text/calendar; charset=UTF-8; method=' . $iTipMessage->method,
        ];
        if (DAV\Server::$exposeVersion) {
            $headers[] = 'X-Sabre-Version: ' . DAV\Version::VERSION;
        }
        $this->mail(
            $recipient,
            $subject,
            $iTipMessage->message->serialize(),
            $headers
        );
        $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';

    }

    // @codeCoverageIgnoreStart
    // This is deemed untestable in a reasonable manner

    /**
     * This function is responsible for sending the actual email.
     *
     * @param string $to Recipient email address
     * @param string $subject Subject of the email
     * @param string $body iCalendar body
     * @param array $headers List of headers
     * @return void
     */
    protected function mail($to, $subject, $body, array $headers) {

        mail($to, $subject, $body, implode("\r\n", $headers));

    }

    // @codeCoverageIgnoreEnd

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Email delivery (rfc6047) for CalDAV scheduling',
            'link'        => 'http://sabre.io/dav/scheduling/',
        ];

    }

}
<?php

namespace Sabre\CalDAV\Schedule;

use Sabre\CalDAV;
use Sabre\CalDAV\Backend;
use Sabre\DAV;
use Sabre\DAVACL;
use Sabre\VObject;

/**
 * The CalDAV scheduling inbox
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Inbox extends DAV\Collection implements IInbox {

    use DAVACL\ACLTrait;

    /**
     * CalDAV backend
     *
     * @var Backend\BackendInterface
     */
    protected $caldavBackend;

    /**
     * The principal Uri
     *
     * @var string
     */
    protected $principalUri;

    /**
     * Constructor
     *
     * @param Backend\SchedulingSupport $caldavBackend
     * @param string $principalUri
     */
    function __construct(Backend\SchedulingSupport $caldavBackend, $principalUri) {

        $this->caldavBackend = $caldavBackend;
        $this->principalUri = $principalUri;

    }

    /**
     * Returns the name of the node.
     *
     * This is used to generate the url.
     *
     * @return string
     */
    function getName() {

        return 'inbox';

    }

    /**
     * Returns an array with all the child nodes
     *
     * @return \Sabre\DAV\INode[]
     */
    function getChildren() {

        $objs = $this->caldavBackend->getSchedulingObjects($this->principalUri);
        $children = [];
        foreach ($objs as $obj) {
            //$obj['acl'] = $this->getACL();
            $obj['principaluri'] = $this->principalUri;
            $children[] = new SchedulingObject($this->caldavBackend, $obj);
        }
        return $children;

    }

    /**
     * Creates a new file in the directory
     *
     * Data will either be supplied as a stream resource, or in certain cases
     * as a string. Keep in mind that you may have to support either.
     *
     * After successful creation of the file, you may choose to return the ETag
     * of the new file here.
     *
     * The returned ETag must be surrounded by double-quotes (The quotes should
     * be part of the actual string).
     *
     * If you cannot accurately determine the ETag, you should not return it.
     * If you don't store the file exactly as-is (you're transforming it
     * somehow) you should also not return an ETag.
     *
     * This means that if a subsequent GET to this new file does not exactly
     * return the same contents of what was submitted here, you are strongly
     * recommended to omit the ETag.
     *
     * @param string $name Name of the file
     * @param resource|string $data Initial payload
     * @return null|string
     */
    function createFile($name, $data = null) {

        $this->caldavBackend->createSchedulingObject($this->principalUri, $name, $data);

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->principalUri;

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        return [
            [
                'privilege' => '{DAV:}read',
                'principal' => '{DAV:}authenticated',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}write-properties',
                'principal' => $this->getOwner(),
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}unbind',
                'principal' => $this->getOwner(),
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}unbind',
                'principal' => $this->getOwner() . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver',
                'principal' => '{DAV:}authenticated',
                'protected' => true,
            ],
        ];

    }

    /**
     * Performs a calendar-query on the contents of this calendar.
     *
     * The calendar-query is defined in RFC4791 : CalDAV. Using the
     * calendar-query it is possible for a client to request a specific set of
     * object, based on contents of iCalendar properties, date-ranges and
     * iCalendar component types (VTODO, VEVENT).
     *
     * This method should just return a list of (relative) urls that match this
     * query.
     *
     * The list of filters are specified as an array. The exact array is
     * documented by \Sabre\CalDAV\CalendarQueryParser.
     *
     * @param array $filters
     * @return array
     */
    function calendarQuery(array $filters) {

        $result = [];
        $validator = new CalDAV\CalendarQueryValidator();

        $objects = $this->caldavBackend->getSchedulingObjects($this->principalUri);
        foreach ($objects as $object) {
            $vObject = VObject\Reader::read($object['calendardata']);
            if ($validator->validate($vObject, $filters)) {
                $result[] = $object['uri'];
            }

            // Destroy circular references to PHP will GC the object.
            $vObject->destroy();
        }
        return $result;

    }

}
<?php

namespace Sabre\CalDAV\Schedule;

/**
 * Implement this interface to have a node be recognized as a CalDAV scheduling
 * outbox.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IOutbox extends \Sabre\DAV\ICollection, \Sabre\DAVACL\IACL {

}
<?php

namespace Sabre\CalDAV\Schedule;

/**
 * The SchedulingObject represents a scheduling object in the Inbox collection
 *
 * @license http://sabre.io/license/ Modified BSD License
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 */
interface ISchedulingObject extends \Sabre\CalDAV\ICalendarObject {

}
<?php

namespace Sabre\CalDAV\Schedule;

use Sabre\CalDAV;
use Sabre\DAV;
use Sabre\DAVACL;

/**
 * The CalDAV scheduling outbox
 *
 * The outbox is mainly used as an endpoint in the tree for a client to do
 * free-busy requests. This functionality is completely handled by the
 * Scheduling plugin, so this object is actually mostly static.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Outbox extends DAV\Collection implements IOutbox {

    use DAVACL\ACLTrait;

    /**
     * The principal Uri
     *
     * @var string
     */
    protected $principalUri;

    /**
     * Constructor
     *
     * @param string $principalUri
     */
    function __construct($principalUri) {

        $this->principalUri = $principalUri;

    }

    /**
     * Returns the name of the node.
     *
     * This is used to generate the url.
     *
     * @return string
     */
    function getName() {

        return 'outbox';

    }

    /**
     * Returns an array with all the child nodes
     *
     * @return \Sabre\DAV\INode[]
     */
    function getChildren() {

        return [];

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->principalUri;

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        return [
            [
                'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send',
                'principal' => $this->getOwner(),
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner(),
                'protected' => true,
            ],
            [
                'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-send',
                'principal' => $this->getOwner() . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner() . '/calendar-proxy-read',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner() . '/calendar-proxy-write',
                'protected' => true,
            ],
        ];

    }

}
<?php

namespace Sabre\CalDAV\Schedule;

use DateTimeZone;
use Sabre\CalDAV\ICalendar;
use Sabre\CalDAV\ICalendarObject;
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Exception\NotImplemented;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Sharing;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\ITip;
use Sabre\VObject\ITip\Message;
use Sabre\VObject\Reader;

/**
 * CalDAV scheduling plugin.
 * =========================
 *
 * This plugin provides the functionality added by the "Scheduling Extensions
 * to CalDAV" standard, as defined in RFC6638.
 *
 * calendar-auto-schedule largely works by intercepting a users request to
 * update their local calendar. If a user creates a new event with attendees,
 * this plugin is supposed to grab the information from that event, and notify
 * the attendees of this.
 *
 * There's 3 possible transports for this:
 * * local delivery
 * * delivery through email (iMip)
 * * server-to-server delivery (iSchedule)
 *
 * iMip is simply, because we just need to add the iTip message as an email
 * attachment. Local delivery is harder, because we both need to add this same
 * message to a local DAV inbox, as well as live-update the relevant events.
 *
 * iSchedule is something for later.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends ServerPlugin {

    /**
     * This is the official CalDAV namespace
     */
    const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';

    /**
     * Reference to main Server object.
     *
     * @var Server
     */
    protected $server;

    /**
     * Returns a list of features for the DAV: HTTP header.
     *
     * @return array
     */
    function getFeatures() {

        return ['calendar-auto-schedule', 'calendar-availability'];

    }

    /**
     * Returns the name of the plugin.
     *
     * Using this name other plugins will be able to access other plugins
     * using Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'caldav-schedule';

    }

    /**
     * Initializes the plugin
     *
     * @param Server $server
     * @return void
     */
    function initialize(Server $server) {

        $this->server = $server;
        $server->on('method:POST',              [$this, 'httpPost']);
        $server->on('propFind',                 [$this, 'propFind']);
        $server->on('propPatch',                [$this, 'propPatch']);
        $server->on('calendarObjectChange',     [$this, 'calendarObjectChange']);
        $server->on('beforeUnbind',             [$this, 'beforeUnbind']);
        $server->on('schedule',                 [$this, 'scheduleLocalDelivery']);
        $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);

        $ns = '{' . self::NS_CALDAV . '}';

        /**
         * This information ensures that the {DAV:}resourcetype property has
         * the correct values.
         */
        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox';
        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox';

        /**
         * Properties we protect are made read-only by the server.
         */
        array_push($server->protectedProperties,
            $ns . 'schedule-inbox-URL',
            $ns . 'schedule-outbox-URL',
            $ns . 'calendar-user-address-set',
            $ns . 'calendar-user-type',
            $ns . 'schedule-default-calendar-URL'
        );

    }

    /**
     * Use this method to tell the server this plugin defines additional
     * HTTP methods.
     *
     * This method is passed a uri. It should only return HTTP methods that are
     * available for the specified uri.
     *
     * @param string $uri
     * @return array
     */
    function getHTTPMethods($uri) {

        try {
            $node = $this->server->tree->getNodeForPath($uri);
        } catch (NotFound $e) {
            return [];
        }

        if ($node instanceof IOutbox) {
            return ['POST'];
        }

        return [];

    }

    /**
     * This method handles POST request for the outbox.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpPost(RequestInterface $request, ResponseInterface $response) {

        // Checking if this is a text/calendar content type
        $contentType = $request->getHeader('Content-Type');
        if (strpos($contentType, 'text/calendar') !== 0) {
            return;
        }

        $path = $request->getPath();

        // Checking if we're talking to an outbox
        try {
            $node = $this->server->tree->getNodeForPath($path);
        } catch (NotFound $e) {
            return;
        }
        if (!$node instanceof IOutbox)
            return;

        $this->server->transactionType = 'post-caldav-outbox';
        $this->outboxRequest($node, $request, $response);

        // Returning false breaks the event chain and tells the server we've
        // handled the request.
        return false;

    }

    /**
     * This method handler is invoked during fetching of properties.
     *
     * We use this event to add calendar-auto-schedule-specific properties.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return void
     */
    function propFind(PropFind $propFind, INode $node) {

        if ($node instanceof DAVACL\IPrincipal) {

            $caldavPlugin = $this->server->getPlugin('caldav');
            $principalUrl = $node->getPrincipalUrl();

            // schedule-outbox-URL property
            $propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) {

                $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
                if (!$calendarHomePath) {
                    return null;
                }
                $outboxPath = $calendarHomePath . '/outbox/';

                return new LocalHref($outboxPath);

            });
            // schedule-inbox-URL property
            $propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) {

                $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
                if (!$calendarHomePath) {
                    return null;
                }
                $inboxPath = $calendarHomePath . '/inbox/';

                return new LocalHref($inboxPath);

            });

            $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) {

                // We don't support customizing this property yet, so in the
                // meantime we just grab the first calendar in the home-set.
                $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);

                if (!$calendarHomePath) {
                    return null;
                }

                $sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set';

                $result = $this->server->getPropertiesForPath($calendarHomePath, [
                    '{DAV:}resourcetype',
                    '{DAV:}share-access',
                    $sccs,
                ], 1);

                foreach ($result as $child) {
                    if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar')) {
                        // Node is either not a calendar
                        continue;
                    }
                    if (isset($child[200]['{DAV:}share-access'])) {
                        $shareAccess = $child[200]['{DAV:}share-access']->getValue();
                        if ($shareAccess !== Sharing\Plugin::ACCESS_NOTSHARED && $shareAccess !== Sharing\Plugin::ACCESS_SHAREDOWNER) {
                            // Node is a shared node, not owned by the relevant
                            // user.
                            continue;
                        }

                    }
                    if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
                        // Either there is no supported-calendar-component-set
                        // (which is fine) or we found one that supports VEVENT.
                        return new LocalHref($child['href']);
                    }
                }

            });

            // The server currently reports every principal to be of type
            // 'INDIVIDUAL'
            $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() {

                return 'INDIVIDUAL';

            });

        }

        // Mapping the old property to the new property.
        $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) {

             // In case it wasn't clear, the only difference is that we map the
            // old property to a different namespace.
             $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
             $subPropFind = new PropFind(
                 $propFind->getPath(),
                 [$availProp]
             );

             $this->server->getPropertiesByNode(
                 $subPropFind,
                 $node
             );

             $propFind->set(
                 '{http://calendarserver.org/ns/}calendar-availability',
                 $subPropFind->get($availProp),
                 $subPropFind->getStatus($availProp)
             );

        });

    }

    /**
     * This method is called during property updates.
     *
     * @param string $path
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatch($path, PropPatch $propPatch) {

        // Mapping the old property to the new property.
        $propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) {

            $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
            $subPropPatch = new PropPatch([$availProp => $value]);
            $this->server->emit('propPatch', [$path, $subPropPatch]);
            $subPropPatch->commit();

            return $subPropPatch->getResult()[$availProp];

        });

    }

    /**
     * This method is triggered whenever there was a calendar object gets
     * created or updated.
     *
     * @param RequestInterface $request HTTP request
     * @param ResponseInterface $response HTTP Response
     * @param VCalendar $vCal Parsed iCalendar object
     * @param mixed $calendarPath Path to calendar collection
     * @param mixed $modified The iCalendar object has been touched.
     * @param mixed $isNew Whether this was a new item or we're updating one
     * @return void
     */
    function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {

        if (!$this->scheduleReply($this->server->httpRequest)) {
            return;
        }

        $calendarNode = $this->server->tree->getNodeForPath($calendarPath);

        $addresses = $this->getAddressesForPrincipal(
            $calendarNode->getOwner()
        );

        if (!$isNew) {
            $node = $this->server->tree->getNodeForPath($request->getPath());
            $oldObj = Reader::read($node->get());
        } else {
            $oldObj = null;
        }

        $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);

        if ($oldObj) {
            // Destroy circular references so PHP will GC the object.
            $oldObj->destroy();
        }

    }

    /**
     * This method is responsible for delivering the ITip message.
     *
     * @param ITip\Message $iTipMessage
     * @return void
     */
    function deliver(ITip\Message $iTipMessage) {

        $this->server->emit('schedule', [$iTipMessage]);
        if (!$iTipMessage->scheduleStatus) {
            $iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
        }
        // In case the change was considered 'insignificant', we are going to
        // remove any error statuses, if any. See ticket #525.
        list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
        if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
            $iTipMessage->scheduleStatus = null;
        }

    }

    /**
     * This method is triggered before a file gets deleted.
     *
     * We use this event to make sure that when this happens, attendees get
     * cancellations, and organizers get 'DECLINED' statuses.
     *
     * @param string $path
     * @return void
     */
    function beforeUnbind($path) {

        // FIXME: We shouldn't trigger this functionality when we're issuing a
        // MOVE. This is a hack.
        if ($this->server->httpRequest->getMethod() === 'MOVE') return;

        $node = $this->server->tree->getNodeForPath($path);

        if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
            return;
        }

        if (!$this->scheduleReply($this->server->httpRequest)) {
            return;
        }

        $addresses = $this->getAddressesForPrincipal(
            $node->getOwner()
        );

        $broker = new ITip\Broker();
        $messages = $broker->parseEvent(null, $addresses, $node->get());

        foreach ($messages as $message) {
            $this->deliver($message);
        }

    }

    /**
     * Event handler for the 'schedule' event.
     *
     * This handler attempts to look at local accounts to deliver the
     * scheduling object.
     *
     * @param ITip\Message $iTipMessage
     * @return void
     */
    function scheduleLocalDelivery(ITip\Message $iTipMessage) {

        $aclPlugin = $this->server->getPlugin('acl');

        // Local delivery is not available if the ACL plugin is not loaded.
        if (!$aclPlugin) {
            return;
        }

        $caldavNS = '{' . self::NS_CALDAV . '}';

        $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
        if (!$principalUri) {
            $iTipMessage->scheduleStatus = '3.7;Could not find principal.';
            return;
        }

        // We found a principal URL, now we need to find its inbox.
        // Unfortunately we may not have sufficient privileges to find this, so
        // we are temporarily turning off ACL to let this come through.
        //
        // Once we support PHP 5.5, this should be wrapped in a try..finally
        // block so we can ensure that this privilege gets added again after.
        $this->server->removeListener('propFind', [$aclPlugin, 'propFind']);

        $result = $this->server->getProperties(
            $principalUri,
            [
                '{DAV:}principal-URL',
                 $caldavNS . 'calendar-home-set',
                 $caldavNS . 'schedule-inbox-URL',
                 $caldavNS . 'schedule-default-calendar-URL',
                '{http://sabredav.org/ns}email-address',
            ]
        );

        // Re-registering the ACL event
        $this->server->on('propFind', [$aclPlugin, 'propFind'], 20);

        if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) {
            $iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
            return;
        }
        if (!isset($result[$caldavNS . 'calendar-home-set'])) {
            $iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
            return;
        }
        if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) {
            $iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
            return;
        }

        $calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref();
        $homePath = $result[$caldavNS . 'calendar-home-set']->getHref();
        $inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref();

        if ($iTipMessage->method === 'REPLY') {
            $privilege = 'schedule-deliver-reply';
        } else {
            $privilege = 'schedule-deliver-invite';
        }

        if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) {
            $iTipMessage->scheduleStatus = '3.8;insufficient privileges: ' . $privilege . ' is required on the recipient schedule inbox.';
            return;
        }

        // Next, we're going to find out if the item already exits in one of
        // the users' calendars.
        $uid = $iTipMessage->uid;

        $newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics';

        $home = $this->server->tree->getNodeForPath($homePath);
        $inbox = $this->server->tree->getNodeForPath($inboxPath);

        $currentObject = null;
        $objectNode = null;
        $isNewNode = false;

        $result = $home->getCalendarObjectByUID($uid);
        if ($result) {
            // There was an existing object, we need to update probably.
            $objectPath = $homePath . '/' . $result;
            $objectNode = $this->server->tree->getNodeForPath($objectPath);
            $oldICalendarData = $objectNode->get();
            $currentObject = Reader::read($oldICalendarData);
        } else {
            $isNewNode = true;
        }

        $broker = new ITip\Broker();
        $newObject = $broker->processMessage($iTipMessage, $currentObject);

        $inbox->createFile($newFileName, $iTipMessage->message->serialize());

        if (!$newObject) {
            // We received an iTip message referring to a UID that we don't
            // have in any calendars yet, and processMessage did not give us a
            // calendarobject back.
            //
            // The implication is that processMessage did not understand the
            // iTip message.
            $iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
            return;
        }

        // Note that we are bypassing ACL on purpose by calling this directly.
        // We may need to look a bit deeper into this later. Supporting ACL
        // here would be nice.
        if ($isNewNode) {
            $calendar = $this->server->tree->getNodeForPath($calendarPath);
            $calendar->createFile($newFileName, $newObject->serialize());
        } else {
            // If the message was a reply, we may have to inform other
            // attendees of this attendees status. Therefore we're shooting off
            // another itipMessage.
            if ($iTipMessage->method === 'REPLY') {
                $this->processICalendarChange(
                    $oldICalendarData,
                    $newObject,
                    [$iTipMessage->recipient],
                    [$iTipMessage->sender]
                );
            }
            $objectNode->put($newObject->serialize());
        }
        $iTipMessage->scheduleStatus = '1.2;Message delivered locally';

    }

    /**
     * This method is triggered whenever a subsystem requests the privileges
     * that are supported on a particular node.
     *
     * We need to add a number of privileges for scheduling purposes.
     *
     * @param INode $node
     * @param array $supportedPrivilegeSet
     */
    function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {

        $ns = '{' . self::NS_CALDAV . '}';
        if ($node instanceof IOutbox) {
            $supportedPrivilegeSet[$ns . 'schedule-send'] = [
                'abstract'   => false,
                'aggregates' => [
                    $ns . 'schedule-send-invite' => [
                        'abstract'   => false,
                        'aggregates' => [],
                    ],
                    $ns . 'schedule-send-reply' => [
                        'abstract'   => false,
                        'aggregates' => [],
                    ],
                    $ns . 'schedule-send-freebusy' => [
                        'abstract'   => false,
                        'aggregates' => [],
                    ],
                    // Privilege from an earlier scheduling draft, but still
                    // used by some clients.
                    $ns . 'schedule-post-vevent' => [
                        'abstract'   => false,
                        'aggregates' => [],
                    ],
                ]
            ];
        }
        if ($node instanceof IInbox) {
            $supportedPrivilegeSet[$ns . 'schedule-deliver'] = [
                'abstract'   => false,
                'aggregates' => [
                    $ns . 'schedule-deliver-invite' => [
                        'abstract'   => false,
                        'aggregates' => [],
                    ],
                    $ns . 'schedule-deliver-reply' => [
                        'abstract'   => false,
                        'aggregates' => [],
                    ],
                    $ns . 'schedule-query-freebusy' => [
                        'abstract'   => false,
                        'aggregates' => [],
                    ],
                ]
            ];
        }

    }

    /**
     * This method looks at an old iCalendar object, a new iCalendar object and
     * starts sending scheduling messages based on the changes.
     *
     * A list of addresses needs to be specified, so the system knows who made
     * the update, because the behavior may be different based on if it's an
     * attendee or an organizer.
     *
     * This method may update $newObject to add any status changes.
     *
     * @param VCalendar|string $oldObject
     * @param VCalendar $newObject
     * @param array $addresses
     * @param array $ignore Any addresses to not send messages to.
     * @param bool $modified A marker to indicate that the original object
     *   modified by this process.
     * @return void
     */
    protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) {

        $broker = new ITip\Broker();
        $messages = $broker->parseEvent($newObject, $addresses, $oldObject);

        if ($messages) $modified = true;

        foreach ($messages as $message) {

            if (in_array($message->recipient, $ignore)) {
                continue;
            }

            $this->deliver($message);

            if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
                if ($message->scheduleStatus) {
                    $newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
                }
                unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);

            } else {

                if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) {

                    if ($attendee->getNormalizedValue() === $message->recipient) {
                        if ($message->scheduleStatus) {
                            $attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
                        }
                        unset($attendee['SCHEDULE-FORCE-SEND']);
                        break;
                    }

                }

            }

        }

    }

    /**
     * Returns a list of addresses that are associated with a principal.
     *
     * @param string $principal
     * @return array
     */
    protected function getAddressesForPrincipal($principal) {

        $CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set';

        $properties = $this->server->getProperties(
            $principal,
            [$CUAS]
        );

        // If we can't find this information, we'll stop processing
        if (!isset($properties[$CUAS])) {
            return;
        }

        $addresses = $properties[$CUAS]->getHrefs();
        return $addresses;

    }

    /**
     * This method handles POST requests to the schedule-outbox.
     *
     * Currently, two types of requests are supported:
     *   * FREEBUSY requests from RFC 6638
     *   * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
     *
     * The latter is from an expired early draft of the CalDAV scheduling
     * extensions, but iCal depends on a feature from that spec, so we
     * implement it.
     *
     * @param IOutbox $outboxNode
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response) {

        $outboxPath = $request->getPath();

        // Parsing the request body
        try {
            $vObject = VObject\Reader::read($request->getBody());
        } catch (VObject\ParseException $e) {
            throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
        }

        // The incoming iCalendar object must have a METHOD property, and a
        // component. The combination of both determines what type of request
        // this is.
        $componentType = null;
        foreach ($vObject->getComponents() as $component) {
            if ($component->name !== 'VTIMEZONE') {
                $componentType = $component->name;
                break;
            }
        }
        if (is_null($componentType)) {
            throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
        }

        // Validating the METHOD
        $method = strtoupper((string)$vObject->METHOD);
        if (!$method) {
            throw new BadRequest('A METHOD property must be specified in iTIP messages');
        }

        // So we support one type of request:
        //
        // REQUEST with a VFREEBUSY component

        $acl = $this->server->getPlugin('acl');

        if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {

            $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-send-freebusy');
            $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);

            // Destroy circular references so PHP can GC the object.
            $vObject->destroy();
            unset($vObject);

        } else {

            throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');

        }

    }

    /**
     * This method is responsible for parsing a free-busy query request and
     * returning it's result.
     *
     * @param IOutbox $outbox
     * @param VObject\Component $vObject
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return string
     */
    protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) {

        $vFreeBusy = $vObject->VFREEBUSY;
        $organizer = $vFreeBusy->ORGANIZER;

        $organizer = (string)$organizer;

        // Validating if the organizer matches the owner of the inbox.
        $owner = $outbox->getOwner();

        $caldavNS = '{' . self::NS_CALDAV . '}';

        $uas = $caldavNS . 'calendar-user-address-set';
        $props = $this->server->getProperties($owner, [$uas]);

        if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
            throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
        }

        if (!isset($vFreeBusy->ATTENDEE)) {
            throw new BadRequest('You must at least specify 1 attendee');
        }

        $attendees = [];
        foreach ($vFreeBusy->ATTENDEE as $attendee) {
            $attendees[] = (string)$attendee;
        }


        if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
            throw new BadRequest('DTSTART and DTEND must both be specified');
        }

        $startRange = $vFreeBusy->DTSTART->getDateTime();
        $endRange = $vFreeBusy->DTEND->getDateTime();

        $results = [];
        foreach ($attendees as $attendee) {
            $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
        }

        $dom = new \DOMDocument('1.0', 'utf-8');
        $dom->formatOutput = true;
        $scheduleResponse = $dom->createElement('cal:schedule-response');
        foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {

            $scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);

        }
        $dom->appendChild($scheduleResponse);

        foreach ($results as $result) {
            $xresponse = $dom->createElement('cal:response');

            $recipient = $dom->createElement('cal:recipient');
            $recipientHref = $dom->createElement('d:href');

            $recipientHref->appendChild($dom->createTextNode($result['href']));
            $recipient->appendChild($recipientHref);
            $xresponse->appendChild($recipient);

            $reqStatus = $dom->createElement('cal:request-status');
            $reqStatus->appendChild($dom->createTextNode($result['request-status']));
            $xresponse->appendChild($reqStatus);

            if (isset($result['calendar-data'])) {

                $calendardata = $dom->createElement('cal:calendar-data');
                $calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
                $xresponse->appendChild($calendardata);

            }
            $scheduleResponse->appendChild($xresponse);
        }

        $response->setStatus(200);
        $response->setHeader('Content-Type', 'application/xml');
        $response->setBody($dom->saveXML());

    }

    /**
     * Returns free-busy information for a specific address. The returned
     * data is an array containing the following properties:
     *
     * calendar-data : A VFREEBUSY VObject
     * request-status : an iTip status code.
     * href: The principal's email address, as requested
     *
     * The following request status codes may be returned:
     *   * 2.0;description
     *   * 3.7;description
     *
     * @param string $email address
     * @param \DateTimeInterface $start
     * @param \DateTimeInterface $end
     * @param VObject\Component $request
     * @return array
     */
    protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) {

        $caldavNS = '{' . self::NS_CALDAV . '}';

        $aclPlugin = $this->server->getPlugin('acl');
        if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7);

        $result = $aclPlugin->principalSearch(
            ['{http://sabredav.org/ns}email-address' => $email],
            [
                '{DAV:}principal-URL',
                $caldavNS . 'calendar-home-set',
                $caldavNS . 'schedule-inbox-URL',
                '{http://sabredav.org/ns}email-address',

            ]
        );

        if (!count($result)) {
            return [
                'request-status' => '3.7;Could not find principal',
                'href'           => 'mailto:' . $email,
            ];
        }

        if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) {
            return [
                'request-status' => '3.7;No calendar-home-set property found',
                'href'           => 'mailto:' . $email,
            ];
        }
        if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) {
            return [
                'request-status' => '3.7;No schedule-inbox-URL property found',
                'href'           => 'mailto:' . $email,
            ];
        }
        $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
        $inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref();

        // Do we have permission?
        $aclPlugin->checkPrivileges($inboxUrl, $caldavNS . 'schedule-query-freebusy');

        // Grabbing the calendar list
        $objects = [];
        $calendarTimeZone = new DateTimeZone('UTC');

        foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
            if (!$node instanceof ICalendar) {
                continue;
            }

            $sct = $caldavNS . 'schedule-calendar-transp';
            $ctz = $caldavNS . 'calendar-timezone';
            $props = $node->getProperties([$sct, $ctz]);

            if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) {
                // If a calendar is marked as 'transparent', it means we must
                // ignore it for free-busy purposes.
                continue;
            }

            if (isset($props[$ctz])) {
                $vtimezoneObj = VObject\Reader::read($props[$ctz]);
                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();

                // Destroy circular references so PHP can garbage collect the object.
                $vtimezoneObj->destroy();

            }

            // Getting the list of object uris within the time-range
            $urls = $node->calendarQuery([
                'name'         => 'VCALENDAR',
                'comp-filters' => [
                    [
                        'name'           => 'VEVENT',
                        'comp-filters'   => [],
                        'prop-filters'   => [],
                        'is-not-defined' => false,
                        'time-range'     => [
                            'start' => $start,
                            'end'   => $end,
                        ],
                    ],
                ],
                'prop-filters'   => [],
                'is-not-defined' => false,
                'time-range'     => null,
            ]);

            $calObjects = array_map(function($url) use ($node) {
                $obj = $node->getChild($url)->get();
                return $obj;
            }, $urls);

            $objects = array_merge($objects, $calObjects);

        }

        $inboxProps = $this->server->getProperties(
            $inboxUrl,
            $caldavNS . 'calendar-availability'
        );

        $vcalendar = new VObject\Component\VCalendar();
        $vcalendar->METHOD = 'REPLY';

        $generator = new VObject\FreeBusyGenerator();
        $generator->setObjects($objects);
        $generator->setTimeRange($start, $end);
        $generator->setBaseObject($vcalendar);
        $generator->setTimeZone($calendarTimeZone);

        if ($inboxProps) {
            $generator->setVAvailability(
                VObject\Reader::read(
                    $inboxProps[$caldavNS . 'calendar-availability']
                )
            );
        }

        $result = $generator->getResult();

        $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email;
        $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID;
        $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;

        return [
            'calendar-data'  => $result,
            'request-status' => '2.0;Success',
            'href'           => 'mailto:' . $email,
        ];
    }

    /**
     * This method checks the 'Schedule-Reply' header
     * and returns false if it's 'F', otherwise true.
     *
     * @param RequestInterface $request
     * @return bool
     */
    private function scheduleReply(RequestInterface $request) {

        $scheduleReply = $request->getHeader('Schedule-Reply');
        return $scheduleReply !== 'F';

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds calendar-auto-schedule, as defined in rfc6638',
            'link'        => 'http://sabre.io/dav/scheduling/',
        ];

    }
}
<?php

namespace Sabre\CalDAV\Schedule;

use Sabre\CalDAV\Backend;
use Sabre\DAV\Exception\MethodNotAllowed;

/**
 * The SchedulingObject represents a scheduling object in the Inbox collection
 *
 * @author Brett (https://github.com/bretten)
 * @license http://sabre.io/license/ Modified BSD License
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 */
class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements ISchedulingObject {

    /**
     /* The CalDAV backend
     *
     * @var Backend\SchedulingSupport
     */
    protected $caldavBackend;

    /**
     * Array with information about this SchedulingObject
     *
     * @var array
     */
    protected $objectData;

    /**
     * Constructor
     *
     * The following properties may be passed within $objectData:
     *
     *   * uri - A unique uri. Only the 'basename' must be passed.
     *   * principaluri - the principal that owns the object.
     *   * calendardata (optional) - The iCalendar data
     *   * etag - (optional) The etag for this object, MUST be encloded with
     *            double-quotes.
     *   * size - (optional) The size of the data in bytes.
     *   * lastmodified - (optional) format as a unix timestamp.
     *   * acl - (optional) Use this to override the default ACL for the node.
     *
     * @param Backend\SchedulingSupport $caldavBackend
     * @param array $objectData
     */
    function __construct(Backend\SchedulingSupport $caldavBackend, array $objectData) {

        $this->caldavBackend = $caldavBackend;

        if (!isset($objectData['uri'])) {
            throw new \InvalidArgumentException('The objectData argument must contain an \'uri\' property');
        }

        $this->objectData = $objectData;

    }

    /**
     * Returns the ICalendar-formatted object
     *
     * @return string
     */
    function get() {

        // Pre-populating the 'calendardata' is optional, if we don't have it
        // already we fetch it from the backend.
        if (!isset($this->objectData['calendardata'])) {
            $this->objectData = $this->caldavBackend->getSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);
        }
        return $this->objectData['calendardata'];

    }

    /**
     * Updates the ICalendar-formatted object
     *
     * @param string|resource $calendarData
     * @return string
     */
    function put($calendarData) {

        throw new MethodNotAllowed('Updating scheduling objects is not supported');

    }

    /**
     * Deletes the scheduling message
     *
     * @return void
     */
    function delete() {

        $this->caldavBackend->deleteSchedulingObject($this->objectData['principaluri'], $this->objectData['uri']);

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->objectData['principaluri'];

    }


    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        // An alternative acl may be specified in the object data.
        //

        if (isset($this->objectData['acl'])) {
            return $this->objectData['acl'];
        }

        // The default ACL
        return [
            [
                'privilege' => '{DAV:}all',
                'principal' => '{DAV:}owner',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}all',
                'principal' => $this->objectData['principaluri'] . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->objectData['principaluri'] . '/calendar-proxy-read',
                'protected' => true,
            ],
        ];

    }

}
<?php

namespace Sabre\CalDAV;

use Sabre\DAV\Sharing\Plugin as SPlugin;

/**
 * This object represents a CalDAV calendar that is shared by a different user.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SharedCalendar extends Calendar implements ISharedCalendar {

    /**
     * Returns the 'access level' for the instance of this shared resource.
     *
     * The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_
     * constants.
     *
     * @return int
     */
    function getShareAccess() {

        return isset($this->calendarInfo['share-access']) ? $this->calendarInfo['share-access'] : SPlugin::ACCESS_NOTSHARED;

    }

    /**
     * This function must return a URI that uniquely identifies the shared
     * resource. This URI should be identical across instances, and is
     * also used in several other XML bodies to connect invites to
     * resources.
     *
     * This may simply be a relative reference to the original shared instance,
     * but it could also be a urn. As long as it's a valid URI and unique.
     *
     * @return string
     */
    function getShareResourceUri() {

        return $this->calendarInfo['share-resource-uri'];

    }

    /**
     * Updates the list of sharees.
     *
     * Every item must be a Sharee object.
     *
     * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
     * @return void
     */
    function updateInvites(array $sharees) {

        $this->caldavBackend->updateInvites($this->calendarInfo['id'], $sharees);

    }

    /**
     * Returns the list of people whom this resource is shared with.
     *
     * Every item in the returned array must be a Sharee object with
     * at least the following properties set:
     *
     * * $href
     * * $shareAccess
     * * $inviteStatus
     *
     * and optionally:
     *
     * * $properties
     *
     * @return \Sabre\DAV\Xml\Element\Sharee[]
     */
    function getInvites() {

        return $this->caldavBackend->getInvites($this->calendarInfo['id']);

    }

    /**
     * Marks this calendar as published.
     *
     * Publishing a calendar should automatically create a read-only, public,
     * subscribable calendar.
     *
     * @param bool $value
     * @return void
     */
    function setPublishStatus($value) {

        $this->caldavBackend->setPublishStatus($this->calendarInfo['id'], $value);

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        $acl = [];

        switch ($this->getShareAccess()) {
            case SPlugin::ACCESS_NOTSHARED :
            case SPlugin::ACCESS_SHAREDOWNER :
                $acl[] = [
                    'privilege' => '{DAV:}share',
                    'principal' => $this->calendarInfo['principaluri'],
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}share',
                    'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
                    'protected' => true,
                ];
                // No break intentional!
            case SPlugin::ACCESS_READWRITE :
                $acl[] = [
                    'privilege' => '{DAV:}write',
                    'principal' => $this->calendarInfo['principaluri'],
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}write',
                    'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
                    'protected' => true,
                ];
                // No break intentional!
            case SPlugin::ACCESS_READ :
                $acl[] = [
                    'privilege' => '{DAV:}write-properties',
                    'principal' => $this->calendarInfo['principaluri'],
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}write-properties',
                    'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}read',
                    'principal' => $this->calendarInfo['principaluri'],
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}read',
                    'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}read',
                    'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy',
                    'principal' => '{DAV:}authenticated',
                    'protected' => true,
                ];
                break;
        }
        return $acl;

    }


    /**
     * This method returns the ACL's for calendar objects in this calendar.
     * The result of this method automatically gets passed to the
     * calendar-object nodes in the calendar.
     *
     * @return array
     */
    function getChildACL() {

        $acl = [];

        switch ($this->getShareAccess()) {
            case SPlugin::ACCESS_NOTSHARED :
                // No break intentional
            case SPlugin::ACCESS_SHAREDOWNER :
                // No break intentional
            case SPlugin::ACCESS_READWRITE:
                $acl[] = [
                    'privilege' => '{DAV:}write',
                    'principal' => $this->calendarInfo['principaluri'],
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}write',
                    'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
                    'protected' => true,
                ];
                // No break intentional
            case SPlugin::ACCESS_READ:
                $acl[] = [
                    'privilege' => '{DAV:}read',
                    'principal' => $this->calendarInfo['principaluri'],
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}read',
                    'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-write',
                    'protected' => true,
                ];
                $acl[] = [
                    'privilege' => '{DAV:}read',
                    'principal' => $this->calendarInfo['principaluri'] . '/calendar-proxy-read',
                    'protected' => true,
                ];
                break;
        }

        return $acl;

    }

}
<?php

namespace Sabre\CalDAV;

use Sabre\DAV;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * This plugin implements support for caldav sharing.
 *
 * This spec is defined at:
 * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
 *
 * See:
 * Sabre\CalDAV\Backend\SharingSupport for all the documentation.
 *
 * Note: This feature is experimental, and may change in between different
 * SabreDAV versions.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SharingPlugin extends DAV\ServerPlugin {

    /**
     * Reference to SabreDAV server object.
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * This method should return a list of server-features.
     *
     * This is for example 'versioning' and is added to the DAV: header
     * in an OPTIONS response.
     *
     * @return array
     */
    function getFeatures() {

        return ['calendarserver-sharing'];

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'caldav-sharing';

    }

    /**
     * This initializes the plugin.
     *
     * This function is called by Sabre\DAV\Server, after
     * addPlugin is called.
     *
     * This method should set up the required event subscriptions.
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;

        if (is_null($this->server->getPlugin('sharing'))) {
            throw new \LogicException('The generic "sharing" plugin must be loaded before the caldav sharing plugin. Call $server->addPlugin(new \Sabre\DAV\Sharing\Plugin()); before this one.');
        }

        array_push(
            $this->server->protectedProperties,
            '{' . Plugin::NS_CALENDARSERVER . '}invite',
            '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes',
            '{' . Plugin::NS_CALENDARSERVER . '}shared-url'
        );

        $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}share'] = 'Sabre\\CalDAV\\Xml\\Request\\Share';
        $this->server->xml->elementMap['{' . Plugin::NS_CALENDARSERVER . '}invite-reply'] = 'Sabre\\CalDAV\\Xml\\Request\\InviteReply';

        $this->server->on('propFind',     [$this, 'propFindEarly']);
        $this->server->on('propFind',     [$this, 'propFindLate'], 150);
        $this->server->on('propPatch',    [$this, 'propPatch'], 40);
        $this->server->on('method:POST',  [$this, 'httpPost']);

    }

    /**
     * This event is triggered when properties are requested for a certain
     * node.
     *
     * This allows us to inject any properties early.
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @return void
     */
    function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {

        if ($node instanceof ISharedCalendar) {

            $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function() use ($node) {

                // Fetching owner information
                $props = $this->server->getPropertiesForPath($node->getOwner(), [
                    '{http://sabredav.org/ns}email-address',
                    '{DAV:}displayname',
                ], 0);

                $ownerInfo = [
                    'href' => $node->getOwner(),
                ];

                if (isset($props[0][200])) {

                    // We're mapping the internal webdav properties to the
                    // elements caldav-sharing expects.
                    if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) {
                        $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address'];
                    }
                    if (isset($props[0][200]['{DAV:}displayname'])) {
                        $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname'];
                    }

                }

                return new Xml\Property\Invite(
                    $node->getInvites(),
                    $ownerInfo
                );

            });

        }

    }

    /**
     * This method is triggered *after* all properties have been retrieved.
     * This allows us to inject the correct resourcetype for calendars that
     * have been shared.
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @return void
     */
    function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {

        if ($node instanceof ISharedCalendar) {
            $shareAccess = $node->getShareAccess();
            if ($rt = $propFind->get('{DAV:}resourcetype')) {
                switch ($shareAccess) {
                    case \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER :
                        $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared-owner');
                        break;
                    case \Sabre\DAV\Sharing\Plugin::ACCESS_READ :
                    case \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE :
                        $rt->add('{' . Plugin::NS_CALENDARSERVER . '}shared');
                        break;

                }
            }
            $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', function() {
                return new Xml\Property\AllowedSharingModes(true, false);
            });

        }

    }

    /**
     * This method is trigged when a user attempts to update a node's
     * properties.
     *
     * A previous draft of the sharing spec stated that it was possible to use
     * PROPPATCH to remove 'shared-owner' from the resourcetype, thus unsharing
     * the calendar.
     *
     * Even though this is no longer in the current spec, we keep this around
     * because OS X 10.7 may still make use of this feature.
     *
     * @param string $path
     * @param DAV\PropPatch $propPatch
     * @return void
     */
    function propPatch($path, DAV\PropPatch $propPatch) {

        $node = $this->server->tree->getNodeForPath($path);
        if (!$node instanceof ISharedCalendar)
            return;

        if ($node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_SHAREDOWNER || $node->getShareAccess() === \Sabre\DAV\Sharing\Plugin::ACCESS_NOTSHARED) {

            $propPatch->handle('{DAV:}resourcetype', function($value) use ($node) {
                if ($value->is('{' . Plugin::NS_CALENDARSERVER . '}shared-owner')) return false;
                $shares = $node->getInvites();
                foreach ($shares as $share) {
                    $share->access = DAV\Sharing\Plugin::ACCESS_NOACCESS;
                }
                $node->updateInvites($shares);

                return true;

            });

        }

    }

    /**
     * We intercept this to handle POST requests on calendars.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return null|bool
     */
    function httpPost(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        // Only handling xml
        $contentType = $request->getHeader('Content-Type');
        if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false)
            return;

        // Making sure the node exists
        try {
            $node = $this->server->tree->getNodeForPath($path);
        } catch (DAV\Exception\NotFound $e) {
            return;
        }

        $requestBody = $request->getBodyAsString();

        // If this request handler could not deal with this POST request, it
        // will return 'null' and other plugins get a chance to handle the
        // request.
        //
        // However, we already requested the full body. This is a problem,
        // because a body can only be read once. This is why we preemptively
        // re-populated the request body with the existing data.
        $request->setBody($requestBody);

        $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);

        switch ($documentType) {

            // Both the DAV:share-resource and CALENDARSERVER:share requests
            // behave identically.
            case '{' . Plugin::NS_CALENDARSERVER . '}share' :

                $sharingPlugin = $this->server->getPlugin('sharing');
                $sharingPlugin->shareResource($path, $message->sharees);

                $response->setStatus(200);
                // Adding this because sending a response body may cause issues,
                // and I wanted some type of indicator the response was handled.
                $response->setHeader('X-Sabre-Status', 'everything-went-well');

                // Breaking the event chain
                return false;

            // The invite-reply document is sent when the user replies to an
            // invitation of a calendar share.
            case '{' . Plugin::NS_CALENDARSERVER . '}invite-reply' :

                // This only works on the calendar-home-root node.
                if (!$node instanceof CalendarHome) {
                    return;
                }
                $this->server->transactionType = 'post-invite-reply';

                // Getting ACL info
                $acl = $this->server->getPlugin('acl');

                // If there's no ACL support, we allow everything
                if ($acl) {
                    $acl->checkPrivileges($path, '{DAV:}write');
                }

                $url = $node->shareReply(
                    $message->href,
                    $message->status,
                    $message->calendarUri,
                    $message->inReplyTo,
                    $message->summary
                );

                $response->setStatus(200);
                // Adding this because sending a response body may cause issues,
                // and I wanted some type of indicator the response was handled.
                $response->setHeader('X-Sabre-Status', 'everything-went-well');

                if ($url) {
                    $writer = $this->server->xml->getWriter();
                    $writer->openMemory();
                    $writer->startDocument();
                    $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}shared-as');
                    $writer->write(new LocalHref($url));
                    $writer->endElement();
                    $response->setHeader('Content-Type', 'application/xml');
                    $response->setBody($writer->outputMemory());

                }

                // Breaking the event chain
                return false;

            case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar' :

                // We can only deal with IShareableCalendar objects
                if (!$node instanceof ISharedCalendar) {
                    return;
                }
                $this->server->transactionType = 'post-publish-calendar';

                // Getting ACL info
                $acl = $this->server->getPlugin('acl');

                // If there's no ACL support, we allow everything
                if ($acl) {
                    $acl->checkPrivileges($path, '{DAV:}share');
                }

                $node->setPublishStatus(true);

                // iCloud sends back the 202, so we will too.
                $response->setStatus(202);

                // Adding this because sending a response body may cause issues,
                // and I wanted some type of indicator the response was handled.
                $response->setHeader('X-Sabre-Status', 'everything-went-well');

                // Breaking the event chain
                return false;

            case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar' :

                // We can only deal with IShareableCalendar objects
                if (!$node instanceof ISharedCalendar) {
                    return;
                }
                $this->server->transactionType = 'post-unpublish-calendar';

                // Getting ACL info
                $acl = $this->server->getPlugin('acl');

                // If there's no ACL support, we allow everything
                if ($acl) {
                    $acl->checkPrivileges($path, '{DAV:}share');
                }

                $node->setPublishStatus(false);

                $response->setStatus(200);

                // Adding this because sending a response body may cause issues,
                // and I wanted some type of indicator the response was handled.
                $response->setHeader('X-Sabre-Status', 'everything-went-well');

                // Breaking the event chain
                return false;

        }



    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds support for caldav-sharing.',
            'link'        => 'http://sabre.io/dav/caldav-sharing/',
        ];

    }
}
<?php

namespace Sabre\CalDAV\Subscriptions;

use Sabre\DAV\ICollection;
use Sabre\DAV\IProperties;

/**
 * ISubscription
 *
 * Nodes implementing this interface represent calendar subscriptions.
 *
 * The subscription node doesn't do much, other than returning and updating
 * subscription-related properties.
 *
 * The following properties should be supported:
 *
 * 1. {DAV:}displayname
 * 2. {http://apple.com/ns/ical/}refreshrate
 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
 *    should not be stripped).
 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
 *    should not be stripped).
 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
 *    attachments should not be stripped).
 * 6. {http://calendarserver.org/ns/}source (Must be a
 *     Sabre\DAV\Property\Href).
 * 7. {http://apple.com/ns/ical/}calendar-color
 * 8. {http://apple.com/ns/ical/}calendar-order
 *
 * It is recommended to support every property.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ISubscription extends ICollection, IProperties {


}
<?php

namespace Sabre\CalDAV\Subscriptions;

use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;

/**
 * This plugin adds calendar-subscription support to your CalDAV server.
 *
 * Some clients support 'managed subscriptions' server-side. This is basically
 * a list of subscription urls a user is using.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends ServerPlugin {

    /**
     * This initializes the plugin.
     *
     * This function is called by Sabre\DAV\Server, after
     * addPlugin is called.
     *
     * This method should set up the required event subscriptions.
     *
     * @param Server $server
     * @return void
     */
    function initialize(Server $server) {

        $server->resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] =
            '{http://calendarserver.org/ns/}subscribed';

        $server->xml->elementMap['{http://calendarserver.org/ns/}source'] =
            'Sabre\\DAV\\Xml\\Property\\Href';

        $server->on('propFind', [$this, 'propFind'], 150);

    }

    /**
     * This method should return a list of server-features.
     *
     * This is for example 'versioning' and is added to the DAV: header
     * in an OPTIONS response.
     *
     * @return array
     */
    function getFeatures() {

        return ['calendarserver-subscribed'];

    }

    /**
     * Triggered after properties have been fetched.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return void
     */
    function propFind(PropFind $propFind, INode $node) {

        // There's a bunch of properties that must appear as a self-closing
        // xml-element. This event handler ensures that this will be the case.
        $props = [
            '{http://calendarserver.org/ns/}subscribed-strip-alarms',
            '{http://calendarserver.org/ns/}subscribed-strip-attachments',
            '{http://calendarserver.org/ns/}subscribed-strip-todos',
        ];

        foreach ($props as $prop) {

            if ($propFind->getStatus($prop) === 200) {
                $propFind->set($prop, '', 200);
            }

        }

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'subscriptions';

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'This plugin allows users to store iCalendar subscriptions in their calendar-home.',
            'link'        => null,
        ];

    }
}
<?php

namespace Sabre\CalDAV\Subscriptions;

use Sabre\CalDAV\Backend\SubscriptionSupport;
use Sabre\DAV\Collection;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAVACL\ACLTrait;
use Sabre\DAVACL\IACL;

/**
 * Subscription Node
 *
 * This node represents a subscription.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Subscription extends Collection implements ISubscription, IACL {

    use ACLTrait;

    /**
     * caldavBackend
     *
     * @var SubscriptionSupport
     */
    protected $caldavBackend;

    /**
     * subscriptionInfo
     *
     * @var array
     */
    protected $subscriptionInfo;

    /**
     * Constructor
     *
     * @param SubscriptionSupport $caldavBackend
     * @param array $subscriptionInfo
     */
    function __construct(SubscriptionSupport $caldavBackend, array $subscriptionInfo) {

        $this->caldavBackend = $caldavBackend;
        $this->subscriptionInfo = $subscriptionInfo;

        $required = [
            'id',
            'uri',
            'principaluri',
            'source',
            ];

        foreach ($required as $r) {
            if (!isset($subscriptionInfo[$r])) {
                throw new \InvalidArgumentException('The ' . $r . ' field is required when creating a subscription node');
            }
        }

    }

    /**
     * Returns the name of the node.
     *
     * This is used to generate the url.
     *
     * @return string
     */
    function getName() {

        return $this->subscriptionInfo['uri'];

    }

    /**
     * Returns the last modification time
     *
     * @return int
     */
    function getLastModified() {

        if (isset($this->subscriptionInfo['lastmodified'])) {
            return $this->subscriptionInfo['lastmodified'];
        }

    }

    /**
     * Deletes the current node
     *
     * @return void
     */
    function delete() {

        $this->caldavBackend->deleteSubscription(
            $this->subscriptionInfo['id']
        );

    }

    /**
     * Returns an array with all the child nodes
     *
     * @return \Sabre\DAV\INode[]
     */
    function getChildren() {

        return [];

    }

    /**
     * Updates properties on this node.
     *
     * This method received a PropPatch object, which contains all the
     * information about the update.
     *
     * To update specific properties, call the 'handle' method on this object.
     * Read the PropPatch documentation for more information.
     *
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatch(PropPatch $propPatch) {

        return $this->caldavBackend->updateSubscription(
            $this->subscriptionInfo['id'],
            $propPatch
        );

    }

    /**
     * Returns a list of properties for this nodes.
     *
     * The properties list is a list of propertynames the client requested,
     * encoded in clark-notation {xmlnamespace}tagname.
     *
     * If the array is empty, it means 'all properties' were requested.
     *
     * Note that it's fine to liberally give properties back, instead of
     * conforming to the list of requested properties.
     * The Server class will filter out the extra.
     *
     * @param array $properties
     * @return array
     */
    function getProperties($properties) {

        $r = [];

        foreach ($properties as $prop) {

            switch ($prop) {
                case '{http://calendarserver.org/ns/}source' :
                    $r[$prop] = new Href($this->subscriptionInfo['source']);
                    break;
                default :
                    if (array_key_exists($prop, $this->subscriptionInfo)) {
                        $r[$prop] = $this->subscriptionInfo[$prop];
                    }
                    break;
            }

        }

        return $r;

    }

    /**
     * Returns the owner principal.
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->subscriptionInfo['principaluri'];

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        return [
            [
                'privilege' => '{DAV:}all',
                'principal' => $this->getOwner(),
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}all',
                'principal' => $this->getOwner() . '/calendar-proxy-write',
                'protected' => true,
            ],
            [
                'privilege' => '{DAV:}read',
                'principal' => $this->getOwner() . '/calendar-proxy-read',
                'protected' => true,
            ]
        ];

    }

}
<?php

namespace Sabre\CalDAV\Xml\Filter;

use Sabre\CalDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\DateTimeParser;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * CalendarData parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-data XML
 * element, as defined in:
 *
 * https://tools.ietf.org/html/rfc4791#section-9.6
 *
 * This element is used in three distinct places in the caldav spec, but in
 * this case, this element class only implements the calendar-data element as
 * it appears in a DAV:prop element, in a calendar-query or calendar-multiget
 * REPORT request.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CalendarData implements XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = [
            'contentType' => $reader->getAttribute('content-type') ?: 'text/calendar',
            'version'     => $reader->getAttribute('version') ?: '2.0',
        ];

        $elems = (array)$reader->parseInnerTree();
        foreach ($elems as $elem) {

            switch ($elem['name']) {
                case '{' . Plugin::NS_CALDAV . '}expand' :

                    $result['expand'] = [
                        'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
                        'end'   => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
                    ];

                    if (!$result['expand']['start'] || !$result['expand']['end']) {
                        throw new BadRequest('The "start" and "end" attributes are required when expanding calendar-data');
                    }
                    if ($result['expand']['end'] <= $result['expand']['start']) {
                        throw new BadRequest('The end-date must be larger than the start-date when expanding calendar-data');
                    }
                    break;
            }

        }

        return $result;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Filter;

use Sabre\CalDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\DateTimeParser;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * CompFilter parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:caldav}comp-filter XML
 * element, as defined in:
 *
 * https://tools.ietf.org/html/rfc4791#section-9.6
 *
 * The result will be spit out as an array.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CompFilter implements XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = [
            'name'           => null,
            'is-not-defined' => false,
            'comp-filters'   => [],
            'prop-filters'   => [],
            'time-range'     => false,
        ];

        $att = $reader->parseAttributes();
        $result['name'] = $att['name'];

        $elems = $reader->parseInnerTree();

        if (is_array($elems)) foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{' . Plugin::NS_CALDAV . '}comp-filter' :
                    $result['comp-filters'][] = $elem['value'];
                    break;
                case '{' . Plugin::NS_CALDAV . '}prop-filter' :
                    $result['prop-filters'][] = $elem['value'];
                    break;
                case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
                    $result['is-not-defined'] = true;
                    break;
                case '{' . Plugin::NS_CALDAV . '}time-range' :
                    if ($result['name'] === 'VCALENDAR') {
                        throw new BadRequest('You cannot add time-range filters on the VCALENDAR component');
                    }
                    $result['time-range'] = [
                        'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
                        'end'   => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
                    ];
                    if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
                        throw new BadRequest('The end-date must be larger than the start-date');
                    }
                    break;

            }

        }

        return $result;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Filter;

use Sabre\CalDAV\Plugin;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * PropFilter parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:caldav}param-filter XML
 * element, as defined in:
 *
 * https://tools.ietf.org/html/rfc4791#section-9.7.3
 *
 * The result will be spit out as an array.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ParamFilter implements XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = [
            'name'           => null,
            'is-not-defined' => false,
            'text-match'     => null,
        ];

        $att = $reader->parseAttributes();
        $result['name'] = $att['name'];

        $elems = $reader->parseInnerTree();

        if (is_array($elems)) foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
                    $result['is-not-defined'] = true;
                    break;
                case '{' . Plugin::NS_CALDAV . '}text-match' :
                    $result['text-match'] = [
                        'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
                        'collation'        => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
                        'value'            => $elem['value'],
                    ];
                    break;

            }

        }

        return $result;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Filter;

use Sabre\CalDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\DateTimeParser;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * PropFilter parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:caldav}prop-filter XML
 * element, as defined in:
 *
 * https://tools.ietf.org/html/rfc4791#section-9.7.2
 *
 * The result will be spit out as an array.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PropFilter implements XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = [
            'name'           => null,
            'is-not-defined' => false,
            'param-filters'  => [],
            'text-match'     => null,
            'time-range'     => false,
        ];

        $att = $reader->parseAttributes();
        $result['name'] = $att['name'];

        $elems = $reader->parseInnerTree();

        if (is_array($elems)) foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{' . Plugin::NS_CALDAV . '}param-filter' :
                    $result['param-filters'][] = $elem['value'];
                    break;
                case '{' . Plugin::NS_CALDAV . '}is-not-defined' :
                    $result['is-not-defined'] = true;
                    break;
                case '{' . Plugin::NS_CALDAV . '}time-range' :
                    $result['time-range'] = [
                        'start' => isset($elem['attributes']['start']) ? DateTimeParser::parseDateTime($elem['attributes']['start']) : null,
                        'end'   => isset($elem['attributes']['end']) ? DateTimeParser::parseDateTime($elem['attributes']['end']) : null,
                    ];
                    if ($result['time-range']['start'] && $result['time-range']['end'] && $result['time-range']['end'] <= $result['time-range']['start']) {
                        throw new BadRequest('The end-date must be larger than the start-date');
                    }
                    break;
                case '{' . Plugin::NS_CALDAV . '}text-match' :
                    $result['text-match'] = [
                        'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
                        'collation'        => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;ascii-casemap',
                        'value'            => $elem['value'],
                    ];
                    break;

            }

        }

        return $result;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Notification;

use Sabre\CalDAV;
use Sabre\CalDAV\SharingPlugin as SharingPlugin;
use Sabre\DAV;
use Sabre\Xml\Writer;

/**
 * This class represents the cs:invite-notification notification element.
 *
 * This element is defined here:
 * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Invite implements NotificationInterface {

    /**
     * A unique id for the message
     *
     * @var string
     */
    protected $id;

    /**
     * Timestamp of the notification
     *
     * @var DateTime
     */
    protected $dtStamp;

    /**
     * A url to the recipient of the notification. This can be an email
     * address (mailto:), or a principal url.
     *
     * @var string
     */
    protected $href;

    /**
     * The type of message, see the SharingPlugin::STATUS_* constants.
     *
     * @var int
     */
    protected $type;

    /**
     * True if access to a calendar is read-only.
     *
     * @var bool
     */
    protected $readOnly;

    /**
     * A url to the shared calendar.
     *
     * @var string
     */
    protected $hostUrl;

    /**
     * Url to the sharer of the calendar
     *
     * @var string
     */
    protected $organizer;

    /**
     * The name of the sharer.
     *
     * @var string
     */
    protected $commonName;

    /**
     * The name of the sharer.
     *
     * @var string
     */
    protected $firstName;

    /**
     * The name of the sharer.
     *
     * @var string
     */
    protected $lastName;

    /**
     * A description of the share request
     *
     * @var string
     */
    protected $summary;

    /**
     * The Etag for the notification
     *
     * @var string
     */
    protected $etag;

    /**
     * The list of supported components
     *
     * @var CalDAV\Xml\Property\SupportedCalendarComponentSet
     */
    protected $supportedComponents;

    /**
     * Creates the Invite notification.
     *
     * This constructor receives an array with the following elements:
     *
     *   * id           - A unique id
     *   * etag         - The etag
     *   * dtStamp      - A DateTime object with a timestamp for the notification.
     *   * type         - The type of notification, see SharingPlugin::STATUS_*
     *                    constants for details.
     *   * readOnly     - This must be set to true, if this is an invite for
     *                    read-only access to a calendar.
     *   * hostUrl      - A url to the shared calendar.
     *   * organizer    - Url to the sharer principal.
     *   * commonName   - The real name of the sharer (optional).
     *   * firstName    - The first name of the sharer (optional).
     *   * lastName     - The last name of the sharer (optional).
     *   * summary      - Description of the share, can be the same as the
     *                    calendar, but may also be modified (optional).
     *   * supportedComponents - An instance of
     *                    Sabre\CalDAV\Property\SupportedCalendarComponentSet.
     *                    This allows the client to determine which components
     *                    will be supported in the shared calendar. This is
     *                    also optional.
     *
     * @param array $values All the options
     */
    function __construct(array $values) {

        $required = [
            'id',
            'etag',
            'href',
            'dtStamp',
            'type',
            'readOnly',
            'hostUrl',
            'organizer',
        ];
        foreach ($required as $item) {
            if (!isset($values[$item])) {
                throw new \InvalidArgumentException($item . ' is a required constructor option');
            }
        }

        foreach ($values as $key => $value) {
            if (!property_exists($this, $key)) {
                throw new \InvalidArgumentException('Unknown option: ' . $key);
            }
            $this->$key = $value;
        }

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-notification');

    }

    /**
     * This method serializes the entire notification, as it is used in the
     * response body.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerializeFull(Writer $writer) {

        $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}';

        $this->dtStamp->setTimezone(new \DateTimezone('GMT'));
        $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));

        $writer->startElement($cs . 'invite-notification');

        $writer->writeElement($cs . 'uid', $this->id);
        $writer->writeElement('{DAV:}href', $this->href);

        switch ($this->type) {

            case DAV\Sharing\Plugin::INVITE_ACCEPTED :
                $writer->writeElement($cs . 'invite-accepted');
                break;
            case DAV\Sharing\Plugin::INVITE_NORESPONSE :
                $writer->writeElement($cs . 'invite-noresponse');
                break;

        }

        $writer->writeElement($cs . 'hosturl', [
            '{DAV:}href' => $writer->contextUri . $this->hostUrl
            ]);

        if ($this->summary) {
            $writer->writeElement($cs . 'summary', $this->summary);
        }

        $writer->startElement($cs . 'access');
        if ($this->readOnly) {
            $writer->writeElement($cs . 'read');
        } else {
            $writer->writeElement($cs . 'read-write');
        }
        $writer->endElement(); // access

        $writer->startElement($cs . 'organizer');
        // If the organizer contains a 'mailto:' part, it means it should be
        // treated as absolute.
        if (strtolower(substr($this->organizer, 0, 7)) === 'mailto:') {
            $writer->writeElement('{DAV:}href', $this->organizer);
        } else {
            $writer->writeElement('{DAV:}href', $writer->contextUri . $this->organizer);
        }
        if ($this->commonName) {
            $writer->writeElement($cs . 'common-name', $this->commonName);
        }
        if ($this->firstName) {
            $writer->writeElement($cs . 'first-name', $this->firstName);
        }
        if ($this->lastName) {
            $writer->writeElement($cs . 'last-name', $this->lastName);
        }
        $writer->endElement(); // organizer

        if ($this->commonName) {
            $writer->writeElement($cs . 'organizer-cn', $this->commonName);
        }
        if ($this->firstName) {
            $writer->writeElement($cs . 'organizer-first', $this->firstName);
        }
        if ($this->lastName) {
            $writer->writeElement($cs . 'organizer-last', $this->lastName);
        }
        if ($this->supportedComponents) {
            $writer->writeElement('{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set', $this->supportedComponents);
        }

        $writer->endElement(); // invite-notification

    }

    /**
     * Returns a unique id for this notification
     *
     * This is just the base url. This should generally be some kind of unique
     * id.
     *
     * @return string
     */
    function getId() {

        return $this->id;

    }

    /**
     * Returns the ETag for this notification.
     *
     * The ETag must be surrounded by literal double-quotes.
     *
     * @return string
     */
    function getETag() {

        return $this->etag;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Notification;

use Sabre\CalDAV;
use Sabre\CalDAV\SharingPlugin;
use Sabre\DAV;
use Sabre\Xml\Writer;

/**
 * This class represents the cs:invite-reply notification element.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class InviteReply implements NotificationInterface {

    /**
     * A unique id for the message
     *
     * @var string
     */
    protected $id;

    /**
     * Timestamp of the notification
     *
     * @var DateTime
     */
    protected $dtStamp;

    /**
     * The unique id of the notification this was a reply to.
     *
     * @var string
     */
    protected $inReplyTo;

    /**
     * A url to the recipient of the original (!) notification.
     *
     * @var string
     */
    protected $href;

    /**
     * The type of message, see the SharingPlugin::STATUS_ constants.
     *
     * @var int
     */
    protected $type;

    /**
     * A url to the shared calendar.
     *
     * @var string
     */
    protected $hostUrl;

    /**
     * A description of the share request
     *
     * @var string
     */
    protected $summary;

    /**
     * Notification Etag
     *
     * @var string
     */
    protected $etag;

    /**
     * Creates the Invite Reply Notification.
     *
     * This constructor receives an array with the following elements:
     *
     *   * id           - A unique id
     *   * etag         - The etag
     *   * dtStamp      - A DateTime object with a timestamp for the notification.
     *   * inReplyTo    - This should refer to the 'id' of the notification
     *                    this is a reply to.
     *   * type         - The type of notification, see SharingPlugin::STATUS_*
     *                    constants for details.
     *   * hostUrl      - A url to the shared calendar.
     *   * summary      - Description of the share, can be the same as the
     *                    calendar, but may also be modified (optional).
     *
     * @param array $values
     */
    function __construct(array $values) {

        $required = [
            'id',
            'etag',
            'href',
            'dtStamp',
            'inReplyTo',
            'type',
            'hostUrl',
        ];
        foreach ($required as $item) {
            if (!isset($values[$item])) {
                throw new \InvalidArgumentException($item . ' is a required constructor option');
            }
        }

        foreach ($values as $key => $value) {
            if (!property_exists($this, $key)) {
                throw new \InvalidArgumentException('Unknown option: ' . $key);
            }
            $this->$key = $value;
        }

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $writer->writeElement('{' . CalDAV\Plugin::NS_CALENDARSERVER . '}invite-reply');

    }

    /**
     * This method serializes the entire notification, as it is used in the
     * response body.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerializeFull(Writer $writer) {

        $cs = '{' . CalDAV\Plugin::NS_CALENDARSERVER . '}';

        $this->dtStamp->setTimezone(new \DateTimezone('GMT'));
        $writer->writeElement($cs . 'dtstamp', $this->dtStamp->format('Ymd\\THis\\Z'));

        $writer->startElement($cs . 'invite-reply');

        $writer->writeElement($cs . 'uid', $this->id);
        $writer->writeElement($cs . 'in-reply-to', $this->inReplyTo);
        $writer->writeElement('{DAV:}href', $this->href);

        switch ($this->type) {

            case DAV\Sharing\Plugin::INVITE_ACCEPTED :
                $writer->writeElement($cs . 'invite-accepted');
                break;
            case DAV\Sharing\Plugin::INVITE_DECLINED :
                $writer->writeElement($cs . 'invite-declined');
                break;

        }

        $writer->writeElement($cs . 'hosturl', [
            '{DAV:}href' => $writer->contextUri . $this->hostUrl
            ]);

        if ($this->summary) {
            $writer->writeElement($cs . 'summary', $this->summary);
        }
        $writer->endElement(); // invite-reply

    }

    /**
     * Returns a unique id for this notification
     *
     * This is just the base url. This should generally be some kind of unique
     * id.
     *
     * @return string
     */
    function getId() {

        return $this->id;

    }

    /**
     * Returns the ETag for this notification.
     *
     * The ETag must be surrounded by literal double-quotes.
     *
     * @return string
     */
    function getETag() {

        return $this->etag;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Notification;

use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * This interface reflects a single notification type.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface NotificationInterface extends XmlSerializable {

    /**
     * This method serializes the entire notification, as it is used in the
     * response body.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerializeFull(Writer $writer);

    /**
     * Returns a unique id for this notification
     *
     * This is just the base url. This should generally be some kind of unique
     * id.
     *
     * @return string
     */
    function getId();

    /**
     * Returns the ETag for this notification.
     *
     * The ETag must be surrounded by literal double-quotes.
     *
     * @return string
     */
    function getETag();

}
<?php

namespace Sabre\CalDAV\Xml\Notification;

use Sabre\CalDAV\Plugin;
use Sabre\Xml\Writer;

/**
 * SystemStatus notification
 *
 * This notification can be used to indicate to the user that the system is
 * down.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SystemStatus implements NotificationInterface {

    const TYPE_LOW = 1;
    const TYPE_MEDIUM = 2;
    const TYPE_HIGH = 3;

    /**
     * A unique id
     *
     * @var string
     */
    protected $id;

    /**
     * The type of alert. This should be one of the TYPE_ constants.
     *
     * @var int
     */
    protected $type;

    /**
     * A human-readable description of the problem.
     *
     * @var string
     */
    protected $description;

    /**
     * A url to a website with more information for the user.
     *
     * @var string
     */
    protected $href;

    /**
     * Notification Etag
     *
     * @var string
     */
    protected $etag;

    /**
     * Creates the notification.
     *
     * Some kind of unique id should be provided. This is used to generate a
     * url.
     *
     * @param string $id
     * @param string $etag
     * @param int $type
     * @param string $description
     * @param string $href
     */
    function __construct($id, $etag, $type = self::TYPE_HIGH, $description = null, $href = null) {

        $this->id = $id;
        $this->type = $type;
        $this->description = $description;
        $this->href = $href;
        $this->etag = $etag;

    }

    /**
     * The serialize method is called during xml writing.
     *
     * It should use the $writer argument to encode this object into XML.
     *
     * Important note: it is not needed to create the parent element. The
     * parent element is already created, and we only have to worry about
     * attributes, child elements and text (if any).
     *
     * Important note 2: If you are writing any new elements, you are also
     * responsible for closing them.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        switch ($this->type) {
            case self::TYPE_LOW :
                $type = 'low';
                break;
            case self::TYPE_MEDIUM :
                $type = 'medium';
                break;
            default :
            case self::TYPE_HIGH :
                $type = 'high';
                break;
        }

        $writer->startElement('{' . Plugin::NS_CALENDARSERVER . '}systemstatus');
        $writer->writeAttribute('type', $type);
        $writer->endElement();

    }

    /**
     * This method serializes the entire notification, as it is used in the
     * response body.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerializeFull(Writer $writer) {

        $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
        switch ($this->type) {
            case self::TYPE_LOW :
                $type = 'low';
                break;
            case self::TYPE_MEDIUM :
                $type = 'medium';
                break;
            default :
            case self::TYPE_HIGH :
                $type = 'high';
                break;
        }

        $writer->startElement($cs . 'systemstatus');
        $writer->writeAttribute('type', $type);


        if ($this->description) {
            $writer->writeElement($cs . 'description', $this->description);
        }
        if ($this->href) {
            $writer->writeElement('{DAV:}href', $this->href);
        }

        $writer->endElement(); // systemstatus

    }

    /**
     * Returns a unique id for this notification
     *
     * This is just the base url. This should generally be some kind of unique
     * id.
     *
     * @return string
     */
    function getId() {

        return $this->id;

    }

    /*
     * Returns the ETag for this notification.
     *
     * The ETag must be surrounded by literal double-quotes.
     *
     * @return string
     */
    function getETag() {

        return $this->etag;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Property;

use Sabre\CalDAV\Plugin;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * AllowedSharingModes
 *
 * This property encodes the 'allowed-sharing-modes' property, as defined by
 * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
 * namespace.
 *
 * This property is a representation of the supported-calendar_component-set
 * property in the CalDAV namespace. It simply requires an array of components,
 * such as VEVENT, VTODO
 *
 * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AllowedSharingModes implements XmlSerializable {

    /**
     * Whether or not a calendar can be shared with another user
     *
     * @var bool
     */
    protected $canBeShared;

    /**
     * Whether or not the calendar can be placed on a public url.
     *
     * @var bool
     */
    protected $canBePublished;

    /**
     * Constructor
     *
     * @param bool $canBeShared
     * @param bool $canBePublished
     * @return void
     */
    function __construct($canBeShared, $canBePublished) {

        $this->canBeShared = $canBeShared;
        $this->canBePublished = $canBePublished;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        if ($this->canBeShared) {
            $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-shared');
        }
        if ($this->canBePublished) {
            $writer->writeElement('{' . Plugin::NS_CALENDARSERVER . '}can-be-published');
        }

    }



}
<?php

namespace Sabre\CalDAV\Xml\Property;

use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * email-address-set property
 *
 * This property represents the email-address-set property in the
 * http://calendarserver.org/ns/ namespace.
 *
 * It's a list of email addresses associated with a user.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class EmailAddressSet implements XmlSerializable {

    /**
     * emails
     *
     * @var array
     */
    private $emails;

    /**
     * __construct
     *
     * @param array $emails
     */
    function __construct(array $emails) {

        $this->emails = $emails;

    }

    /**
     * Returns the email addresses
     *
     * @return array
     */
    function getValue() {

        return $this->emails;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->emails as $email) {

            $writer->writeElement('{http://calendarserver.org/ns/}email-address', $email);

        }

    }

}
<?php

namespace Sabre\CalDAV\Xml\Property;

use Sabre\CalDAV\Plugin;
use Sabre\DAV;
use Sabre\DAV\Xml\Element\Sharee;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * Invite property
 *
 * This property encodes the 'invite' property, as defined by
 * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
 * namespace.
 *
 * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Invite implements XmlSerializable {

    /**
     * The list of users a calendar has been shared to.
     *
     * @var Sharee[]
     */
    protected $sharees;

    /**
     * Creates the property.
     *
     * @param Sharee[] $sharees
     */
    function __construct(array $sharees) {

        $this->sharees = $sharees;

    }

    /**
     * Returns the list of users, as it was passed to the constructor.
     *
     * @return array
     */
    function getValue() {

        return $this->sharees;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $cs = '{' . Plugin::NS_CALENDARSERVER . '}';

        foreach ($this->sharees as $sharee) {

            if ($sharee->access === DAV\Sharing\Plugin::ACCESS_SHAREDOWNER) {
                $writer->startElement($cs . 'organizer');
            } else {
                $writer->startElement($cs . 'user');

                switch ($sharee->inviteStatus) {
                    case DAV\Sharing\Plugin::INVITE_ACCEPTED :
                        $writer->writeElement($cs . 'invite-accepted');
                        break;
                    case DAV\Sharing\Plugin::INVITE_DECLINED :
                        $writer->writeElement($cs . 'invite-declined');
                        break;
                    case DAV\Sharing\Plugin::INVITE_NORESPONSE :
                        $writer->writeElement($cs . 'invite-noresponse');
                        break;
                    case DAV\Sharing\Plugin::INVITE_INVALID :
                        $writer->writeElement($cs . 'invite-invalid');
                        break;
                }

                $writer->startElement($cs . 'access');
                switch ($sharee->access) {
                    case DAV\Sharing\Plugin::ACCESS_READWRITE :
                        $writer->writeElement($cs . 'read-write');
                        break;
                    case DAV\Sharing\Plugin::ACCESS_READ :
                        $writer->writeElement($cs . 'read');
                        break;

                }
                $writer->endElement(); // access

            }

            $href = new DAV\Xml\Property\Href($sharee->href);
            $href->xmlSerialize($writer);

            if (isset($sharee->properties['{DAV:}displayname'])) {
                $writer->writeElement($cs . 'common-name', $sharee->properties['{DAV:}displayname']);
            }
            if ($sharee->comment) {
                $writer->writeElement($cs . 'summary', $sharee->comment);
            }
            $writer->endElement(); // organizer or user

        }

    }

}
<?php

namespace Sabre\CalDAV\Xml\Property;

use Sabre\CalDAV\Plugin;
use Sabre\Xml\Deserializer;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * schedule-calendar-transp property.
 *
 * This property is a representation of the schedule-calendar-transp property.
 * This property is defined in:
 *
 * http://tools.ietf.org/html/rfc6638#section-9.1
 *
 * Its values are either 'transparent' or 'opaque'. If it's transparent, it
 * means that this calendar will not be taken into consideration when a
 * different user queries for free-busy information. If it's 'opaque', it will.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ScheduleCalendarTransp implements Element {

    const TRANSPARENT = 'transparent';
    const OPAQUE = 'opaque';

    /**
     * value
     *
     * @var string
     */
    protected $value;

    /**
     * Creates the property
     *
     * @param string $value
     */
    function __construct($value) {

        if ($value !== self::TRANSPARENT && $value !== self::OPAQUE) {
            throw new \InvalidArgumentException('The value must either be specified as "transparent" or "opaque"');
        }
        $this->value = $value;

    }

    /**
     * Returns the current value
     *
     * @return string
     */
    function getValue() {

        return $this->value;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        switch ($this->value) {
            case self::TRANSPARENT :
                $writer->writeElement('{' . Plugin::NS_CALDAV . '}transparent');
                break;
            case self::OPAQUE :
                $writer->writeElement('{' . Plugin::NS_CALDAV . '}opaque');
                break;
        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = Deserializer\enum($reader, Plugin::NS_CALDAV);

        if (in_array('transparent', $elems)) {
            $value = self::TRANSPARENT;
        } else {
            $value = self::OPAQUE;
        }
        return new self($value);

    }

}
<?php

namespace Sabre\CalDAV\Xml\Property;

use Sabre\CalDAV\Plugin;
use Sabre\Xml\Element;
use Sabre\Xml\ParseException;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * SupportedCalendarComponentSet property.
 *
 * This class represents the
 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set property, as
 * defined in:
 *
 * https://tools.ietf.org/html/rfc4791#section-5.2.3
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedCalendarComponentSet implements Element {

    /**
     * List of supported components.
     *
     * This array will contain values such as VEVENT, VTODO and VJOURNAL.
     *
     * @var array
     */
    protected $components = [];

    /**
     * Creates the property.
     *
     * @param array $components
     */
    function __construct(array $components) {

        $this->components = $components;

    }

    /**
     * Returns the list of supported components
     *
     * @return array
     */
    function getValue() {

        return $this->components;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->components as $component) {

            $writer->startElement('{' . Plugin::NS_CALDAV . '}comp');
            $writer->writeAttributes(['name' => $component]);
            $writer->endElement();

        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = $reader->parseInnerTree();

        $components = [];

        foreach ((array)$elems as $elem) {
            if ($elem['name'] === '{' . Plugin::NS_CALDAV . '}comp') {
                $components[] = $elem['attributes']['name'];
            }
        }

        if (!$components) {
            throw new ParseException('supported-calendar-component-set must have at least one CALDAV:comp element');
        }

        return new self($components);

    }

}
<?php

namespace Sabre\CalDAV\Xml\Property;

use Sabre\CalDAV\Plugin;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * Supported-calendar-data property
 *
 * This property is a representation of the supported-calendar-data property
 * in the CalDAV namespace. SabreDAV only has support for text/calendar;2.0
 * so the value is currently hardcoded.
 *
 * This property is defined in:
 * http://tools.ietf.org/html/rfc4791#section-5.2.4
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedCalendarData implements XmlSerializable {

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $writer->startElement('{' . Plugin::NS_CALDAV . '}calendar-data');
        $writer->writeAttributes([
            'content-type' => 'text/calendar',
            'version'      => '2.0',
        ]);
        $writer->endElement(); // calendar-data
        $writer->startElement('{' . Plugin::NS_CALDAV . '}calendar-data');
        $writer->writeAttributes([
            'content-type' => 'application/calendar+json',
        ]);
        $writer->endElement(); // calendar-data

    }

}
<?php

namespace Sabre\CalDAV\Xml\Property;

use Sabre\CalDAV\Plugin;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * supported-collation-set property
 *
 * This property is a representation of the supported-collation-set property
 * in the CalDAV namespace.
 *
 * This property is defined in:
 * http://tools.ietf.org/html/rfc4791#section-7.5.1
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedCollationSet implements XmlSerializable {

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $collations = [
            'i;ascii-casemap',
            'i;octet',
            'i;unicode-casemap'
        ];

        foreach ($collations as $collation) {
            $writer->writeElement('{' . Plugin::NS_CALDAV . '}supported-collation', $collation);
        }

    }

}
<?php

namespace Sabre\CalDAV\Xml\Request;

use Sabre\CalDAV\Plugin;
use Sabre\Uri;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * CalendarMultiGetReport request parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-multiget
 * REPORT, as defined in:
 *
 * https://tools.ietf.org/html/rfc4791#section-7.9
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CalendarMultiGetReport implements XmlDeserializable {

    /**
     * An array with requested properties.
     *
     * @var array
     */
    public $properties;

    /**
     * This is an array with the urls that are being requested.
     *
     * @var array
     */
    public $hrefs;

    /**
     * If the calendar data must be expanded, this will contain an array with 2
     * elements: start and end.
     *
     * Each may be a DateTime or null.
     *
     * @var array|null
     */
    public $expand = null;

    /**
     * The mimetype of the content that should be returend. Usually
     * text/calendar.
     *
     * @var string
     */
    public $contentType = null;

    /**
     * The version of calendar-data that should be returned. Usually '2.0',
     * referring to iCalendar 2.0.
     *
     * @var string
     */
    public $version = null;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = $reader->parseInnerTree([
            '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
            '{DAV:}prop'                                   => 'Sabre\\Xml\\Element\\KeyValue',
        ]);

        $newProps = [
            'hrefs'      => [],
            'properties' => [],
        ];

        foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{DAV:}prop' :
                    $newProps['properties'] = array_keys($elem['value']);
                    if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) {
                        $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'];
                    }
                    break;
                case '{DAV:}href' :
                    $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
                    break;

            }

        }

        $obj = new self();
        foreach ($newProps as $key => $value) {
            $obj->$key = $value;
        }

        return $obj;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Request;

use Sabre\CalDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * CalendarQueryReport request parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-query
 * REPORT, as defined in:
 *
 * https://tools.ietf.org/html/rfc4791#section-7.9
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CalendarQueryReport implements XmlDeserializable {

    /**
     * An array with requested properties.
     *
     * @var array
     */
    public $properties;

    /**
     * List of property/component filters.
     *
     * @var array
     */
    public $filters;

    /**
     * If the calendar data must be expanded, this will contain an array with 2
     * elements: start and end.
     *
     * Each may be a DateTime or null.
     *
     * @var array|null
     */
    public $expand = null;

    /**
     * The mimetype of the content that should be returend. Usually
     * text/calendar.
     *
     * @var string
     */
    public $contentType = null;

    /**
     * The version of calendar-data that should be returned. Usually '2.0',
     * referring to iCalendar 2.0.
     *
     * @var string
     */
    public $version = null;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = $reader->parseInnerTree([
            '{urn:ietf:params:xml:ns:caldav}comp-filter'   => 'Sabre\\CalDAV\\Xml\\Filter\\CompFilter',
            '{urn:ietf:params:xml:ns:caldav}prop-filter'   => 'Sabre\\CalDAV\\Xml\\Filter\\PropFilter',
            '{urn:ietf:params:xml:ns:caldav}param-filter'  => 'Sabre\\CalDAV\\Xml\\Filter\\ParamFilter',
            '{urn:ietf:params:xml:ns:caldav}calendar-data' => 'Sabre\\CalDAV\\Xml\\Filter\\CalendarData',
            '{DAV:}prop'                                   => 'Sabre\\Xml\\Element\\KeyValue',
        ]);

        $newProps = [
            'filters'    => null,
            'properties' => [],
        ];

        if (!is_array($elems)) $elems = [];

        foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{DAV:}prop' :
                    $newProps['properties'] = array_keys($elem['value']);
                    if (isset($elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'])) {
                        $newProps += $elem['value']['{' . Plugin::NS_CALDAV . '}calendar-data'];
                    }
                    break;
                case '{' . Plugin::NS_CALDAV . '}filter' :
                    foreach ($elem['value'] as $subElem) {
                        if ($subElem['name'] === '{' . Plugin::NS_CALDAV . '}comp-filter') {
                            if (!is_null($newProps['filters'])) {
                                throw new BadRequest('Only one top-level comp-filter may be defined');
                            }
                            $newProps['filters'] = $subElem['value'];
                        }
                    }
                    break;

            }

        }

        if (is_null($newProps['filters'])) {
            throw new BadRequest('The {' . Plugin::NS_CALDAV . '}filter element is required for this request');
        }

        $obj = new self();
        foreach ($newProps as $key => $value) {
            $obj->$key = $value;
        }
        return $obj;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Request;

use Sabre\CalDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\VObject\DateTimeParser;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * FreeBusyQueryReport
 *
 * This class parses the {DAV:}free-busy-query REPORT, as defined in:
 *
 * http://tools.ietf.org/html/rfc3253#section-3.8
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class FreeBusyQueryReport implements XmlDeserializable {

    /**
     * Starttime of report
     *
     * @var DateTime|null
     */
    public $start;

    /**
     * End time of report
     *
     * @var DateTime|null
     */
    public $end;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $timeRange = '{' . Plugin::NS_CALDAV . '}time-range';

        $start = null;
        $end = null;

        foreach ((array)$reader->parseInnerTree([]) as $elem) {

            if ($elem['name'] !== $timeRange) continue;

            $start = empty($elem['attributes']['start']) ?: $elem['attributes']['start'];
            $end = empty($elem['attributes']['end']) ?: $elem['attributes']['end'];

        }
        if (!$start && !$end) {
            throw new BadRequest('The freebusy report must have a time-range element');
        }
        if ($start) {
            $start = DateTimeParser::parseDateTime($start);
        }
        if ($end) {
            $end = DateTimeParser::parseDateTime($end);
        }
        $result = new self();
        $result->start = $start;
        $result->end = $end;

        return $result;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Request;

use Sabre\CalDAV\Plugin;
use Sabre\CalDAV\SharingPlugin;
use Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Element\KeyValue;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * Invite-reply POST request parser
 *
 * This class parses the invite-reply POST request, as defined in:
 *
 * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class InviteReply implements XmlDeserializable {

    /**
     * The sharee calendar user address.
     *
     * This is the address that the original invite was set to
     *
     * @var string
     */
    public $href;

    /**
     * The uri to the calendar that was being shared.
     *
     * @var string
     */
    public $calendarUri;

    /**
     * The id of the invite message that's being responded to
     *
     * @var string
     */
    public $inReplyTo;

    /**
     * An optional message
     *
     * @var string
     */
    public $summary;

    /**
     * Either SharingPlugin::STATUS_ACCEPTED or SharingPlugin::STATUS_DECLINED.
     *
     * @var int
     */
    public $status;

    /**
     * Constructor
     *
     * @param string $href
     * @param string $calendarUri
     * @param string $inReplyTo
     * @param string $summary
     * @param int $status
     */
    function __construct($href, $calendarUri, $inReplyTo, $summary, $status) {

        $this->href = $href;
        $this->calendarUri = $calendarUri;
        $this->inReplyTo = $inReplyTo;
        $this->summary = $summary;
        $this->status = $status;

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = KeyValue::xmlDeserialize($reader);

        $href = null;
        $calendarUri = null;
        $inReplyTo = null;
        $summary = null;
        $status = null;

        foreach ($elems as $name => $value) {

            switch ($name) {

                case '{' . Plugin::NS_CALENDARSERVER . '}hosturl' :
                    foreach ($value as $bla) {
                        if ($bla['name'] === '{DAV:}href') {
                            $calendarUri = $bla['value'];
                        }
                    }
                    break;
                case '{' . Plugin::NS_CALENDARSERVER . '}invite-accepted' :
                    $status = DAV\Sharing\Plugin::INVITE_ACCEPTED;
                    break;
                case '{' . Plugin::NS_CALENDARSERVER . '}invite-declined' :
                    $status = DAV\Sharing\Plugin::INVITE_DECLINED;
                    break;
                case '{' . Plugin::NS_CALENDARSERVER . '}in-reply-to' :
                    $inReplyTo = $value;
                    break;
                case '{' . Plugin::NS_CALENDARSERVER . '}summary' :
                    $summary = $value;
                    break;
                case '{DAV:}href' :
                    $href = $value;
                    break;
            }

        }
        if (is_null($calendarUri)) {
            throw new BadRequest('The {http://calendarserver.org/ns/}hosturl/{DAV:}href element must exist');
        }

        return new self($href, $calendarUri, $inReplyTo, $summary, $status);

    }

}
<?php

namespace Sabre\CalDAV\Xml\Request;

use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * MKCALENDAR parser.
 *
 * This class parses the MKCALENDAR request, as defined in:
 *
 * https://tools.ietf.org/html/rfc4791#section-5.3.1
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class MkCalendar implements XmlDeserializable {

    /**
     * The list of properties that will be set.
     *
     * @var array
     */
    public $properties = [];

    /**
     * Returns the list of properties the calendar will be initialized with.
     *
     * @return array
     */
    function getProperties() {

        return $this->properties;

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $self = new self();

        $elementMap = $reader->elementMap;
        $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
        $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
        $elems = $reader->parseInnerTree($elementMap);

        foreach ($elems as $elem) {
            if ($elem['name'] === '{DAV:}set') {
                $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
            }
        }

        return $self;

    }

}
<?php

namespace Sabre\CalDAV\Xml\Request;

use Sabre\CalDAV\Plugin;
use Sabre\DAV\Xml\Element\Sharee;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * Share POST request parser
 *
 * This class parses the share POST request, as defined in:
 *
 * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Share implements XmlDeserializable {

    /**
     * The list of new people added or updated or removed from the share.
     *
     * @var Sharee[]
     */
    public $sharees = [];

    /**
     * Constructor
     *
     * @param Sharee[] $sharees
     */
    function __construct(array $sharees) {

        $this->sharees = $sharees;

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = $reader->parseGetElements([
            '{' . Plugin::NS_CALENDARSERVER . '}set'    => 'Sabre\\Xml\\Element\\KeyValue',
            '{' . Plugin::NS_CALENDARSERVER . '}remove' => 'Sabre\\Xml\\Element\\KeyValue',
        ]);

        $sharees = [];

        foreach ($elems as $elem) {
            switch ($elem['name']) {

                case '{' . Plugin::NS_CALENDARSERVER . '}set' :
                    $sharee = $elem['value'];

                    $sumElem = '{' . Plugin::NS_CALENDARSERVER . '}summary';
                    $commonName = '{' . Plugin::NS_CALENDARSERVER . '}common-name';

                    $properties = [];
                    if (isset($sharee[$commonName])) {
                        $properties['{DAV:}displayname'] = $sharee[$commonName];
                    }

                    $access = array_key_exists('{' . Plugin::NS_CALENDARSERVER . '}read-write', $sharee)
                        ? \Sabre\DAV\Sharing\Plugin::ACCESS_READWRITE
                        : \Sabre\DAV\Sharing\Plugin::ACCESS_READ;

                    $sharees[] = new Sharee([
                        'href'       => $sharee['{DAV:}href'],
                        'properties' => $properties,
                        'access'     => $access,
                        'comment'    => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null
                    ]);
                    break;

                case '{' . Plugin::NS_CALENDARSERVER . '}remove' :
                    $sharees[] = new Sharee([
                        'href'   => $elem['value']['{DAV:}href'],
                        'access' => \Sabre\DAV\Sharing\Plugin::ACCESS_NOACCESS
                    ]);
                    break;

            }
        }

        return new self($sharees);

    }

}
<?php

namespace Sabre\CardDAV;

use Sabre\DAV;
use Sabre\DAVACL;

/**
 * The AddressBook class represents a CardDAV addressbook, owned by a specific user
 *
 * The AddressBook can contain multiple vcards
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AddressBook extends DAV\Collection implements IAddressBook, DAV\IProperties, DAVACL\IACL, DAV\Sync\ISyncCollection, DAV\IMultiGet {

    use DAVACL\ACLTrait;

    /**
     * This is an array with addressbook information
     *
     * @var array
     */
    protected $addressBookInfo;

    /**
     * CardDAV backend
     *
     * @var Backend\BackendInterface
     */
    protected $carddavBackend;

    /**
     * Constructor
     *
     * @param Backend\BackendInterface $carddavBackend
     * @param array $addressBookInfo
     */
    function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo) {

        $this->carddavBackend = $carddavBackend;
        $this->addressBookInfo = $addressBookInfo;

    }

    /**
     * Returns the name of the addressbook
     *
     * @return string
     */
    function getName() {

        return $this->addressBookInfo['uri'];

    }

    /**
     * Returns a card
     *
     * @param string $name
     * @return Card
     */
    function getChild($name) {

        $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);
        if (!$obj) throw new DAV\Exception\NotFound('Card not found');
        return new Card($this->carddavBackend, $this->addressBookInfo, $obj);

    }

    /**
     * Returns the full list of cards
     *
     * @return array
     */
    function getChildren() {

        $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
        $children = [];
        foreach ($objs as $obj) {
            $obj['acl'] = $this->getChildACL();
            $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
        }
        return $children;

    }

    /**
     * This method receives a list of paths in it's first argument.
     * It must return an array with Node objects.
     *
     * If any children are not found, you do not have to return them.
     *
     * @param string[] $paths
     * @return array
     */
    function getMultipleChildren(array $paths) {

        $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
        $children = [];
        foreach ($objs as $obj) {
            $obj['acl'] = $this->getChildACL();
            $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
        }
        return $children;

    }

    /**
     * Creates a new directory
     *
     * We actually block this, as subdirectories are not allowed in addressbooks.
     *
     * @param string $name
     * @return void
     */
    function createDirectory($name) {

        throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed');

    }

    /**
     * Creates a new file
     *
     * The contents of the new file must be a valid VCARD.
     *
     * This method may return an ETag.
     *
     * @param string $name
     * @param resource $vcardData
     * @return string|null
     */
    function createFile($name, $vcardData = null) {

        if (is_resource($vcardData)) {
            $vcardData = stream_get_contents($vcardData);
        }
        // Converting to UTF-8, if needed
        $vcardData = DAV\StringUtil::ensureUTF8($vcardData);

        return $this->carddavBackend->createCard($this->addressBookInfo['id'], $name, $vcardData);

    }

    /**
     * Deletes the entire addressbook.
     *
     * @return void
     */
    function delete() {

        $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);

    }

    /**
     * Renames the addressbook
     *
     * @param string $newName
     * @return void
     */
    function setName($newName) {

        throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');

    }

    /**
     * Returns the last modification date as a unix timestamp.
     *
     * @return void
     */
    function getLastModified() {

        return null;

    }

    /**
     * Updates properties on this node.
     *
     * This method received a PropPatch object, which contains all the
     * information about the update.
     *
     * To update specific properties, call the 'handle' method on this object.
     * Read the PropPatch documentation for more information.
     *
     * @param DAV\PropPatch $propPatch
     * @return void
     */
    function propPatch(DAV\PropPatch $propPatch) {

        return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $propPatch);

    }

    /**
     * Returns a list of properties for this nodes.
     *
     * The properties list is a list of propertynames the client requested,
     * encoded in clark-notation {xmlnamespace}tagname
     *
     * If the array is empty, it means 'all properties' were requested.
     *
     * @param array $properties
     * @return array
     */
    function getProperties($properties) {

        $response = [];
        foreach ($properties as $propertyName) {

            if (isset($this->addressBookInfo[$propertyName])) {

                $response[$propertyName] = $this->addressBookInfo[$propertyName];

            }

        }

        return $response;

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->addressBookInfo['principaluri'];

    }


    /**
     * This method returns the ACL's for card nodes in this address book.
     * The result of this method automatically gets passed to the
     * card nodes in this address book.
     *
     * @return array
     */
    function getChildACL() {

        return [
            [
                'privilege' => '{DAV:}all',
                'principal' => $this->getOwner(),
                'protected' => true,
            ],
        ];

    }


    /**
     * This method returns the current sync-token for this collection.
     * This can be any string.
     *
     * If null is returned from this function, the plugin assumes there's no
     * sync information available.
     *
     * @return string|null
     */
    function getSyncToken() {

        if (
            $this->carddavBackend instanceof Backend\SyncSupport &&
            isset($this->addressBookInfo['{DAV:}sync-token'])
        ) {
            return $this->addressBookInfo['{DAV:}sync-token'];
        }
        if (
            $this->carddavBackend instanceof Backend\SyncSupport &&
            isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token'])
        ) {
            return $this->addressBookInfo['{http://sabredav.org/ns}sync-token'];
        }

    }

    /**
     * The getChanges method returns all the changes that have happened, since
     * the specified syncToken and the current collection.
     *
     * This function should return an array, such as the following:
     *
     * [
     *   'syncToken' => 'The current synctoken',
     *   'added'   => [
     *      'new.txt',
     *   ],
     *   'modified'   => [
     *      'modified.txt',
     *   ],
     *   'deleted' => [
     *      'foo.php.bak',
     *      'old.txt'
     *   ]
     * ];
     *
     * The syncToken property should reflect the *current* syncToken of the
     * collection, as reported getSyncToken(). This is needed here too, to
     * ensure the operation is atomic.
     *
     * If the syncToken is specified as null, this is an initial sync, and all
     * members should be reported.
     *
     * The modified property is an array of nodenames that have changed since
     * the last token.
     *
     * The deleted property is an array with nodenames, that have been deleted
     * from collection.
     *
     * The second argument is basically the 'depth' of the report. If it's 1,
     * you only have to report changes that happened only directly in immediate
     * descendants. If it's 2, it should also include changes from the nodes
     * below the child collections. (grandchildren)
     *
     * The third (optional) argument allows a client to specify how many
     * results should be returned at most. If the limit is not specified, it
     * should be treated as infinite.
     *
     * If the limit (infinite or not) is higher than you're willing to return,
     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
     *
     * If the syncToken is expired (due to data cleanup) or unknown, you must
     * return null.
     *
     * The limit is 'suggestive'. You are free to ignore it.
     *
     * @param string $syncToken
     * @param int $syncLevel
     * @param int $limit
     * @return array
     */
    function getChanges($syncToken, $syncLevel, $limit = null) {

        if (!$this->carddavBackend instanceof Backend\SyncSupport) {
            return null;
        }

        return $this->carddavBackend->getChangesForAddressBook(
            $this->addressBookInfo['id'],
            $syncToken,
            $syncLevel,
            $limit
        );

    }
}
<?php

namespace Sabre\CardDAV;

use Sabre\DAV;
use Sabre\DAV\MkCol;
use Sabre\DAVACL;
use Sabre\Uri;

/**
 * AddressBook Home class
 *
 * This collection contains a list of addressbooks associated with one user.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AddressBookHome extends DAV\Collection implements DAV\IExtendedCollection, DAVACL\IACL {

    use DAVACL\ACLTrait;

    /**
     * Principal uri
     *
     * @var array
     */
    protected $principalUri;

    /**
     * carddavBackend
     *
     * @var Backend\BackendInterface
     */
    protected $carddavBackend;

    /**
     * Constructor
     *
     * @param Backend\BackendInterface $carddavBackend
     * @param string $principalUri
     */
    function __construct(Backend\BackendInterface $carddavBackend, $principalUri) {

        $this->carddavBackend = $carddavBackend;
        $this->principalUri = $principalUri;

    }

    /**
     * Returns the name of this object
     *
     * @return string
     */
    function getName() {

        list(, $name) = Uri\split($this->principalUri);
        return $name;

    }

    /**
     * Updates the name of this object
     *
     * @param string $name
     * @return void
     */
    function setName($name) {

        throw new DAV\Exception\MethodNotAllowed();

    }

    /**
     * Deletes this object
     *
     * @return void
     */
    function delete() {

        throw new DAV\Exception\MethodNotAllowed();

    }

    /**
     * Returns the last modification date
     *
     * @return int
     */
    function getLastModified() {

        return null;

    }

    /**
     * Creates a new file under this object.
     *
     * This is currently not allowed
     *
     * @param string $filename
     * @param resource $data
     * @return void
     */
    function createFile($filename, $data = null) {

        throw new DAV\Exception\MethodNotAllowed('Creating new files in this collection is not supported');

    }

    /**
     * Creates a new directory under this object.
     *
     * This is currently not allowed.
     *
     * @param string $filename
     * @return void
     */
    function createDirectory($filename) {

        throw new DAV\Exception\MethodNotAllowed('Creating new collections in this collection is not supported');

    }

    /**
     * Returns a single addressbook, by name
     *
     * @param string $name
     * @todo needs optimizing
     * @return AddressBook
     */
    function getChild($name) {

        foreach ($this->getChildren() as $child) {
            if ($name == $child->getName())
                return $child;

        }
        throw new DAV\Exception\NotFound('Addressbook with name \'' . $name . '\' could not be found');

    }

    /**
     * Returns a list of addressbooks
     *
     * @return array
     */
    function getChildren() {

        $addressbooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
        $objs = [];
        foreach ($addressbooks as $addressbook) {
            $objs[] = new AddressBook($this->carddavBackend, $addressbook);
        }
        return $objs;

    }

    /**
     * Creates a new address book.
     *
     * @param string $name
     * @param MkCol $mkCol
     * @throws DAV\Exception\InvalidResourceType
     * @return void
     */
    function createExtendedCollection($name, MkCol $mkCol) {

        if (!$mkCol->hasResourceType('{' . Plugin::NS_CARDDAV . '}addressbook')) {
            throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection');
        }
        $properties = $mkCol->getRemainingValues();
        $mkCol->setRemainingResultCode(201);
        $this->carddavBackend->createAddressBook($this->principalUri, $name, $properties);

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->principalUri;

    }

}
<?php

namespace Sabre\CardDAV;

use Sabre\DAVACL;

/**
 * AddressBook rootnode
 *
 * This object lists a collection of users, which can contain addressbooks.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AddressBookRoot extends DAVACL\AbstractPrincipalCollection {

    /**
     * Principal Backend
     *
     * @var DAVACL\PrincipalBackend\BackendInterface
     */
    protected $principalBackend;

    /**
     * CardDAV backend
     *
     * @var Backend\BackendInterface
     */
    protected $carddavBackend;

    /**
     * Constructor
     *
     * This constructor needs both a principal and a carddav backend.
     *
     * By default this class will show a list of addressbook collections for
     * principals in the 'principals' collection. If your main principals are
     * actually located in a different path, use the $principalPrefix argument
     * to override this.
     *
     * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend
     * @param Backend\BackendInterface $carddavBackend
     * @param string $principalPrefix
     */
    function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, Backend\BackendInterface $carddavBackend, $principalPrefix = 'principals') {

        $this->carddavBackend = $carddavBackend;
        parent::__construct($principalBackend, $principalPrefix);

    }

    /**
     * Returns the name of the node
     *
     * @return string
     */
    function getName() {

        return Plugin::ADDRESSBOOK_ROOT;

    }

    /**
     * This method returns a node for a principal.
     *
     * The passed array contains principal information, and is guaranteed to
     * at least contain a uri item. Other properties may or may not be
     * supplied by the authentication backend.
     *
     * @param array $principal
     * @return \Sabre\DAV\INode
     */
    function getChildForPrincipal(array $principal) {

        return new AddressBookHome($this->carddavBackend, $principal['uri']);

    }

}
<?php

namespace Sabre\CardDAV\Backend;

/**
 * CardDAV abstract Backend
 *
 * This class serves as a base-class for addressbook backends
 *
 * This class doesn't do much, but it was added for consistency.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractBackend implements BackendInterface {

    /**
     * Returns a list of cards.
     *
     * This method should work identical to getCard, but instead return all the
     * cards in the list as an array.
     *
     * If the backend supports this, it may allow for some speed-ups.
     *
     * @param mixed $addressBookId
     * @param array $uris
     * @return array
     */
    function getMultipleCards($addressBookId, array $uris) {

        return array_map(function($uri) use ($addressBookId) {
            return $this->getCard($addressBookId, $uri);
        }, $uris);

    }

}
<?php

namespace Sabre\CardDAV\Backend;

/**
 * CardDAV Backend Interface
 *
 * Any CardDAV backend must implement this interface.
 *
 * Note that there are references to 'addressBookId' scattered throughout the
 * class. The value of the addressBookId is completely up to you, it can be any
 * arbitrary value you can use as an unique identifier.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface BackendInterface {

    /**
     * Returns the list of addressbooks for a specific user.
     *
     * Every addressbook should have the following properties:
     *   id - an arbitrary unique id
     *   uri - the 'basename' part of the url
     *   principaluri - Same as the passed parameter
     *
     * Any additional clark-notation property may be passed besides this. Some
     * common ones are :
     *   {DAV:}displayname
     *   {urn:ietf:params:xml:ns:carddav}addressbook-description
     *   {http://calendarserver.org/ns/}getctag
     *
     * @param string $principalUri
     * @return array
     */
    function getAddressBooksForUser($principalUri);

    /**
     * Updates properties for an address book.
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param string $addressBookId
     * @param \Sabre\DAV\PropPatch $propPatch
     * @return void
     */
    function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch);

    /**
     * Creates a new address book.
     *
     * This method should return the id of the new address book. The id can be
     * in any format, including ints, strings, arrays or objects.
     *
     * @param string $principalUri
     * @param string $url Just the 'basename' of the url.
     * @param array $properties
     * @return mixed
     */
    function createAddressBook($principalUri, $url, array $properties);

    /**
     * Deletes an entire addressbook and all its contents
     *
     * @param mixed $addressBookId
     * @return void
     */
    function deleteAddressBook($addressBookId);

    /**
     * Returns all cards for a specific addressbook id.
     *
     * This method should return the following properties for each card:
     *   * carddata - raw vcard data
     *   * uri - Some unique url
     *   * lastmodified - A unix timestamp
     *
     * It's recommended to also return the following properties:
     *   * etag - A unique etag. This must change every time the card changes.
     *   * size - The size of the card in bytes.
     *
     * If these last two properties are provided, less time will be spent
     * calculating them. If they are specified, you can also ommit carddata.
     * This may speed up certain requests, especially with large cards.
     *
     * @param mixed $addressbookId
     * @return array
     */
    function getCards($addressbookId);

    /**
     * Returns a specfic card.
     *
     * The same set of properties must be returned as with getCards. The only
     * exception is that 'carddata' is absolutely required.
     *
     * If the card does not exist, you must return false.
     *
     * @param mixed $addressBookId
     * @param string $cardUri
     * @return array
     */
    function getCard($addressBookId, $cardUri);

    /**
     * Returns a list of cards.
     *
     * This method should work identical to getCard, but instead return all the
     * cards in the list as an array.
     *
     * If the backend supports this, it may allow for some speed-ups.
     *
     * @param mixed $addressBookId
     * @param array $uris
     * @return array
     */
    function getMultipleCards($addressBookId, array $uris);

    /**
     * Creates a new card.
     *
     * The addressbook id will be passed as the first argument. This is the
     * same id as it is returned from the getAddressBooksForUser method.
     *
     * The cardUri is a base uri, and doesn't include the full path. The
     * cardData argument is the vcard body, and is passed as a string.
     *
     * It is possible to return an ETag from this method. This ETag is for the
     * newly created resource, and must be enclosed with double quotes (that
     * is, the string itself must contain the double quotes).
     *
     * You should only return the ETag if you store the carddata as-is. If a
     * subsequent GET request on the same card does not have the same body,
     * byte-by-byte and you did return an ETag here, clients tend to get
     * confused.
     *
     * If you don't return an ETag, you can just return null.
     *
     * @param mixed $addressBookId
     * @param string $cardUri
     * @param string $cardData
     * @return string|null
     */
    function createCard($addressBookId, $cardUri, $cardData);

    /**
     * Updates a card.
     *
     * The addressbook id will be passed as the first argument. This is the
     * same id as it is returned from the getAddressBooksForUser method.
     *
     * The cardUri is a base uri, and doesn't include the full path. The
     * cardData argument is the vcard body, and is passed as a string.
     *
     * It is possible to return an ETag from this method. This ETag should
     * match that of the updated resource, and must be enclosed with double
     * quotes (that is: the string itself must contain the actual quotes).
     *
     * You should only return the ETag if you store the carddata as-is. If a
     * subsequent GET request on the same card does not have the same body,
     * byte-by-byte and you did return an ETag here, clients tend to get
     * confused.
     *
     * If you don't return an ETag, you can just return null.
     *
     * @param mixed $addressBookId
     * @param string $cardUri
     * @param string $cardData
     * @return string|null
     */
    function updateCard($addressBookId, $cardUri, $cardData);

    /**
     * Deletes a card
     *
     * @param mixed $addressBookId
     * @param string $cardUri
     * @return bool
     */
    function deleteCard($addressBookId, $cardUri);

}
<?php

namespace Sabre\CardDAV\Backend;

use Sabre\CardDAV;
use Sabre\DAV;

/**
 * PDO CardDAV backend
 *
 * This CardDAV backend uses PDO to store addressbooks
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PDO extends AbstractBackend implements SyncSupport {

    /**
     * PDO connection
     *
     * @var PDO
     */
    protected $pdo;

    /**
     * The PDO table name used to store addressbooks
     */
    public $addressBooksTableName = 'addressbooks';

    /**
     * The PDO table name used to store cards
     */
    public $cardsTableName = 'cards';

    /**
     * The table name that will be used for tracking changes in address books.
     *
     * @var string
     */
    public $addressBookChangesTableName = 'addressbookchanges';

    /**
     * Sets up the object
     *
     * @param \PDO $pdo
     */
    function __construct(\PDO $pdo) {

        $this->pdo = $pdo;

    }

    /**
     * Returns the list of addressbooks for a specific user.
     *
     * @param string $principalUri
     * @return array
     */
    function getAddressBooksForUser($principalUri) {

        $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, synctoken FROM ' . $this->addressBooksTableName . ' WHERE principaluri = ?');
        $stmt->execute([$principalUri]);

        $addressBooks = [];

        foreach ($stmt->fetchAll() as $row) {

            $addressBooks[] = [
                'id'                                                          => $row['id'],
                'uri'                                                         => $row['uri'],
                'principaluri'                                                => $row['principaluri'],
                '{DAV:}displayname'                                           => $row['displayname'],
                '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'],
                '{http://calendarserver.org/ns/}getctag'                      => $row['synctoken'],
                '{http://sabredav.org/ns}sync-token'                          => $row['synctoken'] ? $row['synctoken'] : '0',
            ];

        }

        return $addressBooks;

    }


    /**
     * Updates properties for an address book.
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param string $addressBookId
     * @param \Sabre\DAV\PropPatch $propPatch
     * @return void
     */
    function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) {

        $supportedProperties = [
            '{DAV:}displayname',
            '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description',
        ];

        $propPatch->handle($supportedProperties, function($mutations) use ($addressBookId) {

            $updates = [];
            foreach ($mutations as $property => $newValue) {

                switch ($property) {
                    case '{DAV:}displayname' :
                        $updates['displayname'] = $newValue;
                        break;
                    case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
                        $updates['description'] = $newValue;
                        break;
                }
            }
            $query = 'UPDATE ' . $this->addressBooksTableName . ' SET ';
            $first = true;
            foreach ($updates as $key => $value) {
                if ($first) {
                    $first = false;
                } else {
                    $query .= ', ';
                }
                $query .= ' ' . $key . ' = :' . $key . ' ';
            }
            $query .= ' WHERE id = :addressbookid';

            $stmt = $this->pdo->prepare($query);
            $updates['addressbookid'] = $addressBookId;

            $stmt->execute($updates);

            $this->addChange($addressBookId, "", 2);

            return true;

        });

    }

    /**
     * Creates a new address book
     *
     * @param string $principalUri
     * @param string $url Just the 'basename' of the url.
     * @param array $properties
     * @return int Last insert id
     */
    function createAddressBook($principalUri, $url, array $properties) {

        $values = [
            'displayname'  => null,
            'description'  => null,
            'principaluri' => $principalUri,
            'uri'          => $url,
        ];

        foreach ($properties as $property => $newValue) {

            switch ($property) {
                case '{DAV:}displayname' :
                    $values['displayname'] = $newValue;
                    break;
                case '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' :
                    $values['description'] = $newValue;
                    break;
                default :
                    throw new DAV\Exception\BadRequest('Unknown property: ' . $property);
            }

        }

        $query = 'INSERT INTO ' . $this->addressBooksTableName . ' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)';
        $stmt = $this->pdo->prepare($query);
        $stmt->execute($values);
        return $this->pdo->lastInsertId(
            $this->addressBooksTableName . '_id_seq'
        );

    }

    /**
     * Deletes an entire addressbook and all its contents
     *
     * @param int $addressBookId
     * @return void
     */
    function deleteAddressBook($addressBookId) {

        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
        $stmt->execute([$addressBookId]);

        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
        $stmt->execute([$addressBookId]);

        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->addressBookChangesTableName . ' WHERE addressbookid = ?');
        $stmt->execute([$addressBookId]);

    }

    /**
     * Returns all cards for a specific addressbook id.
     *
     * This method should return the following properties for each card:
     *   * carddata - raw vcard data
     *   * uri - Some unique url
     *   * lastmodified - A unix timestamp
     *
     * It's recommended to also return the following properties:
     *   * etag - A unique etag. This must change every time the card changes.
     *   * size - The size of the card in bytes.
     *
     * If these last two properties are provided, less time will be spent
     * calculating them. If they are specified, you can also ommit carddata.
     * This may speed up certain requests, especially with large cards.
     *
     * @param mixed $addressbookId
     * @return array
     */
    function getCards($addressbookId) {

        $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ?');
        $stmt->execute([$addressbookId]);

        $result = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $row['etag'] = '"' . $row['etag'] . '"';
            $row['lastmodified'] = (int)$row['lastmodified'];
            $result[] = $row;
        }
        return $result;

    }

    /**
     * Returns a specific card.
     *
     * The same set of properties must be returned as with getCards. The only
     * exception is that 'carddata' is absolutely required.
     *
     * If the card does not exist, you must return false.
     *
     * @param mixed $addressBookId
     * @param string $cardUri
     * @return array
     */
    function getCard($addressBookId, $cardUri) {

        $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified, etag, size FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ? LIMIT 1');
        $stmt->execute([$addressBookId, $cardUri]);

        $result = $stmt->fetch(\PDO::FETCH_ASSOC);

        if (!$result) return false;

        $result['etag'] = '"' . $result['etag'] . '"';
        $result['lastmodified'] = (int)$result['lastmodified'];
        return $result;

    }

    /**
     * Returns a list of cards.
     *
     * This method should work identical to getCard, but instead return all the
     * cards in the list as an array.
     *
     * If the backend supports this, it may allow for some speed-ups.
     *
     * @param mixed $addressBookId
     * @param array $uris
     * @return array
     */
    function getMultipleCards($addressBookId, array $uris) {

        $query = 'SELECT id, uri, lastmodified, etag, size, carddata FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri IN (';
        // Inserting a whole bunch of question marks
        $query .= implode(',', array_fill(0, count($uris), '?'));
        $query .= ')';

        $stmt = $this->pdo->prepare($query);
        $stmt->execute(array_merge([$addressBookId], $uris));
        $result = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $row['etag'] = '"' . $row['etag'] . '"';
            $row['lastmodified'] = (int)$row['lastmodified'];
            $result[] = $row;
        }
        return $result;

    }

    /**
     * Creates a new card.
     *
     * The addressbook id will be passed as the first argument. This is the
     * same id as it is returned from the getAddressBooksForUser method.
     *
     * The cardUri is a base uri, and doesn't include the full path. The
     * cardData argument is the vcard body, and is passed as a string.
     *
     * It is possible to return an ETag from this method. This ETag is for the
     * newly created resource, and must be enclosed with double quotes (that
     * is, the string itself must contain the double quotes).
     *
     * You should only return the ETag if you store the carddata as-is. If a
     * subsequent GET request on the same card does not have the same body,
     * byte-by-byte and you did return an ETag here, clients tend to get
     * confused.
     *
     * If you don't return an ETag, you can just return null.
     *
     * @param mixed $addressBookId
     * @param string $cardUri
     * @param string $cardData
     * @return string|null
     */
    function createCard($addressBookId, $cardUri, $cardData) {

        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->cardsTableName . ' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)');

        $etag = md5($cardData);

        $stmt->execute([
            $cardData,
            $cardUri,
            time(),
            $addressBookId,
            strlen($cardData),
            $etag,
        ]);

        $this->addChange($addressBookId, $cardUri, 1);

        return '"' . $etag . '"';

    }

    /**
     * Updates a card.
     *
     * The addressbook id will be passed as the first argument. This is the
     * same id as it is returned from the getAddressBooksForUser method.
     *
     * The cardUri is a base uri, and doesn't include the full path. The
     * cardData argument is the vcard body, and is passed as a string.
     *
     * It is possible to return an ETag from this method. This ETag should
     * match that of the updated resource, and must be enclosed with double
     * quotes (that is: the string itself must contain the actual quotes).
     *
     * You should only return the ETag if you store the carddata as-is. If a
     * subsequent GET request on the same card does not have the same body,
     * byte-by-byte and you did return an ETag here, clients tend to get
     * confused.
     *
     * If you don't return an ETag, you can just return null.
     *
     * @param mixed $addressBookId
     * @param string $cardUri
     * @param string $cardData
     * @return string|null
     */
    function updateCard($addressBookId, $cardUri, $cardData) {

        $stmt = $this->pdo->prepare('UPDATE ' . $this->cardsTableName . ' SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?');

        $etag = md5($cardData);
        $stmt->execute([
            $cardData,
            time(),
            strlen($cardData),
            $etag,
            $cardUri,
            $addressBookId
        ]);

        $this->addChange($addressBookId, $cardUri, 2);

        return '"' . $etag . '"';

    }

    /**
     * Deletes a card
     *
     * @param mixed $addressBookId
     * @param string $cardUri
     * @return bool
     */
    function deleteCard($addressBookId, $cardUri) {

        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->cardsTableName . ' WHERE addressbookid = ? AND uri = ?');
        $stmt->execute([$addressBookId, $cardUri]);

        $this->addChange($addressBookId, $cardUri, 3);

        return $stmt->rowCount() === 1;

    }

    /**
     * The getChanges method returns all the changes that have happened, since
     * the specified syncToken in the specified address book.
     *
     * This function should return an array, such as the following:
     *
     * [
     *   'syncToken' => 'The current synctoken',
     *   'added'   => [
     *      'new.txt',
     *   ],
     *   'modified'   => [
     *      'updated.txt',
     *   ],
     *   'deleted' => [
     *      'foo.php.bak',
     *      'old.txt'
     *   ]
     * ];
     *
     * The returned syncToken property should reflect the *current* syncToken
     * of the addressbook, as reported in the {http://sabredav.org/ns}sync-token
     * property. This is needed here too, to ensure the operation is atomic.
     *
     * If the $syncToken argument is specified as null, this is an initial
     * sync, and all members should be reported.
     *
     * The modified property is an array of nodenames that have changed since
     * the last token.
     *
     * The deleted property is an array with nodenames, that have been deleted
     * from collection.
     *
     * The $syncLevel argument is basically the 'depth' of the report. If it's
     * 1, you only have to report changes that happened only directly in
     * immediate descendants. If it's 2, it should also include changes from
     * the nodes below the child collections. (grandchildren)
     *
     * The $limit argument allows a client to specify how many results should
     * be returned at most. If the limit is not specified, it should be treated
     * as infinite.
     *
     * If the limit (infinite or not) is higher than you're willing to return,
     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
     *
     * If the syncToken is expired (due to data cleanup) or unknown, you must
     * return null.
     *
     * The limit is 'suggestive'. You are free to ignore it.
     *
     * @param string $addressBookId
     * @param string $syncToken
     * @param int $syncLevel
     * @param int $limit
     * @return array
     */
    function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {

        // Current synctoken
        $stmt = $this->pdo->prepare('SELECT synctoken FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
        $stmt->execute([$addressBookId]);
        $currentToken = $stmt->fetchColumn(0);

        if (is_null($currentToken)) return null;

        $result = [
            'syncToken' => $currentToken,
            'added'     => [],
            'modified'  => [],
            'deleted'   => [],
        ];

        if ($syncToken) {

            $query = "SELECT uri, operation FROM " . $this->addressBookChangesTableName . " WHERE synctoken >= ? AND synctoken < ? AND addressbookid = ? ORDER BY synctoken";
            if ($limit > 0) $query .= " LIMIT " . (int)$limit;

            // Fetching all changes
            $stmt = $this->pdo->prepare($query);
            $stmt->execute([$syncToken, $currentToken, $addressBookId]);

            $changes = [];

            // This loop ensures that any duplicates are overwritten, only the
            // last change on a node is relevant.
            while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

                $changes[$row['uri']] = $row['operation'];

            }

            foreach ($changes as $uri => $operation) {

                switch ($operation) {
                    case 1:
                        $result['added'][] = $uri;
                        break;
                    case 2:
                        $result['modified'][] = $uri;
                        break;
                    case 3:
                        $result['deleted'][] = $uri;
                        break;
                }

            }
        } else {
            // No synctoken supplied, this is the initial sync.
            $query = "SELECT uri FROM " . $this->cardsTableName . " WHERE addressbookid = ?";
            $stmt = $this->pdo->prepare($query);
            $stmt->execute([$addressBookId]);

            $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
        }
        return $result;

    }

    /**
     * Adds a change record to the addressbookchanges table.
     *
     * @param mixed $addressBookId
     * @param string $objectUri
     * @param int $operation 1 = add, 2 = modify, 3 = delete
     * @return void
     */
    protected function addChange($addressBookId, $objectUri, $operation) {

        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->addressBookChangesTableName . ' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM ' . $this->addressBooksTableName . ' WHERE id = ?');
        $stmt->execute([
            $objectUri,
            $addressBookId,
            $operation,
            $addressBookId
        ]);
        $stmt = $this->pdo->prepare('UPDATE ' . $this->addressBooksTableName . ' SET synctoken = synctoken + 1 WHERE id = ?');
        $stmt->execute([
            $addressBookId
        ]);

    }
}
<?php

namespace Sabre\CardDAV\Backend;

/**
 * WebDAV-sync support for CardDAV backends.
 *
 * In order for backends to advertise support for WebDAV-sync, this interface
 * must be implemented.
 *
 * Implementing this can result in a significant reduction of bandwidth and CPU
 * time.
 *
 * For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
 * property from getAddressBooksForUser.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface SyncSupport extends BackendInterface {

    /**
     * The getChanges method returns all the changes that have happened, since
     * the specified syncToken in the specified address book.
     *
     * This function should return an array, such as the following:
     *
     * [
     *   'syncToken' => 'The current synctoken',
     *   'added'   => [
     *      'new.txt',
     *   ],
     *   'modified'   => [
     *      'modified.txt',
     *   ],
     *   'deleted' => [
     *      'foo.php.bak',
     *      'old.txt'
     *   ]
     * ];
     *
     * The returned syncToken property should reflect the *current* syncToken
     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
     * property. This is needed here too, to ensure the operation is atomic.
     *
     * If the $syncToken argument is specified as null, this is an initial
     * sync, and all members should be reported.
     *
     * The modified property is an array of nodenames that have changed since
     * the last token.
     *
     * The deleted property is an array with nodenames, that have been deleted
     * from collection.
     *
     * The $syncLevel argument is basically the 'depth' of the report. If it's
     * 1, you only have to report changes that happened only directly in
     * immediate descendants. If it's 2, it should also include changes from
     * the nodes below the child collections. (grandchildren)
     *
     * The $limit argument allows a client to specify how many results should
     * be returned at most. If the limit is not specified, it should be treated
     * as infinite.
     *
     * If the limit (infinite or not) is higher than you're willing to return,
     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
     *
     * If the syncToken is expired (due to data cleanup) or unknown, you must
     * return null.
     *
     * The limit is 'suggestive'. You are free to ignore it.
     *
     * @param string $addressBookId
     * @param string $syncToken
     * @param int $syncLevel
     * @param int $limit
     * @return array
     */
    function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null);

}
<?php

namespace Sabre\CardDAV;

use Sabre\DAV;
use Sabre\DAVACL;

/**
 * The Card object represents a single Card from an addressbook
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Card extends DAV\File implements ICard, DAVACL\IACL {

    use DAVACL\ACLTrait;

    /**
     * CardDAV backend
     *
     * @var Backend\BackendInterface
     */
    protected $carddavBackend;

    /**
     * Array with information about this Card
     *
     * @var array
     */
    protected $cardData;

    /**
     * Array with information about the containing addressbook
     *
     * @var array
     */
    protected $addressBookInfo;

    /**
     * Constructor
     *
     * @param Backend\BackendInterface $carddavBackend
     * @param array $addressBookInfo
     * @param array $cardData
     */
    function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo, array $cardData) {

        $this->carddavBackend = $carddavBackend;
        $this->addressBookInfo = $addressBookInfo;
        $this->cardData = $cardData;

    }

    /**
     * Returns the uri for this object
     *
     * @return string
     */
    function getName() {

        return $this->cardData['uri'];

    }

    /**
     * Returns the VCard-formatted object
     *
     * @return string
     */
    function get() {

        // Pre-populating 'carddata' is optional. If we don't yet have it
        // already, we fetch it from the backend.
        if (!isset($this->cardData['carddata'])) {
            $this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']);
        }
        return $this->cardData['carddata'];

    }

    /**
     * Updates the VCard-formatted object
     *
     * @param string $cardData
     * @return string|null
     */
    function put($cardData) {

        if (is_resource($cardData))
            $cardData = stream_get_contents($cardData);

        // Converting to UTF-8, if needed
        $cardData = DAV\StringUtil::ensureUTF8($cardData);

        $etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'], $this->cardData['uri'], $cardData);
        $this->cardData['carddata'] = $cardData;
        $this->cardData['etag'] = $etag;

        return $etag;

    }

    /**
     * Deletes the card
     *
     * @return void
     */
    function delete() {

        $this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']);

    }

    /**
     * Returns the mime content-type
     *
     * @return string
     */
    function getContentType() {

        return 'text/vcard; charset=utf-8';

    }

    /**
     * Returns an ETag for this object
     *
     * @return string
     */
    function getETag() {

        if (isset($this->cardData['etag'])) {
            return $this->cardData['etag'];
        } else {
            $data = $this->get();
            if (is_string($data)) {
                return '"' . md5($data) . '"';
            } else {
                // We refuse to calculate the md5 if it's a stream.
                return null;
            }
        }

    }

    /**
     * Returns the last modification date as a unix timestamp
     *
     * @return int
     */
    function getLastModified() {

        return isset($this->cardData['lastmodified']) ? $this->cardData['lastmodified'] : null;

    }

    /**
     * Returns the size of this object in bytes
     *
     * @return int
     */
    function getSize() {

        if (array_key_exists('size', $this->cardData)) {
            return $this->cardData['size'];
        } else {
            return strlen($this->get());
        }

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->addressBookInfo['principaluri'];

    }


    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        // An alternative acl may be specified through the cardData array.
        if (isset($this->cardData['acl'])) {
            return $this->cardData['acl'];
        }

        return [
            [
                'privilege' => '{DAV:}all',
                'principal' => $this->addressBookInfo['principaluri'],
                'protected' => true,
            ],
        ];

    }

}
<?php

namespace Sabre\CardDAV;

use Sabre\DAV;

/**
 * AddressBook interface
 *
 * Implement this interface to allow a node to be recognized as an addressbook.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IAddressBook extends DAV\ICollection {

}
<?php

namespace Sabre\CardDAV;

use Sabre\DAV;

/**
 * Card interface
 *
 * Extend the ICard interface to allow your custom nodes to be picked up as
 * 'Cards'.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ICard extends DAV\IFile {

}
<?php

namespace Sabre\CardDAV;

/**
 * IDirectory interface
 *
 * Implement this interface to have an addressbook marked as a 'directory'. A
 * directory is an (often) global addressbook.
 *
 * A full description can be found in the IETF draft:
 *   - draft-daboo-carddav-directory-gateway
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IDirectory extends IAddressBook {

}
<?php

namespace Sabre\CardDAV;

use Sabre\DAV;
use Sabre\DAV\Exception\ReportNotSupported;
use Sabre\DAV\Xml\Property\LocalHref;
use Sabre\DAVACL;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject;

/**
 * CardDAV plugin
 *
 * The CardDAV plugin adds CardDAV functionality to the WebDAV server
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends DAV\ServerPlugin {

    /**
     * Url to the addressbooks
     */
    const ADDRESSBOOK_ROOT = 'addressbooks';

    /**
     * xml namespace for CardDAV elements
     */
    const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';

    /**
     * Add urls to this property to have them automatically exposed as
     * 'directories' to the user.
     *
     * @var array
     */
    public $directories = [];

    /**
     * Server class
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
     * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
     * capping it to 10M here.
     */
    protected $maxResourceSize = 10000000;

    /**
     * Initializes the plugin
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        /* Events */
        $server->on('propFind',            [$this, 'propFindEarly']);
        $server->on('propFind',            [$this, 'propFindLate'], 150);
        $server->on('report',              [$this, 'report']);
        $server->on('onHTMLActionsPanel',  [$this, 'htmlActionsPanel']);
        $server->on('beforeWriteContent',  [$this, 'beforeWriteContent']);
        $server->on('beforeCreateFile',    [$this, 'beforeCreateFile']);
        $server->on('afterMethod:GET',     [$this, 'httpAfterGet']);

        $server->xml->namespaceMap[self::NS_CARDDAV] = 'card';

        $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport';
        $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport';

        /* Mapping Interfaces to {DAV:}resourcetype values */
        $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook';
        $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{' . self::NS_CARDDAV . '}directory';

        /* Adding properties that may never be changed */
        $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data';
        $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size';
        $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set';
        $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set';

        $server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href';

        $this->server = $server;

    }

    /**
     * Returns a list of supported features.
     *
     * This is used in the DAV: header in the OPTIONS and PROPFIND requests.
     *
     * @return array
     */
    function getFeatures() {

        return ['addressbook'];

    }

    /**
     * Returns a list of reports this plugin supports.
     *
     * This will be used in the {DAV:}supported-report-set property.
     * Note that you still need to subscribe to the 'report' event to actually
     * implement them
     *
     * @param string $uri
     * @return array
     */
    function getSupportedReportSet($uri) {

        $node = $this->server->tree->getNodeForPath($uri);
        if ($node instanceof IAddressBook || $node instanceof ICard) {
            return [
                 '{' . self::NS_CARDDAV . '}addressbook-multiget',
                 '{' . self::NS_CARDDAV . '}addressbook-query',
            ];
        }
        return [];

    }


    /**
     * Adds all CardDAV-specific properties
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @return void
     */
    function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {

        $ns = '{' . self::NS_CARDDAV . '}';

        if ($node instanceof IAddressBook) {

            $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
            $propFind->handle($ns . 'supported-address-data', function() {
                return new Xml\Property\SupportedAddressData();
            });
            $propFind->handle($ns . 'supported-collation-set', function() {
                return new Xml\Property\SupportedCollationSet();
            });

        }
        if ($node instanceof DAVACL\IPrincipal) {

            $path = $propFind->getPath();

            $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) {
                return new LocalHref($this->getAddressBookHomeForPrincipal($path) . '/');
            });

            if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() {
                return new LocalHref($this->directories);
            });

        }

        if ($node instanceof ICard) {

            // The address-data property is not supposed to be a 'real'
            // property, but in large chunks of the spec it does act as such.
            // Therefore we simply expose it as a property.
            $propFind->handle('{' . self::NS_CARDDAV . '}address-data', function() use ($node) {
                $val = $node->get();
                if (is_resource($val))
                    $val = stream_get_contents($val);

                return $val;

            });

        }

    }

    /**
     * This functions handles REPORT requests specific to CardDAV
     *
     * @param string $reportName
     * @param \DOMNode $dom
     * @param mixed $path
     * @return bool
     */
    function report($reportName, $dom, $path) {

        switch ($reportName) {
            case '{' . self::NS_CARDDAV . '}addressbook-multiget' :
                $this->server->transactionType = 'report-addressbook-multiget';
                $this->addressbookMultiGetReport($dom);
                return false;
            case '{' . self::NS_CARDDAV . '}addressbook-query' :
                $this->server->transactionType = 'report-addressbook-query';
                $this->addressBookQueryReport($dom);
                return false;
            default :
                return;

        }


    }

    /**
     * Returns the addressbook home for a given principal
     *
     * @param string $principal
     * @return string
     */
    protected function getAddressbookHomeForPrincipal($principal) {

        list(, $principalId) = \Sabre\HTTP\URLUtil::splitPath($principal);
        return self::ADDRESSBOOK_ROOT . '/' . $principalId;

    }


    /**
     * This function handles the addressbook-multiget REPORT.
     *
     * This report is used by the client to fetch the content of a series
     * of urls. Effectively avoiding a lot of redundant requests.
     *
     * @param Xml\Request\AddressBookMultiGetReport $report
     * @return void
     */
    function addressbookMultiGetReport($report) {

        $contentType = $report->contentType;
        $version = $report->version;
        if ($version) {
            $contentType .= '; version=' . $version;
        }

        $vcardType = $this->negotiateVCard(
            $contentType
        );

        $propertyList = [];
        $paths = array_map(
            [$this->server, 'calculateUri'],
            $report->hrefs
        );
        foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) {

            if (isset($props['200']['{' . self::NS_CARDDAV . '}address-data'])) {

                $props['200']['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard(
                    $props[200]['{' . self::NS_CARDDAV . '}address-data'],
                    $vcardType
                );

            }
            $propertyList[] = $props;

        }

        $prefer = $this->server->getHTTPPrefer();

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
        $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));

    }

    /**
     * This method is triggered before a file gets updated with new content.
     *
     * This plugin uses this method to ensure that Card nodes receive valid
     * vcard data.
     *
     * @param string $path
     * @param DAV\IFile $node
     * @param resource $data
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @return void
     */
    function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {

        if (!$node instanceof ICard)
            return;

        $this->validateVCard($data, $modified);

    }

    /**
     * This method is triggered before a new file is created.
     *
     * This plugin uses this method to ensure that Card nodes receive valid
     * vcard data.
     *
     * @param string $path
     * @param resource $data
     * @param DAV\ICollection $parentNode
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @return void
     */
    function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {

        if (!$parentNode instanceof IAddressBook)
            return;

        $this->validateVCard($data, $modified);

    }

    /**
     * Checks if the submitted iCalendar data is in fact, valid.
     *
     * An exception is thrown if it's not.
     *
     * @param resource|string $data
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @return void
     */
    protected function validateVCard(&$data, &$modified) {

        // If it's a stream, we convert it to a string first.
        if (is_resource($data)) {
            $data = stream_get_contents($data);
        }

        $before = $data;

        try {

            // If the data starts with a [, we can reasonably assume we're dealing
            // with a jCal object.
            if (substr($data, 0, 1) === '[') {
                $vobj = VObject\Reader::readJson($data);

                // Converting $data back to iCalendar, as that's what we
                // technically support everywhere.
                $data = $vobj->serialize();
                $modified = true;
            } else {
                $vobj = VObject\Reader::read($data);
            }

        } catch (VObject\ParseException $e) {

            throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: ' . $e->getMessage());

        }

        if ($vobj->name !== 'VCARD') {
            throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
        }

        $options = VObject\Node::PROFILE_CARDDAV;
        $prefer = $this->server->getHTTPPrefer();

        if ($prefer['handling'] !== 'strict') {
            $options |= VObject\Node::REPAIR;
        }

        $messages = $vobj->validate($options);

        $highestLevel = 0;
        $warningMessage = null;

        // $messages contains a list of problems with the vcard, along with
        // their severity.
        foreach ($messages as $message) {

            if ($message['level'] > $highestLevel) {
                // Recording the highest reported error level.
                $highestLevel = $message['level'];
                $warningMessage = $message['message'];
            }

            switch ($message['level']) {

                case 1 :
                    // Level 1 means that there was a problem, but it was repaired.
                    $modified = true;
                    break;
                case 2 :
                    // Level 2 means a warning, but not critical
                    break;
                case 3 :
                    // Level 3 means a critical error
                    throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: ' . $message['message']);

            }

        }
        if ($warningMessage) {
            $this->server->httpResponse->setHeader(
                'X-Sabre-Ew-Gross',
                'vCard validation warning: ' . $warningMessage
            );

            // Re-serializing object.
            $data = $vobj->serialize();
            if (!$modified && strcmp($data, $before) !== 0) {
                // This ensures that the system does not send an ETag back.
                $modified = true;
            }
        }

        // Destroy circular references to PHP will GC the object.
        $vobj->destroy();
    }


    /**
     * This function handles the addressbook-query REPORT
     *
     * This report is used by the client to filter an addressbook based on a
     * complex query.
     *
     * @param Xml\Request\AddressBookQueryReport $report
     * @return void
     */
    protected function addressbookQueryReport($report) {

        $depth = $this->server->getHTTPDepth(0);

        if ($depth == 0) {
            $candidateNodes = [
                $this->server->tree->getNodeForPath($this->server->getRequestUri())
            ];
            if (!$candidateNodes[0] instanceof ICard) {
                throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
            }
        } else {
            $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
        }

        $contentType = $report->contentType;
        if ($report->version) {
            $contentType .= '; version=' . $report->version;
        }

        $vcardType = $this->negotiateVCard(
            $contentType
        );

        $validNodes = [];
        foreach ($candidateNodes as $node) {

            if (!$node instanceof ICard)
                continue;

            $blob = $node->get();
            if (is_resource($blob)) {
                $blob = stream_get_contents($blob);
            }

            if (!$this->validateFilters($blob, $report->filters, $report->test)) {
                continue;
            }

            $validNodes[] = $node;

            if ($report->limit && $report->limit <= count($validNodes)) {
                // We hit the maximum number of items, we can stop now.
                break;
            }

        }

        $result = [];
        foreach ($validNodes as $validNode) {

            if ($depth == 0) {
                $href = $this->server->getRequestUri();
            } else {
                $href = $this->server->getRequestUri() . '/' . $validNode->getName();
            }

            list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);

            if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) {

                $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard(
                    $props[200]['{' . self::NS_CARDDAV . '}address-data'],
                    $vcardType,
                    $report->addressDataProperties
                );

            }
            $result[] = $props;

        }

        $prefer = $this->server->getHTTPPrefer();

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
        $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));

    }

    /**
     * Validates if a vcard makes it throught a list of filters.
     *
     * @param string $vcardData
     * @param array $filters
     * @param string $test anyof or allof (which means OR or AND)
     * @return bool
     */
    function validateFilters($vcardData, array $filters, $test) {


        if (!$filters) return true;
        $vcard = VObject\Reader::read($vcardData);

        foreach ($filters as $filter) {

            $isDefined = isset($vcard->{$filter['name']});
            if ($filter['is-not-defined']) {
                if ($isDefined) {
                    $success = false;
                } else {
                    $success = true;
                }
            } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {

                // We only need to check for existence
                $success = $isDefined;

            } else {

                $vProperties = $vcard->select($filter['name']);

                $results = [];
                if ($filter['param-filters']) {
                    $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
                }
                if ($filter['text-matches']) {
                    $texts = [];
                    foreach ($vProperties as $vProperty)
                        $texts[] = $vProperty->getValue();

                    $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
                }

                if (count($results) === 1) {
                    $success = $results[0];
                } else {
                    if ($filter['test'] === 'anyof') {
                        $success = $results[0] || $results[1];
                    } else {
                        $success = $results[0] && $results[1];
                    }
                }

            } // else

            // There are two conditions where we can already determine whether
            // or not this filter succeeds.
            if ($test === 'anyof' && $success) {

                // Destroy circular references to PHP will GC the object.
                $vcard->destroy();

                return true;
            }
            if ($test === 'allof' && !$success) {

                // Destroy circular references to PHP will GC the object.
                $vcard->destroy();

                return false;
            }

        } // foreach


        // Destroy circular references to PHP will GC the object.
        $vcard->destroy();

        // If we got all the way here, it means we haven't been able to
        // determine early if the test failed or not.
        //
        // This implies for 'anyof' that the test failed, and for 'allof' that
        // we succeeded. Sounds weird, but makes sense.
        return $test === 'allof';

    }

    /**
     * Validates if a param-filter can be applied to a specific property.
     *
     * @todo currently we're only validating the first parameter of the passed
     *       property. Any subsequence parameters with the same name are
     *       ignored.
     * @param array $vProperties
     * @param array $filters
     * @param string $test
     * @return bool
     */
    protected function validateParamFilters(array $vProperties, array $filters, $test) {

        foreach ($filters as $filter) {

            $isDefined = false;
            foreach ($vProperties as $vProperty) {
                $isDefined = isset($vProperty[$filter['name']]);
                if ($isDefined) break;
            }

            if ($filter['is-not-defined']) {
                if ($isDefined) {
                    $success = false;
                } else {
                    $success = true;
                }

            // If there's no text-match, we can just check for existence
            } elseif (!$filter['text-match'] || !$isDefined) {

                $success = $isDefined;

            } else {

                $success = false;
                foreach ($vProperties as $vProperty) {
                    // If we got all the way here, we'll need to validate the
                    // text-match filter.
                    $success = DAV\StringUtil::textMatch($vProperty[$filter['name']]->getValue(), $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']);
                    if ($success) break;
                }
                if ($filter['text-match']['negate-condition']) {
                    $success = !$success;
                }

            } // else

            // There are two conditions where we can already determine whether
            // or not this filter succeeds.
            if ($test === 'anyof' && $success) {
                return true;
            }
            if ($test === 'allof' && !$success) {
                return false;
            }

        }

        // If we got all the way here, it means we haven't been able to
        // determine early if the test failed or not.
        //
        // This implies for 'anyof' that the test failed, and for 'allof' that
        // we succeeded. Sounds weird, but makes sense.
        return $test === 'allof';

    }

    /**
     * Validates if a text-filter can be applied to a specific property.
     *
     * @param array $texts
     * @param array $filters
     * @param string $test
     * @return bool
     */
    protected function validateTextMatches(array $texts, array $filters, $test) {

        foreach ($filters as $filter) {

            $success = false;
            foreach ($texts as $haystack) {
                $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);

                // Breaking on the first match
                if ($success) break;
            }
            if ($filter['negate-condition']) {
                $success = !$success;
            }

            if ($success && $test === 'anyof')
                return true;

            if (!$success && $test == 'allof')
                return false;


        }

        // If we got all the way here, it means we haven't been able to
        // determine early if the test failed or not.
        //
        // This implies for 'anyof' that the test failed, and for 'allof' that
        // we succeeded. Sounds weird, but makes sense.
        return $test === 'allof';

    }

    /**
     * This event is triggered when fetching properties.
     *
     * This event is scheduled late in the process, after most work for
     * propfind has been done.
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @return void
     */
    function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {

        // If the request was made using the SOGO connector, we must rewrite
        // the content-type property. By default SabreDAV will send back
        // text/x-vcard; charset=utf-8, but for SOGO we must strip that last
        // part.
        if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird') === false) {
            return;
        }
        $contentType = $propFind->get('{DAV:}getcontenttype');
        list($part) = explode(';', $contentType);
        if ($part === 'text/x-vcard' || $part === 'text/vcard') {
            $propFind->set('{DAV:}getcontenttype', 'text/x-vcard');
        }

    }

    /**
     * This method is used to generate HTML output for the
     * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users
     * can use to create new addressbooks.
     *
     * @param DAV\INode $node
     * @param string $output
     * @return bool
     */
    function htmlActionsPanel(DAV\INode $node, &$output) {

        if (!$node instanceof AddressBookHome)
            return;

        $output .= '<tr><td colspan="2"><form method="post" action="">
            <h3>Create new address book</h3>
            <input type="hidden" name="sabreAction" value="mkcol" />
            <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CARDDAV . '}addressbook" />
            <label>Name (uri):</label> <input type="text" name="name" /><br />
            <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
            <input type="submit" value="create" />
            </form>
            </td></tr>';

        return false;

    }

    /**
     * This event is triggered after GET requests.
     *
     * This is used to transform data into jCal, if this was requested.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function httpAfterGet(RequestInterface $request, ResponseInterface $response) {

        if (strpos($response->getHeader('Content-Type'), 'text/vcard') === false) {
            return;
        }

        $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);

        $newBody = $this->convertVCard(
            $response->getBody(),
            $target
        );

        $response->setBody($newBody);
        $response->setHeader('Content-Type', $mimeType . '; charset=utf-8');
        $response->setHeader('Content-Length', strlen($newBody));

    }

    /**
     * This helper function performs the content-type negotiation for vcards.
     *
     * It will return one of the following strings:
     * 1. vcard3
     * 2. vcard4
     * 3. jcard
     *
     * It defaults to vcard3.
     *
     * @param string $input
     * @param string $mimeType
     * @return string
     */
    protected function negotiateVCard($input, &$mimeType = null) {

        $result = HTTP\Util::negotiate(
            $input,
            [
                // Most often used mime-type. Version 3
                'text/x-vcard',
                // The correct standard mime-type. Defaults to version 3 as
                // well.
                'text/vcard',
                // vCard 4
                'text/vcard; version=4.0',
                // vCard 3
                'text/vcard; version=3.0',
                // jCard
                'application/vcard+json',
            ]
        );

        $mimeType = $result;
        switch ($result) {

            default :
            case 'text/x-vcard' :
            case 'text/vcard' :
            case 'text/vcard; version=3.0' :
                $mimeType = 'text/vcard';
                return 'vcard3';
            case 'text/vcard; version=4.0' :
                return 'vcard4';
            case 'application/vcard+json' :
                return 'jcard';

        // @codeCoverageIgnoreStart
        }
        // @codeCoverageIgnoreEnd

    }

    /**
     * Converts a vcard blob to a different version, or jcard.
     *
     * @param string|resource $data
     * @param string $target
     * @param array $propertiesFilter
     * @return string
     */
    protected function convertVCard($data, $target, array $propertiesFilter = null) {

        if (is_resource($data)) {
            $data = stream_get_contents($data);
        }
        $input = VObject\Reader::read($data);
        if (!empty($propertiesFilter)) {
            $propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter);
            $keys = array_unique(array_map(function($child) {
                return $child->name;
            }, $input->children()));
            $keys = array_diff($keys, $propertiesFilter);
            foreach ($keys as $key) {
                unset($input->$key);
            }
            $data = $input->serialize();
        }
        $output = null;
        try {

            switch ($target) {
                default :
                case 'vcard3' :
                    if ($input->getDocumentType() === VObject\Document::VCARD30) {
                        // Do nothing
                        return $data;
                    }
                    $output = $input->convert(VObject\Document::VCARD30);
                    return $output->serialize();
                case 'vcard4' :
                    if ($input->getDocumentType() === VObject\Document::VCARD40) {
                        // Do nothing
                        return $data;
                    }
                    $output = $input->convert(VObject\Document::VCARD40);
                    return $output->serialize();
                case 'jcard' :
                    $output = $input->convert(VObject\Document::VCARD40);
                    return json_encode($output);

            }

        } finally {

            // Destroy circular references to PHP will GC the object.
            $input->destroy();
            if (!is_null($output)) {
                $output->destroy();
            }
        }

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'carddav';

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds support for CardDAV (rfc6352)',
            'link'        => 'http://sabre.io/dav/carddav/',
        ];

    }

}
<?php

namespace Sabre\CardDAV;

use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\VObject;

/**
 * VCF Exporter
 *
 * This plugin adds the ability to export entire address books as .vcf files.
 * This is useful for clients that don't support CardDAV yet. They often do
 * support vcf files.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @author Thomas Tanghus (http://tanghus.net/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VCFExportPlugin extends DAV\ServerPlugin {

    /**
     * Reference to Server class
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * Initializes the plugin and registers event handlers
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;
        $this->server->on('method:GET', [$this, 'httpGet'], 90);
        $server->on('browserButtonActions', function($path, $node, &$actions) {
            if ($node instanceof IAddressBook) {
                $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="book"></span></a>';
            }
        });
    }

    /**
     * Intercepts GET requests on addressbook urls ending with ?export.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpGet(RequestInterface $request, ResponseInterface $response) {

        $queryParams = $request->getQueryParameters();
        if (!array_key_exists('export', $queryParams)) return;

        $path = $request->getPath();

        $node = $this->server->tree->getNodeForPath($path);

        if (!($node instanceof IAddressBook)) return;

        $this->server->transactionType = 'get-addressbook-export';

        // Checking ACL, if available.
        if ($aclPlugin = $this->server->getPlugin('acl')) {
            $aclPlugin->checkPrivileges($path, '{DAV:}read');
        }

        $nodes = $this->server->getPropertiesForPath($path, [
            '{' . Plugin::NS_CARDDAV . '}address-data',
        ], 1);

        $format = 'text/directory';

        $output = null;
        $filenameExtension = null;

        switch ($format) {
            case 'text/directory':
                $output = $this->generateVCF($nodes);
                $filenameExtension = '.vcf';
                break;
        }

        $filename = preg_replace(
            '/[^a-zA-Z0-9-_ ]/um',
            '',
            $node->getName()
        );
        $filename .= '-' . date('Y-m-d') . $filenameExtension;

        $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
        $response->setHeader('Content-Type', $format);

        $response->setStatus(200);
        $response->setBody($output);

        // Returning false to break the event chain
        return false;

    }

    /**
     * Merges all vcard objects, and builds one big vcf export
     *
     * @param array $nodes
     * @return string
     */
    function generateVCF(array $nodes) {

        $output = "";

        foreach ($nodes as $node) {

            if (!isset($node[200]['{' . Plugin::NS_CARDDAV . '}address-data'])) {
                continue;
            }
            $nodeData = $node[200]['{' . Plugin::NS_CARDDAV . '}address-data'];

            // Parsing this node so VObject can clean up the output.
            $vcard = VObject\Reader::read($nodeData);
            $output .= $vcard->serialize();

            // Destroy circular references to PHP will GC the object.
            $vcard->destroy();

        }

        return $output;

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'vcf-export';

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds the ability to export CardDAV addressbooks as a single vCard file.',
            'link'        => 'http://sabre.io/dav/vcf-export-plugin/',
        ];

    }

}
<?php

namespace Sabre\CardDAV\Xml\Filter;

use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * AddressData parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:carddav}address-data XML
 * element, as defined in:
 *
 * http://tools.ietf.org/html/rfc6352#section-10.4
 *
 * This element is used in two distinct places, but this one specifically
 * encodes the address-data element as it appears in the addressbook-query
 * adressbook-multiget REPORT requests.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AddressData implements XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = [
            'contentType' => $reader->getAttribute('content-type') ?: 'text/vcard',
            'version'     => $reader->getAttribute('version') ?: '3.0',
        ];

        $elems = (array)$reader->parseInnerTree();
        $result['addressDataProperties'] = array_map(function($element) {
            return $element['attributes']['name'];
        }, $elems);

        return $result;

    }

}
<?php

namespace Sabre\CardDAV\Xml\Filter;

use Sabre\CardDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;

/**
 * ParamFilter parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:carddav}param-filter XML
 * element, as defined in:
 *
 * http://tools.ietf.org/html/rfc6352#section-10.5.2
 *
 * The result will be spit out as an array.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class ParamFilter implements Element {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = [
            'name'           => null,
            'is-not-defined' => false,
            'text-match'     => null,
        ];

        $att = $reader->parseAttributes();
        $result['name'] = $att['name'];

        $elems = $reader->parseInnerTree();

        if (is_array($elems)) foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{' . Plugin::NS_CARDDAV . '}is-not-defined' :
                    $result['is-not-defined'] = true;
                    break;
                case '{' . Plugin::NS_CARDDAV . '}text-match' :
                    $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';

                    if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
                        throw new BadRequest('Unknown match-type: ' . $matchType);
                    }
                    $result['text-match'] = [
                        'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
                        'collation'        => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
                        'value'            => $elem['value'],
                        'match-type'       => $matchType,
                    ];
                    break;

            }

        }

        return $result;

    }

}
<?php

namespace Sabre\CardDAV\Xml\Filter;

use Sabre\CardDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * PropFilter parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:carddav}prop-filter XML
 * element, as defined in:
 *
 * http://tools.ietf.org/html/rfc6352#section-10.5.1
 *
 * The result will be spit out as an array.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PropFilter implements XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = [
            'name'           => null,
            'test'           => 'anyof',
            'is-not-defined' => false,
            'param-filters'  => [],
            'text-matches'   => [],
        ];

        $att = $reader->parseAttributes();
        $result['name'] = $att['name'];

        if (isset($att['test']) && $att['test'] === 'allof') {
            $result['test'] = 'allof';
        }

        $elems = $reader->parseInnerTree();

        if (is_array($elems)) foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{' . Plugin::NS_CARDDAV . '}param-filter' :
                    $result['param-filters'][] = $elem['value'];
                    break;
                case '{' . Plugin::NS_CARDDAV . '}is-not-defined' :
                    $result['is-not-defined'] = true;
                    break;
                case '{' . Plugin::NS_CARDDAV . '}text-match' :
                    $matchType = isset($elem['attributes']['match-type']) ? $elem['attributes']['match-type'] : 'contains';

                    if (!in_array($matchType, ['contains', 'equals', 'starts-with', 'ends-with'])) {
                        throw new BadRequest('Unknown match-type: ' . $matchType);
                    }
                    $result['text-matches'][] = [
                        'negate-condition' => isset($elem['attributes']['negate-condition']) && $elem['attributes']['negate-condition'] === 'yes',
                        'collation'        => isset($elem['attributes']['collation']) ? $elem['attributes']['collation'] : 'i;unicode-casemap',
                        'value'            => $elem['value'],
                        'match-type'       => $matchType,
                    ];
                    break;

            }

        }

        return $result;

    }

}
<?php

namespace Sabre\CardDAV\Xml\Property;

use Sabre\CardDAV\Plugin;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * Supported-address-data property
 *
 * This property is a representation of the supported-address-data property
 * in the CardDAV namespace.
 *
 * This property is defined in:
 *
 * http://tools.ietf.org/html/rfc6352#section-6.2.2
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedAddressData implements XmlSerializable {

    /**
     * supported versions
     *
     * @var array
     */
    protected $supportedData = [];

    /**
     * Creates the property
     *
     * @param array|null $supportedData
     */
    function __construct(array $supportedData = null) {

        if (is_null($supportedData)) {
            $supportedData = [
                ['contentType' => 'text/vcard', 'version' => '3.0'],
                ['contentType' => 'text/vcard', 'version' => '4.0'],
                ['contentType' => 'application/vcard+json', 'version' => '4.0'],
            ];
        }

        $this->supportedData = $supportedData;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->supportedData as $supported) {
            $writer->startElement('{' . Plugin::NS_CARDDAV . '}address-data-type');
            $writer->writeAttributes([
                'content-type' => $supported['contentType'],
                'version'      => $supported['version']
                ]);
            $writer->endElement(); // address-data-type
        }

    }

}
<?php

namespace Sabre\CardDAV\Xml\Property;

use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * supported-collation-set property
 *
 * This property is a representation of the supported-collation-set property
 * in the CardDAV namespace.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedCollationSet implements XmlSerializable {

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach (['i;ascii-casemap', 'i;octet', 'i;unicode-casemap'] as $coll) {
            $writer->writeElement('{urn:ietf:params:xml:ns:carddav}supported-collation', $coll);
        }

    }

}
<?php

namespace Sabre\CardDAV\Xml\Request;

use Sabre\CardDAV\Plugin;
use Sabre\Uri;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * AddressBookMultiGetReport request parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-multiget
 * REPORT, as defined in:
 *
 * http://tools.ietf.org/html/rfc6352#section-8.7
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AddressBookMultiGetReport implements XmlDeserializable {

    /**
     * An array with requested properties.
     *
     * @var array
     */
    public $properties;

    /**
     * This is an array with the urls that are being requested.
     *
     * @var array
     */
    public $hrefs;

    /**
     * The mimetype of the content that should be returend. Usually
     * text/vcard.
     *
     * @var string
     */
    public $contentType = null;

    /**
     * The version of vcard data that should be returned. Usually 3.0,
     * referring to vCard 3.0.
     *
     * @var string
     */
    public $version = null;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = $reader->parseInnerTree([
            '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
            '{DAV:}prop'                                   => 'Sabre\\Xml\\Element\\KeyValue',
        ]);

        $newProps = [
            'hrefs'      => [],
            'properties' => []
        ];

        foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{DAV:}prop' :
                    $newProps['properties'] = array_keys($elem['value']);
                    if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) {
                        $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'];
                    }
                    break;
                case '{DAV:}href' :
                    $newProps['hrefs'][] = Uri\resolve($reader->contextUri, $elem['value']);
                    break;

            }

        }

        $obj = new self();
        foreach ($newProps as $key => $value) {
            $obj->$key = $value;
        }
        return $obj;

    }

}
<?php

namespace Sabre\CardDAV\Xml\Request;

use Sabre\CardDAV\Plugin;
use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * AddressBookQueryReport request parser.
 *
 * This class parses the {urn:ietf:params:xml:ns:carddav}addressbook-query
 * REPORT, as defined in:
 *
 * http://tools.ietf.org/html/rfc6352#section-8.6
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AddressBookQueryReport implements XmlDeserializable {

    /**
     * An array with requested properties.
     *
     * @var array
     */
    public $properties;

    /**
     * An array with requested vcard properties.
     *
     * @var array
     */
    public $addressDataProperties = [];

    /**
     * List of property/component filters.
     *
     * This is an array with filters. Every item is a property filter. Every
     * property filter has the following keys:
     *   * name - name of the component to filter on
     *   * test - anyof or allof
     *   * is-not-defined - Test for non-existence
     *   * param-filters - A list of parameter filters on the property
     *   * text-matches - A list of text values the filter needs to match
     *
     * Each param-filter has the following keys:
     *   * name - name of the parameter
     *   * is-not-defined - Test for non-existence
     *   * text-match - Match the parameter value
     *
     * Each text-match in property filters, and the single text-match in
     * param-filters have the following keys:
     *
     *   * value - value to match
     *   * match-type - contains, starts-with, ends-with, equals
     *   * negate-condition - Do the opposite match
     *   * collation - Usually i;unicode-casemap
     *
     * @var array
     */
    public $filters;

    /**
     * The number of results the client wants
     *
     * null means it wasn't specified, which in most cases means 'all results'.
     *
     * @var int|null
     */
    public $limit;

    /**
     * Either 'anyof' or 'allof'
     *
     * @var string
     */
    public $test;

    /**
     * The mimetype of the content that should be returend. Usually
     * text/vcard.
     *
     * @var string
     */
    public $contentType = null;

    /**
     * The version of vcard data that should be returned. Usually 3.0,
     * referring to vCard 3.0.
     *
     * @var string
     */
    public $version = null;


    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = (array)$reader->parseInnerTree([
            '{urn:ietf:params:xml:ns:carddav}prop-filter'  => 'Sabre\\CardDAV\\Xml\\Filter\\PropFilter',
            '{urn:ietf:params:xml:ns:carddav}param-filter' => 'Sabre\\CardDAV\\Xml\\Filter\\ParamFilter',
            '{urn:ietf:params:xml:ns:carddav}address-data' => 'Sabre\\CardDAV\\Xml\\Filter\\AddressData',
            '{DAV:}prop'                                   => 'Sabre\\Xml\\Element\\KeyValue',
        ]);

        $newProps = [
            'filters'    => null,
            'properties' => [],
            'test'       => 'anyof',
            'limit'      => null,
        ];

        if (!is_array($elems)) $elems = [];

        foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{DAV:}prop' :
                    $newProps['properties'] = array_keys($elem['value']);
                    if (isset($elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'])) {
                        $newProps += $elem['value']['{' . Plugin::NS_CARDDAV . '}address-data'];
                    }
                    break;
                case '{' . Plugin::NS_CARDDAV . '}filter' :

                    if (!is_null($newProps['filters'])) {
                        throw new BadRequest('You can only include 1 {' . Plugin::NS_CARDDAV . '}filter element');
                    }
                    if (isset($elem['attributes']['test'])) {
                        $newProps['test'] = $elem['attributes']['test'];
                        if ($newProps['test'] !== 'allof' && $newProps['test'] !== 'anyof') {
                            throw new BadRequest('The "test" attribute must be one of "allof" or "anyof"');
                        }
                    }

                    $newProps['filters'] = [];
                    foreach ((array)$elem['value'] as $subElem) {
                        if ($subElem['name'] === '{' . Plugin::NS_CARDDAV . '}prop-filter') {
                            $newProps['filters'][] = $subElem['value'];
                        }
                    }
                    break;
                case '{' . Plugin::NS_CARDDAV . '}limit' :
                    foreach ($elem['value'] as $child) {
                        if ($child['name'] === '{' . Plugin::NS_CARDDAV . '}nresults') {
                            $newProps['limit'] = (int)$child['value'];
                        }
                    }
                    break;

            }

        }

        if (is_null($newProps['filters'])) {
            /*
             * We are supposed to throw this error, but KDE sometimes does not
             * include the filter element, and we need to treat it as if no
             * filters are supplied
             */
            //throw new BadRequest('The {' . Plugin::NS_CARDDAV . '}filter element is required for this request');
            $newProps['filters'] = [];

        }

        $obj = new self();
        foreach ($newProps as $key => $value) {
            $obj->$key = $value;
        }

        return $obj;

    }

}
<?php

namespace Sabre\DAV\Auth\Backend;

use Sabre\DAV;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * HTTP Basic authentication backend class
 *
 * This class can be used by authentication objects wishing to use HTTP Basic
 * Most of the digest logic is handled, implementors just need to worry about
 * the validateUserPass method.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author James David Low (http://jameslow.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractBasic implements BackendInterface {

    /**
     * Authentication Realm.
     *
     * The realm is often displayed by browser clients when showing the
     * authentication dialog.
     *
     * @var string
     */
    protected $realm = 'sabre/dav';

    /**
     * This is the prefix that will be used to generate principal urls.
     *
     * @var string
     */
    protected $principalPrefix = 'principals/';

    /**
     * Validates a username and password
     *
     * This method should return true or false depending on if login
     * succeeded.
     *
     * @param string $username
     * @param string $password
     * @return bool
     */
    abstract protected function validateUserPass($username, $password);

    /**
     * Sets the authentication realm for this backend.
     *
     * @param string $realm
     * @return void
     */
    function setRealm($realm) {

        $this->realm = $realm;

    }

    /**
     * When this method is called, the backend must check if authentication was
     * successful.
     *
     * The returned value must be one of the following
     *
     * [true, "principals/username"]
     * [false, "reason for failure"]
     *
     * If authentication was successful, it's expected that the authentication
     * backend returns a so-called principal url.
     *
     * Examples of a principal url:
     *
     * principals/admin
     * principals/user1
     * principals/users/joe
     * principals/uid/123457
     *
     * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
     * return a string such as:
     *
     * principals/users/[username]
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return array
     */
    function check(RequestInterface $request, ResponseInterface $response) {

        $auth = new HTTP\Auth\Basic(
            $this->realm,
            $request,
            $response
        );

        $userpass = $auth->getCredentials();
        if (!$userpass) {
            return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"];
        }
        if (!$this->validateUserPass($userpass[0], $userpass[1])) {
            return [false, "Username or password was incorrect"];
        }
        return [true, $this->principalPrefix . $userpass[0]];

    }

    /**
     * This method is called when a user could not be authenticated, and
     * authentication was required for the current request.
     *
     * This gives you the opportunity to set authentication headers. The 401
     * status code will already be set.
     *
     * In this case of Basic Auth, this would for example mean that the
     * following header needs to be set:
     *
     * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
     *
     * Keep in mind that in the case of multiple authentication backends, other
     * WWW-Authenticate headers may already have been set, and you'll want to
     * append your own WWW-Authenticate header instead of overwriting the
     * existing one.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function challenge(RequestInterface $request, ResponseInterface $response) {

        $auth = new HTTP\Auth\Basic(
            $this->realm,
            $request,
            $response
        );
        $auth->requireLogin();

    }

}
<?php

namespace Sabre\DAV\Auth\Backend;

use Sabre\DAV;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * HTTP Bearer authentication backend class
 *
 * This class can be used by authentication objects wishing to use HTTP Bearer
 * Most of the digest logic is handled, implementors just need to worry about
 * the validateBearerToken method.
 *
 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
 * @author François Kooman (https://tuxed.net/)
 * @author James David Low (http://jameslow.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractBearer implements BackendInterface {

    /**
     * Authentication Realm.
     *
     * The realm is often displayed by browser clients when showing the
     * authentication dialog.
     *
     * @var string
     */
    protected $realm = 'sabre/dav';

    /**
     * Validates a Bearer token
     *
     * This method should return the full principal url, or false if the
     * token was incorrect.
     *
     * @param string $bearerToken
     * @return string|false
     */
    abstract protected function validateBearerToken($bearerToken);

    /**
     * Sets the authentication realm for this backend.
     *
     * @param string $realm
     * @return void
     */
    function setRealm($realm) {

        $this->realm = $realm;

    }

    /**
     * When this method is called, the backend must check if authentication was
     * successful.
     *
     * The returned value must be one of the following
     *
     * [true, "principals/username"]
     * [false, "reason for failure"]
     *
     * If authentication was successful, it's expected that the authentication
     * backend returns a so-called principal url.
     *
     * Examples of a principal url:
     *
     * principals/admin
     * principals/user1
     * principals/users/joe
     * principals/uid/123457
     *
     * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
     * return a string such as:
     *
     * principals/users/[username]
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return array
     */
    function check(RequestInterface $request, ResponseInterface $response) {

        $auth = new HTTP\Auth\Bearer(
            $this->realm,
            $request,
            $response
        );

        $bearerToken = $auth->getToken($request);
        if (!$bearerToken) {
            return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"];
        }
        $principalUrl = $this->validateBearerToken($bearerToken);
        if (!$principalUrl) {
            return [false, "Bearer token was incorrect"];
        }
        return [true, $principalUrl];

    }

    /**
     * This method is called when a user could not be authenticated, and
     * authentication was required for the current request.
     *
     * This gives you the opportunity to set authentication headers. The 401
     * status code will already be set.
     *
     * In this case of Bearer Auth, this would for example mean that the
     * following header needs to be set:
     *
     * $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV');
     *
     * Keep in mind that in the case of multiple authentication backends, other
     * WWW-Authenticate headers may already have been set, and you'll want to
     * append your own WWW-Authenticate header instead of overwriting the
     * existing one.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function challenge(RequestInterface $request, ResponseInterface $response) {

        $auth = new HTTP\Auth\Bearer(
            $this->realm,
            $request,
            $response
        );
        $auth->requireLogin();

    }

}
<?php

namespace Sabre\DAV\Auth\Backend;

use Sabre\DAV;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * HTTP Digest authentication backend class
 *
 * This class can be used by authentication objects wishing to use HTTP Digest
 * Most of the digest logic is handled, implementors just need to worry about
 * the getDigestHash method
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractDigest implements BackendInterface {

    /**
     * Authentication Realm.
     *
     * The realm is often displayed by browser clients when showing the
     * authentication dialog.
     *
     * @var string
     */
    protected $realm = 'SabreDAV';

    /**
     * This is the prefix that will be used to generate principal urls.
     *
     * @var string
     */
    protected $principalPrefix = 'principals/';

    /**
     * Sets the authentication realm for this backend.
     *
     * Be aware that for Digest authentication, the realm influences the digest
     * hash. Choose the realm wisely, because if you change it later, all the
     * existing hashes will break and nobody can authenticate.
     *
     * @param string $realm
     * @return void
     */
    function setRealm($realm) {

        $this->realm = $realm;

    }

    /**
     * Returns a users digest hash based on the username and realm.
     *
     * If the user was not known, null must be returned.
     *
     * @param string $realm
     * @param string $username
     * @return string|null
     */
    abstract function getDigestHash($realm, $username);

    /**
     * When this method is called, the backend must check if authentication was
     * successful.
     *
     * The returned value must be one of the following
     *
     * [true, "principals/username"]
     * [false, "reason for failure"]
     *
     * If authentication was successful, it's expected that the authentication
     * backend returns a so-called principal url.
     *
     * Examples of a principal url:
     *
     * principals/admin
     * principals/user1
     * principals/users/joe
     * principals/uid/123457
     *
     * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
     * return a string such as:
     *
     * principals/users/[username]
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return array
     */
    function check(RequestInterface $request, ResponseInterface $response) {

        $digest = new HTTP\Auth\Digest(
            $this->realm,
            $request,
            $response
        );
        $digest->init();

        $username = $digest->getUsername();

        // No username was given
        if (!$username) {
            return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"];
        }

        $hash = $this->getDigestHash($this->realm, $username);
        // If this was false, the user account didn't exist
        if ($hash === false || is_null($hash)) {
            return [false, "Username or password was incorrect"];
        }
        if (!is_string($hash)) {
            throw new DAV\Exception('The returned value from getDigestHash must be a string or null');
        }

        // If this was false, the password or part of the hash was incorrect.
        if (!$digest->validateA1($hash)) {
            return [false, "Username or password was incorrect"];
        }

        return [true, $this->principalPrefix . $username];

    }

    /**
     * This method is called when a user could not be authenticated, and
     * authentication was required for the current request.
     *
     * This gives you the opportunity to set authentication headers. The 401
     * status code will already be set.
     *
     * In this case of Basic Auth, this would for example mean that the
     * following header needs to be set:
     *
     * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
     *
     * Keep in mind that in the case of multiple authentication backends, other
     * WWW-Authenticate headers may already have been set, and you'll want to
     * append your own WWW-Authenticate header instead of overwriting the
     * existing one.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function challenge(RequestInterface $request, ResponseInterface $response) {

        $auth = new HTTP\Auth\Digest(
            $this->realm,
            $request,
            $response
        );
        $auth->init();

        $oldStatus = $response->getStatus() ?: 200;
        $auth->requireLogin();

        // Preventing the digest utility from modifying the http status code,
        // this should be handled by the main plugin.
        $response->setStatus($oldStatus);

    }

}
<?php

namespace Sabre\DAV\Auth\Backend;

use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * Apache authenticator
 *
 * This authentication backend assumes that authentication has been
 * configured in apache, rather than within SabreDAV.
 *
 * Make sure apache is properly configured for this to work.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Apache implements BackendInterface {

    /**
     * This is the prefix that will be used to generate principal urls.
     *
     * @var string
     */
    protected $principalPrefix = 'principals/';

    /**
     * When this method is called, the backend must check if authentication was
     * successful.
     *
     * The returned value must be one of the following
     *
     * [true, "principals/username"]
     * [false, "reason for failure"]
     *
     * If authentication was successful, it's expected that the authentication
     * backend returns a so-called principal url.
     *
     * Examples of a principal url:
     *
     * principals/admin
     * principals/user1
     * principals/users/joe
     * principals/uid/123457
     *
     * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
     * return a string such as:
     *
     * principals/users/[username]
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return array
     */
    function check(RequestInterface $request, ResponseInterface $response) {

        $remoteUser = $request->getRawServerValue('REMOTE_USER');
        if (is_null($remoteUser)) {
            $remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER');
        }
        if (is_null($remoteUser)) {
            return [false, 'No REMOTE_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly'];
        }

        return [true, $this->principalPrefix . $remoteUser];

    }

    /**
     * This method is called when a user could not be authenticated, and
     * authentication was required for the current request.
     *
     * This gives you the opportunity to set authentication headers. The 401
     * status code will already be set.
     *
     * In this case of Basic Auth, this would for example mean that the
     * following header needs to be set:
     *
     * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
     *
     * Keep in mind that in the case of multiple authentication backends, other
     * WWW-Authenticate headers may already have been set, and you'll want to
     * append your own WWW-Authenticate header instead of overwriting the
     * existing one.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function challenge(RequestInterface $request, ResponseInterface $response) {

    }

}
<?php

namespace Sabre\DAV\Auth\Backend;

use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * This is the base class for any authentication object.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface BackendInterface {

    /**
     * When this method is called, the backend must check if authentication was
     * successful.
     *
     * The returned value must be one of the following
     *
     * [true, "principals/username"]
     * [false, "reason for failure"]
     *
     * If authentication was successful, it's expected that the authentication
     * backend returns a so-called principal url.
     *
     * Examples of a principal url:
     *
     * principals/admin
     * principals/user1
     * principals/users/joe
     * principals/uid/123457
     *
     * If you don't use WebDAV ACL (RFC3744) we recommend that you simply
     * return a string such as:
     *
     * principals/users/[username]
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return array
     */
    function check(RequestInterface $request, ResponseInterface $response);

    /**
     * This method is called when a user could not be authenticated, and
     * authentication was required for the current request.
     *
     * This gives you the opportunity to set authentication headers. The 401
     * status code will already be set.
     *
     * In this case of Basic Auth, this would for example mean that the
     * following header needs to be set:
     *
     * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV');
     *
     * Keep in mind that in the case of multiple authentication backends, other
     * WWW-Authenticate headers may already have been set, and you'll want to
     * append your own WWW-Authenticate header instead of overwriting the
     * existing one.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function challenge(RequestInterface $request, ResponseInterface $response);

}
<?php

namespace Sabre\DAV\Auth\Backend;

/**
 * Extremely simply HTTP Basic auth backend.
 *
 * This backend basically works by calling a callback, which receives a
 * username and password.
 * The callback must return true or false depending on if authentication was
 * correct.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class BasicCallBack extends AbstractBasic {

    /**
     * Callback
     *
     * @var callable
     */
    protected $callBack;

    /**
     * Creates the backend.
     *
     * A callback must be provided to handle checking the username and
     * password.
     *
     * @param callable $callBack
     * @return void
     */
    function __construct(callable $callBack) {

        $this->callBack = $callBack;

    }

    /**
     * Validates a username and password
     *
     * This method should return true or false depending on if login
     * succeeded.
     *
     * @param string $username
     * @param string $password
     * @return bool
     */
    protected function validateUserPass($username, $password) {

        $cb = $this->callBack;
        return $cb($username, $password);

    }

}
<?php

namespace Sabre\DAV\Auth\Backend;

use Sabre\DAV;

/**
 * This is an authentication backend that uses a file to manage passwords.
 *
 * The backend file must conform to Apache's htdigest format
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class File extends AbstractDigest {

    /**
     * List of users
     *
     * @var array
     */
    protected $users = [];

    /**
     * Creates the backend object.
     *
     * If the filename argument is passed in, it will parse out the specified file first.
     *
     * @param string|null $filename
     */
    function __construct($filename = null) {

        if (!is_null($filename))
            $this->loadFile($filename);

    }

    /**
     * Loads an htdigest-formatted file. This method can be called multiple times if
     * more than 1 file is used.
     *
     * @param string $filename
     * @return void
     */
    function loadFile($filename) {

        foreach (file($filename, FILE_IGNORE_NEW_LINES) as $line) {

            if (substr_count($line, ":") !== 2)
                throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons');

            list($username, $realm, $A1) = explode(':', $line);

            if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1))
                throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash');

            $this->users[$realm . ':' . $username] = $A1;

        }

    }

    /**
     * Returns a users' information
     *
     * @param string $realm
     * @param string $username
     * @return string
     */
    function getDigestHash($realm, $username) {

        return isset($this->users[$realm . ':' . $username]) ? $this->users[$realm . ':' . $username] : false;

    }

}
<?php

namespace Sabre\DAV\Auth\Backend;

/**
 * This is an authentication backend that uses a database to manage passwords.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PDO extends AbstractDigest {

    /**
     * Reference to PDO connection
     *
     * @var PDO
     */
    protected $pdo;

    /**
     * PDO table name we'll be using
     *
     * @var string
     */
    public $tableName = 'users';


    /**
     * Creates the backend object.
     *
     * If the filename argument is passed in, it will parse out the specified file fist.
     *
     * @param \PDO $pdo
     */
    function __construct(\PDO $pdo) {

        $this->pdo = $pdo;

    }

    /**
     * Returns the digest hash for a user.
     *
     * @param string $realm
     * @param string $username
     * @return string|null
     */
    function getDigestHash($realm, $username) {

        $stmt = $this->pdo->prepare('SELECT digesta1 FROM ' . $this->tableName . ' WHERE username = ?');
        $stmt->execute([$username]);
        return $stmt->fetchColumn() ?: null;

    }

}
<?php

namespace Sabre\DAV\Auth;

use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * This plugin provides Authentication for a WebDAV server.
 *
 * It works by providing a Auth\Backend class. Several examples of these
 * classes can be found in the Backend directory.
 *
 * It's possible to provide more than one backend to this plugin. If more than
 * one backend was provided, each backend will attempt to authenticate. Only if
 * all backends fail, we throw a 401.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends ServerPlugin {

    /**
     * By default this plugin will require that the user is authenticated,
     * and refuse any access if the user is not authenticated.
     *
     * If this setting is set to false, we let the user through, whether they
     * are authenticated or not.
     *
     * This is useful if you want to allow both authenticated and
     * unauthenticated access to your server.
     *
     * @param bool
     */
    public $autoRequireLogin = true;

    /**
     * authentication backends
     */
    protected $backends;

    /**
     * The currently logged in principal. Will be `null` if nobody is currently
     * logged in.
     *
     * @var string|null
     */
    protected $currentPrincipal;

    /**
     * Creates the authentication plugin
     *
     * @param Backend\BackendInterface $authBackend
     */
    function __construct(Backend\BackendInterface $authBackend = null) {

        if (!is_null($authBackend)) {
            $this->addBackend($authBackend);
        }

    }

    /**
     * Adds an authentication backend to the plugin.
     *
     * @param Backend\BackendInterface $authBackend
     * @return void
     */
    function addBackend(Backend\BackendInterface $authBackend) {

        $this->backends[] = $authBackend;

    }

    /**
     * Initializes the plugin. This function is automatically called by the server
     *
     * @param Server $server
     * @return void
     */
    function initialize(Server $server) {

        $server->on('beforeMethod', [$this, 'beforeMethod'], 10);

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'auth';

    }

    /**
     * Returns the currently logged-in principal.
     *
     * This will return a string such as:
     *
     * principals/username
     * principals/users/username
     *
     * This method will return null if nobody is logged in.
     *
     * @return string|null
     */
    function getCurrentPrincipal() {

        return $this->currentPrincipal;

    }

    /**
     * This method is called before any HTTP method and forces users to be authenticated
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function beforeMethod(RequestInterface $request, ResponseInterface $response) {

        if ($this->currentPrincipal) {

            // We already have authentication information. This means that the
            // event has already fired earlier, and is now likely fired for a
            // sub-request.
            //
            // We don't want to authenticate users twice, so we simply don't do
            // anything here. See Issue #700 for additional reasoning.
            //
            // This is not a perfect solution, but will be fixed once the
            // "currently authenticated principal" is information that's not
            // not associated with the plugin, but rather per-request.
            //
            // See issue #580 for more information about that.
            return;

        }

        $authResult = $this->check($request, $response);

        if ($authResult[0]) {
            // Auth was successful
            $this->currentPrincipal = $authResult[1];
            $this->loginFailedReasons = null;
            return;
        }



        // If we got here, it means that no authentication backend was
        // successful in authenticating the user.
        $this->currentPrincipal = null;
        $this->loginFailedReasons = $authResult[1];

        if ($this->autoRequireLogin) {
            $this->challenge($request, $response);
            throw new NotAuthenticated(implode(', ', $authResult[1]));
        }

    }

    /**
     * Checks authentication credentials, and logs the user in if possible.
     *
     * This method returns an array. The first item in the array is a boolean
     * indicating if login was successful.
     *
     * If login was successful, the second item in the array will contain the
     * current principal url/path of the logged in user.
     *
     * If login was not successful, the second item in the array will contain a
     * an array with strings. The strings are a list of reasons why login was
     * unsuccessful. For every auth backend there will be one reason, so usually
     * there's just one.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return array
     */
    function check(RequestInterface $request, ResponseInterface $response) {

        if (!$this->backends) {
            throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.');
        }
        $reasons = [];
        foreach ($this->backends as $backend) {

            $result = $backend->check(
                $request,
                $response
            );

            if (!is_array($result) || count($result) !== 2 || !is_bool($result[0]) || !is_string($result[1])) {
                throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.');
            }

            if ($result[0]) {
                $this->currentPrincipal = $result[1];
                // Exit early
                return [true, $result[1]];
            }
            $reasons[] = $result[1];

        }

        return [false, $reasons];

    }

    /**
     * This method sends authentication challenges to the user.
     *
     * This method will for example cause a HTTP Basic backend to set a
     * WWW-Authorization header, indicating to the client that it should
     * authenticate.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return array
     */
    function challenge(RequestInterface $request, ResponseInterface $response) {

        foreach ($this->backends as $backend) {
            $backend->challenge($request, $response);
        }

    }

    /**
     * List of reasons why login failed for the last login operation.
     *
     * @var string[]|null
     */
    protected $loginFailedReasons;

    /**
     * Returns a list of reasons why login was unsuccessful.
     *
     * This method will return the login failed reasons for the last login
     * operation. One for each auth backend.
     *
     * This method returns null if the last authentication attempt was
     * successful, or if there was no authentication attempt yet.
     *
     * @return string[]|null
     */
    function getLoginFailedReasons() {

        return $this->loginFailedReasons;

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Generic authentication plugin',
            'link'        => 'http://sabre.io/dav/authentication/',
        ];

    }

}
                (       @                                                                                          5  ~                Q  
                                                      ^                                                                       #                                  t                                     V                                        
                            `                                                                  @                                                                                      .                            w                                                                                                                                                                                              W              
                                                                U    )                    +        8                                     \                  `           w                8                     {                w                                                                                                N                        ~            V                                                         l          +                                 3        /                  B                                                     a                      (                       #                                         {                               
                #                            +                                            0              +                                                          ?                               G                         #                9                                                       j                                                                                                             +              F                         
            C                                     I                                     n                                       6                                                                                                                                                             ~                                                                           p  g  P                                                                                    <x< | ?The MIT License (MIT)

Copyright (c) 2014 Waybury

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.@font-face {
  font-family: 'Icons';
  src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot');
  src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot?#iconic-sm') format('embedded-opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.woff') format('woff'), url('?sabreAction=asset&assetName=openiconic/open-iconic.ttf') format('truetype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.otf') format('opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.svg#iconic-sm') format('svg');
  font-weight: normal;
  font-style: normal;
}

.oi[data-glyph].oi-text-replace {
  font-size: 0;
  line-height: 0;
}

.oi[data-glyph].oi-text-replace:before {
  width: 1em;
  text-align: center;
}

.oi[data-glyph]:before {
  font-family: 'Icons';
  display: inline-block;
  speak: none;
  line-height: 1;
  vertical-align: baseline;
  font-weight: normal;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.oi[data-glyph]:empty:before {
  width: 1em;
  text-align: center;
  box-sizing: content-box;
}

.oi[data-glyph].oi-align-left:before {
  text-align: left;
}

.oi[data-glyph].oi-align-right:before {
  text-align: right;
}

.oi[data-glyph].oi-align-center:before {
  text-align: center;
}

.oi[data-glyph].oi-flip-horizontal:before {
  -webkit-transform: scale(-1, 1);
  -ms-transform: scale(-1, 1);
  transform: scale(-1, 1);
}
.oi[data-glyph].oi-flip-vertical:before {
  -webkit-transform: scale(1, -1);
  -ms-transform: scale(-1, 1);
  transform: scale(1, -1);
}
.oi[data-glyph].oi-flip-horizontal-vertical:before {
  -webkit-transform: scale(-1, -1);
  -ms-transform: scale(-1, 1);
  transform: scale(-1, -1);
}


.oi[data-glyph=account-login]:before { content:'\e000'; }

.oi[data-glyph=account-logout]:before { content:'\e001'; }

.oi[data-glyph=action-redo]:before { content:'\e002'; }

.oi[data-glyph=action-undo]:before { content:'\e003'; }

.oi[data-glyph=align-center]:before { content:'\e004'; }

.oi[data-glyph=align-left]:before { content:'\e005'; }

.oi[data-glyph=align-right]:before { content:'\e006'; }

.oi[data-glyph=aperture]:before { content:'\e007'; }

.oi[data-glyph=arrow-bottom]:before { content:'\e008'; }

.oi[data-glyph=arrow-circle-bottom]:before { content:'\e009'; }

.oi[data-glyph=arrow-circle-left]:before { content:'\e00a'; }

.oi[data-glyph=arrow-circle-right]:before { content:'\e00b'; }

.oi[data-glyph=arrow-circle-top]:before { content:'\e00c'; }

.oi[data-glyph=arrow-left]:before { content:'\e00d'; }

.oi[data-glyph=arrow-right]:before { content:'\e00e'; }

.oi[data-glyph=arrow-thick-bottom]:before { content:'\e00f'; }

.oi[data-glyph=arrow-thick-left]:before { content:'\e010'; }

.oi[data-glyph=arrow-thick-right]:before { content:'\e011'; }

.oi[data-glyph=arrow-thick-top]:before { content:'\e012'; }

.oi[data-glyph=arrow-top]:before { content:'\e013'; }

.oi[data-glyph=audio-spectrum]:before { content:'\e014'; }

.oi[data-glyph=audio]:before { content:'\e015'; }

.oi[data-glyph=badge]:before { content:'\e016'; }

.oi[data-glyph=ban]:before { content:'\e017'; }

.oi[data-glyph=bar-chart]:before { content:'\e018'; }

.oi[data-glyph=basket]:before { content:'\e019'; }

.oi[data-glyph=battery-empty]:before { content:'\e01a'; }

.oi[data-glyph=battery-full]:before { content:'\e01b'; }

.oi[data-glyph=beaker]:before { content:'\e01c'; }

.oi[data-glyph=bell]:before { content:'\e01d'; }

.oi[data-glyph=bluetooth]:before { content:'\e01e'; }

.oi[data-glyph=bold]:before { content:'\e01f'; }

.oi[data-glyph=bolt]:before { content:'\e020'; }

.oi[data-glyph=book]:before { content:'\e021'; }

.oi[data-glyph=bookmark]:before { content:'\e022'; }

.oi[data-glyph=box]:before { content:'\e023'; }

.oi[data-glyph=briefcase]:before { content:'\e024'; }

.oi[data-glyph=british-pound]:before { content:'\e025'; }

.oi[data-glyph=browser]:before { content:'\e026'; }

.oi[data-glyph=brush]:before { content:'\e027'; }

.oi[data-glyph=bug]:before { content:'\e028'; }

.oi[data-glyph=bullhorn]:before { content:'\e029'; }

.oi[data-glyph=calculator]:before { content:'\e02a'; }

.oi[data-glyph=calendar]:before { content:'\e02b'; }

.oi[data-glyph=camera-slr]:before { content:'\e02c'; }

.oi[data-glyph=caret-bottom]:before { content:'\e02d'; }

.oi[data-glyph=caret-left]:before { content:'\e02e'; }

.oi[data-glyph=caret-right]:before { content:'\e02f'; }

.oi[data-glyph=caret-top]:before { content:'\e030'; }

.oi[data-glyph=cart]:before { content:'\e031'; }

.oi[data-glyph=chat]:before { content:'\e032'; }

.oi[data-glyph=check]:before { content:'\e033'; }

.oi[data-glyph=chevron-bottom]:before { content:'\e034'; }

.oi[data-glyph=chevron-left]:before { content:'\e035'; }

.oi[data-glyph=chevron-right]:before { content:'\e036'; }

.oi[data-glyph=chevron-top]:before { content:'\e037'; }

.oi[data-glyph=circle-check]:before { content:'\e038'; }

.oi[data-glyph=circle-x]:before { content:'\e039'; }

.oi[data-glyph=clipboard]:before { content:'\e03a'; }

.oi[data-glyph=clock]:before { content:'\e03b'; }

.oi[data-glyph=cloud-download]:before { content:'\e03c'; }

.oi[data-glyph=cloud-upload]:before { content:'\e03d'; }

.oi[data-glyph=cloud]:before { content:'\e03e'; }

.oi[data-glyph=cloudy]:before { content:'\e03f'; }

.oi[data-glyph=code]:before { content:'\e040'; }

.oi[data-glyph=cog]:before { content:'\e041'; }

.oi[data-glyph=collapse-down]:before { content:'\e042'; }

.oi[data-glyph=collapse-left]:before { content:'\e043'; }

.oi[data-glyph=collapse-right]:before { content:'\e044'; }

.oi[data-glyph=collapse-up]:before { content:'\e045'; }

.oi[data-glyph=command]:before { content:'\e046'; }

.oi[data-glyph=comment-square]:before { content:'\e047'; }

.oi[data-glyph=compass]:before { content:'\e048'; }

.oi[data-glyph=contrast]:before { content:'\e049'; }

.oi[data-glyph=copywriting]:before { content:'\e04a'; }

.oi[data-glyph=credit-card]:before { content:'\e04b'; }

.oi[data-glyph=crop]:before { content:'\e04c'; }

.oi[data-glyph=dashboard]:before { content:'\e04d'; }

.oi[data-glyph=data-transfer-download]:before { content:'\e04e'; }

.oi[data-glyph=data-transfer-upload]:before { content:'\e04f'; }

.oi[data-glyph=delete]:before { content:'\e050'; }

.oi[data-glyph=dial]:before { content:'\e051'; }

.oi[data-glyph=document]:before { content:'\e052'; }

.oi[data-glyph=dollar]:before { content:'\e053'; }

.oi[data-glyph=double-quote-sans-left]:before { content:'\e054'; }

.oi[data-glyph=double-quote-sans-right]:before { content:'\e055'; }

.oi[data-glyph=double-quote-serif-left]:before { content:'\e056'; }

.oi[data-glyph=double-quote-serif-right]:before { content:'\e057'; }

.oi[data-glyph=droplet]:before { content:'\e058'; }

.oi[data-glyph=eject]:before { content:'\e059'; }

.oi[data-glyph=elevator]:before { content:'\e05a'; }

.oi[data-glyph=ellipses]:before { content:'\e05b'; }

.oi[data-glyph=envelope-closed]:before { content:'\e05c'; }

.oi[data-glyph=envelope-open]:before { content:'\e05d'; }

.oi[data-glyph=euro]:before { content:'\e05e'; }

.oi[data-glyph=excerpt]:before { content:'\e05f'; }

.oi[data-glyph=expand-down]:before { content:'\e060'; }

.oi[data-glyph=expand-left]:before { content:'\e061'; }

.oi[data-glyph=expand-right]:before { content:'\e062'; }

.oi[data-glyph=expand-up]:before { content:'\e063'; }

.oi[data-glyph=external-link]:before { content:'\e064'; }

.oi[data-glyph=eye]:before { content:'\e065'; }

.oi[data-glyph=eyedropper]:before { content:'\e066'; }

.oi[data-glyph=file]:before { content:'\e067'; }

.oi[data-glyph=fire]:before { content:'\e068'; }

.oi[data-glyph=flag]:before { content:'\e069'; }

.oi[data-glyph=flash]:before { content:'\e06a'; }

.oi[data-glyph=folder]:before { content:'\e06b'; }

.oi[data-glyph=fork]:before { content:'\e06c'; }

.oi[data-glyph=fullscreen-enter]:before { content:'\e06d'; }

.oi[data-glyph=fullscreen-exit]:before { content:'\e06e'; }

.oi[data-glyph=globe]:before { content:'\e06f'; }

.oi[data-glyph=graph]:before { content:'\e070'; }

.oi[data-glyph=grid-four-up]:before { content:'\e071'; }

.oi[data-glyph=grid-three-up]:before { content:'\e072'; }

.oi[data-glyph=grid-two-up]:before { content:'\e073'; }

.oi[data-glyph=hard-drive]:before { content:'\e074'; }

.oi[data-glyph=header]:before { content:'\e075'; }

.oi[data-glyph=headphones]:before { content:'\e076'; }

.oi[data-glyph=heart]:before { content:'\e077'; }

.oi[data-glyph=home]:before { content:'\e078'; }

.oi[data-glyph=image]:before { content:'\e079'; }

.oi[data-glyph=inbox]:before { content:'\e07a'; }

.oi[data-glyph=infinity]:before { content:'\e07b'; }

.oi[data-glyph=info]:before { content:'\e07c'; }

.oi[data-glyph=italic]:before { content:'\e07d'; }

.oi[data-glyph=justify-center]:before { content:'\e07e'; }

.oi[data-glyph=justify-left]:before { content:'\e07f'; }

.oi[data-glyph=justify-right]:before { content:'\e080'; }

.oi[data-glyph=key]:before { content:'\e081'; }

.oi[data-glyph=laptop]:before { content:'\e082'; }

.oi[data-glyph=layers]:before { content:'\e083'; }

.oi[data-glyph=lightbulb]:before { content:'\e084'; }

.oi[data-glyph=link-broken]:before { content:'\e085'; }

.oi[data-glyph=link-intact]:before { content:'\e086'; }

.oi[data-glyph=list-rich]:before { content:'\e087'; }

.oi[data-glyph=list]:before { content:'\e088'; }

.oi[data-glyph=location]:before { content:'\e089'; }

.oi[data-glyph=lock-locked]:before { content:'\e08a'; }

.oi[data-glyph=lock-unlocked]:before { content:'\e08b'; }

.oi[data-glyph=loop-circular]:before { content:'\e08c'; }

.oi[data-glyph=loop-square]:before { content:'\e08d'; }

.oi[data-glyph=loop]:before { content:'\e08e'; }

.oi[data-glyph=magnifying-glass]:before { content:'\e08f'; }

.oi[data-glyph=map-marker]:before { content:'\e090'; }

.oi[data-glyph=map]:before { content:'\e091'; }

.oi[data-glyph=media-pause]:before { content:'\e092'; }

.oi[data-glyph=media-play]:before { content:'\e093'; }

.oi[data-glyph=media-record]:before { content:'\e094'; }

.oi[data-glyph=media-skip-backward]:before { content:'\e095'; }

.oi[data-glyph=media-skip-forward]:before { content:'\e096'; }

.oi[data-glyph=media-step-backward]:before { content:'\e097'; }

.oi[data-glyph=media-step-forward]:before { content:'\e098'; }

.oi[data-glyph=media-stop]:before { content:'\e099'; }

.oi[data-glyph=medical-cross]:before { content:'\e09a'; }

.oi[data-glyph=menu]:before { content:'\e09b'; }

.oi[data-glyph=microphone]:before { content:'\e09c'; }

.oi[data-glyph=minus]:before { content:'\e09d'; }

.oi[data-glyph=monitor]:before { content:'\e09e'; }

.oi[data-glyph=moon]:before { content:'\e09f'; }

.oi[data-glyph=move]:before { content:'\e0a0'; }

.oi[data-glyph=musical-note]:before { content:'\e0a1'; }

.oi[data-glyph=paperclip]:before { content:'\e0a2'; }

.oi[data-glyph=pencil]:before { content:'\e0a3'; }

.oi[data-glyph=people]:before { content:'\e0a4'; }

.oi[data-glyph=person]:before { content:'\e0a5'; }

.oi[data-glyph=phone]:before { content:'\e0a6'; }

.oi[data-glyph=pie-chart]:before { content:'\e0a7'; }

.oi[data-glyph=pin]:before { content:'\e0a8'; }

.oi[data-glyph=play-circle]:before { content:'\e0a9'; }

.oi[data-glyph=plus]:before { content:'\e0aa'; }

.oi[data-glyph=power-standby]:before { content:'\e0ab'; }

.oi[data-glyph=print]:before { content:'\e0ac'; }

.oi[data-glyph=project]:before { content:'\e0ad'; }

.oi[data-glyph=pulse]:before { content:'\e0ae'; }

.oi[data-glyph=puzzle-piece]:before { content:'\e0af'; }

.oi[data-glyph=question-mark]:before { content:'\e0b0'; }

.oi[data-glyph=rain]:before { content:'\e0b1'; }

.oi[data-glyph=random]:before { content:'\e0b2'; }

.oi[data-glyph=reload]:before { content:'\e0b3'; }

.oi[data-glyph=resize-both]:before { content:'\e0b4'; }

.oi[data-glyph=resize-height]:before { content:'\e0b5'; }

.oi[data-glyph=resize-width]:before { content:'\e0b6'; }

.oi[data-glyph=rss-alt]:before { content:'\e0b7'; }

.oi[data-glyph=rss]:before { content:'\e0b8'; }

.oi[data-glyph=script]:before { content:'\e0b9'; }

.oi[data-glyph=share-boxed]:before { content:'\e0ba'; }

.oi[data-glyph=share]:before { content:'\e0bb'; }

.oi[data-glyph=shield]:before { content:'\e0bc'; }

.oi[data-glyph=signal]:before { content:'\e0bd'; }

.oi[data-glyph=signpost]:before { content:'\e0be'; }

.oi[data-glyph=sort-ascending]:before { content:'\e0bf'; }

.oi[data-glyph=sort-descending]:before { content:'\e0c0'; }

.oi[data-glyph=spreadsheet]:before { content:'\e0c1'; }

.oi[data-glyph=star]:before { content:'\e0c2'; }

.oi[data-glyph=sun]:before { content:'\e0c3'; }

.oi[data-glyph=tablet]:before { content:'\e0c4'; }

.oi[data-glyph=tag]:before { content:'\e0c5'; }

.oi[data-glyph=tags]:before { content:'\e0c6'; }

.oi[data-glyph=target]:before { content:'\e0c7'; }

.oi[data-glyph=task]:before { content:'\e0c8'; }

.oi[data-glyph=terminal]:before { content:'\e0c9'; }

.oi[data-glyph=text]:before { content:'\e0ca'; }

.oi[data-glyph=thumb-down]:before { content:'\e0cb'; }

.oi[data-glyph=thumb-up]:before { content:'\e0cc'; }

.oi[data-glyph=timer]:before { content:'\e0cd'; }

.oi[data-glyph=transfer]:before { content:'\e0ce'; }

.oi[data-glyph=trash]:before { content:'\e0cf'; }

.oi[data-glyph=underline]:before { content:'\e0d0'; }

.oi[data-glyph=vertical-align-bottom]:before { content:'\e0d1'; }

.oi[data-glyph=vertical-align-center]:before { content:'\e0d2'; }

.oi[data-glyph=vertical-align-top]:before { content:'\e0d3'; }

.oi[data-glyph=video]:before { content:'\e0d4'; }

.oi[data-glyph=volume-high]:before { content:'\e0d5'; }

.oi[data-glyph=volume-low]:before { content:'\e0d6'; }

.oi[data-glyph=volume-off]:before { content:'\e0d7'; }

.oi[data-glyph=warning]:before { content:'\e0d8'; }

.oi[data-glyph=wifi]:before { content:'\e0d9'; }

.oi[data-glyph=wrench]:before { content:'\e0da'; }

.oi[data-glyph=x]:before { content:'\e0db'; }

.oi[data-glyph=yen]:before { content:'\e0dc'; }

.oi[data-glyph=zoom-in]:before { content:'\e0dd'; }

.oi[data-glyph=zoom-out]:before { content:'\e0de'; }
hZ  Y                    LP                      bp                  
 I c o n s    i c o n i c    V e r s i o n   1 . 0 . 0      U n t i t l e d 1         
   PFFTMkL  Y   OS/24Qb  X   `cmap   @  Bcvt       gasp   Y   glyf  P  JhheadPh      6hhea     $hmtx     locaͽD    maxp4   8    namebz  R  post3  U8  c      pb_<      χ    χ  "                H    "                     h             @ .        0   p0   '                         PfEd      H  e                      
                          X                    X        X  X                               X     X             X                                             X  X                                                                                      X              X     X                                                                                                                          X                 X  X             X    X  X  X              X        X                            X                                           X                                                                                                  X                                       <                                                                                                                                                                                                                                                                                             * * * H f     4Fh*<Npf
0j|FFv,:HVd"4T|	"	T	t		


$
>
X
r

(Fp8Pt

0
F
l



@x
*x,Dbf F|*Hj  @`L:j*Nv2@Rfz0>r0H8Rjl*d4fBf , \ x   !!D!t!!""B"h""#"#F####$$L$|$$%4          . /< 2< 2  /< 2< 233'3#Ҹ            !!5!!5!5!,pdpDdddd          !!5!!!!',pdpDddddd     d     3'354&"4"ddВ͑22hh      d      4&"3'3541"͒Вdd͑hh22             !!!!!!!! dXd dXddddddd            !!!!!!!! X Xddddddd               !!!!!!!! X Xddddddd           2%6&54%#%.'!jaH\pp^*Kt1b;2p"&ku408wVSj         333d ,              &%#	#L,,, L,,                &%	5!5!L,, L                &%!!L,, L,               &%333L L,      d X   !!%,Xd     d X   
5!5!,Xd        X    33	3 ,       d    !!,)    d    	5!5!,       X    	###)/          ###d               3#3#3#3#dddddddd p,      " X 
   &  &546%'64'&54%'654'wEXXEw?jwwEXXEE;U;;EXHV~|VHuQ7wuHVXDH,)H;RU:;TR;H),          2"&427'vvvvd6\6dd vvvdd                &%"&32654J|8N]Na| J|aN8}]8|a           	 
  3!!3#3#dd,            % 1  63##!"&5#536!"26=4$"26=4dd **** dnd~dddd      d     !23##!"54!		dd	V	d
ȿ		F	dp       d    !23##!"54		dd	V	
ȿ		F	      $ -  46;!2+#!"&54765#"#"&#!/\2"R> >R"2A9z *(;/>RR>-=(

{i
           
    2!476543"&>vBBd;R; vRo5QQ5oR);;        &  
    3#5'7'77772^2KKKKd KKKKddd          %  !2#!52654&#!3264&#3264&#Rv,AOh);;),d);;)d>XX> vRF7xJhd;));;R;X|X            3#, ,           !732!"3!!"&57>7d,dd2&>X+ dd,*dX>&+           !'               !!!35!#!"5 ,)		 ddgg		           '  3232#!"&=4;546353!27#!"5,);	D	;)		 ;)d		d);ddd				       X  $  2#4&#"3#!!5767#53.5476,^jd22/ZWz}27 j^22#;wdagdzWOd
G<]?E       ( ,  ;!2#!"&5546"2647"3!264&#!"D**,XD`****p            .'? 2#"'>54	)	O6
wnOoDB=I)		6O
vO7o&#xI7       g  46327.>2763232+#"/&=4&#"&'#"&546?&5+"&546;67'.	$IVI$
sD2NA$@?>>&BN2Et BXM87MXB
8#*" (#F%"#WR&"&H#( "#8          32+%'"&'#"=4;bQ		[z"H&'g[				U}'		             #  !2#!"54!5353353335335			V	ddddddddd 				ddddd,dddd                  !!!#!"53533533535335D	V	ddddddddd d		dddddddddd           ( 0  32;2#!"546;2?6"264$"264&2"&4^R			X>^**~vvvR;;R; 					>X		**vvv;R;;R        X   ! pXp            p           	p pp      X   	!Xp      X   ( 0  46;2!2#!"&'./#"#"& 2"&4$2"&4
&Q
6
Q**I**& H

))S****           !!#!'!dd,dpdd,d    E
    ./7>O*H"JYEMI'tR*E IZGHK't     2 X   7	ppXp        &    p      &    	'7'p pp       2 X   	''Xp         
    &%'LXdHB L8dHB               &777'7'LHHHH L8HHHH           %   232!546;543!532#!"&546I*KKKKv 2KK2Gv                 &$"264%3&/Jd"&H$0 JL$!H(0           ,  2#54&"!&5463>;26323'3=46h+9	X|XvRJȖ h2U12>XX>213RvVrpȖ             2#'#&5463>#"&=#h+9	vRZ* h2U113RvVr>22      d    2#!"&463>h+9X>>RvvRh2U1>XvvVr           2&5462#!"&463>{Mk)220h+9X>>RvvR dr^#28hȒh2U1>XvvVr     d   	   3#3#'%3#7ddddddddXddddd           '  37'#/'7/5?'77"264^d2wE/ww2Hw2d2wH2ww2Hw|XX|X w2Hw2d2wH2ww2Ht2d2wH2X|XX|            
  !!!!! p  dd            
  3#3#'ddXd  Xp            
  3#3#ddp  X           
  !!!!! p  dd        ' 0 9 = F O  235462+32"&=#"&46;5#"&4";54$"326435"26=!264&#X|XdX|XX>22>XX|XdX|XX>22>X*2s*2d*,*X>22>XX|XdX|XX>22>XX|XdX|*22*dd*22*          
  43!2'!"5				
	                  &$"264'"264Jdddy** JLd,2**            
    &%264&J| J                !!!!!!!!$2"&4  X** ddddddd**    d  	     !2!546!#!"&5735335 ddddKKdddd               3!73##5!#53!ddd2ddddddd^2^ dd2d>ddddd^2            , 4    &$"264$2"&4467;272"&57'&$2"&4J**[);;R;ZI** JL**wZ;R;;)[F**          
  33	3!!,  ,pd            
  !!### , dd,    d     !!%77'7'X^HHHH,HHHH      d      #4&"#4%7"&546JddU;;>OvvO||XXiCRvvRCi                !!!	!%3535!5,D,dddd pp ddddddd      ;  332#5&+"+#5#"'&=3;26=4&'%.=46;dK1 ,d

1<I4KdK1 ,d

1<I4Kd3d^
2AN324Idd2d^
2AN324I      d     !	!,,X      d     !!, ,,,    d  
   "3!46!"3!46,Su԰pSu԰duS,|duS,|      d  
   !#5265#!#5265#,|Su,|Su|duS,|duS   X   "  "&54>7"3264&#"&54,"
(YE77EY(
_*vR); "
+ro=||=or+
JRv*;)           	!!!  d      X     	!!,,X     ,      3#%3#%3#,,     d   	  !%%! ppddp         
   !%%5!',, XX&ddd      #  2&"!!!#327#"&'!73&5#73>Xc\NK)ZrV^jl6 "2j8oYd-7ddNz8m[d@$d               !!!!!!3#73#73#D ddddddddddddddddd             
  !!!!! p  dd            
  3#3#ddXp  X           
  3#3#'ddd  Xp            
  !!!!! p  dd          	   3#!53!!''7dXdd dd pd     d   # 5  2#"./>"264'2327"&462cLE1$		OVEG\G).HMdvvv
;R;;!5@@5)gOA>XX>9E2"dvvv
);;R;     " " &  ;632762#"/!'&546K^B+*,,A]>w?ǀ<]A++,|,B^A}?ń<           !!!	!,D, pp          ,  .54>4#!4>4E5++=A-E5+!191 E5+?%!191  8@h8.]GRF\.8@h8(P@BABN8@h8fb(P@BABN             3#!!!5#dd,,p  dd        
  33337#dȖdB ,            !!!!#!"&5, D ddd>          0  26;26=.5462+""&54675.54X|X7--7X|X7-X>&.X|X7--7 X>1Mv	;M1>XX>1M;>XH,>XX>1MM1>          
  !'!7!7'dp dp           
  7!7'!'dpd pԖd            G U    &%"".&"32676&#".7>'&>54&'&>7&2?&LPI
	+!*L
&882J*01

D9#9;	
 L,#	CG#
6
mC:(,!(

m

.           ''!!apdd,dp  dpdd/dpd               # ' + / 3 7 ; ?  3#73#73#73#3#73#73#73#3#73#73#73#3#73#73#73#ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd  	               #  3#%3#%3#3#%3#%3#3#%3#%3#,,,,,, dd              !!!!!!!!,,,, ,,          
  !  !2#!"&543!27#!"5$"264Xj;** K		**         3  !#"!54&+5!#";!5326=!;!532654&+,2,2,222222dddpdddd        %   232+"&54&"+"&=46;542duud2|d^SuuSd|        462462	&vvvv;;RvvRRvv;W9          
  	'#5###ddm            
  !!735'7 ddddd dpddd,            !2#!"543373ddddd dd,      X   (  2>32#"&'#"&46"3267.!"3264&_i3^7]kk]7^33_6]kk]3113?**?s1UU1311Xz:@xx@::@xxd<P<4004dd<P<           %   2"&422653#"&54>54&"#4R;;R;p|Xd*dX>?W22*d ;R;;RY=%>XV@%qZ
>            !#3!53>?#zz"N` dddXA             !!!!!!!!   dXddddddd              !!!!!!!!   Xddddddd               !!!!!!!!   Xddddddd              2#"'#!5&54$"264Вh2UR;;R; Вd2h.;R;;R         X     ;!23#!"&=35463353dDdPddX^`dd,            	   !!%3!5!73!5!pdp,dp, ppddpd      # = P  632#"&5467!632#"&5467!632#"&+.546746;2+"#"&Q

p
jdd


8 *         & J P  3#53%6#"&54?64'&#"&54?66;26326?632&'&473##dd"7*::V+,4
V,,,5o*;;dd da*:;U,,4U,,*::dd      & O  6'&'&54632?64'&#"&5476#"'&'&67632&'&4?6K"7*::Ta+*91V4| 9.V,5o*;;P*:;T1U4	.U*::P                 !!!!!!!!!!!!,p,p,p,,dddd,ddd             # + /  2"&47!!2"&47!!2"&47!!2"&47!!**X**X**X**X**dd**dd**dd**d             	% pd ,d     X    23!3546"354ڤvddT:vRdpdR:*dd*     X    23!!54&"#4ڤvd:T:d vRp*::*R     d  
   "3'346##5265#Suddvd|SudduS|d|duS        
   !3'35!##!53!#dXddpd2ddd d2d,          5!#5463!!53#!'Xd;)pd<(ddd);ddd(<d    "   #   #"&/&'#"&2654&"",
d<*+dMY^jВ ͑VPd+*<d	)"ujhВ       X     2.54$"264>XX)gOA~vvv |8~n  +}u8|Lvvv     !    & .  !!%46;!!5+"&2.54"264 X|XK%&4( ** dsX>,})(>:J>**     dX    3#3#X      dX   	X     dX   2"&4     d     !p,,    d     	!pX    d    3#,,      d    	!3X    dX   !!X            !3#!5#3p p          !!!!!!   ddd      X  <  632"&=46;263226=46232!46;5.=46);;R;.uu*k2);p;)2k;));;)!:2SuuS22nj;));jn2      ,    !!           $  ;!2+32!46;5#"&5546!"d);;)dPXd;));d`p   
   327#"&5469G(Ђ?Ayꦂ        #355#3'35#'735#^dddddddd          "&46325"&4632> X|XX>X|XX>Xx >XX|X	*s>XX|X	"       0  2"&47>72?64'&"2767"&476R;::$gH$@E;&U2344iE}PP9;::$Hg$AE?"R4344gE}PP;            '#5Xddp ddp              *  2#"'654'62"&4#54'627!546&>XX>!	H.|XX|Xq"),eFF")) uu(,~VXduuu?&BH>TT?&&?              2"&427!546=uuu	YYPoo ΓddtQddQt             !2#!"54!"2642d,** d**               26#"'%.54^"e(͑}ag4< s)gjÃQ){F{    #  46;!2+32!'!46;5#"#"&\2d);8,;)d2 *;)dd,);         
    &%%L,, L"p          3!!#!5!,,, ,          3#2654&/7 &5467,dd>(^В4-%>&?GH> p NKzhh;g#N1S͑R1               !!!2+5!#"54!!p		[[	p d				              3#3#3#3#3#ddddddDDXddddd          >73#'&'&'#536Ei:H
J&3D)T8t	VW%%d[ׅXd"1          9  462327>32#".'&##47>54&"#34'.8X8

,88,
		8X8
	,88,
		8X8

,88,
	X    , 0  2	#54>7>54'&#"'6763#}A<)*!d+*"	&TO%d
1BHdd;6W6_/6_.!()"%-O1BDd          3 G [  2.#"."&#"&5463>;2632"&=46;2632"&=46%;2632"&=46h+9?1K.
PfP
4-5vR*** h2U13P
*41??1&:PRvVr`dddd      '  5#355#"'5'+5357'5#53276;X502dggd022ddd"&d˿d"&          27!7&#"327#"&6ywmZw||~VEuwwnZXEwJ           	  !'!pp pp         	  #335# ,      d X 	  35
5#%,,X         
    2#4$#2 #4&#2#)Ƞ&ȯ}Su עd}duS           
   #  2#4.#2#4&#2#4&#2"&4dBd_zd|duS)R;;R; zߠ_d戦d|Sud;R;;R           !  !2!54&"!#!"&=326546,);p*;));d*; ;)Ȗ);;)Ȗ&);           3#!53#!"&546!5#">3Kd->--+Fldd}--&-dVC         	5#"4>3,d&-mȒȚvG           7"'&'&=6$}%J=@HpH@=J8n82+		sQ/55/Qs\OzJ$            3#3#3#3#Xdddddddd D,          
  33###'73,dddddd ddpdd          
    33'33#!!!!d,p Xddddd            
    33'3!!!!3#dp, Xddddd               # '  !2#!"&546353!5353!5353!5K--v--7dddddd---&-ddddddddddddd        	  !''!d,dd, ,    	           ' / 7 ? G   2"&42"&4$2"&42"&42"&4$2"&42"&4$2"&42"&4{******vvv********** **G****Gvvv ********G**              !2#!"&546!"264"xP** $d**              !	6"264,R;;R; ;R;;R    d X  
   3%3'7$"264,T,Nw***X,Nz**                  &$"264$2"&46"264JvvvT::T: JLvvv:T::T        	   !!!57!'7/ddDdpdddddpdd             !2#!"547!5				HddHN, 				NHddH2dd          !#4&+;!53265#"# 2;)2p2);2 );dd&;)         3#!2+"&'&'&#dd^!g/$
m*p%           ;2#!276763#/4!*m`dd%JIp       *  !#&#"2654'7 &54675# "&47>7,d2#.)hВ^ͬd*	]d^Вh/,"@=͑*	G        
  5!5!!!'XXpXddddd           '  3232!46;46326532653#!"5,d);d);D;)d;d*d*d2 ;);)););^^          32653+"&5!!d;R;dvR2gdD p);;)pRvuSd            # '  32+"54!32+"5432+"54!!				a			ݶ			 												pd           # + 3  32#54!32#5432#54!!3+"5%3+"5%3+"5
	b	޵	 		,	
,	
				d	[[	dd		[		[		           '  !!32+"54!32+"54!32+"54 				5			5			dd												    d X   !273#'#!"&5462ddddXdd            '  3#'#3!52#527>4&'#&3NBB,%&bb#(AUUA +*! d
У	dkkd	4!"6	     X     3#'#33NBB, +*! d	4!"6	         3#'#3NBB            32#!"=7635355R&	V	&ddd 	VQ		Q	dd         &#"'2&#"'62"&455zrc5KUWK6`LR;;R;mR[^X>U//U>;R;;R      !  "  237#"'#"'&47&546 "264'^^g--+)#**^^hS0+hv**      
   >7&''67.'3eS&0fR$3eS&0fR$0fR$3eS&0fR$3eS&          3333!!!#5!5!5!53d&d,d, ,dddddd    "   ' 3   #"&/&'#"&$"3276?654%33##5#53",g<*+gMYВh`@5dddddd ͑VPg+*<g)"iВ;CYh.ddddd   "   ' +   #"&/&'#"&$"3276?654!!",g<*+gMYВh`@5p, ͑VPg+*<g)"iВ;CYh6d              B                        %<       	v                	       	  
   	     	  J   	  b  	    	   C r e a t e d   b y   P . J .   O n o r i   w i t h   F o n t F o r g e   2 . 0   ( h t t p : / / f o n t f o r g e . s f . n e t )  Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net)  I c o n s  Icons  i c o n i c  iconic  F o n t F o r g e   2 . 0   :   U n t i t l e d 1   :   3 0 - 4 - 2 0 1 4  FontForge 2.0 : Untitled1 : 30-4-2014  U n t i t l e d 1  Untitled1  V e r s i o n   1 . 0 . 0    Version 1.0.0   o p e n - i c o n i c  open-iconic          2                         	
 D E F G H I
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  123456789101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcddde            =    χ    χOTTO 
    CFF na  T  IEFFTMkL  R   OS/24Q     `cmap 
    BheadPh      6hhea      $hmtx    N  ~maxp P      namebz  p  post 2  4          dZ_<      χ    χ  "                H    "                  P        0   p0   '                         PfEd      H  e                            B                        %<       	v                	       	  
   	     	  J   	  b  	    	   C r e a t e d   b y   P . J .   O n o r i   w i t h   F o n t F o r g e   2 . 0   ( h t t p : / / f o n t f o r g e . s f . n e t )  Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net)  I c o n s  Icons  i c o n i c  iconic  F o n t F o r g e   2 . 0   :   U n t i t l e d 1   :   3 0 - 4 - 2 0 1 4  FontForge 2.0 : Untitled1 : 30-4-2014  U n t i t l e d 1  Untitled1  V e r s i o n   1 . 0 . 0    Version 1.0.0   o p e n - i c o n i c  open-iconic                 <                                                                                                                                                                                                                                                                                                     2                      open-iconic   : 
 _
 _&
   #E           	 
             " $ & ( * , . 0 2 4 6 8 : < > @ B D F H J L N P R T V X Z \ ^ ` b d f h j l n p r t v x z | ~                                                                 
 "$&(*,.02468:<>@BDFHJLNPRTVXZ\^`bdfhjlnprtvxz|~123456789101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcddde1.0.0Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net)Untitled1Icons    B C D E F G 	

 !"#$%&'()*+,-. /0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^   # L {   Dh-S}  ;^Hx]nm			
S
r


5Pp 
O#Fh;j;=g"&[-o;]\Bf[j{.U;zU L    !I!!"#$$%
%!%_%%&1&x&'3''''((-(B(](q(()i)t)**i*+J+n,,j,--k--..m.//01112 2M2w23-3344y445 5Q556B66677Y778=8919d9::@::;';;;<!<<=K===.YYY\\PP 
$\'$'$'\*\\PP 
\\*\*$$\PU11UY'\\\\'U11U\VPU11UY'\\\\'U11UP!
P\"
P\!
P\"
P!
\"
\!
\"
P!
\"
\!
\"
$PFJxRl~uPF@?BZ&rstrr`FlC5tVYv\\\\vP\$#
''\\\\P$#
'\\\\$#
'\\\$#
'\\\\\'\v\\\\\\P\\\P\\\v\\\\\v\\\v$$
''%
P'\\\BC]%'CTjՋ݋ݬFCTB77jCTUCӹ]B: ggtVTTYgyiFDyqootyyCËtg.v$$$\&
'u'lihhil$'
'͋vh77hvǋ͋::ZOI::IOZ\\PPP$\\\\8!~}~'y{ph~$@\(
(
$\\P>S\'R'$P>S\'R'$\\ot!zj]=|pxi^eepq|x|]j!uoqxpt,Y6"\6$11#W-9Gais[Wsa9W1'PT^¸v**@@@****@֋u-u'**'*''*'*'\\\\'\'¸^T$T^^T'P61\'¸^TT^^T''*H88HH8*v\\xdlldx8HouuoY'''.v$\\\\P!
\$_\\\T^]U'SouPS^T\'\'\\\{{yPy{{\=FMUqi`i`{TQFSY'^qWFF{싋ԮΙF@wË}}ud{ffWQ'$$Px|uwPouPPuoP')
\uoouuoou\$v|~|rtTCb{BPNB(U8;]t|(

(OA-WywS{zzFousqoutuv=cv{tvouͮ]dr$uo4ptxh{v=uoGuoIo=;==:nupxptPuYxJJrCF\>'\'''\''\'$''\''\\P\P>'''\''\''$'''\''\\--J߉!8HH8M9-JMuoouuoou1111'*
$$$$.v$$$$$.v$$$$$\ot*r878؏~}Pewz\qwptV+
+
v$\P\\$$'P
	ZYX!!DB@D!"#***$$$$**v$$$$$**v*****$$$$$****$#
\FC@@'$#
*F,
\\8ouuoY@}}@}@uo'}}}}@\\8$'
'-
Y'rnnmrstIv\V
!5t11fjmH8YSaQ{$x|uw**\\\\**uov$V
!5t11fjmr\\\\!aQV\You\VP.
dhk#]C9\\.
P''\\'\\$\'\\'\YoCCoY'YlYCYYFYoYYH88HH88H\/
\\\\$!
v\$
0
\\\\\v\0
$
\$\\\!
$\\$'/
*P8HH88H'Y8HH88HY8HH8YH88HH8Y'H8'uoYYou$uoouuoY\''*'Youuoouuoouuoouv\\\$'
'-
\''''*)
$$'
'::::\P!
\'!
\"
*+
\P}}@}\}P\''\''v$$'''''VY''V\$\\\*@$pHHppHHppHHppHHp'-
@'1
\({0@rT^¸^Tr0{x|uw1
\\\P!
vP\!
$\\\\\P\\싋V=,
$p$PpHHp::::pHHpPe9rP?11P9P3\\P$$$''''''2
$8\P'@FSSFYGR{Jq}Y~~''in|{'`I}w-wstom@\P3
\3
\P4
4
\$P5
\5
\\'\'P\6
\\6
uiio9999oLJuoT^§uoouuo1\$$'/
v\\\\\p7
7
7
P'$\$\\$$$\v$$\$*P**Pe'\'\'\\PN-^@Sr'\iikYx'
9'ԋϠƮV[Fm=2=Tr⋛ߋ͋vhRJFP'PP\'\!
\'''\!
\\\\\$/
v\$
0
\$\\v\0
$
\\\\\\\/
$$\\$'!
\''\**'**$\'P****'8
'T^^TT^¸{{wou+Px{uw{LJ.ŋPS(RRIJ-zo$P$$$v$'$\\'\\\')Z\\\\'\ǽ*\\v$
$\'$\Y\_.v**\I\**\\\\\VouPVv\\\*8HH8ЋITwnPwbTI8HiY\ƟH88HH8ԋITwPouuo\zy|
ƟH8v9
$:
v:
*9
$#
'vwsyyy̐fpalh(M]8}ЋwsT?2Z'ehjV\t]of/W}tjuË{uqpxunp~Y~ģS''\_'$$!
P''''P\''''P\''''P\''''\\\\\\7
7
7
7
7
7
7
7
7
\\;
\;
;
\;
$ou=*{{yy{{)
\\P'uo$ouuoY'You**ouuoY'You$'uo**v\'$\\$P99'Youuo\ou2ou\uoY99TYg000000v\\\$P$$\\\\\'P''''\\''$$''\$\=\'''\''\\\F&((&ڋIS܋F:NSQIRK<'\V]V]\aAq$եU]]qUA`]^.\\\T^^TT^¸^T*8HH8䧡uoo'55K'ouuoou䋯(H8\'7=X<W>Y'6XWYP!
\!
\!
P\"
P!
\!
\!
\"
P!
\!
\!
\"
v\\\\{}}Ƌ\\\'<
\$x|uw'*ouP*'uo'$''\'$$$$$\=
\=
'dJ*v{tvou*uo*$\v{tvou$\uo\*}w{qt\xsuootuo'qwptv$[\['''\\Sdwjj__~w|ouёcdJcc**~w|ou**ً=ojgywy**==>S{Ҥuo|w~__iiEcc̳**uo\=Sdwjj~w|ouёcdJcc**``fqq|outozx}О**ً=ojgywy\{YGyOO**==>S{Ҥuo|w~iiEcc̳**zvv~YP;
'$$\'P\;
'$$\'P+
*"
\+
*"
\+
*"
\+
*"
v$'$\\P11''$$'1'÷_S'\÷$\11÷÷_S\$$$'\1$$$P99'*\*\'*\22'99$\$\'**'*싋\''$\vP'T^^T''\*\*\*''+U11UU11UƋŚ'{}ËË}{'ċU11U"  "vP$::\\::'8
+\\ou*'싋싋$*ousq%8HH8'****H8')
\\\P>
\>
PP::::::::$P$$$P$$$\P>
\P\\Pv\$\$\\\$\\$\\$\\\YP!
!
!
'(\**P`h__\T^¸\^T\x|uwY(!s!YT^^T$^TY!(uoouuoY222uo\/
'$Px|uwou''T^^T^T'uoP'$=P2V)DqGGqD)	2c}`^qGGqvP**********'****'**'****'**\Y_'\'''-|yz?
j!Ԣ]b|yzd?
cPXWwed  B  BFab_cd_FFFGό̋ceLecyFkkkjkk[[<[[ڋً=dXXv''\\$$$\\$$\$$\'\X_gpWUB6nopH8'8H11H8o`XPlHԦe]YI\:l_'_ul_W::l__VMlFFM_\\$$11e 73 ' 7 UNAd::AU\\\\'b'*Y)
v*4dLK!{zz*!YlU11UD$=-TUgA7,6Ulh\ot\'T^^T'^T'\uo$qwpt$#
'\\\v\\\\\\\p%
x$el;KU(U11UU11UW9eM=l]D;ۯŹ\$\\$\\\\$vPPPPP''\'\'\'\'\{PabaP'443o{=monnAmAnA_^a!v\\\\\\Pows~~wŵŵaQoyws~~wŵaQowys~~ȖaQQaaQ'YP6Lqcccc}Ye͋͋{yytr8ltY_Y_FK	rޭˋ	z``K8YP'\\$V
!5t11WYgܥŻЋЋ[KܜʋdT˜ŋЋaQx|uw\ou\uo`'x|uw'ouuo x|uw'ouuo$P'Yhrvq!B*Bvuh''7S7_$'-R7Rs'\*\*'oCc*O'\*'$PpHHppHHpFUTAj7::::ދhTB(v$::$$::$$::$$::$v\\\\\\\\\\\\v\\\\MM\;;\uu\\2\\'\'rFFrtt\'299\*
$$$'\T^^Touuoou*'\T^L¸$uo,*$\,^T$PchhcchV''$\'a:.t[\'\\v\P\$'aZ(\\VTWl:& V_llu':xPnjjehKY	v$
'PP\\v$\'\'\'''\$\'\\v\\@
'\\\'\'$v\\@
'$$\'\'\$Pchhcchhc'''\2
\'''\2
\'''\2
v$'\'\\'\****) $1
3 '1
1+
's&
*% +
P!@+
c 1
a+
 '1
'y{{ypy{p{y'Y)
vP''<
\\\ =\\')
\\$'
'-
'&
'÷_SS__SS_÷PPP''3!'''\\$$$P=@@@@C'*'\8\¸*ouuoY'$You*¸^T\P%
$yxUqq~d*$yvvPPx|w~q*Uyxxyro$uo*ouЋ~qf%
'\P'>s'DU11UU11U~-immlurr\wwwwkwwvP'''\*\*\*싋\\T^^T'T^^TP^T'^Tubu'ouuoou'ouuoou\\$1$'$T^^TT^$P'P\\\8PA
6A
v\B
$!
\'\\\\\7PC
7C
/u'0\\!
\SJSg0ISIS\\\8P!
\A

B

A
ouuo$ou*''''*uov$$'CD
\u=--K=5uzz{'  ,
tqq\\E
v$'D
\඗E
.v$D
>x:>܈>0\'''''\\\\PfF3lO;AuMc6ǋƋynMC<*
+'\\PkloddLdw--\\}}{V)
!P!!6988879;6! 87;  ;69 !6;9689896v\uu'''\\uu'{e{+\\\\F
'G
Y''''''+\\F
'G
*\'

ｽ\
 # (     = D K e      PX`sz"-5CXvI'$$'P''qGGqqGGqqGGqqGGq$$1111pHHppHHppHHppHHpuo'ouuoouuoouuoouT^^TT^¸^Touuoouuo****C****CC****C****::::::::
!5t111VaQ\\\ouuoouuo$'$9922'99\\\1111$********''****$$**¸^TT^^TT^¸'$$\8HH88H\\\\vJvJS\S\\$\\͋jaU11UU11UƋŚ${}ËË}{$ċU11UPxUi`~~}cfOuO                             X                    X        X  X                               X     X             X                                             X  X                                                                                      X              X     X                                                                                                                          X                 X  X             X    X  X  X              X        X                            X                                           X                                                                                                  X                               =    χ    χ<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2014-4-30: Created.
-->
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
Created by FontForge 20120731 at Wed Apr 30 22:56:47 2014
 By P.J. Onori
Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net)
</metadata>
<defs>
<font id="open-iconic" horiz-adv-x="800" >
  <font-face 
    font-family="Icons"
    font-weight="400"
    font-stretch="normal"
    units-per-em="800"
    panose-1="2 0 5 3 0 0 0 0 0 0"
    ascent="800"
    descent="0"
    bbox="-0.25 -101 802 800.126"
    underline-thickness="50"
    underline-position="-100"
    unicode-range="U+E000-E0DE"
  />
    <missing-glyph />
    <glyph glyph-name="" unicode="&#xe000;" 
d="M300 700h500v-700h-500v100h400v500h-400v100zM400 500l200 -150l-200 -150v100h-400v100h400v100z" />
    <glyph glyph-name="1" unicode="&#xe001;" 
d="M300 700h500v-700h-500v100h400v500h-400v100zM200 500v-100h400v-100h-400v-100l-200 150z" />
    <glyph glyph-name="2" unicode="&#xe002;" 
d="M350 700c193 0 350 -157 350 -350v-50h100l-200 -200l-200 200h100v50c0 138 -112 250 -250 250s-250 -112 -250 -250c0 193 157 350 350 350z" />
    <glyph glyph-name="3" unicode="&#xe003;" 
d="M450 700c193 0 350 -157 350 -350c0 138 -112 250 -250 250s-250 -112 -250 -250v-50h100l-200 -200l-200 200h100v50c0 193 157 350 350 350z" />
    <glyph glyph-name="4" unicode="&#xe004;" 
d="M0 700h800v-100h-800v100zM100 500h600v-100h-600v100zM0 300h800v-100h-800v100zM100 100h600v-100h-600v100z" />
    <glyph glyph-name="5" unicode="&#xe005;" 
d="M0 700h800v-100h-800v100zM0 500h600v-100h-600v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100z" />
    <glyph glyph-name="6" unicode="&#xe006;" 
d="M0 700h800v-100h-800v100zM200 500h600v-100h-600v100zM0 300h800v-100h-800v100zM200 100h600v-100h-600v100z" />
    <glyph glyph-name="7" unicode="&#xe007;" 
d="M400 700c75 0 144 -23 203 -59l-72 -225l-322 234c57 31 122 50 191 50zM125 588l191 -138l-310 -222c-4 23 -6 47 -6 72c0 114 49 215 125 288zM688 575c69 -72 112 -168 112 -275c0 -35 -4 -68 -12 -100h-222zM216 256l112 -350c-128 23 -232 109 -287 222zM372 100
h372c-64 -109 -177 -186 -310 -197z" />
    <glyph glyph-name="8" unicode="&#xe008;" horiz-adv-x="600" 
d="M200 800h100v-500h200l-247 -300l-253 300h200v500z" />
    <glyph glyph-name="9" unicode="&#xe009;" 
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM300 700v-300h-200l300 -300l300 300h-200v300h-200z" />
    <glyph glyph-name="a" unicode="&#xe00a;" 
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700l-300 -300l300 -300v200h300v200h-300v200z" />
    <glyph glyph-name="b" unicode="&#xe00b;" 
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700v-200h-300v-200h300v-200l300 300z" />
    <glyph glyph-name="c" unicode="&#xe00c;" 
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700l-300 -300h200v-300h200v300h200z" />
    <glyph glyph-name="d" unicode="&#xe00d;" 
d="M300 600v-200h500v-100h-500v-200l-300 247z" />
    <glyph glyph-name="e" unicode="&#xe00e;" 
d="M500 600l300 -247l-300 -253v200h-500v100h500v200z" />
    <glyph glyph-name="f" unicode="&#xe00f;" horiz-adv-x="600" 
d="M200 800h200v-500h200l-297 -300l-303 300h200v500z" />
    <glyph glyph-name="10" unicode="&#xe010;" 
d="M300 700v-200h500v-200h-500v-200l-300 297z" />
    <glyph glyph-name="11" unicode="&#xe011;" 
d="M500 700l300 -297l-300 -303v200h-500v200h500v200z" />
    <glyph glyph-name="12" unicode="&#xe012;" horiz-adv-x="600" 
d="M297 800l303 -300h-200v-500h-200v500h-200z" />
    <glyph glyph-name="13" unicode="&#xe013;" horiz-adv-x="600" 
d="M247 800l253 -300h-200v-500h-100v500h-200z" />
    <glyph glyph-name="14" unicode="&#xe014;" 
d="M400 800h100v-800h-100v800zM200 700h100v-600h-100v600zM600 600h100v-400h-100v400zM0 500h100v-200h-100v200z" />
    <glyph glyph-name="15" unicode="&#xe015;" 
d="M119 600l69 -72c-55 -54 -88 -130 -88 -212s33 -156 88 -210l-69 -72c-73 72 -119 172 -119 282s46 212 119 284zM681 600c73 -73 119 -174 119 -284s-46 -210 -119 -282l-69 72c55 54 88 126 88 210s-33 157 -88 212zM259 460l69 -72c-18 -18 -28 -45 -28 -72
s10 -51 28 -69l-69 -72c-36 36 -59 86 -59 141s23 108 59 144zM541 459c36 -36 59 -87 59 -143s-23 -105 -59 -141l-69 72c18 18 28 41 28 69s-10 54 -28 72z" />
    <glyph glyph-name="16" unicode="&#xe016;" horiz-adv-x="400" 
d="M200 800c110 0 200 -90 200 -200s-90 -200 -200 -200s-200 90 -200 200s90 200 200 200zM100 319c31 -11 65 -19 100 -19s69 8 100 19v-319l-100 100l-100 -100v319z" />
    <glyph glyph-name="17" unicode="&#xe017;" 
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300c0 -66 21 -126 56 -175l419 419c-49 35 -109 56 -175 56zM644 575l-419 -419c49 -35 109 -56 175 -56c166 0 300 134 300 300
c0 66 -21 126 -56 175z" />
    <glyph glyph-name="18" unicode="&#xe018;" 
d="M0 700h100v-600h700v-100h-800v700zM500 700h200v-500h-200v500zM200 500h200v-300h-200v300z" />
    <glyph glyph-name="19" unicode="&#xe019;" 
d="M397 800c13 1 24 -4 34 -13c2 -1 214 -254 241 -287h128v-100h-100v-366c0 -18 -16 -34 -34 -34h-532c-18 0 -34 16 -34 34v366h-100v100h128l234 281c8 10 22 18 35 19zM400 672l-144 -172h288zM250 300c-28 0 -50 -22 -50 -50v-100c0 -28 22 -50 50 -50s50 22 50 50
v100c0 28 -22 50 -50 50zM550 300c-28 0 -50 -22 -50 -50v-100c0 -28 22 -50 50 -50s50 22 50 50v100c0 28 -22 50 -50 50z" />
    <glyph glyph-name="1a" unicode="&#xe01a;" 
d="M9 700h682c6 0 9 -4 9 -10v-190h100v-200h-100v-191c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v582c0 6 3 9 9 9zM100 600v-400h500v400h-500z" />
    <glyph glyph-name="1b" unicode="&#xe01b;" 
d="M9 700h682c6 0 9 -4 9 -10v-190h100v-200h-100v-191c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v582c0 6 3 9 9 9z" />
    <glyph glyph-name="1c" unicode="&#xe01c;" 
d="M92 650c0 23 19 50 45 50h3h5h5h500c28 0 50 -22 50 -50s-22 -50 -50 -50h-50v-141c9 -17 120 -231 166 -309c15 -26 34 -61 34 -106c0 -39 -15 -77 -41 -103c-27 -27 -65 -41 -103 -41h-512c-39 0 -77 15 -103 41c-27 27 -41 65 -41 103c0 45 19 79 34 106
c46 78 157 292 166 309v141h-50c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51zM500 600h-200v-162l-6 -10s-65 -123 -122 -228h456c-57 105 -122 228 -122 228l-6 10v162z" />
    <glyph glyph-name="1d" unicode="&#xe01d;" 
d="M400 800c110 0 200 -90 200 -200c0 -104 52 -198 134 -266c42 -34 66 -82 66 -134h-800c0 52 24 100 66 134c82 68 134 162 134 266c0 110 90 200 200 200zM300 100h200c0 -55 -45 -100 -100 -100s-100 45 -100 100z" />
    <glyph glyph-name="1e" unicode="&#xe01e;" horiz-adv-x="600" 
d="M150 800h50l350 -250l-225 -147l225 -153l-350 -250h-50v250l-75 -75l-75 75l150 150l-150 150l75 75l75 -75v250zM250 650v-200l150 100zM250 350v-200l150 100z" />
    <glyph glyph-name="1f" unicode="&#xe01f;" 
d="M0 800h500c110 0 200 -90 200 -200c0 -47 -17 -91 -44 -125c85 -40 144 -125 144 -225c0 -138 -112 -250 -250 -250h-550v100c55 0 100 45 100 100v400c0 55 -45 100 -100 100v100zM300 700v-200h100c55 0 100 45 100 100s-45 100 -100 100h-100zM300 400v-300h150
c83 0 150 67 150 150s-67 150 -150 150h-150z" />
    <glyph glyph-name="20" unicode="&#xe020;" horiz-adv-x="600" 
d="M300 800v-300h200l-300 -500v300h-200z" />
    <glyph glyph-name="21" unicode="&#xe021;" 
d="M100 800h300v-300l100 100l100 -100v300h50c28 0 50 -22 50 -50v-550h-550c-28 0 -50 -22 -50 -50s22 -50 50 -50h550v-100h-550c-83 0 -150 67 -150 150v550l3 19c8 39 39 70 78 78z" />
    <glyph glyph-name="22" unicode="&#xe022;" horiz-adv-x="400" 
d="M0 800h400v-800l-200 200l-200 -200v800z" />
    <glyph glyph-name="23" unicode="&#xe023;" 
d="M0 800h800v-100h-800v100zM0 600h300v-103h203v103h297v-591c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v591z" />
    <glyph glyph-name="24" unicode="&#xe024;" 
d="M300 800h200c55 0 100 -46 100 -100v-100h191c6 0 9 -3 9 -9v-241c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v241c0 6 3 9 9 9h191v100c0 54 45 100 100 100zM300 700v-100h200v100h-200zM0 209c16 -5 32 -9 50 -9h700c18 0 34 4 50 9v-200c0 -6 -3 -9 -9 -9h-782
c-6 0 -9 3 -9 9v200z" />
    <glyph glyph-name="25" unicode="&#xe025;" horiz-adv-x="600" 
d="M300 800c58 0 110 -16 147 -53s53 -89 53 -147h-100c0 39 -11 61 -25 75s-36 25 -75 25c-35 0 -55 -10 -72 -31s-28 -55 -28 -94c0 -51 20 -107 28 -175h172v-100h-178c-14 -60 -49 -127 -113 -200h491v-100h-600v122l16 12c69 69 95 121 106 166h-122v100h125
c-8 50 -25 106 -25 175c0 58 16 113 50 156s88 69 150 69z" />
    <glyph glyph-name="26" unicode="&#xe026;" 
d="M34 700h4h3h4h5h700c28 0 50 -22 50 -50v-700c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v700v2c0 20 15 42 34 48zM150 600c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50zM350 600c-28 0 -50 -22 -50 -50s22 -50 50 -50h300c28 0 50 22 50 50
s-22 50 -50 50h-300zM100 400v-400h600v400h-600z" />
    <glyph glyph-name="27" unicode="&#xe027;" 
d="M744 797l9 -3l41 -41c4 -4 3 -11 0 -15l-266 -375c-3 -5 -10 -11 -15 -13l-25 -12c-23 72 -78 127 -150 150l12 25l13 15l375 266zM266 400c74 0 134 -61 134 -134c0 -148 -118 -266 -266 -266c-48 0 -94 15 -134 38c80 46 134 129 134 228c0 73 59 134 132 134z" />
    <glyph glyph-name="28" unicode="&#xe028;" 
d="M9 451c0 23 19 50 46 50c8 0 19 -3 26 -7l131 -66l29 22c-79 81 -1 250 118 250s197 -167 119 -250l28 -22l131 66c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-115 -56c9 -16 19 -33 25 -50h68c28 0 50 -22 50 -50s-22 -50 -50 -50h-50
c0 -23 -2 -45 -6 -66l78 -40c21 -5 37 -28 37 -49c0 -28 -22 -50 -50 -50c-10 0 -23 5 -31 11l-65 35c-24 -46 -59 -83 -100 -107c-35 19 -63 42 -63 69v135v4v5v6v5v5v87c0 28 -22 50 -50 50c-25 0 -46 -17 -50 -40c1 -3 1 -8 1 -11s0 -8 -1 -11v-82v-4v-5v-144
c0 -28 -27 -53 -62 -72c-41 25 -76 64 -100 110l-66 -35c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50c0 21 16 44 37 49l78 40c-4 21 -6 43 -6 66h-50h-5c-28 0 -50 22 -50 50c0 26 22 50 50 50h5h69c6 17 16 34 25 50l-116 56c-16 7 -28 27 -28 45z" />
    <glyph glyph-name="29" unicode="&#xe029;" 
d="M610 700h81c6 0 9 -3 9 -9v-582c0 -6 -3 -9 -9 -9h-91v597zM210 503l290 147v-500l-250 125v-3c-14 0 -25 -11 -28 -25l72 -178c11 -25 0 -55 -25 -66s-55 0 -66 25l-103 272h-91c-6 0 -9 3 -9 9v182c0 6 3 9 9 9h182z" />
    <glyph glyph-name="2a" unicode="&#xe02a;" 
d="M9 800h682c6 0 9 -3 9 -9v-782c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v782c0 6 3 9 9 9zM100 700v-200h500v200h-500zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400v-300h100v300h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100z" />
    <glyph glyph-name="2b" unicode="&#xe02b;" 
d="M0 800h700v-200h-700v200zM0 500h700v-491c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v491zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100z" />
    <glyph glyph-name="2c" unicode="&#xe02c;" 
d="M409 800h182c6 0 10 -4 12 -9l94 -182c2 -5 6 -9 12 -9h82c6 0 9 -3 9 -9v-582c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v441c0 83 67 150 150 150h141c6 0 10 4 12 9l94 182c2 5 6 9 12 9zM150 500c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z
M500 500c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200zM500 400c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
    <glyph glyph-name="2d" unicode="&#xe02d;" 
d="M0 600h800l-400 -400z" />
    <glyph glyph-name="2e" unicode="&#xe02e;" horiz-adv-x="400" 
d="M400 800v-800l-400 400z" />
    <glyph glyph-name="2f" unicode="&#xe02f;" horiz-adv-x="400" 
d="M0 800l400 -400l-400 -400v800z" />
    <glyph glyph-name="30" unicode="&#xe030;" 
d="M400 600l400 -400h-800z" />
    <glyph glyph-name="31" unicode="&#xe031;" 
d="M0 550c0 23 20 50 46 50h3h5h4h200c17 0 37 -13 44 -28l38 -72h444c14 0 19 -10 15 -22l-81 -253c-4 -13 -21 -25 -35 -25h-350c-14 0 -30 12 -34 25c-27 83 -54 167 -81 250l-10 25h-150c-2 0 -5 -1 -7 -1c-28 0 -51 23 -51 51zM358 100c28 0 50 -22 50 -50
s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM658 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
    <glyph glyph-name="32" unicode="&#xe032;" 
d="M0 700h500v-100h-300v-300h-100l-100 -100v500zM300 500h500v-500l-100 100h-400v400z" />
    <glyph glyph-name="33" unicode="&#xe033;" 
d="M641 700l140 -141c-137 -143 -279 -280 -418 -421l-72 -69c-76 71 -148 146 -222 219l-69 71l141 141c51 -48 101 -97 150 -147c117 116 231 234 350 347z" />
    <glyph glyph-name="34" unicode="&#xe034;" 
d="M150 600l250 -250l250 250l150 -150l-400 -400l-400 400z" />
    <glyph glyph-name="35" unicode="&#xe035;" horiz-adv-x="600" 
d="M400 800l150 -150l-250 -250l250 -250l-150 -150l-400 400z" />
    <glyph glyph-name="36" unicode="&#xe036;" horiz-adv-x="600" 
d="M150 800l400 -400l-400 -400l-150 150l250 250l-250 250z" />
    <glyph glyph-name="37" unicode="&#xe037;" 
d="M400 600l400 -400l-150 -150l-250 250l-250 -250l-150 150z" />
    <glyph glyph-name="38" unicode="&#xe038;" 
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM600 622l-250 -250l-100 100l-72 -72l172 -172l322 322z" />
    <glyph glyph-name="39" unicode="&#xe039;" 
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM250 622l-72 -72l150 -150l-150 -150l72 -72l150 150l150 -150l72 72l-150 150l150 150l-72 72l-150 -150z" />
    <glyph glyph-name="3a" unicode="&#xe03a;" 
d="M350 800c28 0 50 -22 50 -50v-50h75c14 0 25 -11 25 -25v-75h-300v75c0 14 11 25 25 25h75v50c0 28 22 50 50 50zM25 700h75v-200h500v200h75c14 0 25 -11 25 -25v-650c0 -14 -11 -25 -25 -25h-650c-14 0 -25 11 -25 25v650c0 14 11 25 25 25z" />
    <glyph glyph-name="3b" unicode="&#xe03b;" 
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM350 600h100v-181c23 -24 47 -47 72 -69l-72 -72c-27 30 -55 59 -84 88l-16 12
v222z" />
    <glyph glyph-name="3c" unicode="&#xe03c;" 
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -18 -4 -34 -9 -50h-191v50c0 83 -67 150 -150 150s-150 -67 -150 -150v-50h-272c-17 30 -28 63 -28 100c0 110 90 200 200 200c23 114 129 200 250 200zM434 400h3h4c3 0 6 1 9 1c28 0 50 -22 50 -50v-1
v-150h150l-200 -200l-200 200h150v150v2c0 20 15 42 34 48z" />
    <glyph glyph-name="3d" unicode="&#xe03d;" 
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -18 -4 -34 -9 -50h-141l-200 200l-200 -200h-222c-17 30 -28 63 -28 100c0 110 90 200 200 200c23 114 129 200 250 200zM450 350l250 -250h-200v-50c0 -28 -22 -50 -50 -50s-50 22 -50 50v50h-200z" />
    <glyph glyph-name="3e" unicode="&#xe03e;" 
d="M450 700c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -83 -67 -150 -150 -150h-450c-110 0 -200 90 -200 200s90 200 200 200c23 114 129 200 250 200z" />
    <glyph glyph-name="3f" unicode="&#xe03f;" 
d="M250 800c82 0 154 -40 200 -100c-143 -1 -270 -84 -325 -209c-37 -9 -70 -26 -100 -47c-16 32 -25 67 -25 106c0 138 112 250 250 250zM450 600c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -83 -67 -150 -150 -150h-450c-110 0 -200 90 -200 200
s90 200 200 200c23 114 129 200 250 200z" />
    <glyph glyph-name="40" unicode="&#xe040;" 
d="M500 700h100l-300 -600h-100zM100 600h100l-100 -200l100 -200h-100l-100 200zM600 600h100l100 -200l-100 -200h-100l100 200z" />
    <glyph glyph-name="41" unicode="&#xe041;" 
d="M350 800h100l50 -119l28 -12l119 50l69 -72l-47 -119l12 -28l119 -50v-100l-119 -50l-12 -28l50 -119l-72 -72l-119 50l-28 -12l-50 -119h-100l-50 119l-28 12l-119 -50l-72 72l50 116l-12 31l-119 50v100l119 50l12 28l-50 119l72 72l119 -50l28 12zM400 550
c-83 0 -150 -67 -150 -150s67 -150 150 -150s150 67 150 150s-67 150 -150 150z" />
    <glyph glyph-name="42" unicode="&#xe042;" 
d="M0 800h800v-200h-800v200zM200 500h400l-200 -200zM0 100h800v-100h-800v100z" />
    <glyph glyph-name="43" unicode="&#xe043;" 
d="M0 800h100v-800h-100v800zM600 800h200v-800h-200v800zM500 600v-400l-200 200z" />
    <glyph glyph-name="44" unicode="&#xe044;" 
d="M0 800h200v-800h-200v800zM700 800h100v-800h-100v800zM300 600l200 -200l-200 -200v400z" />
    <glyph glyph-name="45" unicode="&#xe045;" 
d="M0 800h800v-100h-800v100zM400 500l200 -200h-400zM0 200h800v-200h-800v200z" />
    <glyph glyph-name="46" unicode="&#xe046;" 
d="M150 700c83 0 150 -67 150 -150v-50h100v50c0 83 67 150 150 150s150 -67 150 -150s-67 -150 -150 -150h-50v-100h50c83 0 150 -67 150 -150s-67 -150 -150 -150s-150 67 -150 150v50h-100v-50c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150h50v100h-50
c-83 0 -150 67 -150 150s67 150 150 150zM150 600c-28 0 -50 -22 -50 -50s22 -50 50 -50h50v50c0 28 -22 50 -50 50zM550 600c-28 0 -50 -22 -50 -50v-50h50c28 0 50 22 50 50s-22 50 -50 50zM300 400v-100h100v100h-100zM150 200c-28 0 -50 -22 -50 -50s22 -50 50 -50
s50 22 50 50v50h-50zM500 200v-50c0 -28 22 -50 50 -50s50 22 50 50s-22 50 -50 50h-50z" />
    <glyph glyph-name="47" unicode="&#xe047;" 
d="M0 791c0 6 3 9 9 9h782c6 0 9 -4 9 -10v-790l-200 200h-591c-6 0 -9 3 -9 9v582z" />
    <glyph glyph-name="48" unicode="&#xe048;" 
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM600 600l-100 -300l-300 -100l100 300zM400 450c-28 0 -50 -22 -50 -50
s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
    <glyph glyph-name="49" unicode="&#xe049;" 
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700v-600c166 0 300 134 300 300s-134 300 -300 300z" />
    <glyph glyph-name="4a" unicode="&#xe04a;" 
d="M0 800h800v-100h-800v100zM0 600h500v-100h-500v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100zM750 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
    <glyph glyph-name="4b" unicode="&#xe04b;" 
d="M25 700h750c14 0 25 -11 25 -25v-75h-800v75c0 14 11 25 25 25zM0 500h800v-375c0 -14 -11 -25 -25 -25h-750c-14 0 -25 11 -25 25v375zM100 300v-100h100v100h-100zM300 300v-100h100v100h-100z" />
    <glyph glyph-name="4c" unicode="&#xe04c;" 
d="M100 800h100v-100h450l100 100l50 -50l-100 -100v-450h100v-100h-100v-100h-100v100h-500v500h-100v100h100v100zM200 600v-350l350 350h-350zM600 550l-350 -350h350v350z" />
    <glyph glyph-name="4d" unicode="&#xe04d;" 
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM400 600c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z
M200 452c0 20 15 42 34 48h3h3h8c12 0 28 -7 36 -16l91 -90l25 6c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100l6 25l-90 91c-9 8 -16 24 -16 36zM550 500c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
    <glyph glyph-name="4e" unicode="&#xe04e;" 
d="M300 800h200v-300h200l-300 -300l-300 300h200v300zM0 100h800v-100h-800v100z" />
    <glyph glyph-name="4f" unicode="&#xe04f;" 
d="M0 800h800v-100h-800v100zM400 600l300 -300h-200v-300h-200v300h-200z" />
    <glyph glyph-name="50" unicode="&#xe050;" 
d="M200 700h600v-600h-600l-200 300zM350 622l-72 -72l150 -150l-150 -150l72 -72l150 150l150 -150l72 72l-150 150l150 150l-72 72l-150 -150z" />
    <glyph glyph-name="51" unicode="&#xe051;" 
d="M400 700c220 0 400 -180 400 -400h-100c0 166 -134 300 -300 300s-300 -134 -300 -300h-100c0 220 180 400 400 400zM341 491l59 -88l59 88c82 -25 141 -101 141 -191c0 -110 -90 -200 -200 -200s-200 90 -200 200c0 90 59 166 141 191z" />
    <glyph glyph-name="52" unicode="&#xe052;" 
d="M0 800h300v-400h400v-400h-700v800zM400 800l300 -300h-300v300zM100 600v-100h100v100h-100zM100 400v-100h100v100h-100zM100 200v-100h400v100h-400z" />
    <glyph glyph-name="53" unicode="&#xe053;" horiz-adv-x="600" 
d="M200 700h100v-100h75c30 0 58 -6 81 -22c24 -15 44 -44 44 -78v-100h-100v94c-4 3 -13 6 -25 6h-250c-13 0 -25 -12 -25 -25v-50c0 -14 20 -40 34 -44l257 -65c66 -16 109 -73 109 -141v-50c0 -69 -56 -125 -125 -125h-75v-100h-100v100h-75c-30 0 -58 6 -81 22
c-24 15 -44 44 -44 78v100h100v-94c4 -3 13 -6 25 -6h250c13 0 25 12 25 25v50c0 14 -20 40 -34 44l-257 65c-66 16 -109 73 -109 141v50c0 69 56 125 125 125h75v100z" />
    <glyph glyph-name="54" unicode="&#xe054;" 
d="M0 700h300v-300l-300 -300v600zM500 700h300v-300l-300 -300v600z" />
    <glyph glyph-name="55" unicode="&#xe055;" 
d="M300 700v-600h-300v300zM800 700v-600h-300v300z" />
    <glyph glyph-name="56" unicode="&#xe056;" 
d="M300 700v-100c-111 0 -200 -89 -200 -200h200v-300h-300v300c0 165 135 300 300 300zM800 700v-100c-111 0 -200 -89 -200 -200h200v-300h-300v300c0 165 135 300 300 300z" />
    <glyph glyph-name="57" unicode="&#xe057;" 
d="M0 700h300v-300c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-200v300zM500 700h300v-300c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-200v300z" />
    <glyph glyph-name="58" unicode="&#xe058;" horiz-adv-x="600" 
d="M300 800l34 -34c11 -11 266 -269 266 -488c0 -165 -135 -300 -300 -300s-300 135 -300 300c0 219 255 477 266 488zM150 328c-28 0 -50 -22 -50 -50c0 -110 90 -200 200 -200c28 0 50 22 50 50s-22 50 -50 50c-55 0 -100 45 -100 100c0 28 -22 50 -50 50z" />
    <glyph glyph-name="59" unicode="&#xe059;" 
d="M400 800l400 -500h-800zM0 200h800v-200h-800v200z" />
    <glyph glyph-name="5a" unicode="&#xe05a;" horiz-adv-x="600" 
d="M300 800l300 -300h-600zM0 300h600l-300 -300z" />
    <glyph glyph-name="5b" unicode="&#xe05b;" 
d="M0 500h200v-200h-200v200zM300 500h200v-200h-200v200zM600 500h200v-200h-200v200z" />
    <glyph glyph-name="5c" unicode="&#xe05c;" 
d="M0 700h800v-100l-400 -200l-400 200v100zM0 500l400 -200l400 200v-400h-800v400z" />
    <glyph glyph-name="5d" unicode="&#xe05d;" 
d="M400 800l400 -200v-600h-800v600zM400 688l-300 -150v-188l300 -150l300 150v188zM200 500h400v-100l-200 -100l-200 100v100z" />
    <glyph glyph-name="5e" unicode="&#xe05e;" 
d="M600 700c69 0 134 -19 191 -50l-16 -106c-49 35 -109 56 -175 56c-131 0 -240 -84 -281 -200h331l-16 -100h-334c0 -36 8 -68 19 -100h297l-16 -100h-222c55 -61 133 -100 222 -100c78 0 147 30 200 78v-122c-59 -35 -127 -56 -200 -56c-147 0 -274 82 -344 200h-256
l19 100h197c-8 32 -16 66 -16 100h-200l25 100h191c45 172 198 300 384 300z" />
    <glyph glyph-name="5f" unicode="&#xe05f;" 
d="M0 700h700v-100h-700v100zM0 500h500v-100h-500v100zM0 300h800v-100h-800v100zM0 100h100v-100h-100v100zM200 100h100v-100h-100v100zM400 100h100v-100h-100v100z" />
    <glyph glyph-name="60" unicode="&#xe060;" 
d="M0 800h800v-100h-800v100zM200 600h400l-200 -200zM0 200h800v-200h-800v200z" />
    <glyph glyph-name="61" unicode="&#xe061;" 
d="M0 800h100v-800h-100v800zM600 800h200v-800h-200v800zM200 600l200 -200l-200 -200v400z" />
    <glyph glyph-name="62" unicode="&#xe062;" 
d="M0 800h200v-800h-200v800zM700 800h100v-800h-100v800zM600 600v-400l-200 200z" />
    <glyph glyph-name="63" unicode="&#xe063;" 
d="M0 800h800v-200h-800v200zM400 400l200 -200h-400zM0 100h800v-100h-800v100z" />
    <glyph glyph-name="64" unicode="&#xe064;" 
d="M0 800h200v-100h-100v-600h600v100h100v-200h-800v800zM400 800h400v-400l-150 150l-250 -250l-100 100l250 250z" />
    <glyph glyph-name="65" unicode="&#xe065;" 
d="M403 700c247 0 397 -300 397 -300s-150 -300 -397 -300c-253 0 -403 300 -403 300s150 300 403 300zM400 600c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200zM400 500c10 0 19 -3 28 -6c-16 -8 -28 -24 -28 -44c0 -28 22 -50 50 -50
c20 0 36 12 44 28c3 -9 6 -18 6 -28c0 -55 -45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
    <glyph glyph-name="66" unicode="&#xe066;" horiz-adv-x="900" 
d="M331 700h3h3c3 1 7 1 10 1c12 0 29 -8 37 -17l94 -93l66 65c57 57 156 57 212 0c59 -58 59 -154 0 -212l-65 -66l93 -94c10 -8 18 -25 18 -38c0 -28 -22 -50 -50 -50c-13 0 -32 9 -40 20l-62 65l-366 -365l-12 -16h-272v272l375 381l-63 63c-9 8 -16 24 -16 36
c0 20 16 42 35 48zM447 481l-313 -315l128 -132l316 316z" />
    <glyph glyph-name="67" unicode="&#xe067;" 
d="M0 800h300v-400h400v-400h-700v800zM400 800l300 -300h-300v300z" />
    <glyph glyph-name="68" unicode="&#xe068;" 
d="M200 800c0 0 200 -100 200 -300s-298 -302 -200 -500c0 0 -200 100 -200 300s300 300 200 500zM500 500c0 0 200 -100 200 -300c0 -150 -60 -200 -100 -200h-300c0 200 300 300 200 500z" />
    <glyph glyph-name="69" unicode="&#xe069;" 
d="M0 800h100v-800h-100v800zM200 800h300v-100h300l-200 -203l200 -197h-400v100h-200v400z" />
    <glyph glyph-name="6a" unicode="&#xe06a;" horiz-adv-x="400" 
d="M150 800h150l-100 -200h200l-150 -300h150l-300 -300l-100 300h134l66 200h-200z" />
    <glyph glyph-name="6b" unicode="&#xe06b;" 
d="M0 800h300v-100h500v-100h-800v200zM0 500h800v-450c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v450z" />
    <glyph glyph-name="6c" unicode="&#xe06c;" 
d="M150 800c83 0 150 -67 150 -150c0 -66 -41 -121 -100 -141v-118c15 5 33 9 50 9h200c28 0 50 22 50 50v59c-59 20 -100 75 -100 141c0 83 67 150 150 150s150 -67 150 -150c0 -66 -41 -121 -100 -141v-59c0 -82 -68 -150 -150 -150h-200c-14 0 -25 -7 -34 -16
c50 -24 84 -74 84 -134c0 -83 -67 -150 -150 -150s-150 67 -150 150c0 66 41 121 100 141v218c-59 20 -100 75 -100 141c0 83 67 150 150 150z" />
    <glyph glyph-name="6d" unicode="&#xe06d;" 
d="M0 800h400l-150 -150l150 -150l-100 -100l-150 150l-150 -150v400zM500 400l150 -150l150 150v-400h-400l150 150l-150 150z" />
    <glyph glyph-name="6e" unicode="&#xe06e;" 
d="M100 800l150 -150l150 150v-400h-400l150 150l-150 150zM400 400h400l-150 -150l150 -150l-100 -100l-150 150l-150 -150v400z" />
    <glyph glyph-name="6f" unicode="&#xe06f;" 
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700c-56 0 -108 -17 -153 -44l22 -19c33 -18 13 -48 -13 -59c-29 -13 -77 10 -65 -41c14 -55 -27 -3 -47 -15c-43 -25 49 -152 31 -156c-14 -3 -40 34 -59 34
c-8 0 -13 -5 -16 -10c1 -30 10 -57 19 -84c28 -10 74 1 97 -22c47 -28 100 -118 78 -162c33 -13 68 -22 106 -22c100 0 189 49 244 125c3 24 -5 44 -47 44c-69 0 -156 14 -153 97c2 46 101 108 66 143c-30 31 12 39 12 66c0 37 -65 32 -69 50s20 36 41 56
c-30 10 -61 19 -94 19zM631 591c-38 -11 -95 -35 -87 -53c5 -15 55 -2 68 -13c11 -10 15 -58 44 -31l19 22c-12 27 -26 53 -44 75z" />
    <glyph glyph-name="70" unicode="&#xe070;" 
d="M703 800l97 -100l-400 -400l-100 100l-200 -203l-100 100l300 303l100 -100zM0 100h800v-100h-800v100z" />
    <glyph glyph-name="71" unicode="&#xe071;" 
d="M0 700h100v-100h-100v100zM200 700h100v-100h-100v100zM400 700h100v-100h-100v100zM600 700h100v-100h-100v100zM0 500h100v-100h-100v100zM200 500h100v-100h-100v100zM400 500h100v-100h-100v100zM600 500h100v-100h-100v100zM0 300h100v-100h-100v100zM200 300h100
v-100h-100v100zM400 300h100v-100h-100v100zM600 300h100v-100h-100v100zM0 100h100v-100h-100v100zM200 100h100v-100h-100v100zM400 100h100v-100h-100v100zM600 100h100v-100h-100v100z" />
    <glyph glyph-name="72" unicode="&#xe072;" 
d="M0 800h200v-200h-200v200zM300 800h200v-200h-200v200zM600 800h200v-200h-200v200zM0 500h200v-200h-200v200zM300 500h200v-200h-200v200zM600 500h200v-200h-200v200zM0 200h200v-200h-200v200zM300 200h200v-200h-200v200zM600 200h200v-200h-200v200z" />
    <glyph glyph-name="73" unicode="&#xe073;" 
d="M0 800h300v-300h-300v300zM500 800h300v-300h-300v300zM0 300h300v-300h-300v300zM500 300h300v-300h-300v300z" />
    <glyph glyph-name="74" unicode="&#xe074;" 
d="M19 800h662c11 0 19 -8 19 -19v-331c0 -28 -22 -50 -50 -50h-600c-28 0 -50 22 -50 50v331c0 11 8 19 19 19zM0 309c16 -5 32 -9 50 -9h600c18 0 34 4 50 9v-290c0 -11 -8 -19 -19 -19h-662c-11 0 -19 8 -19 19v290zM550 200c-28 0 -50 -22 -50 -50s22 -50 50 -50
s50 22 50 50s-22 50 -50 50z" />
    <glyph glyph-name="75" unicode="&#xe075;" 
d="M0 700h300v-100h-50c-28 0 -50 -22 -50 -50v-150h300v150c0 28 -22 50 -50 50h-50v100h300v-100h-50c-28 0 -50 -22 -50 -50v-400c0 -28 22 -50 50 -50h50v-100h-300v100h50c28 0 50 22 50 50v150h-300v-150c0 -28 22 -50 50 -50h50v-100h-300v100h50c28 0 50 22 50 50
v400c0 28 -22 50 -50 50h-50v100z" />
    <glyph glyph-name="76" unicode="&#xe076;" 
d="M400 700c165 0 300 -135 300 -300v-100h50c28 0 50 -22 50 -50v-200c0 -28 -22 -50 -50 -50h-100c-28 0 -50 22 -50 50v350c0 111 -89 200 -200 200s-200 -89 -200 -200v-350c0 -28 -22 -50 -50 -50h-100c-28 0 -50 22 -50 50v200c0 28 22 50 50 50h50v100
c0 165 135 300 300 300z" />
    <glyph glyph-name="77" unicode="&#xe077;" 
d="M0 500c0 109 91 200 200 200s200 -91 200 -200c0 109 91 200 200 200s200 -91 200 -200c0 -55 -22 -104 -59 -141l-341 -343l-341 343c-37 36 -59 86 -59 141z" />
    <glyph glyph-name="78" unicode="&#xe078;" 
d="M400 700l400 -300l-100 3v-403h-200v200h-200v-200h-200v400h-100z" />
    <glyph glyph-name="79" unicode="&#xe079;" 
d="M0 800h800v-800h-800v800zM100 700v-300l100 100l400 -400h100v100l-200 200l100 100l100 -100v300h-600z" />
    <glyph glyph-name="7a" unicode="&#xe07a;" 
d="M19 800h762c11 0 19 -8 19 -19v-762c0 -11 -8 -19 -19 -19h-762c-11 0 -19 8 -19 19v762c0 11 8 19 19 19zM100 600v-300h100l100 -100h200l100 100h100v300h-600z" />
    <glyph glyph-name="7b" unicode="&#xe07b;" 
d="M200 600c79 0 143 -56 200 -122c58 66 119 122 200 122c131 0 200 -101 200 -200s-69 -200 -200 -200c-81 0 -142 56 -200 122c-58 -66 -121 -122 -200 -122c-131 0 -200 101 -200 200s69 200 200 200zM200 500c-74 0 -100 -54 -100 -100s26 -100 100 -100
c42 0 88 47 134 100c-46 53 -92 100 -134 100zM600 500c-43 0 -89 -47 -134 -100c45 -53 91 -100 134 -100c74 0 100 54 100 100s-26 100 -100 100z" />
    <glyph glyph-name="7c" unicode="&#xe07c;" horiz-adv-x="400" 
d="M300 800c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100zM150 550c83 0 150 -69 150 -150c0 -66 -100 -214 -100 -250c0 -28 22 -50 50 -50s50 22 50 50h100c0 -83 -67 -150 -150 -150s-150 64 -150 150s100 222 100 250s-22 50 -50 50
s-50 -22 -50 -50h-100c0 83 67 150 150 150z" />
    <glyph glyph-name="7d" unicode="&#xe07d;" 
d="M200 800h500v-100h-122c-77 -197 -156 -392 -234 -588l-6 -12h162v-100h-500v100h122c77 197 156 392 234 588l7 12h-163v100z" />
    <glyph glyph-name="7e" unicode="&#xe07e;" 
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM100 100h600v-100h-600v100z" />
    <glyph glyph-name="7f" unicode="&#xe07f;" 
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100z" />
    <glyph glyph-name="80" unicode="&#xe080;" 
d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM200 100h600v-100h-600v100z" />
    <glyph glyph-name="81" unicode="&#xe081;" 
d="M550 800c138 0 250 -112 250 -250s-112 -250 -250 -250c-16 0 -30 3 -44 6l-6 -6v-100h-200v-200h-300v200l306 306c-3 14 -6 28 -6 44c0 138 112 250 250 250zM600 700c-55 0 -100 -45 -100 -100s45 -100 100 -100s100 45 100 100s-45 100 -100 100z" />
    <glyph glyph-name="82" unicode="&#xe082;" 
d="M134 600h3h4h4h5h500c28 0 50 -22 50 -50v-350h100v-150c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v150h100v350v2c0 20 15 42 34 48zM200 500v-300h100v-100h200v100h100v300h-400z" />
    <glyph glyph-name="83" unicode="&#xe083;" 
d="M0 800h400v-400h-400v400zM500 600h100v-400h-400v100h300v300zM700 400h100v-400h-400v100h300v300z" />
    <glyph glyph-name="84" unicode="&#xe084;" horiz-adv-x="600" 
d="M337 694c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-300 -150c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50c0 21 16 44 37 49zM437 544c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-400 -200c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50
c0 21 16 44 37 49zM437 344c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-106 -56c24 -4 43 -26 43 -50c0 -28 -23 -51 -51 -51c-2 0 -6 1 -8 1h-200c-26 1 -48 24 -48 50c0 16 12 36 26 44zM151 -50c0 23 20 50 46 50h3h4h5h100c28 0 50 -22 50 -50
s-22 -50 -50 -50h-100c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51z" />
    <glyph glyph-name="85" unicode="&#xe085;" 
d="M199 800h100v-200h-200v100h100v100zM587 797c18 1 38 1 56 -3c36 -8 69 -26 97 -54c78 -77 78 -203 0 -281l-150 -150c-8 -13 -27 -24 -42 -24c-28 0 -50 22 -50 50c0 15 10 35 23 43l150 150c40 40 40 105 0 144c-40 40 -110 34 -144 0l-43 -44c-8 -13 -28 -24 -43 -24
c-28 0 -50 22 -50 50c0 15 11 35 24 43l44 44c33 33 72 53 128 56zM209 490c4 5 13 16 21 16h4c2 0 6 1 8 1c28 0 50 -22 50 -50c0 -11 -7 -27 -15 -35l-150 -150c-40 -40 -40 -105 0 -144c40 -40 110 -34 144 0l44 44c8 13 28 24 43 24c28 0 50 -22 50 -50
c0 -15 -11 -35 -24 -43l-44 -44c-22 -22 -48 -37 -75 -47c-71 -25 -150 -9 -206 47c-78 77 -78 203 0 281zM499 200h200v-100h-100v-100h-100v200z" />
    <glyph glyph-name="86" unicode="&#xe086;" 
d="M587 797c18 1 38 1 56 -3c36 -8 69 -26 97 -54c78 -77 78 -203 0 -281l-150 -150c-62 -62 -131 -81 -181 -78s-70 17 -85 25s-26 27 -26 44c0 28 22 51 50 51c8 0 19 -3 26 -7c0 0 15 -11 41 -13c26 -1 63 4 106 47l150 150c40 40 40 105 0 144c-40 40 -110 34 -144 0
c-8 -13 -27 -24 -42 -24c-28 0 -50 22 -50 50c0 15 11 35 24 43c33 33 72 53 128 56zM387 566c50 -2 63 -17 84 -22s38 -28 38 -49c0 -28 -22 -50 -50 -50c-10 0 -24 5 -32 11c0 0 -19 9 -47 10s-63 -4 -103 -44l-150 -150c-40 -40 -40 -105 0 -144c40 -40 110 -34 144 0
c8 13 28 24 43 24c28 0 50 -22 50 -50c0 -15 -11 -35 -24 -43c-22 -22 -48 -37 -75 -47c-71 -25 -150 -9 -206 47c-78 77 -78 203 0 281l150 150c60 60 128 78 178 76z" />
    <glyph glyph-name="87" unicode="&#xe087;" 
d="M0 700h300v-300h-300v300zM400 700h400v-100h-400v100zM400 500h300v-100h-300v100zM0 300h300v-300h-300v300zM400 300h400v-100h-400v100zM400 100h300v-100h-300v100z" />
    <glyph glyph-name="88" unicode="&#xe088;" 
d="M50 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 700h600v-100h-600v100zM50 500c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 500h600v-100h-600v100zM50 300c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50
s22 50 50 50zM200 300h600v-100h-600v100zM50 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 100h600v-100h-600v100z" />
    <glyph glyph-name="89" unicode="&#xe089;" 
d="M800 800l-400 -800l-100 300l-300 100z" />
    <glyph glyph-name="8a" unicode="&#xe08a;" horiz-adv-x="600" 
d="M300 700c110 0 200 -90 200 -200v-100h100v-400h-600v400h100v100c0 110 90 200 200 200zM300 600c-56 0 -100 -44 -100 -100v-100h200v100c0 56 -44 100 -100 100z" />
    <glyph glyph-name="8b" unicode="&#xe08b;" horiz-adv-x="600" 
d="M300 800c110 0 200 -90 200 -200v-200h100v-400h-600v400h400v200c0 56 -44 100 -100 100s-100 -44 -100 -100h-100c0 110 90 200 200 200z" />
    <glyph glyph-name="8c" unicode="&#xe08c;" 
d="M400 700v-100c-111 0 -200 -89 -200 -200h100l-150 -200l-150 200h100c0 165 135 300 300 300zM650 600l150 -200h-100c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-100z" />
    <glyph glyph-name="8d" unicode="&#xe08d;" 
d="M100 800h600v-300h100l-150 -250l-150 250h100v200h-400v-100h-100v200zM150 550l150 -250h-100v-200h400v100h100v-200h-600v300h-100z" />
    <glyph glyph-name="8e" unicode="&#xe08e;" 
d="M600 700l200 -150l-200 -150v100h-500v-100h-100v100c0 55 45 100 100 100h500v100zM200 300v-100h500v100h100v-100c0 -54 -46 -100 -100 -100h-500v-100l-200 150z" />
    <glyph glyph-name="8f" unicode="&#xe08f;" horiz-adv-x="900" 
d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c5 -3 12 -8 16 -12l100 -100c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-100 100c-4 3 -9 9 -12 13c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 200
c142 0 250 108 250 250c0 139 -111 250 -250 250s-250 -111 -250 -250s111 -250 250 -250z" />
    <glyph glyph-name="90" unicode="&#xe090;" horiz-adv-x="600" 
d="M300 800c166 0 300 -134 300 -300c0 -200 -300 -500 -300 -500s-300 300 -300 500c0 166 134 300 300 300zM300 700c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200z" />
    <glyph glyph-name="91" unicode="&#xe091;" horiz-adv-x="900" 
d="M0 800h800v-541c1 -3 1 -8 1 -11s0 -7 -1 -10v-238h-800v800zM495 250c0 26 22 50 50 50h5h150v400h-600v-600h600v100h-150h-5c-28 0 -50 22 -50 50zM350 600c83 0 150 -67 150 -150c0 -100 -150 -250 -150 -250s-150 150 -150 250c0 83 67 150 150 150zM350 500
c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
    <glyph glyph-name="92" unicode="&#xe092;" horiz-adv-x="600" 
d="M0 700h200v-600h-200v600zM400 700h200v-600h-200v600z" />
    <glyph glyph-name="93" unicode="&#xe093;" horiz-adv-x="600" 
d="M0 700l600 -300l-600 -300v600z" />
    <glyph glyph-name="94" unicode="&#xe094;" horiz-adv-x="600" 
d="M300 700c166 0 300 -134 300 -300s-134 -300 -300 -300s-300 134 -300 300s134 300 300 300z" />
    <glyph glyph-name="95" unicode="&#xe095;" 
d="M400 700v-600l-400 300zM400 400l400 300v-600z" />
    <glyph glyph-name="96" unicode="&#xe096;" 
d="M0 700l400 -300l-400 -300v600zM400 100v600l400 -300z" />
    <glyph glyph-name="97" unicode="&#xe097;" 
d="M0 700h200v-600h-200v600zM200 400l500 300v-600z" />
    <glyph glyph-name="98" unicode="&#xe098;" 
d="M0 700l500 -300l-500 -300v600zM500 100v600h200v-600h-200z" />
    <glyph glyph-name="99" unicode="&#xe099;" horiz-adv-x="600" 
d="M0 700h600v-600h-600v600z" />
    <glyph glyph-name="9a" unicode="&#xe09a;" 
d="M200 800h400v-200h200v-400h-200v-200h-400v200h-200v400h200v200z" />
    <glyph glyph-name="9b" unicode="&#xe09b;" 
d="M0 700h800v-100h-800v100zM0 403h800v-100h-800v100zM0 103h800v-100h-800v100z" />
    <glyph glyph-name="9c" unicode="&#xe09c;" horiz-adv-x="600" 
d="M278 700c7 2 13 4 22 4c55 0 100 -45 100 -100v-4v-200c0 -55 -45 -100 -100 -100s-100 45 -100 100v200v2c0 44 35 88 78 98zM34 500h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-50c0 -111 89 -200 200 -200s200 89 200 200v50c0 28 22 50 50 50s50 -22 50 -50v-50
c0 -148 -109 -270 -250 -294v-106h50c55 0 100 -45 100 -100h-400c0 55 45 100 100 100h50v106c-141 24 -250 146 -250 294v50v2c0 20 15 42 34 48z" />
    <glyph glyph-name="9d" unicode="&#xe09d;" 
d="M0 500h800v-200h-800v200z" />
    <glyph glyph-name="9e" unicode="&#xe09e;" 
d="M34 700h4h3h4h5h700c28 0 50 -22 50 -50v-500c0 -28 -22 -50 -50 -50h-250v-100h100c55 0 100 -45 100 -100h-600c0 55 45 100 100 100h100v100h-250c-28 0 -50 22 -50 50v500v2c0 20 15 42 34 48zM100 600v-400h600v400h-600z" />
    <glyph glyph-name="9f" unicode="&#xe09f;" 
d="M272 700c-14 -40 -22 -84 -22 -128c0 -221 179 -400 400 -400c45 0 88 11 128 25c-53 -158 -202 -275 -378 -275c-221 0 -400 179 -400 400c0 176 114 325 272 378z" />
    <glyph glyph-name="a0" unicode="&#xe0a0;" 
d="M350 700l150 -150h-100v-150h150v100l150 -150l-150 -150v100h-150v-150h100l-150 -150l-150 150h100v150h-150v-100l-150 150l150 150v-100h150v150h-100z" />
    <glyph glyph-name="a1" unicode="&#xe0a1;" 
d="M800 800v-550c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150c17 0 35 -4 50 -9v206c-201 -6 -327 -27 -400 -50v-397c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150c17 0 35 -4 50 -9v409s100 100 600 100z" />
    <glyph glyph-name="a2" unicode="&#xe0a2;" horiz-adv-x="700" 
d="M499 700c51 0 102 -20 141 -59c78 -77 78 -203 0 -281l-250 -244c-48 -48 -127 -48 -175 0s-48 127 0 175c32 32 64 65 96 97l69 -69l-59 -63l-38 -34c-10 -10 -10 -28 0 -38s28 -10 38 0l250 247c38 40 39 103 0 141c-40 40 -105 40 -144 0v-3l-278 -272
c-67 -69 -68 -179 0 -247c69 -69 181 -69 250 0c39 44 83 84 125 125l69 -69l-125 -125c-107 -107 -281 -107 -388 0s-107 281 0 388l278 272c38 39 90 59 141 59z" />
    <glyph glyph-name="a3" unicode="&#xe0a3;" 
d="M600 800l200 -200l-100 -100l-200 200zM400 600l200 -200l-400 -400h-200v200z" />
    <glyph glyph-name="a4" unicode="&#xe0a4;" 
d="M550 800c83 0 150 -90 150 -200s-67 -200 -150 -200c-22 0 -41 5 -59 16c6 27 9 55 9 84c0 85 -27 158 -72 212c27 52 71 88 122 88zM250 700c83 0 150 -90 150 -200s-67 -200 -150 -200s-150 90 -150 200s67 200 150 200zM725 384c44 -22 75 -66 75 -118v-166h-200v66
c0 50 -17 96 -44 134c67 2 126 33 169 84zM75 284c44 -53 106 -84 175 -84s131 31 175 84c44 -22 75 -66 75 -118v-166h-500v166c0 52 31 96 75 118z" />
    <glyph glyph-name="a5" unicode="&#xe0a5;" 
d="M400 800c110 0 200 -112 200 -250s-90 -250 -200 -250s-200 112 -200 250s90 250 200 250zM191 300c54 -61 128 -100 209 -100s155 39 209 100c107 -4 191 -92 191 -200v-100h-800v100c0 108 84 196 191 200z" />
    <glyph glyph-name="a6" unicode="&#xe0a6;" horiz-adv-x="600" 
d="M19 800h462c11 0 19 -8 19 -19v-762c0 -11 -8 -19 -19 -19h-462c-11 0 -19 8 -19 19v762c0 11 8 19 19 19zM100 700v-500h300v500h-300zM250 150c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
    <glyph glyph-name="a7" unicode="&#xe0a7;" 
d="M350 800c17 0 34 0 50 -3v-397l-297 297c63 64 150 103 247 103zM500 694c169 -24 300 -168 300 -344c0 -193 -157 -350 -350 -350c-85 0 -162 31 -222 81l272 272v341zM91 562l237 -234l-216 -212c-69 54 -112 139 -112 234c0 84 36 158 91 212z" />
    <glyph glyph-name="a8" unicode="&#xe0a8;" 
d="M92 650c0 23 20 50 46 50h3h4h5h400c28 0 50 -22 50 -50s-22 -50 -50 -50h-50v-200h100c55 0 100 -45 100 -100h-300v-300l-56 -100l-44 100v300h-300c0 55 45 100 100 100h100v200h-50c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51z" />
    <glyph glyph-name="a9" unicode="&#xe0a9;" 
d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM300 600v-400l300 200z" />
    <glyph glyph-name="aa" unicode="&#xe0aa;" 
d="M300 800h200v-300h300v-200h-300v-300h-200v300h-300v200h300v300z" />
    <glyph glyph-name="ab" unicode="&#xe0ab;" 
d="M300 800h100v-400h-100v400zM172 656l62 -78l-40 -31c-58 -46 -94 -117 -94 -197c0 -139 111 -250 250 -250s250 111 250 250c0 80 -39 151 -97 197l-37 31l62 78l38 -31c82 -64 134 -163 134 -275c0 -193 -157 -350 -350 -350s-350 157 -350 350c0 112 54 211 134 275z
" />
    <glyph glyph-name="ac" unicode="&#xe0ac;" 
d="M200 800h400v-200h-400v200zM9 500h782c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-91v200h-600v-200h-91c-6 0 -9 3 -9 9v282c0 6 3 9 9 9zM200 300h400v-300h-400v300z" />
    <glyph glyph-name="ad" unicode="&#xe0ad;" 
d="M0 700h100v-700h-100v700zM700 700h100v-700h-100v700zM200 600h200v-100h-200v100zM300 400h200v-100h-200v100zM400 200h200v-100h-200v100z" />
    <glyph glyph-name="ae" unicode="&#xe0ae;" 
d="M325 700c42 -141 87 -280 131 -419c29 74 59 148 88 222c29 -58 57 -116 87 -172h169v-100h-231l-13 28c-37 -92 -74 -184 -112 -275c-39 127 -79 255 -119 382c-41 -133 -83 -265 -125 -397c-28 88 -56 175 -84 262h-116v100h188l9 -34l3 -6c42 137 83 273 125 409z" />
    <glyph glyph-name="af" unicode="&#xe0af;" 
d="M200 700c0 58 42 100 100 100s100 -42 100 -100c0 -28 -20 -48 -31 -72c-2 -6 -3 -16 -3 -28h234v-234c12 0 22 3 28 6c24 10 44 28 72 28c58 0 100 -42 100 -100s-42 -100 -100 -100c-28 0 -48 20 -72 31c-6 2 -16 3 -28 3v-234h-234c0 12 3 22 6 28c10 24 28 44 28 72
c0 58 -42 100 -100 100s-100 -42 -100 -100c0 -28 20 -48 31 -72c2 -6 3 -16 3 -28h-234v600h234c0 12 -3 22 -6 28c-10 24 -28 44 -28 72z" />
    <glyph glyph-name="b0" unicode="&#xe0b0;" horiz-adv-x="500" 
d="M247 700c83 0 147 -20 190 -59s60 -93 60 -141c0 -117 -66 -181 -116 -225s-84 -67 -84 -150v-25h-100v25c0 117 69 181 119 225s81 67 81 150c0 25 -8 48 -28 66s-56 34 -122 34s-97 -18 -116 -37s-27 -43 -31 -69l-100 12c5 38 19 88 59 128s103 66 188 66zM197 0h100
v-100h-100v100z" />
    <glyph glyph-name="b1" unicode="&#xe0b1;" 
d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -69 -48 -127 -112 -144c-22 55 -75 94 -138 94c-20 0 -39 -5 -56 -12c-17 64 -75 112 -144 112s-127 -48 -144 -112c-17 7 -36 12 -56 12c-37 0 -71 -16 -97 -38c-33 36 -53 86 -53 138
c0 110 90 200 200 200c23 114 129 200 250 200zM334 300h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-200c0 -28 -22 -50 -50 -50s-50 22 -50 50v200v2c0 20 15 42 34 48zM134 200h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-100c0 -28 -22 -50 -50 -50s-50 22 -50 50v100v2
c0 20 15 42 34 48zM534 200h3h4c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-100c0 -28 -22 -50 -50 -50s-50 22 -50 50v100v2c0 20 15 42 34 48z" />
    <glyph glyph-name="b2" unicode="&#xe0b2;" 
d="M600 700l200 -150l-200 -150v100h-53v-3l-150 -187l175 -207v-3h28v100l200 -150l-200 -150v100h-25c-35 0 -57 10 -78 34v4l-163 190l-153 -190c-21 -26 -46 -38 -81 -38h-100v100h103v3l163 203l-163 191v3h-103v100h100c35 0 57 -10 78 -34v-4l150 -174l141 174
c21 26 46 38 81 38h50v100z" />
    <glyph glyph-name="b3" unicode="&#xe0b3;" 
d="M400 700c109 0 208 -47 281 -119l119 119v-300h-300l109 110c-55 55 -126 90 -209 90c-166 0 -300 -134 -300 -300s134 -300 300 -300c84 0 158 33 212 88l69 -69c-72 -73 -171 -119 -281 -119c-220 0 -400 180 -400 400s180 400 400 400z" />
    <glyph glyph-name="b4" unicode="&#xe0b4;" 
d="M400 800h400v-400l-166 166l-400 -400l166 -166h-400v400l166 -166l400 400z" />
    <glyph glyph-name="b5" unicode="&#xe0b5;" horiz-adv-x="600" 
d="M250 800l250 -300h-200v-200h200l-250 -300l-250 300h200v200h-200z" />
    <glyph glyph-name="b6" unicode="&#xe0b6;" 
d="M300 600v-200h200v200l300 -250l-300 -250v200h-200v-200l-300 250z" />
    <glyph glyph-name="b7" unicode="&#xe0b7;" 
d="M0 800c441 0 800 -359 800 -800h-200c0 333 -267 600 -600 600v200zM0 500c275 0 500 -225 500 -500h-200c0 167 -133 300 -300 300v200zM0 200c111 0 200 -89 200 -200h-200v200z" />
    <glyph glyph-name="b8" unicode="&#xe0b8;" 
d="M100 800c386 0 700 -314 700 -700h-100c0 332 -268 600 -600 600v100zM100 600c276 0 500 -224 500 -500h-100c0 222 -178 400 -400 400v100zM100 400c165 0 300 -135 300 -300h-100c0 111 -89 200 -200 200v100zM100 200c55 0 100 -45 100 -100s-45 -100 -100 -100
s-100 45 -100 100s45 100 100 100z" />
    <glyph glyph-name="b9" unicode="&#xe0b9;" 
d="M300 800h400c55 0 100 -45 100 -100v-200h-400v150c0 28 -22 50 -50 50s-50 -22 -50 -50v-250h400v-300c0 -55 -45 -100 -100 -100h-500c-55 0 -100 45 -100 100v200h100v-150c0 -28 22 -50 50 -50s50 22 50 50v550c0 55 45 100 100 100z" />
    <glyph glyph-name="ba" unicode="&#xe0ba;" 
d="M75 700h225v-100h-200v-500h400v100h100v-125c0 -40 -35 -75 -75 -75h-450c-40 0 -75 35 -75 75v550c0 40 35 75 75 75zM600 700l200 -200l-200 -200v100h-200c-94 0 -173 -65 -194 -153c23 199 189 353 394 353v100z" />
    <glyph glyph-name="bb" unicode="&#xe0bb;" 
d="M500 700l300 -284l-300 -316v200h-100c-200 0 -348 -102 -400 -300c0 295 100 500 500 500v200z" />
    <glyph glyph-name="bc" unicode="&#xe0bc;" 
d="M381 791l19 9l19 -9c127 -53 253 -108 381 -160v-31c0 -166 -67 -313 -147 -419c-40 -53 -83 -97 -125 -128s-82 -53 -128 -53s-86 22 -128 53s-85 75 -125 128c-80 107 -147 253 -147 419v31c128 52 254 107 381 160zM400 100v591l-294 -122c8 -126 58 -243 122 -328
c35 -46 73 -86 106 -110s62 -31 66 -31z" />
    <glyph glyph-name="bd" unicode="&#xe0bd;" 
d="M600 800h100v-800h-100v800zM400 700h100v-700h-100v700zM200 500h100v-500h-100v500zM0 300h100v-300h-100v300z" />
    <glyph glyph-name="be" unicode="&#xe0be;" 
d="M300 800h100v-200h200l100 -100l-100 -100h-200v-400h-100v500h-200l-100 100l100 100h200v100z" />
    <glyph glyph-name="bf" unicode="&#xe0bf;" 
d="M200 800h100v-600h200l-250 -200l-250 200h200v600zM400 800h200v-100h-200v100zM400 600h300v-100h-300v100zM400 400h400v-100h-400v100z" />
    <glyph glyph-name="c0" unicode="&#xe0c0;" 
d="M200 800h100v-600h200l-250 -200l-250 200h200v600zM400 800h400v-100h-400v100zM400 600h300v-100h-300v100zM400 400h200v-100h-200v100z" />
    <glyph glyph-name="c1" unicode="&#xe0c1;" 
d="M75 700h650c40 0 75 -35 75 -75v-550c0 -40 -35 -75 -75 -75h-650c-40 0 -75 35 -75 75v550c0 40 35 75 75 75zM100 600v-100h100v100h-100zM300 600v-100h400v100h-400zM100 400v-100h100v100h-100zM300 400v-100h400v100h-400zM100 200v-100h100v100h-100zM300 200
v-100h400v100h-400z" />
    <glyph glyph-name="c2" unicode="&#xe0c2;" 
d="M400 800l100 -300h300l-250 -200l100 -300l-250 200l-250 -200l100 300l-250 200h300z" />
    <glyph glyph-name="c3" unicode="&#xe0c3;" 
d="M400 800c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM150 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM650 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM400 600c110 0 200 -90 200 -200
s-90 -200 -200 -200s-200 90 -200 200s90 200 200 200zM50 450c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM750 450c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM150 200c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50
s22 50 50 50zM650 200c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM400 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" />
    <glyph glyph-name="c4" unicode="&#xe0c4;" 
d="M34 800h632c18 0 34 -16 34 -34v-732c0 -18 -16 -34 -34 -34h-632c-18 0 -34 16 -34 34v732c0 18 16 34 34 34zM100 700v-500h500v500h-500zM350 150c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
    <glyph glyph-name="c5" unicode="&#xe0c5;" 
d="M0 800h300l500 -500l-300 -300l-500 500v300zM200 700c-55 0 -100 -45 -100 -100s45 -100 100 -100s100 45 100 100s-45 100 -100 100z" />
    <glyph glyph-name="c6" unicode="&#xe0c6;" 
d="M0 600h200l300 -300l-200 -200l-300 300v200zM340 600h160l300 -300l-200 -200l-78 78l119 122zM150 500c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
    <glyph glyph-name="c7" unicode="&#xe0c7;" 
d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM400 600c110 0 200 -90 200 -200s-90 -200 -200 -200s-200 90 -200 200
s90 200 200 200zM400 500c-56 0 -100 -44 -100 -100s44 -100 100 -100s100 44 100 100s-44 100 -100 100z" />
    <glyph glyph-name="c8" unicode="&#xe0c8;" 
d="M0 700h559l-100 -100h-359v-500h500v159l100 100v-359h-700v700zM700 700l100 -100l-400 -400l-200 200l100 100l100 -100z" />
    <glyph glyph-name="c9" unicode="&#xe0c9;" 
d="M9 800h782c6 0 9 -3 9 -9v-782c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v782c0 6 3 9 9 9zM150 722l-72 -72l100 -100l-100 -100l72 -72l172 172zM400 500v-100h300v100h-300z" />
    <glyph glyph-name="ca" unicode="&#xe0ca;" 
d="M0 800h800v-200h-50c0 55 -45 100 -100 100h-150v-550c0 -28 22 -50 50 -50h50v-100h-400v100h50c28 0 50 22 50 50v550h-150c-55 0 -100 -45 -100 -100h-50v200z" />
    <glyph glyph-name="cb" unicode="&#xe0cb;" 
d="M0 700h100v-400h-100v400zM200 700h350c21 0 39 -13 47 -31c0 0 103 -291 103 -319s-22 -50 -50 -50h-150c-28 0 -50 -25 -50 -50s39 -158 47 -184c8 -27 -5 -55 -31 -63c-27 -8 -53 5 -66 31s-110 219 -128 238c-19 18 -44 28 -72 28v400z" />
    <glyph glyph-name="cc" unicode="&#xe0cc;" 
d="M444 700l22 -3c26 -8 39 -36 31 -63s-47 -159 -47 -184s22 -50 50 -50h150c28 0 50 -22 50 -50s-103 -319 -103 -319c-8 -18 -26 -31 -47 -31h-350v400c28 0 53 10 72 28c18 19 115 212 128 238c10 20 25 32 44 34zM0 400h100v-400h-100v400z" />
    <glyph glyph-name="cd" unicode="&#xe0cd;" 
d="M200 700h300v-100h-100v-6c25 -4 50 -8 72 -16l-35 -94c-29 10 -57 16 -87 16c-139 0 -250 -111 -250 -250s111 -250 250 -250s250 111 250 250c0 31 -5 61 -16 91l94 34c13 -38 22 -80 22 -125c0 -193 -157 -350 -350 -350s-350 157 -350 350c0 176 130 323 300 347v3
h-100v100zM700 588c0 0 -296 -353 -316 -372c-20 -20 -52 -20 -72 0s-20 52 0 72s388 300 388 300z" />
    <glyph glyph-name="ce" unicode="&#xe0ce;" 
d="M600 700l200 -150l-200 -150v100h-600v100h600v100zM200 300v-100h600v-100h-600v-100l-200 150z" />
    <glyph glyph-name="cf" unicode="&#xe0cf;" 
d="M300 800h100c55 0 100 -45 100 -100h100c55 0 100 -45 100 -100h-700c0 55 45 100 100 100h100c0 55 45 100 100 100zM100 500h100v-350c0 -28 22 -50 50 -50s50 22 50 50v350h100v-350c0 -28 22 -50 50 -50s50 22 50 50v350h100v-481c0 -11 -8 -19 -19 -19h-462
c-11 0 -19 8 -19 19v481z" />
    <glyph glyph-name="d0" unicode="&#xe0d0;" 
d="M100 800h200v-400c0 -55 45 -100 100 -100s100 45 100 100v400h100v-400c0 -110 -90 -200 -200 -200h-50c-138 0 -250 90 -250 200v400zM0 100h700v-100h-700v100z" />
    <glyph glyph-name="d1" unicode="&#xe0d1;" 
d="M9 700h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM609 700h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM309 500h182c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v282
c0 6 3 9 9 9zM0 100h800v-100h-800v100z" />
    <glyph glyph-name="d2" unicode="&#xe0d2;" 
d="M10 700h181c6 0 9 -3 9 -9v-191h-200v191c0 6 4 9 10 9zM610 700h181c6 0 9 -3 9 -9v-191h-200v191c0 6 4 9 10 9zM310 600h181c6 0 9 -3 9 -9v-91h-200v91c0 6 4 9 10 9zM0 400h800v-100h-800v100zM0 200h200v-191c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v191zM300 200
h200v-91c0 -6 -3 -9 -9 -9h-181c-6 0 -10 3 -10 9v91zM600 200h200v-191c0 -6 -3 -9 -9 -9h-181c-6 0 -10 3 -10 9v191z" />
    <glyph glyph-name="d3" unicode="&#xe0d3;" 
d="M0 700h800v-100h-800v100zM9 500h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM309 500h182c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v282c0 6 3 9 9 9zM609 500h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182
c-6 0 -9 3 -9 9v482c0 6 3 9 9 9z" />
    <glyph glyph-name="d4" unicode="&#xe0d4;" 
d="M50 600h500c28 0 50 -22 50 -50v-150l100 100h100v-300h-100l-100 100v-150c0 -28 -22 -50 -50 -50h-500c-28 0 -50 22 -50 50v400c0 28 22 50 50 50z" />
    <glyph glyph-name="d5" unicode="&#xe0d5;" 
d="M334 800h66v-800h-66l-134 200h-200v400h200zM500 600v100c26 0 52 -4 75 -10c130 -32 225 -150 225 -290s-95 -259 -225 -291c-23 -6 -49 -9 -75 -9v100c16 0 33 2 50 6c86 22 150 100 150 194s-64 172 -150 194h-3c-16 4 -32 6 -47 6zM500 500l22 -3h3v-3
c42 -12 75 -49 75 -94c0 -46 -32 -85 -75 -97l-25 -3v200z" />
    <glyph glyph-name="d6" unicode="&#xe0d6;" horiz-adv-x="600" 
d="M334 800h66v-800h-66l-134 200h-200v400h200zM500 500l22 -3h3v-3c42 -12 75 -49 75 -94c0 -46 -32 -85 -75 -97l-25 -3v200z" />
    <glyph glyph-name="d7" unicode="&#xe0d7;" horiz-adv-x="400" 
d="M334 800h66v-800h-66l-134 200h-200v400h200z" />
    <glyph glyph-name="d8" unicode="&#xe0d8;" 
d="M309 800h82c6 0 10 -4 12 -9l294 -682l3 -19v-81c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v81l3 19l294 682c2 5 6 9 12 9zM300 500v-200h100v200h-100zM300 200v-100h100v100h-100z" />
    <glyph glyph-name="d9" unicode="&#xe0d9;" 
d="M375 700c138 0 269 -39 378 -109l-53 -82c-93 60 -205 91 -325 91c-119 0 -229 -35 -322 -94l-53 88c109 69 238 106 375 106zM378 400c79 0 151 -23 213 -62l-53 -85c-46 29 -101 47 -160 47c-60 0 -114 -17 -162 -47l-54 85c62 40 136 62 216 62zM375 100
c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" />
    <glyph glyph-name="da" unicode="&#xe0da;" horiz-adv-x="900" 
d="M551 700c16 0 30 -3 44 -6l-94 -94v-200h200l94 94c3 -14 6 -28 6 -44c0 -138 -112 -250 -250 -250c-32 0 -62 6 -90 16l-288 -288c-20 -19 -46 -28 -72 -28s-52 11 -72 31c-39 39 -39 102 0 141l291 287c-11 28 -19 59 -19 91c0 138 112 250 250 250zM101 50
c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" />
    <glyph glyph-name="db" unicode="&#xe0db;" 
d="M141 700c85 -80 167 -164 250 -247c83 82 165 167 250 247l140 -141c-80 -85 -164 -167 -247 -250c82 -83 167 -165 247 -250l-140 -140c-85 80 -167 164 -250 247c-83 -82 -165 -167 -250 -247l-141 140c80 85 164 167 247 250c-82 83 -167 165 -247 250z" />
    <glyph glyph-name="dc" unicode="&#xe0dc;" 
d="M0 800h100l231 -300h38l231 300h100l-225 -300h225v-100h-300v-100h300v-100h-300v-200h-100v200h-300v100h300v100h-300v100h225z" />
    <glyph glyph-name="dd" unicode="&#xe0dd;" horiz-adv-x="900" 
d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c4 -2 10 -6 13 -9l103 -103c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-103 103c-3 2 -7 7 -9 10c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 700
c-139 0 -250 -111 -250 -250s111 -250 250 -250c60 0 120 22 160 59c8 12 21 26 34 32l3 3c34 43 53 97 53 156c0 139 -111 250 -250 250zM300 600h100v-100h100v-100h-100v-100h-100v100h-100v100h100v100z" />
    <glyph glyph-name="de" unicode="&#xe0de;" horiz-adv-x="900" 
d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c4 -2 10 -6 13 -9l103 -103c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-103 103c-3 2 -7 7 -9 10c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 700
c-139 0 -250 -111 -250 -250s111 -250 250 -250c60 0 120 22 160 59c8 12 21 26 34 32l3 3c34 43 53 97 53 156c0 139 -111 250 -250 250zM200 500h300v-100h-300v100z" />
  </font>
</defs></svg>
       pFFTMkL      OS/24Qb     `cmap   x  Bcvt  2 2  Y   fpgm
x9  Y  	gasp     Y   glyf    J6headPh  L   6hhea  M,   $hmtx   MP  loca4  P  maxpa	  R    namebz  R  post3  U@  cprep  cT          =    χ    χ    0   p0   '                         PfEd      H  e                               <                                                                                                                                                                                                                                                                                                  33'3#Ҹ          !!5!!5!5!,pdpDdddd          !!5!!!!',pdpDddddd     d     3'354&"4"ddВ͑22hh      d      4&"3'3541"͒Вdd͑hh22             !!!!!!!! dXd dXddddddd            !!!!!!!! X Xddddddd               !!!!!!!! X Xddddddd           2%6&54%#%.'!jaH\pp^*Kt1b;2p"&ku408wVSj         333d ,              &%#	#L,,, L,,                &%	5!5!L,, L                &%!!L,, L,               &%333L L,      d X   !!%,Xd     d X   
5!5!,Xd        X    33	3 ,       d    !!,)    d    	5!5!,       X    	###)/          ###d               3#3#3#3#dddddddd p,      " X 
   &  &546%'64'&54%'654'wEXXEw?jwwEXXEE;U;;EXHV~|VHuQ7wuHVXDH,)H;RU:;TR;H),          2"&427'vvvvd6\6dd vvvdd                &%"&32654J|8N]Na| J|aN8}]8|a           	 
  3!!3#3#dd,            % 1  63##!"&5#536!"26=4$"26=4dd **** dnd~dddd      d     !23##!"54!		dd	V	d
ȿ		F	dp       d    !23##!"54		dd	V	
ȿ		F	      $ -  46;!2+#!"&54765#"#"&#!/\2"R> >R"2A9z *(;/>RR>-=(

{i
           
    2!476543"&>vBBd;R; vRo5QQ5oR);;        &  
    3#5'7'77772^2KKKKd KKKKddd          %  !2#!52654&#!3264&#3264&#Rv,AOh);;),d);;)d>XX> vRF7xJhd;));;R;X|X            3#, ,           !732!"3!!"&57>7d,dd2&>X+ dd,*dX>&+           !'               !!!35!#!"5 ,)		 ddgg		           '  3232#!"&=4;546353!27#!"5,);	D	;)		 ;)d		d);ddd				       X  $  2#4&#"3#!!5767#53.5476,^jd22/ZWz}27 j^22#;wdagdzWOd
G<]?E       ( ,  ;!2#!"&5546"2647"3!264&#!"D**,XD`****p            .'? 2#"'>54	)	O6
wnOoDB=I)		6O
vO7o&#xI7       g  46327.>2763232+#"/&=4&#"&'#"&546?&5+"&546;67'.	$IVI$
sD2NA$@?>>&BN2Et BXM87MXB
8#*" (#F%"#WR&"&H#( "#8          32+%'"&'#"=4;bQ		[z"H&'g[				U}'		             #  !2#!"54!5353353335335			V	ddddddddd 				ddddd,dddd                  !!!#!"53533533535335D	V	ddddddddd d		dddddddddd           ( 0  32;2#!"546;2?6"264$"264&2"&4^R			X>^**~vvvR;;R; 					>X		**vvv;R;;R        X   ! pXp            p           	p pp      X   	!Xp      X   ( 0  46;2!2#!"&'./#"#"& 2"&4$2"&4
&Q
6
Q**I**& H

))S****           !!#!'!dd,dpdd,d    E
    ./7>O*H"JYEMI'tR*E IZGHK't     2 X   7	ppXp        &    p      &    	'7'p pp       2 X   	''Xp         
    &%'LXdHB L8dHB               &777'7'LHHHH L8HHHH           %   232!546;543!532#!"&546I*KKKKv 2KK2Gv                 &$"264%3&/Jd"&H$0 JL$!H(0           ,  2#54&"!&5463>;26323'3=46h+9	X|XvRJȖ h2U12>XX>213RvVrpȖ             2#'#&5463>#"&=#h+9	vRZ* h2U113RvVr>22      d    2#!"&463>h+9X>>RvvRh2U1>XvvVr           2&5462#!"&463>{Mk)220h+9X>>RvvR dr^#28hȒh2U1>XvvVr     d   	   3#3#'%3#7ddddddddXddddd           '  37'#/'7/5?'77"264^d2wE/ww2Hw2d2wH2ww2Hw|XX|X w2Hw2d2wH2ww2Ht2d2wH2X|XX|            
  !!!!! p  dd            
  3#3#'ddXd  Xp            
  3#3#ddp  X           
  !!!!! p  dd        ' 0 9 = F O  235462+32"&=#"&46;5#"&4";54$"326435"26=!264&#X|XdX|XX>22>XX|XdX|XX>22>X*2s*2d*,*X>22>XX|XdX|XX>22>XX|XdX|*22*dd*22*          
  43!2'!"5				
	                  &$"264'"264Jdddy** JLd,2**            
    &%264&J| J                !!!!!!!!$2"&4  X** ddddddd**    d  	     !2!546!#!"&5735335 ddddKKdddd               3!73##5!#53!ddd2ddddddd^2^ dd2d>ddddd^2            , 4    &$"264$2"&4467;272"&57'&$2"&4J**[);;R;ZI** JL**wZ;R;;)[F**          
  33	3!!,  ,pd            
  !!### , dd,    d     !!%77'7'X^HHHH,HHHH      d      #4&"#4%7"&546JddU;;>OvvO||XXiCRvvRCi                !!!	!%3535!5,D,dddd pp ddddddd      ;  332#5&+"+#5#"'&=3;26=4&'%.=46;dK1 ,d

1<I4KdK1 ,d

1<I4Kd3d^
2AN324Idd2d^
2AN324I      d     !	!,,X      d     !!, ,,,    d  
   "3!46!"3!46,Su԰pSu԰duS,|duS,|      d  
   !#5265#!#5265#,|Su,|Su|duS,|duS   X   "  "&54>7"3264&#"&54,"
(YE77EY(
_*vR); "
+ro=||=or+
JRv*;)           	!!!  d      X     	!!,,X     ,      3#%3#%3#,,     d   	  !%%! ppddp         
   !%%5!',, XX&ddd      #  2&"!!!#327#"&'!73&5#73>Xc\NK)ZrV^jl6 "2j8oYd-7ddNz8m[d@$d               !!!!!!3#73#73#D ddddddddddddddddd             
  !!!!! p  dd            
  3#3#ddXp  X           
  3#3#'ddd  Xp            
  !!!!! p  dd          	   3#!53!!''7dXdd dd pd     d   # 5  2#"./>"264'2327"&462cLE1$		OVEG\G).HMdvvv
;R;;!5@@5)gOA>XX>9E2"dvvv
);;R;     " " &  ;632762#"/!'&546K^B+*,,A]>w?ǀ<]A++,|,B^A}?ń<           !!!	!,D, pp          ,  .54>4#!4>4E5++=A-E5+!191 E5+?%!191  8@h8.]GRF\.8@h8(P@BABN8@h8fb(P@BABN             3#!!!5#dd,,p  dd        
  33337#dȖdB ,            !!!!#!"&5, D ddd>          0  26;26=.5462+""&54675.54X|X7--7X|X7-X>&.X|X7--7 X>1Mv	;M1>XX>1M;>XH,>XX>1MM1>          
  !'!7!7'dp dp           
  7!7'!'dpd pԖd            G U    &%"".&"32676&#".7>'&>54&'&>7&2?&LPI
	+!*L
&882J*01

D9#9;	
 L,#	CG#
6
mC:(,!(

m

.           ''!!apdd,dp  dpdd/dpd               # ' + / 3 7 ; ?  3#73#73#73#3#73#73#73#3#73#73#73#3#73#73#73#ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd  	               #  3#%3#%3#3#%3#%3#3#%3#%3#,,,,,, dd              !!!!!!!!,,,, ,,          
  !  !2#!"&543!27#!"5$"264Xj;** K		**         3  !#"!54&+5!#";!5326=!;!532654&+,2,2,222222dddpdddd        %   232+"&54&"+"&=46;542duud2|d^SuuSd|        462462	&vvvv;;RvvRRvv;W9          
  	'#5###ddm            
  !!735'7 ddddd dpddd,            !2#!"543373ddddd dd,      X   (  2>32#"&'#"&46"3267.!"3264&_i3^7]kk]7^33_6]kk]3113?**?s1UU1311Xz:@xx@::@xxd<P<4004dd<P<           %   2"&422653#"&54>54&"#4R;;R;p|Xd*dX>?W22*d ;R;;RY=%>XV@%qZ
>            !#3!53>?#zz"N` dddXA             !!!!!!!!   dXddddddd              !!!!!!!!   Xddddddd               !!!!!!!!   Xddddddd              2#"'#!5&54$"264Вh2UR;;R; Вd2h.;R;;R         X     ;!23#!"&=35463353dDdPddX^`dd,            	   !!%3!5!73!5!pdp,dp, ppddpd      # = P  632#"&5467!632#"&5467!632#"&+.546746;2+"#"&Q

p
jdd


8 *         & J P  3#53%6#"&54?64'&#"&54?66;26326?632&'&473##dd"7*::V+,4
V,,,5o*;;dd da*:;U,,4U,,*::dd      & O  6'&'&54632?64'&#"&5476#"'&'&67632&'&4?6K"7*::Ta+*91V4| 9.V,5o*;;P*:;T1U4	.U*::P                 !!!!!!!!!!!!,p,p,p,,dddd,ddd             # + /  2"&47!!2"&47!!2"&47!!2"&47!!**X**X**X**X**dd**dd**dd**d             	% pd ,d     X    23!3546"354ڤvddT:vRdpdR:*dd*     X    23!!54&"#4ڤvd:T:d vRp*::*R     d  
   "3'346##5265#Suddvd|SudduS|d|duS        
   !3'35!##!53!#dXddpd2ddd d2d,          5!#5463!!53#!'Xd;)pd<(ddd);ddd(<d    "   #   #"&/&'#"&2654&"",
d<*+dMY^jВ ͑VPd+*<d	)"ujhВ       X     2.54$"264>XX)gOA~vvv |8~n  +}u8|Lvvv     !    & .  !!%46;!!5+"&2.54"264 X|XK%&4( ** dsX>,})(>:J>**     dX    3#3#X      dX   	X     dX   2"&4     d     !p,,    d     	!pX    d    3#,,      d    	!3X    dX   !!X            !3#!5#3p p          !!!!!!   ddd      X  <  632"&=46;263226=46232!46;5.=46);;R;.uu*k2);p;)2k;));;)!:2SuuS22nj;));jn2      ,    !!           $  ;!2+32!46;5#"&5546!"d);;)dPXd;));d`p   
   327#"&5469G(Ђ?Ayꦂ        #355#3'35#'735#^dddddddd          "&46325"&4632> X|XX>X|XX>Xx >XX|X	*s>XX|X	"       0  2"&47>72?64'&"2767"&476R;::$gH$@E;&U2344iE}PP9;::$Hg$AE?"R4344gE}PP;            '#5Xddp ddp              *  2#"'654'62"&4#54'627!546&>XX>!	H.|XX|Xq"),eFF")) uu(,~VXduuu?&BH>TT?&&?              2"&427!546=uuu	YYPoo ΓddtQddQt             !2#!"54!"2642d,** d**               26#"'%.54^"e(͑}ag4< s)gjÃQ){F{    #  46;!2+32!'!46;5#"#"&\2d);8,;)d2 *;)dd,);         
    &%%L,, L"p          3!!#!5!,,, ,          3#2654&/7 &5467,dd>(^В4-%>&?GH> p NKzhh;g#N1S͑R1               !!!2+5!#"54!!p		[[	p d				              3#3#3#3#3#ddddddDDXddddd          >73#'&'&'#536Ei:H
J&3D)T8t	VW%%d[ׅXd"1          9  462327>32#".'&##47>54&"#34'.8X8

,88,
		8X8
	,88,
		8X8

,88,
	X    , 0  2	#54>7>54'&#"'6763#}A<)*!d+*"	&TO%d
1BHdd;6W6_/6_.!()"%-O1BDd          3 G [  2.#"."&#"&5463>;2632"&=46;2632"&=46%;2632"&=46h+9?1K.
PfP
4-5vR*** h2U13P
*41??1&:PRvVr`dddd      '  5#355#"'5'+5357'5#53276;X502dggd022ddd"&d˿d"&          27!7&#"327#"&6ywmZw||~VEuwwnZXEwJ           	  !'!pp pp         	  #335# ,      d X 	  35
5#%,,X         
    2#4$#2 #4&#2#)Ƞ&ȯ}Su עd}duS           
   #  2#4.#2#4&#2#4&#2"&4dBd_zd|duS)R;;R; zߠ_d戦d|Sud;R;;R           !  !2!54&"!#!"&=326546,);p*;));d*; ;)Ȗ);;)Ȗ&);           3#!53#!"&546!5#">3Kd->--+Fldd}--&-dVC         	5#"4>3,d&-mȒȚvG           7"'&'&=6$}%J=@HpH@=J8n82+		sQ/55/Qs\OzJ$            3#3#3#3#Xdddddddd D,          
  33###'73,dddddd ddpdd          
    33'33#!!!!d,p Xddddd            
    33'3!!!!3#dp, Xddddd               # '  !2#!"&546353!5353!5353!5K--v--7dddddd---&-ddddddddddddd        	  !''!d,dd, ,    	           ' / 7 ? G   2"&42"&4$2"&42"&42"&4$2"&42"&4$2"&42"&4{******vvv********** **G****Gvvv ********G**              !2#!"&546!"264"xP** $d**              !	6"264,R;;R; ;R;;R    d X  
   3%3'7$"264,T,Nw***X,Nz**                  &$"264$2"&46"264JvvvT::T: JLvvv:T::T        	   !!!57!'7/ddDdpdddddpdd             !2#!"547!5				HddHN, 				NHddH2dd          !#4&+;!53265#"# 2;)2p2);2 );dd&;)         3#!2+"&'&'&#dd^!g/$
m*p%           ;2#!276763#/4!*m`dd%JIp       *  !#&#"2654'7 &54675# "&47>7,d2#.)hВ^ͬd*	]d^Вh/,"@=͑*	G        
  5!5!!!'XXpXddddd           '  3232!46;46326532653#!"5,d);d);D;)d;d*d*d2 ;);)););^^          32653+"&5!!d;R;dvR2gdD p);;)pRvuSd            # '  32+"54!32+"5432+"54!!				a			ݶ			 												pd           # + 3  32#54!32#5432#54!!3+"5%3+"5%3+"5
	b	޵	 		,	
,	
				d	[[	dd		[		[		           '  !!32+"54!32+"54!32+"54 				5			5			dd												    d X   !273#'#!"&5462ddddXdd            '  3#'#3!52#527>4&'#&3NBB,%&bb#(AUUA +*! d
У	dkkd	4!"6	     X     3#'#33NBB, +*! d	4!"6	         3#'#3NBB            32#!"=7635355R&	V	&ddd 	VQ		Q	dd         &#"'2&#"'62"&455zrc5KUWK6`LR;;R;mR[^X>U//U>;R;;R      !  "  237#"'#"'&47&546 "264'^^g--+)#**^^hS0+hv**      
   >7&''67.'3eS&0fR$3eS&0fR$0fR$3eS&0fR$3eS&          3333!!!#5!5!5!53d&d,d, ,dddddd    "   ' 3   #"&/&'#"&$"3276?654%33##5#53",g<*+gMYВh`@5dddddd ͑VPg+*<g)"iВ;CYh.ddddd   "   ' +   #"&/&'#"&$"3276?654!!",g<*+gMYВh`@5p, ͑VPg+*<g)"iВ;CYh6d        ѷ_<      χ    χ  "                H    "                      
                          X                    X        X  X                               X     X             X                                             X  X                                                                                      X              X     X                                                                                                                          X                 X  X             X    X  X  X              X        X                            X                                           X                                                                                                  X                              0 N j    .Pr$6XNpRd.r.^"0>L
<d	
	<	\			

&
@
Z

.X 8\
 

.
T
x



(``,JNl.d0R(Ht4"R6^(:Nbv&Zv0t :RTLtN|*N  D `   !!,!\!!!"*"P""#
#.#l####$4$d$$%       h           n    	                B                        %<       	v                	       	  
   	     	  J   	  b  	    	   C r e a t e d   b y   P . J .   O n o r i   w i t h   F o n t F o r g e   2 . 0   ( h t t p : / / f o n t f o r g e . s f . n e t )  Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net)  I c o n s  Icons  i c o n i c  iconic  F o n t F o r g e   2 . 0   :   U n t i t l e d 1   :   3 0 - 4 - 2 0 1 4  FontForge 2.0 : Untitled1 : 30-4-2014  U n t i t l e d 1  Untitled1  V e r s i o n   1 . 0 . 0    Version 1.0.0   o p e n - i c o n i c  open-iconic          2                         	
 D E F G H I
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  123456789101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcddde                     2 2 , `f-, d P&ZE[X!#!X PPX!@Y 8PX!8YY 
Ead(PX!
E 0PX!0Y PX f a 
PX`  PX!
` 6PX!6``YYY +YY# PXeYY-, E %ad CPX#B#B!!Y`-,#!#! dbB #B
 *! C   +0%QX`PaRYX#Y! @SX +!@Y# PXeY-,C+  C`B-,#B#  #Bab`*-,  E EcEb`D`-,  E  +# %` E#a d  PX! 0PX @YY# PXeY%#aDD`-,EaD-	,`  	CJ PX 	#BY
CJ RX 
#BY-
,  b  c#aC` ` #B#-,KTXDY$
e#x-,KQXKSXDY!Y$e#x-
, CUXCaB
+Y C%B	%B
%B# %PX C`%B #a	*!#a #a	*! C`%B%a	*!Y	CG
CG`b EcEb`  #DC >C`B-, ETX #B `a

  BB`
+m+"Y-, +-,+-,+-,+-,+-,+-,+-,+-,+-,	+-,+ ETX #B `a

  BB`
+m+"Y-, +-,+-,+-,+-,+-,+- ,+-!,+-",+-#,	+-$, <`-%, `
` C#`C%a`$*!-&,%+%*-',  G  EcEb`#a8# UX G  EcEb`#a8!Y-(, ETX '*0"Y-),+ ETX '*0"Y-*, 5`-+, EcEb +EcEb +      D>#8**-,, < G EcEb` Ca8--,.<-., < G EcEb` CaCc8-/, % . G #B%IG#G#a Xb!Y#B.*-0, %%G#G#aE+e.#  <8-1, %% .G#G#a #BE+ `PX @QX  &YBB# C #G#G#a#F`Cb`  + a C`d#CadPXCaC`Y%ba#  &#Fa8#CF%CG#G#a` Cb`#  +#C` +%a%b&a %`d#%`dPX!#!Y#  &#Fa8Y-2,    & .G#G#a#<8-3,  #B   F#G +#a8-4, %%G#G#a TX. <#!%%G#G#a %%G#G#a%%I%aEc# Xb!YcEb`#.#  <8#!Y-5,  C .G#G#a ` `fb#  <8-6,# .F%FRX <Y.&+-7,# .F%FPX <Y.&+-8,# .F%FRX <Y# .F%FPX <Y.&+-9,0+# .F%FRX <Y.&+-:,1+  <#B8# .F%FRX <Y.&+C.&+-;, %& .G#G#aE+# < .#8&+-<,%B %% .G#G#a #BE+ `PX @QX  &YBB# GCb`  + a C`d#CadPXCaC`Y%ba%Fa8# <#8!  F#G +#a8!Y&+-=,0+.&+->,1+!#  <#B#8&+C.&+-?,  G #B .,*-@,  G #B .,*-A, -*-B,/*-C, E# . F#a8&+-D,#BC+-E,  <+-F, <+-G, <+-H,<+-I,  =+-J, =+-K, =+-L,=+-M,  9+-N, 9+-O, 9+-P,9+-Q,  ;+-R, ;+-S, ;+-T,;+-U,  >+-V, >+-W, >+-X,>+-Y,  :+-Z, :+-[, :+-\,:+-],2+.&+-^,2+6+-_,2+7+-`, 2+8+-a,3+.&+-b,3+6+-c,3+7+-d,3+8+-e,4+.&+-f,4+6+-g,4+7+-h,4+8+-i,5+.&+-j,5+6+-k,5+7+-l,5+8+-m,+e$Px0-   K RXY  c #D#pE  K QKSZX4(Y`f UX%aEc#b#D
 *
*
*Y(	ERDD$QX@XD&QX XDYYYY D wOFF     0t 
    Y                       FFTM  0      kLOS/2  L   F   `4Qbcmap     B  B cvt          gasp         glyf    '  Jhhead  )   .   6Phhhea  )      $hmtx  )   c   loca  *4    ͽDmaxp  +        4 name  ,  j  bzpost  -    c3xc```d 3΃h O/  xc`a8ɀ B3603223 pÃ{
@LҌHJ$	  xc```f`F| @{I*`LF& Ā
=  yy       x{tՙ4e4HdKc%r0qbA:mBX$aӄK}Mk>m@pڔS8}yKe	K{|ݜ-};ݖ+F6h\t\d]C? 
ϊno,A{@,`6?ѷ+ow6.-@>ă<\1"!Aɒ%sn,Vɒ^ʊreǰ¿*|EuZruGT|. 
sC;8?Qתr%d
B!,Z_qZt%Zw7u^Kq:wX
zanF-Ur8-AWĵ&CSނxR\oDI{>"CM/|ܬ
_)rs.i_z#zʭ5PcY]i!YC73"
|CV0 tDKK^" <}\XÕ^9V\l0>$duàRy%,%VA+B[eo.rߴё%%Ki;Wϻu󧎙wZ,ĭjrine_7
E|So$$sn:`M<),UGSJ,(VDBo$ |ϥNT*GǹCG1e>>5Q"{K%Hp}7yۻV<j>xr߻'3]c=ӥɮ`2W-\(iȓGO/\Ӝ_C!otw)JJ`wIJ!}}lms[vvߍmyby($]ޗ\9;5{, XX:߁~E\^DX`XH|0φ
w#+
GFqɶ '!=[/?t>GjJw1.ݜ>rN:>c.?־x9H#rz`<(qߕt]Z'H/%FQ55K;v&-Zrǅ rŦ$W,ɄL<IY03{<d	Ǡ}=1]6]DW4"Y%_7qL
rpyzvci=IFՊwi44P	<ȖnK@s߸'{woL)jh=:Yd^LE(EX[ogjuj
w^>@a٥nQB%̥q|;\NeȠ7ZTk\CAY9^<[@` M"%[汌uh9"	!N@.IQuMVW.q
/X0AU^W\*qϻqɜ*;d~p3d~/ݺ~$u l@eZQ.>S:/AWU|)c^dh0;8w$g,I6=
"rV'!vh䋅"l!I5mY&SBNp&wѴXuK~{Nu-8[>
s:Nk\")YNyĆA'E-8zί~B?l}:y86"DAm8]	RGpgJsosx[f=H%V^\#Hq%YڔT3vqEj!a1T~d+CDZyVmxni@}aMᏨrgӸ*xB@V#< q[%V-^WF)&0>
Z&_p{YU|v{RdR}	UGQO5˂	xR7, 	zzi9!*Id9wnIo#e3K엤i){x6B$ =/|b$rЖ@O/+ߒgzqIOBmf>He&ЪY!=P@7CéG@n7ՖH}\Ȍg랲 	y&iZT'1K&(08
E{(xi+G66(p,W1g]
CLPLonջd3gdC92jc1H5?d
	.k=}
aI[32+6WQ6zRmJߜy*A^ԁXwl03, @oo2"päQ_i?~c붥r?prMto})xHsA[T$R]^r&QF*"T/ng
߄$ҿْ@®֦DĴƥZpqޠ:,|T*>RӉFY~N/Ba KҢ]djkr<x/J\LOy&pvOA){r\k23E kY9|7PxO4yaX[/x7+G@&6;9g]ŵ'ye&ayfɈ ȖIx 
Cyz< 'w]G_λj0a{;""/m$;}d߽@C S
퇑9OIiP%iy0ޠ-޴leV;eʦ\hUZ)#X)j+W1WYS| 8sym˟d4ckwƛFM5-{ĴUڪ$ӽYELq/ZnT[5ؿ1@cD&MIrWq	Bq]::1=zԫM0;3+|?fgW
jJ6(eaʈI4֠=`20T:AR8A7B
^L@׬25lG`{%
akNÞT,@R) Qs,o$q	$LSXp83O#UgeʒA D?xUZʪOk-mp:8,4{D#"5w@"E!k.@iiBP7!*4K5i'9SP4Cy.6@eٰu]ۓY|^̽h[|ｉ8x[Q
ϑvuZld95&$s)A8tȌf!3WM7d	^ϝ>eσ+KjANVL%
x)ߑAD'Pꥥ3xԯ7	2}4jkv9,4!
"m
]ӜWe?{cYC67"|C36qwRhzZ,鋚lB^H?Uptxwg-mTBw\r;\6RS}:E&A/P(8:B\P̌kPBhr(=e55O:Gg'JP~;Tw\/CԛfBF6lM5<3ߧπFd߬DY-M# 'V=lGܣ8Z*@xs8D`V{EM5_ǷͰ\DǹB$:ZH*
Һt)Isǡ8M`i5=KJ R-R)EfGd0wD8qc_뭠M#΀ޔhHIwܮG`?2c_pjNpmEapk~籾#lqqN
xM K/u!S.SXג<L(Z a
3Xm0(4 ЖL`vR]VR'|O'EaIڢZ:^̦"@3ɛ;^z,F!	q7s菞/
0:'Cܨ-cw7
>eRfh]Rޥ5r`|axñj҇cK3::-Jsr-3]4Rf
: ^и:Y'^ucN&P-6Hk
@ň)NH/ܼ5余hnR娏$n!ߴ)x3vn		-3jW[3u.VD٭ |6;bY`F 'S<ϋ̖nM;eskȟ3_ylHe5< rb񫯛/lp(7)%}'"!70!wPchZ3ظymKMG֏tv5ivF7e57L߮M[6oao^Fu!1+|Hzc'/N;N311g	-&UݨVA=
n3`zKAYFjCL$
#	.y|Pvb"MWF[ 5Dk|w<r%u#CT&E|шУ3$ŸRƋa*YϢfYuYِUURX4ھյ+ 
	nC(DJ%
(bOШ_LD4%ʏTik[$<nBq_X mM׶FyYYCH,lm&oB=|I%?)}Jw\9Z]Ucv1Z|Ͳ"xZZ6.tWsíBKdAhAYM߆&̩qbxj:S.0p5ve]Ed?|# q>%];9NB	 O`
*kNDIX:6ddFֆwj4Lq-H=Z̞mkA!Td5r(ĕ,m&d>G?S!oBTR[,6Ij' yUS9tX =M\ڽ;*W
ڨlβ'sZ
VHʪp)cPfs>|j
:>ixh;>jH
DR,
d>m>]Bi,!7͕qA#
XͻL3FB"E%Ȑ5@н7 X>g&Ul0-B+BM̐g33:b~GQk_PbIANAC~C$7n#;S;o)d0ɪ侑{G UmzM߰kCnݺTQkQ>f:nJx_=^љ;y
*0@T9W|ʾM|S3M7?!I^27N ]Θc@CWW!1dB1luXbخԾ:gXv5Gy.&;kyA *&ZWPeo*ԧVCgeӞE_4$u>»4Cg E%î?C=U2fBqds,F;/:ZPʿAѵcpXX^bO;Bes"s906g:2R-5!a94{233ʫmr5htx0ڎg{JBu6qjDJpdIrQXI-+
 nrEнHl_#J5[3&7d/b{K%#ydo<'KvUV}^UB4@Thq,jؗ<0VFRSTAFj1 5V+h@F)k{nTe߹~
ݞ;V@DP!iuc#ЩbǍXcij_"ܐ=.\+ؾ83b1Ctߝ2Z""
EAjwEWi+2זC}m3S)t߶P;eQ̠OAx=cktX;uq[ʰdnlNӘ2lQ51T9?!C-pqnlf5Lm4S [΢/mP
^"!kp=el:pC)XNScC(gzaf0\ϔyoƇo	'M2pXjpxLfAmxuxݍ^ 3@nݷćw ؿͫ0:8 ;q~ʻ-ν{Z.
Ê{nçF_KQA&+/^p=d\nB~ʞ*ZY㕩ޔu?mDߗQ2뢒*>!RU:| :UfQY(
w,mf 뻓Ż.9Gp1d݂;2]ζK[f`3HcQ,/'>8r16m<Mt :#&|	5eݰ)@_,rԏ
boEr!SS}b$k6E7O PWz{hj[p2nC1
.>7ԝ϶*e2CmgXh fa~;w:^]XcXI+Oi[T).gzzaPZkű,bzeqZ TKKgo{sg/}[|Rǟ女{͡#R,(Iy"4Lf-{N9OAz-3_eJÕ~"u	K6$yKtF9~q/@?Q:(ʤ .h<$DPNH?ӥ=MRJ˪
ۼln>cĮ_E"빅=́DS$rs\.r >]$Pj(ǀdcЙN9[O=(b'fС˥"&1'2V^g$27waW!u+-e>g&Ϧ
ъf^gGcd#?,wޛݧ^=uUq@8"[F6BPBm[2f] )xl4HzA=I?Ez£'n#o1J́Cm?~Iৗ33C=*Ơ[>`Gu+b(e֓) 譟qI Y4PetTG£@	|1n/1ևzHҼs 8wSAC׹ ׎1<U:툍|G/\xtzndbk'X*"<0P}
]2)D:%| .@eQYbEj\˷h]FEӂ OAB?WS٫3{F_x>n_һ~^30B4ȵV(!0YY+92D)PqP"2\z3F+m<)UupttUjB> /i[E
}dy?\1)>
24eӠ6v7d\;&JKIJ.#[&qK
nZBQu(nG3ޝ#[V֝<-ʺ;#`B@3V_ܗrIeOXHpjhںӻZ5y[봤:4;/?vP</zj[4Pbs7>4CIYT,H"|6_L5EVT2u'i'8VO4
UGpbJr靵!O06z;'GŻn:NM~o-w`2q?4BM
A$y֜?Rg~Dgd<OJcP{X?LN+U<gs4Zgjʚ;ۊIʚɐh_?*I?qah`~I>_=}Tl3C!*Ҭ
w[%_$[[O˱/Ye,Kڂ#KL7lgq>,QxǜEk\	v	U1Em\8ʎdXƈ^D$(ezذ6jkr@0 ۫Q.]uU՟8l^zkc?K˘9!<Nĭ05l$PA9
S7MNnxj;wj"I7{8}+7s;=wj{OIJ;:%IlK݆܊eWŨt13hxHWsF02O:Ʃ(tlnJY!8B2zlU5jш?gBY}-p/ o.tQ?~`4P\/$FeAM>;îkdkpR{V'hB_RSɕ_܂snulJݎ'?wmĨh}xs,oqx҃ϹWcrӘlK?[^4zy`ȎMи6WD$Y1=-Hf?
;FQ5bfck=cC0Ys7c $z
7D,%)KĿJa0UL>i>紃>hP/t:Jя仵RnYMV\ʥJ66޲t1&Ƃ6saLw@
4u?fKVcc^A/lzwR cԵd@Aԋl/DBmp4%ዛg'pRy7xG(۷\Wb|nj:TuWsI 9iS=IK5u .]$2tCx_zXGi&ǃÃMQyxxKխ:$nCZJcgi,R#s1~i+,a"qg'L(,ՠ.LPEFKP&HG
Cyeȴv=`F~'͘q!Rxܑ8kmKmf̨$컁*n茫PoCLuw&h۽Iw;om߀ 둞kfП%ti?蓺^CNWg[c@9`C,+EDiҨsTq^XztV\
Ũje1`㜓)YA	ٱe#(V{S';7ݻoW"|@ÊԛϼD;|gPy^wEzj^G=M`^%sIx"xtx<ʓæ+B%iwH7.	<zn
nqĴG۴Ww'ɱ{?{TRI-5Ry޲n3tl:M1WD_{e8.1fS֮5T21?t|v7b*88rYHx8tyC}K̾S_^ʚOO|N>5L"YW_o"tv{\BgLҸY4RI;*z045yXn4rOl
y:Su:uBƃC7k,`?b xc`d`` $[x~
@#L Q M	  xc`d``V?PC Lv  xcT`b F.f)8988sa4z|v"tcݟDLRÍQakA!BPº  8*,    * * * H f     4Fh*<Npf
0j|FFv,:HVd"4T|	"	T	t		


$
>
X
r

(Fp8Pt

0
F
l



@x
*x,Dbf F|*Hj  @`L:j*Nv2@Rfz0>r0H8Rjl*d4fBf , \ x   !!D!t!!""B"h""#"#F####$$L$|$$%4       h             @ .    x}=N0ş%P&/He*&*UK{?RDEՍqn ` L#Js 4
O\B
_˸7+{U4ĳNRؕ	=pOes1\ťx\ÅxA4fpcqiCqtX,yPW"?`mRf7>x)5饜X
$3rgZI2RlK{vL5UՎgaQ\%.bWj=IsS:ZGh=9θ^N궢 88)MKr5SmMbe ZR-C-Im {mr  xmCeG _}mmwƶn}/9I&m۶mH2'ۤΩUjѿ={dG_se$<|,B,")i,b,,R,2,r,
J*jZ:zlFl&lflt,O d+fe;gvd'0iLg3ٙ]ؕ؝=ؓ؛}ؗ؟88C88#8Jc`̄8c8888S88388s88Y\\l.2.
*:n&nn6n.> 0(8OhO4,</"/2*:o&o6.>!1)9_%_5-=?#?3+;Se:#ce	2Q&d2MfLd;Y#ke
Q6f"V&;Ȏe'rrN(\\rN[y'|OYUcB`S
A!*$PzDS4V)zŠbV,bUlXq)%䔼RPJI)+^*5Ai4QUv^9(G夜r\<Vt*FŪ8TJR*EW*MePLwzީ>^}U'QNիըTjQUjSTǪNͨY5ՂZTKjYjU
jcN`58
^C5$
YCkA{/* Start of reset */

* {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}
body {
    margin: 0;
}


/**
 * Define consistent border, margin, and padding.
 */
fieldset {
  border: 1px solid #c0c0c0;
  margin: 0 2px;
  padding: 0.35em 0.625em 0.75em;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}
td,
th {
  padding: 0;
}

/** End of reset */


body {
  font-family: 'Roboto', sans-serif;
  font-size: 14px;
  line-height: 22px;
  font-weight: 300;
}
h1 {
  font-size: 42px;
  line-height: 44px;
  padding-bottom: 5px;
  color: #b10610;
  margin-top: 10px;
  margin-bottom: 30px;
}
h2 {
  color: #333333;
  font-size: 28px;
  line-height: 44px;
  font-weight: 300;
}
h3 {
  font-size: 21px;
  margin-top: 20px;
  margin-bottom: 10px;
}
a {
  color: #31a1cd;
}
h1 a {
  text-decoration: none;
}
h2 a {
  color: #333333;
}
a:visited {
  color: #6098a2;
}
h2 a:visited {
  color: #333333;
}
a:hover {
  color: #b10610;
}
hr {
  border: none;
  border-top: 1px dashed #c9ea75;
  margin-top: 30px;
  margin-bottom: 30px;
}
header {
  background: #eeeeee;
}
header a {
  font-size: 28px;
  font-weight: 500;
  color: #333;
  text-decoration: none;
}
.logo {
  padding: 5px 10px;
}
.logo img {
  vertical-align: middle;
  border: 0;
}
input, button, select {
    font: inherit;
    color: inherit;
}

input[type=text], select {
  border: 1px solid #bbbbbb;
  line-height: 22px;
  padding: 5px 10px;
  border-radius: 3px;
}

nav {
    padding: 5px;
}

.btn, button, input[type=submit] {
  display: inline-block;
  color: white;
  background: #4fa3ac;
  padding: 9px 15px;
  border-radius: 2px;
  border: 0;
  text-decoration: none;
}
a.btn:visited {
    color: white;
}

.btn.disabled {
  background: #eeeeee;
  color: #bbbbbb;
}
section {
  margin: 40px 10px;
}

section table {
    height: 40px;
}

.nodeTable tr {
    border-bottom: 3px solid white;
}

.nodeTable td {
    padding: 10px 10px 10px 10px;

}

.nodeTable a {
    text-decoration: none;
}

.nodeTable .nameColumn {
  font-weight: bold;
  padding: 10px 20px;
  background: #ebf5f6;
  min-width: 200px;
}
.nodeTable .oi {
  color: #b10610;
}

.propTable tr {
    height: 40px;
}

.propTable th {
  background: #f6f6f6;
  padding: 0 10px;
  text-align: left;
}

.propTable td {
  padding: 0 10px;
  background: #eeeeee;
}

.propTable pre {
    font-size: 80%;
    background: #f8f8f8;
}

.actions {
  border: 1px dotted #76baa6;
  padding: 20px;
  margin-bottom: 20px;

}

.actions h3 {
  margin-top: 10px;
  margin-bottom: 30px;
  padding-bottom: 20px;
  border-bottom: 1px solid #eeeeee;
}

.actions label {
    width: 150px;
    display: inline-block;
    line-height: 40px;
}

.actions input[type=text], select {
    width: 450px;
}

.actions input[type=submit] {
    display: inline-block;
    margin-left: 153px;
}

footer {
  padding: 50px 0;
  font-size: 80%;
  text-align: center;
}

ul.tree {
    list-style: none;
    margin: 0;
    padding: 0;
}

ul.tree ul {
    list-style: none;
    padding-left: 10px;
    border-left: 4px solid #ccc;
}
PNG

   
IHDR   2   2   ?  
IDATh޵{UI T !(P@B,DQ1Q!Hy{{^)	o) *!QLsw&3!LUWt{{sO7|ğG fp]'| #:;z\k*BR>x/~Rp{
SF0Yrm?ìjj
5$EHJD->|S<̓: >A"mBC`H` ?Ӗ=}0m7NוcYè9r]&GhPPa(4KK?'悷P@,ˠV	CsUpE{aZ$)S H=Wtmj#E2X7³%jV
|޻[""@k*ZC$+!yf2UpAJ5@l`J8>f
s`WB-3$ U G%r8/|RLp5g:\cK,V8x&8q0j-exs8sk4d2*ډfjEM K[>5[ _`qYχvVNjyrBG2hfWUiTL5o~ 9A`Wh`TP-PSI~,AUl"Ji
Ѷ7%gKkxIUt0VJnD`Jd.|6 ~g|sT&yr;]MаhWxƮ`\0W0A<̭̈́!<R26ˍ땷atz
8!`U7]=UVwg3h_w$7$L7iؓ;O۟n𞔉 &pc&P)5%y)SxzEgxhNHـLDù>:	Qq,| /ރM1+wE{zE V#S\l8l8W,5PDD'/.֩~(,삱hw
kpx+5:v<Xt_8uh%U&`b*'ཊKC9"k)3yZǭx y8>#fix!%Ef ^82'~j3ְB3/F {P&RZJnY˒ao^&Ue~h*B3nX*NCv޳p9QsjL{^{|x3L[NMKx:fgYHU̖be"PɜÇwݞdd@$oNn?ú)+9b;	 !|)+-dlPj *JNռJnU78NCSxr2WS:[L[|Nv&_NM
NqH%|xK:N] r[*c#X&ݺ9[TCHz፿J"<!Zy#^TZbTNhR \la$pEg*إ
g%CVM
>Ihx??̊ZOG&mct0h-5
֬YZݚQyX%8A/Z?|.:)-8`}
θv?;Hx!lU/5l%4,81WZXNM|TTj]E:y"&gϰc^Wpt/ۅ&qFHa
[ô&iEҔ2ʈZW03#BAٰ W̨6UvK8	K>xWj,[zpFi7lF(?.X2kkS	ج8i(
O5!|or.gipj%A
eFM>qm qˢt+U K?(4*m0ƥ.MψbW3;zBȁ,lR]I{V9I/s4~{`j:ݓh͒]FXٯ!l,s{࿶I1.˰%̰]X$J7+p)LlK2o#M(";oP
r+Ji^\mjdVl['4PN>w3{ZVÑ}FMWi``뽩2R-$;	*,z2<4bm4~C扤{g$$' Upo"$p!X4z3t܂^VwMY<`$[;Fj!|29Hr0qCc)|}yq`
x]1EQoCPюT;l~{'eL섇"ҾӐdh} [J>j~:o5b `{I!q-%CZ:rTrM-{e_\KONo
4\~@w>){R9+^aq\9]lf}l] ڼ5pz 0YcJl봩jgky'\Y	7WQryG    IENDB`<?php

namespace Sabre\DAV\Browser;

use Sabre\DAV;
use Sabre\DAV\Inode;
use Sabre\DAV\PropFind;
use Sabre\HTTP\URLUtil;

/**
 * GuessContentType plugin
 *
 * A lot of the built-in File objects just return application/octet-stream
 * as a content-type by default. This is a problem for some clients, because
 * they expect a correct contenttype.
 *
 * There's really no accurate, fast and portable way to determine the contenttype
 * so this extension does what the rest of the world does, and guesses it based
 * on the file extension.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class GuessContentType extends DAV\ServerPlugin {

    /**
     * List of recognized file extensions
     *
     * Feel free to add more
     *
     * @var array
     */
    public $extensionMap = [

        // images
        'jpg' => 'image/jpeg',
        'gif' => 'image/gif',
        'png' => 'image/png',

        // groupware
        'ics' => 'text/calendar',
        'vcf' => 'text/vcard',

        // text
        'txt' => 'text/plain',

    ];

    /**
     * Initializes the plugin
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        // Using a relatively low priority (200) to allow other extensions
        // to set the content-type first.
        $server->on('propFind', [$this, 'propFind'], 200);

    }

    /**
     * Our PROPFIND handler
     *
     * Here we set a contenttype, if the node didn't already have one.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return void
     */
    function propFind(PropFind $propFind, INode $node) {

        $propFind->handle('{DAV:}getcontenttype', function() use ($propFind) {

            list(, $fileName) = URLUtil::splitPath($propFind->getPath());
            return $this->getContentType($fileName);

        });

    }

    /**
     * Simple method to return the contenttype
     *
     * @param string $fileName
     * @return string
     */
    protected function getContentType($fileName) {

        // Just grabbing the extension
        $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1));
        if (isset($this->extensionMap[$extension])) {
            return $this->extensionMap[$extension];
        }
        return 'application/octet-stream';

    }

}
<?php

namespace Sabre\DAV\Browser;

/**
 * WebDAV properties that implement this interface are able to generate their
 * own html output for the browser plugin.
 *
 * This is only useful for display purposes, and might make it a bit easier for
 * people to read and understand the value of some properties.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface HtmlOutput {

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html);

}
<?php

namespace Sabre\DAV\Browser;

use Sabre\Uri;
use Sabre\Xml\Service as XmlService;

/**
 * This class provides a few utility functions for easily generating HTML for
 * the browser plugin.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class HtmlOutputHelper {

    /**
     * Link to the root of the application.
     *
     * @var string
     */
    protected $baseUri;

    /**
     * List of xml namespaces.
     *
     * @var array
     */
    protected $namespaceMap;

    /**
     * Creates the object.
     *
     * baseUri must point to the root of the application. This will be used to
     * easily generate links.
     *
     * The namespaceMap contains an array with the list of xml namespaces and
     * their prefixes. WebDAV uses a lot of XML with complex namespaces, so
     * that can be used to make output a lot shorter.
     *
     * @param string $baseUri
     * @param array $namespaceMap
     */
    function __construct($baseUri, array $namespaceMap) {

        $this->baseUri = $baseUri;
        $this->namespaceMap = $namespaceMap;

    }

    /**
     * Generates a 'full' url based on a relative one.
     *
     * For relative urls, the base of the application is taken as the reference
     * url, not the 'current url of the current request'.
     *
     * Absolute urls are left alone.
     *
     * @param string $path
     * @return string
     */
    function fullUrl($path) {

        return Uri\resolve($this->baseUri, $path);

    }

    /**
     * Escape string for HTML output.
     *
     * @param string $input
     * @return string
     */
    function h($input) {

        return htmlspecialchars($input, ENT_COMPAT, 'UTF-8');

    }

    /**
     * Generates a full <a>-tag.
     *
     * Url is automatically expanded. If label is not specified, we re-use the
     * url.
     *
     * @param string $url
     * @param string $label
     * @return string
     */
    function link($url, $label = null) {

        $url = $this->h($this->fullUrl($url));
        return '<a href="' . $url . '">' . ($label ? $this->h($label) : $url) . '</a>';

    }

    /**
     * This method takes an xml element in clark-notation, and turns it into a
     * shortened version with a prefix, if it was a known namespace.
     *
     * @param string $element
     * @return string
     */
    function xmlName($element) {

        list($ns, $localName) = XmlService::parseClarkNotation($element);
        if (isset($this->namespaceMap[$ns])) {
            $propName = $this->namespaceMap[$ns] . ':' . $localName;
        } else {
            $propName = $element;
        }
        return "<span title=\"" . $this->h($element) . "\">" . $this->h($propName) . "</span>";

    }

}
<?php

namespace Sabre\DAV\Browser;

use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * This is a simple plugin that will map any GET request for non-files to
 * PROPFIND allprops-requests.
 *
 * This should allow easy debugging of PROPFIND
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class MapGetToPropFind extends DAV\ServerPlugin {

    /**
     * reference to server class
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * Initializes the plugin and subscribes to events
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;
        $this->server->on('method:GET', [$this, 'httpGet'], 90);
    }

    /**
     * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpGet(RequestInterface $request, ResponseInterface $response) {

        $node = $this->server->tree->getNodeForPath($request->getPath());
        if ($node instanceof DAV\IFile) return;

        $subRequest = clone $request;
        $subRequest->setMethod('PROPFIND');

        $this->server->invokeMethod($subRequest, $response);
        return false;

    }

}
<?php

namespace Sabre\DAV\Browser;

use Sabre\DAV;
use Sabre\DAV\MkCol;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\HTTP\URLUtil;

/**
 * Browser Plugin
 *
 * This plugin provides a html representation, so that a WebDAV server may be accessed
 * using a browser.
 *
 * The class intercepts GET requests to collection resources and generates a simple
 * html index.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends DAV\ServerPlugin {

    /**
     * reference to server class
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * enablePost turns on the 'actions' panel, which allows people to create
     * folders and upload files straight from a browser.
     *
     * @var bool
     */
    protected $enablePost = true;

    /**
     * A list of properties that are usually not interesting. This can cut down
     * the browser output a bit by removing the properties that most people
     * will likely not want to see.
     *
     * @var array
     */
    public $uninterestingProperties = [
        '{DAV:}supportedlock',
        '{DAV:}acl-restrictions',
//        '{DAV:}supported-privilege-set',
        '{DAV:}supported-method-set',
    ];

    /**
     * Creates the object.
     *
     * By default it will allow file creation and uploads.
     * Specify the first argument as false to disable this
     *
     * @param bool $enablePost
     */
    function __construct($enablePost = true) {

        $this->enablePost = $enablePost;

    }

    /**
     * Initializes the plugin and subscribes to events
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;
        $this->server->on('method:GET', [$this, 'httpGetEarly'], 90);
        $this->server->on('method:GET', [$this, 'httpGet'], 200);
        $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200);
        if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']);
    }

    /**
     * This method intercepts GET requests that have ?sabreAction=info
     * appended to the URL
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpGetEarly(RequestInterface $request, ResponseInterface $response) {

        $params = $request->getQueryParameters();
        if (isset($params['sabreAction']) && $params['sabreAction'] === 'info') {
            return $this->httpGet($request, $response);
        }

    }

    /**
     * This method intercepts GET requests to collections and returns the html
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpGet(RequestInterface $request, ResponseInterface $response) {

        // We're not using straight-up $_GET, because we want everything to be
        // unit testable.
        $getVars = $request->getQueryParameters();

        // CSP headers
        $response->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");

        $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null;

        switch ($sabreAction) {

            case 'asset' :
                // Asset handling, such as images
                $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null);
                return false;
            default :
            case 'info' :
                try {
                    $this->server->tree->getNodeForPath($request->getPath());
                } catch (DAV\Exception\NotFound $e) {
                    // We're simply stopping when the file isn't found to not interfere
                    // with other plugins.
                    return;
                }

                $response->setStatus(200);
                $response->setHeader('Content-Type', 'text/html; charset=utf-8');

                $response->setBody(
                    $this->generateDirectoryIndex($request->getPath())
                );

                return false;

            case 'plugins' :
                $response->setStatus(200);
                $response->setHeader('Content-Type', 'text/html; charset=utf-8');

                $response->setBody(
                    $this->generatePluginListing()
                );

                return false;

        }

    }

    /**
     * Handles POST requests for tree operations.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpPOST(RequestInterface $request, ResponseInterface $response) {

        $contentType = $request->getHeader('Content-Type');
        list($contentType) = explode(';', $contentType);
        if ($contentType !== 'application/x-www-form-urlencoded' &&
            $contentType !== 'multipart/form-data') {
                return;
        }
        $postVars = $request->getPostData();

        if (!isset($postVars['sabreAction']))
            return;

        $uri = $request->getPath();

        if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) {

            switch ($postVars['sabreAction']) {

                case 'mkcol' :
                    if (isset($postVars['name']) && trim($postVars['name'])) {
                        // Using basename() because we won't allow slashes
                        list(, $folderName) = URLUtil::splitPath(trim($postVars['name']));

                        if (isset($postVars['resourceType'])) {
                            $resourceType = explode(',', $postVars['resourceType']);
                        } else {
                            $resourceType = ['{DAV:}collection'];
                        }

                        $properties = [];
                        foreach ($postVars as $varName => $varValue) {
                            // Any _POST variable in clark notation is treated
                            // like a property.
                            if ($varName[0] === '{') {
                                // PHP will convert any dots to underscores.
                                // This leaves us with no way to differentiate
                                // the two.
                                // Therefore we replace the string *DOT* with a
                                // real dot. * is not allowed in uris so we
                                // should be good.
                                $varName = str_replace('*DOT*', '.', $varName);
                                $properties[$varName] = $varValue;
                            }
                        }

                        $mkCol = new MkCol(
                            $resourceType,
                            $properties
                        );
                        $this->server->createCollection($uri . '/' . $folderName, $mkCol);
                    }
                    break;

                // @codeCoverageIgnoreStart
                case 'put' :

                    if ($_FILES) $file = current($_FILES);
                    else break;

                    list(, $newName) = URLUtil::splitPath(trim($file['name']));
                    if (isset($postVars['name']) && trim($postVars['name']))
                        $newName = trim($postVars['name']);

                    // Making sure we only have a 'basename' component
                    list(, $newName) = URLUtil::splitPath($newName);

                    if (is_uploaded_file($file['tmp_name'])) {
                        $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r'));
                    }
                    break;
                // @codeCoverageIgnoreEnd

            }

        }
        $response->setHeader('Location', $request->getUrl());
        $response->setStatus(302);
        return false;

    }

    /**
     * Escapes a string for html.
     *
     * @param string $value
     * @return string
     */
    function escapeHTML($value) {

        return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');

    }

    /**
     * Generates the html directory index for a given url
     *
     * @param string $path
     * @return string
     */
    function generateDirectoryIndex($path) {

        $html = $this->generateHeader($path ? $path : '/', $path);

        $node = $this->server->tree->getNodeForPath($path);
        if ($node instanceof DAV\ICollection) {

            $html .= "<section><h1>Nodes</h1>\n";
            $html .= "<table class=\"nodeTable\">";

            $subNodes = $this->server->getPropertiesForChildren($path, [
                '{DAV:}displayname',
                '{DAV:}resourcetype',
                '{DAV:}getcontenttype',
                '{DAV:}getcontentlength',
                '{DAV:}getlastmodified',
            ]);

            foreach ($subNodes as $subPath => $subProps) {

                $subNode = $this->server->tree->getNodeForPath($subPath);
                $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($subPath);
                list(, $displayPath) = URLUtil::splitPath($subPath);

                $subNodes[$subPath]['subNode'] = $subNode;
                $subNodes[$subPath]['fullPath'] = $fullPath;
                $subNodes[$subPath]['displayPath'] = $displayPath;
            }
            uasort($subNodes, [$this, 'compareNodes']);

            foreach ($subNodes as $subProps) {
                $type = [
                    'string' => 'Unknown',
                    'icon'   => 'cog',
                ];
                if (isset($subProps['{DAV:}resourcetype'])) {
                    $type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']);
                }

                $html .= '<tr>';
                $html .= '<td class="nameColumn"><a href="' . $this->escapeHTML($subProps['fullPath']) . '"><span class="oi" data-glyph="' . $this->escapeHTML($type['icon']) . '"></span> ' . $this->escapeHTML($subProps['displayPath']) . '</a></td>';
                $html .= '<td class="typeColumn">' . $this->escapeHTML($type['string']) . '</td>';
                $html .= '<td>';
                if (isset($subProps['{DAV:}getcontentlength'])) {
                    $html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'] . ' bytes');
                }
                $html .= '</td><td>';
                if (isset($subProps['{DAV:}getlastmodified'])) {
                    $lastMod = $subProps['{DAV:}getlastmodified']->getTime();
                    $html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a'));
                }
                $html .= '</td>';

                $buttonActions = '';
                if ($subProps['subNode'] instanceof DAV\IFile) {
                    $buttonActions = '<a href="' . $this->escapeHTML($subProps['fullPath']) . '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>';
                }
                $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]);

                $html .= '<td>' . $buttonActions . '</td>';
                $html .= '</tr>';
            }

            $html .= '</table>';

        }

        $html .= "</section>";
        $html .= "<section><h1>Properties</h1>";
        $html .= "<table class=\"propTable\">";

        // Allprops request
        $propFind = new PropFindAll($path);
        $properties = $this->server->getPropertiesByNode($propFind, $node);

        $properties = $propFind->getResultForMultiStatus()[200];

        foreach ($properties as $propName => $propValue) {
            if (!in_array($propName, $this->uninterestingProperties)) {
                $html .= $this->drawPropertyRow($propName, $propValue);
            }

        }


        $html .= "</table>";
        $html .= "</section>";

        /* Start of generating actions */

        $output = '';
        if ($this->enablePost) {
            $this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]);
        }

        if ($output) {

            $html .= "<section><h1>Actions</h1>";
            $html .= "<div class=\"actions\">\n";
            $html .= $output;
            $html .= "</div>\n";
            $html .= "</section>\n";
        }

        $html .= $this->generateFooter();

        $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';");

        return $html;

    }

    /**
     * Generates the 'plugins' page.
     *
     * @return string
     */
    function generatePluginListing() {

        $html = $this->generateHeader('Plugins');

        $html .= "<section><h1>Plugins</h1>";
        $html .= "<table class=\"propTable\">";
        foreach ($this->server->getPlugins() as $plugin) {
            $info = $plugin->getPluginInfo();
            $html .= '<tr><th>' . $info['name'] . '</th>';
            $html .= '<td>' . $info['description'] . '</td>';
            $html .= '<td>';
            if (isset($info['link']) && $info['link']) {
                $html .= '<a href="' . $this->escapeHTML($info['link']) . '"><span class="oi" data-glyph="book"></span></a>';
            }
            $html .= '</td></tr>';
        }
        $html .= "</table>";
        $html .= "</section>";

        /* Start of generating actions */

        $html .= $this->generateFooter();

        return $html;

    }

    /**
     * Generates the first block of HTML, including the <head> tag and page
     * header.
     *
     * Returns footer.
     *
     * @param string $title
     * @param string $path
     * @return string
     */
    function generateHeader($title, $path = null) {

        $version = '';
        if (DAV\Server::$exposeVersion) {
            $version = DAV\Version::VERSION;
        }

        $vars = [
            'title'     => $this->escapeHTML($title),
            'favicon'   => $this->escapeHTML($this->getAssetUrl('favicon.ico')),
            'style'     => $this->escapeHTML($this->getAssetUrl('sabredav.css')),
            'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')),
            'logo'      => $this->escapeHTML($this->getAssetUrl('sabredav.png')),
            'baseUrl'   => $this->server->getBaseUri(),
        ];

        $html = <<<HTML
<!DOCTYPE html>
<html>
<head>
    <title>$vars[title] - sabre/dav $version</title>
    <link rel="shortcut icon" href="$vars[favicon]"   type="image/vnd.microsoft.icon" />
    <link rel="stylesheet"    href="$vars[style]"     type="text/css" />
    <link rel="stylesheet"    href="$vars[iconstyle]" type="text/css" />

</head>
<body>
    <header>
        <div class="logo">
            <a href="$vars[baseUrl]"><img src="$vars[logo]" alt="sabre/dav" /> $vars[title]</a>
        </div>
    </header>

    <nav>
HTML;

        // If the path is empty, there's no parent.
        if ($path)  {
            list($parentUri) = URLUtil::splitPath($path);
            $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($parentUri);
            $html .= '<a href="' . $fullPath . '" class="btn">⇤ Go to parent</a>';
        } else {
            $html .= '<span class="btn disabled">⇤ Go to parent</span>';
        }

        $html .= ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>';

        $html .= "</nav>";

        return $html;

    }

    /**
     * Generates the page footer.
     *
     * Returns html.
     *
     * @return string
     */
    function generateFooter() {

        $version = '';
        if (DAV\Server::$exposeVersion) {
            $version = DAV\Version::VERSION;
        }
        return <<<HTML
<footer>Generated by SabreDAV $version (c)2007-2016 <a href="http://sabre.io/">http://sabre.io/</a></footer>
</body>
</html>
HTML;

    }

    /**
     * This method is used to generate the 'actions panel' output for
     * collections.
     *
     * This specifically generates the interfaces for creating new files, and
     * creating new directories.
     *
     * @param DAV\INode $node
     * @param mixed $output
     * @param string $path
     * @return void
     */
    function htmlActionsPanel(DAV\INode $node, &$output, $path) {

        if (!$node instanceof DAV\ICollection)
            return;

        // We also know fairly certain that if an object is a non-extended
        // SimpleCollection, we won't need to show the panel either.
        if (get_class($node) === 'Sabre\\DAV\\SimpleCollection')
            return;

        $output .= <<<HTML
<form method="post" action="">
<h3>Create new folder</h3>
<input type="hidden" name="sabreAction" value="mkcol" />
<label>Name:</label> <input type="text" name="name" /><br />
<input type="submit" value="create" />
</form>
<form method="post" action="" enctype="multipart/form-data">
<h3>Upload file</h3>
<input type="hidden" name="sabreAction" value="put" />
<label>Name (optional):</label> <input type="text" name="name" /><br />
<label>File:</label> <input type="file" name="file" /><br />
<input type="submit" value="upload" />
</form>
HTML;

    }

    /**
     * This method takes a path/name of an asset and turns it into url
     * suiteable for http access.
     *
     * @param string $assetName
     * @return string
     */
    protected function getAssetUrl($assetName) {

        return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName);

    }

    /**
     * This method returns a local pathname to an asset.
     *
     * @param string $assetName
     * @throws DAV\Exception\NotFound
     * @return string
     */
    protected function getLocalAssetPath($assetName) {

        $assetDir = __DIR__ . '/assets/';
        $path = $assetDir . $assetName;

        // Making sure people aren't trying to escape from the base path.
        $path = str_replace('\\', '/', $path);
        if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
            throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
        }
        if (strpos(realpath($path), realpath($assetDir)) === 0 && file_exists($path)) {
            return $path;
        }
        throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected');
    }

    /**
     * This method reads an asset from disk and generates a full http response.
     *
     * @param string $assetName
     * @return void
     */
    protected function serveAsset($assetName) {

        $assetPath = $this->getLocalAssetPath($assetName);

        // Rudimentary mime type detection
        $mime = 'application/octet-stream';
        $map = [
            'ico' => 'image/vnd.microsoft.icon',
            'png' => 'image/png',
            'css' => 'text/css',
        ];

        $ext = substr($assetName, strrpos($assetName, '.') + 1);
        if (isset($map[$ext])) {
            $mime = $map[$ext];
        }

        $this->server->httpResponse->setHeader('Content-Type', $mime);
        $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath));
        $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600');
        $this->server->httpResponse->setStatus(200);
        $this->server->httpResponse->setBody(fopen($assetPath, 'r'));

    }

    /**
     * Sort helper function: compares two directory entries based on type and
     * display name. Collections sort above other types.
     *
     * @param array $a
     * @param array $b
     * @return int
     */
    protected function compareNodes($a, $b) {

        $typeA = (isset($a['{DAV:}resourcetype']))
            ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue()))
            : false;

        $typeB = (isset($b['{DAV:}resourcetype']))
            ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue()))
            : false;

        // If same type, sort alphabetically by filename:
        if ($typeA === $typeB) {
            return strnatcasecmp($a['displayPath'], $b['displayPath']);
        }
        return (($typeA < $typeB) ? 1 : -1);

    }

    /**
     * Maps a resource type to a human-readable string and icon.
     *
     * @param array $resourceTypes
     * @param DAV\INode $node
     * @return array
     */
    private function mapResourceType(array $resourceTypes, $node) {

        if (!$resourceTypes) {
            if ($node instanceof DAV\IFile) {
                return [
                    'string' => 'File',
                    'icon'   => 'file',
                ];
            } else {
                return [
                    'string' => 'Unknown',
                    'icon'   => 'cog',
                ];
            }
        }

        $types = [
            '{http://calendarserver.org/ns/}calendar-proxy-write' => [
                'string' => 'Proxy-Write',
                'icon'   => 'people',
            ],
            '{http://calendarserver.org/ns/}calendar-proxy-read' => [
                'string' => 'Proxy-Read',
                'icon'   => 'people',
            ],
            '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [
                'string' => 'Outbox',
                'icon'   => 'inbox',
            ],
            '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [
                'string' => 'Inbox',
                'icon'   => 'inbox',
            ],
            '{urn:ietf:params:xml:ns:caldav}calendar' => [
                'string' => 'Calendar',
                'icon'   => 'calendar',
            ],
            '{http://calendarserver.org/ns/}shared-owner' => [
                'string' => 'Shared',
                'icon'   => 'calendar',
            ],
            '{http://calendarserver.org/ns/}subscribed' => [
                'string' => 'Subscription',
                'icon'   => 'calendar',
            ],
            '{urn:ietf:params:xml:ns:carddav}directory' => [
                'string' => 'Directory',
                'icon'   => 'globe',
            ],
            '{urn:ietf:params:xml:ns:carddav}addressbook' => [
                'string' => 'Address book',
                'icon'   => 'book',
            ],
            '{DAV:}principal' => [
                'string' => 'Principal',
                'icon'   => 'person',
            ],
            '{DAV:}collection' => [
                'string' => 'Collection',
                'icon'   => 'folder',
            ],
        ];

        $info = [
            'string' => [],
            'icon'   => 'cog',
        ];
        foreach ($resourceTypes as $k => $resourceType) {
            if (isset($types[$resourceType])) {
                $info['string'][] = $types[$resourceType]['string'];
            } else {
                $info['string'][] = $resourceType;
            }
        }
        foreach ($types as $key => $resourceInfo) {
            if (in_array($key, $resourceTypes)) {
                $info['icon'] = $resourceInfo['icon'];
                break;
            }
        }
        $info['string'] = implode(', ', $info['string']);

        return $info;

    }

    /**
     * Draws a table row for a property
     *
     * @param string $name
     * @param mixed $value
     * @return string
     */
    private function drawPropertyRow($name, $value) {

        $html = new HtmlOutputHelper(
            $this->server->getBaseUri(),
            $this->server->xml->namespaceMap
        );

        return "<tr><th>" . $html->xmlName($name) . "</th><td>" . $this->drawPropertyValue($html, $value) . "</td></tr>";

    }

    /**
     * Draws a table row for a property
     *
     * @param HtmlOutputHelper $html
     * @param mixed $value
     * @return string
     */
    private function drawPropertyValue($html, $value) {

        if (is_scalar($value)) {
            return $html->h($value);
        } elseif ($value instanceof HtmlOutput) {
            return $value->toHtml($html);
        } elseif ($value instanceof \Sabre\Xml\XmlSerializable) {

            // There's no default html output for this property, we're going
            // to output the actual xml serialization instead.
            $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri());
            // removing first and last line, as they contain our root
            // element.
            $xml = explode("\n", $xml);
            $xml = array_slice($xml, 2, -2);
            return "<pre>" . $html->h(implode("\n", $xml)) . "</pre>";

        } else {
            return "<em>unknown</em>";
        }

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins;
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'browser';

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
            'link'        => 'http://sabre.io/dav/browser-plugin/',
        ];

    }

}
<?php

namespace Sabre\DAV\Browser;

use Sabre\DAV\PropFind;

/**
 * This class is used by the browser plugin to trick the system in returning
 * every defined property.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PropFindAll extends PropFind {

    /**
     * Creates the PROPFIND object
     *
     * @param string $path
     */
    function __construct($path) {

        parent::__construct($path, []);

    }

    /**
     * Handles a specific property.
     *
     * This method checks whether the specified property was requested in this
     * PROPFIND request, and if so, it will call the callback and use the
     * return value for it's value.
     *
     * Example:
     *
     * $propFind->handle('{DAV:}displayname', function() {
     *      return 'hello';
     * });
     *
     * Note that handle will only work the first time. If null is returned, the
     * value is ignored.
     *
     * It's also possible to not pass a callback, but immediately pass a value
     *
     * @param string $propertyName
     * @param mixed $valueOrCallBack
     * @return void
     */
    function handle($propertyName, $valueOrCallBack) {

        if (is_callable($valueOrCallBack)) {
            $value = $valueOrCallBack();
        } else {
            $value = $valueOrCallBack;
        }
        if (!is_null($value)) {
            $this->result[$propertyName] = [200, $value];
        }

    }

    /**
     * Sets the value of the property
     *
     * If status is not supplied, the status will default to 200 for non-null
     * properties, and 404 for null properties.
     *
     * @param string $propertyName
     * @param mixed $value
     * @param int $status
     * @return void
     */
    function set($propertyName, $value, $status = null) {

        if (is_null($status)) {
            $status = is_null($value) ? 404 : 200;
        }
        $this->result[$propertyName] = [$status, $value];

    }

    /**
     * Returns the current value for a property.
     *
     * @param string $propertyName
     * @return mixed
     */
    function get($propertyName) {

        return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;

    }

    /**
     * Returns the current status code for a property name.
     *
     * If the property does not appear in the list of requested properties,
     * null will be returned.
     *
     * @param string $propertyName
     * @return int|null
     */
    function getStatus($propertyName) {

        return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : 404;

    }

    /**
     * Returns all propertynames that have a 404 status, and thus don't have a
     * value yet.
     *
     * @return array
     */
    function get404Properties() {

        $result = [];
        foreach ($this->result as $propertyName => $stuff) {
            if ($stuff[0] === 404) {
                $result[] = $propertyName;
            }
        }
        // If there's nothing in this list, we're adding one fictional item.
        if (!$result) {
            $result[] = '{http://sabredav.org/ns}idk';
        }
        return $result;

    }

}
<?php

namespace Sabre\DAV;

use Sabre\HTTP;
use Sabre\Uri;

/**
 * SabreDAV DAV client
 *
 * This client wraps around Curl to provide a convenient API to a WebDAV
 * server.
 *
 * NOTE: This class is experimental, it's api will likely change in the future.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Client extends HTTP\Client {

    /**
     * The xml service.
     *
     * Uset this service to configure the property and namespace maps.
     *
     * @var mixed
     */
    public $xml;

    /**
     * The elementMap
     *
     * This property is linked via reference to $this->xml->elementMap.
     * It's deprecated as of version 3.0.0, and should no longer be used.
     *
     * @deprecated
     * @var array
     */
    public $propertyMap = [];

    /**
     * Base URI
     *
     * This URI will be used to resolve relative urls.
     *
     * @var string
     */
    protected $baseUri;

    /**
     * Basic authentication
     */
    const AUTH_BASIC = 1;

    /**
     * Digest authentication
     */
    const AUTH_DIGEST = 2;

    /**
     * NTLM authentication
     */
    const AUTH_NTLM = 4;

    /**
     * Identity encoding, which basically does not nothing.
     */
    const ENCODING_IDENTITY = 1;

    /**
     * Deflate encoding
     */
    const ENCODING_DEFLATE = 2;

    /**
     * Gzip encoding
     */
    const ENCODING_GZIP = 4;

    /**
     * Sends all encoding headers.
     */
    const ENCODING_ALL = 7;

    /**
     * Content-encoding
     *
     * @var int
     */
    protected $encoding = self::ENCODING_IDENTITY;

    /**
     * Constructor
     *
     * Settings are provided through the 'settings' argument. The following
     * settings are supported:
     *
     *   * baseUri
     *   * userName (optional)
     *   * password (optional)
     *   * proxy (optional)
     *   * authType (optional)
     *   * encoding (optional)
     *
     *  authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST
     *  and self::AUTH_NTLM. If you know which authentication method will be
     *  used, it's recommended to set it, as it will save a great deal of
     *  requests to 'discover' this information.
     *
     *  Encoding is a bitmap with one of the ENCODING constants.
     *
     * @param array $settings
     */
    function __construct(array $settings) {

        if (!isset($settings['baseUri'])) {
            throw new \InvalidArgumentException('A baseUri must be provided');
        }

        parent::__construct();

        $this->baseUri = $settings['baseUri'];

        if (isset($settings['proxy'])) {
            $this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']);
        }

        if (isset($settings['userName'])) {
            $userName = $settings['userName'];
            $password = isset($settings['password']) ? $settings['password'] : '';

            if (isset($settings['authType'])) {
                $curlType = 0;
                if ($settings['authType'] & self::AUTH_BASIC) {
                    $curlType |= CURLAUTH_BASIC;
                }
                if ($settings['authType'] & self::AUTH_DIGEST) {
                    $curlType |= CURLAUTH_DIGEST;
                }
                if ($settings['authType'] & self::AUTH_NTLM) {
                    $curlType |= CURLAUTH_NTLM;
                }
            } else {
                $curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST;
            }

            $this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType);
            $this->addCurlSetting(CURLOPT_USERPWD, $userName . ':' . $password);

        }

        if (isset($settings['encoding'])) {
            $encoding = $settings['encoding'];

            $encodings = [];
            if ($encoding & self::ENCODING_IDENTITY) {
                $encodings[] = 'identity';
            }
            if ($encoding & self::ENCODING_DEFLATE) {
                $encodings[] = 'deflate';
            }
            if ($encoding & self::ENCODING_GZIP) {
                $encodings[] = 'gzip';
            }
            $this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings));
        }

        $this->addCurlSetting(CURLOPT_USERAGENT, 'sabre-dav/' . Version::VERSION . ' (http://sabre.io/)');

        $this->xml = new Xml\Service();
        // BC
        $this->propertyMap = & $this->xml->elementMap;

    }

    /**
     * Does a PROPFIND request
     *
     * The list of requested properties must be specified as an array, in clark
     * notation.
     *
     * The returned array will contain a list of filenames as keys, and
     * properties as values.
     *
     * The properties array will contain the list of properties. Only properties
     * that are actually returned from the server (without error) will be
     * returned, anything else is discarded.
     *
     * Depth should be either 0 or 1. A depth of 1 will cause a request to be
     * made to the server to also return all child resources.
     *
     * @param string $url
     * @param array $properties
     * @param int $depth
     * @return array
     */
    function propFind($url, array $properties, $depth = 0) {

        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->formatOutput = true;
        $root = $dom->createElementNS('DAV:', 'd:propfind');
        $prop = $dom->createElement('d:prop');

        foreach ($properties as $property) {

            list(
                $namespace,
                $elementName
            ) = \Sabre\Xml\Service::parseClarkNotation($property);

            if ($namespace === 'DAV:') {
                $element = $dom->createElement('d:' . $elementName);
            } else {
                $element = $dom->createElementNS($namespace, 'x:' . $elementName);
            }

            $prop->appendChild($element);
        }

        $dom->appendChild($root)->appendChild($prop);
        $body = $dom->saveXML();

        $url = $this->getAbsoluteUrl($url);

        $request = new HTTP\Request('PROPFIND', $url, [
            'Depth'        => $depth,
            'Content-Type' => 'application/xml'
        ], $body);

        $response = $this->send($request);

        if ((int)$response->getStatus() >= 400) {
            throw new HTTP\ClientHttpException($response);
        }

        $result = $this->parseMultiStatus($response->getBodyAsString());

        // If depth was 0, we only return the top item
        if ($depth === 0) {
            reset($result);
            $result = current($result);
            return isset($result[200]) ? $result[200] : [];
        }

        $newResult = [];
        foreach ($result as $href => $statusList) {

            $newResult[$href] = isset($statusList[200]) ? $statusList[200] : [];

        }

        return $newResult;

    }

    /**
     * Updates a list of properties on the server
     *
     * The list of properties must have clark-notation properties for the keys,
     * and the actual (string) value for the value. If the value is null, an
     * attempt is made to delete the property.
     *
     * @param string $url
     * @param array $properties
     * @return bool
     */
    function propPatch($url, array $properties) {

        $propPatch = new Xml\Request\PropPatch();
        $propPatch->properties = $properties;
        $xml = $this->xml->write(
            '{DAV:}propertyupdate',
            $propPatch
        );

        $url = $this->getAbsoluteUrl($url);
        $request = new HTTP\Request('PROPPATCH', $url, [
            'Content-Type' => 'application/xml',
        ], $xml);
        $response = $this->send($request);

        if ($response->getStatus() >= 400) {
            throw new HTTP\ClientHttpException($response);
        }

        if ($response->getStatus() === 207) {
            // If it's a 207, the request could still have failed, but the
            // information is hidden in the response body.
            $result = $this->parseMultiStatus($response->getBodyAsString());

            $errorProperties = [];
            foreach ($result as $href => $statusList) {
                foreach ($statusList as $status => $properties) {

                    if ($status >= 400) {
                        foreach ($properties as $propName => $propValue) {
                            $errorProperties[] = $propName . ' (' . $status . ')';
                        }
                    }

                }
            }
            if ($errorProperties) {

                throw new HTTP\ClientException('PROPPATCH failed. The following properties errored: ' . implode(', ', $errorProperties));
            }
        }
        return true;

    }

    /**
     * Performs an HTTP options request
     *
     * This method returns all the features from the 'DAV:' header as an array.
     * If there was no DAV header, or no contents this method will return an
     * empty array.
     *
     * @return array
     */
    function options() {

        $request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl(''));
        $response = $this->send($request);

        $dav = $response->getHeader('Dav');
        if (!$dav) {
            return [];
        }

        $features = explode(',', $dav);
        foreach ($features as &$v) {
            $v = trim($v);
        }
        return $features;

    }

    /**
     * Performs an actual HTTP request, and returns the result.
     *
     * If the specified url is relative, it will be expanded based on the base
     * url.
     *
     * The returned array contains 3 keys:
     *   * body - the response body
     *   * httpCode - a HTTP code (200, 404, etc)
     *   * headers - a list of response http headers. The header names have
     *     been lowercased.
     *
     * For large uploads, it's highly recommended to specify body as a stream
     * resource. You can easily do this by simply passing the result of
     * fopen(..., 'r').
     *
     * This method will throw an exception if an HTTP error was received. Any
     * HTTP status code above 399 is considered an error.
     *
     * Note that it is no longer recommended to use this method, use the send()
     * method instead.
     *
     * @param string $method
     * @param string $url
     * @param string|resource|null $body
     * @param array $headers
     * @throws ClientException, in case a curl error occurred.
     * @return array
     */
    function request($method, $url = '', $body = null, array $headers = []) {

        $url = $this->getAbsoluteUrl($url);

        $response = $this->send(new HTTP\Request($method, $url, $headers, $body));
        return [
            'body'       => $response->getBodyAsString(),
            'statusCode' => (int)$response->getStatus(),
            'headers'    => array_change_key_case($response->getHeaders()),
        ];

    }

    /**
     * Returns the full url based on the given url (which may be relative). All
     * urls are expanded based on the base url as given by the server.
     *
     * @param string $url
     * @return string
     */
    function getAbsoluteUrl($url) {

        return Uri\resolve(
            $this->baseUri,
            $url
        );

    }

    /**
     * Parses a WebDAV multistatus response body
     *
     * This method returns an array with the following structure
     *
     * [
     *   'url/to/resource' => [
     *     '200' => [
     *        '{DAV:}property1' => 'value1',
     *        '{DAV:}property2' => 'value2',
     *     ],
     *     '404' => [
     *        '{DAV:}property1' => null,
     *        '{DAV:}property2' => null,
     *     ],
     *   ],
     *   'url/to/resource2' => [
     *      .. etc ..
     *   ]
     * ]
     *
     *
     * @param string $body xml body
     * @return array
     */
    function parseMultiStatus($body) {

        $multistatus = $this->xml->expect('{DAV:}multistatus', $body);

        $result = [];

        foreach ($multistatus->getResponses() as $response) {

            $result[$response->getHref()] = $response->getResponseProperties();

        }

        return $result;

    }

}
<?php

namespace Sabre\DAV;

/**
 * Collection class
 *
 * This is a helper class, that should aid in getting collections classes setup.
 * Most of its methods are implemented, and throw permission denied exceptions
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class Collection extends Node implements ICollection {

    /**
     * Returns a child object, by its name.
     *
     * This method makes use of the getChildren method to grab all the child
     * nodes, and compares the name.
     * Generally its wise to override this, as this can usually be optimized
     *
     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
     * exist.
     *
     * @param string $name
     * @throws Exception\NotFound
     * @return INode
     */
    function getChild($name) {

        foreach ($this->getChildren() as $child) {

            if ($child->getName() === $name) return $child;

        }
        throw new Exception\NotFound('File not found: ' . $name);

    }

    /**
     * Checks is a child-node exists.
     *
     * It is generally a good idea to try and override this. Usually it can be optimized.
     *
     * @param string $name
     * @return bool
     */
    function childExists($name) {

        try {

            $this->getChild($name);
            return true;

        } catch (Exception\NotFound $e) {

            return false;

        }

    }

    /**
     * Creates a new file in the directory
     *
     * Data will either be supplied as a stream resource, or in certain cases
     * as a string. Keep in mind that you may have to support either.
     *
     * After successful creation of the file, you may choose to return the ETag
     * of the new file here.
     *
     * The returned ETag must be surrounded by double-quotes (The quotes should
     * be part of the actual string).
     *
     * If you cannot accurately determine the ETag, you should not return it.
     * If you don't store the file exactly as-is (you're transforming it
     * somehow) you should also not return an ETag.
     *
     * This means that if a subsequent GET to this new file does not exactly
     * return the same contents of what was submitted here, you are strongly
     * recommended to omit the ETag.
     *
     * @param string $name Name of the file
     * @param resource|string $data Initial payload
     * @return null|string
     */
    function createFile($name, $data = null) {

        throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')');

    }

    /**
     * Creates a new subdirectory
     *
     * @param string $name
     * @throws Exception\Forbidden
     * @return void
     */
    function createDirectory($name) {

        throw new Exception\Forbidden('Permission denied to create directory');

    }


}
<?php

namespace Sabre\DAV;

use Sabre\DAV\Exception\BadRequest;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\Xml\ParseException;

/**
 * The core plugin provides all the basic features for a WebDAV server.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CorePlugin extends ServerPlugin {

    /**
     * Reference to server object.
     *
     * @var Server
     */
    protected $server;

    /**
     * Sets up the plugin
     *
     * @param Server $server
     * @return void
     */
    function initialize(Server $server) {

        $this->server = $server;
        $server->on('method:GET',       [$this, 'httpGet']);
        $server->on('method:OPTIONS',   [$this, 'httpOptions']);
        $server->on('method:HEAD',      [$this, 'httpHead']);
        $server->on('method:DELETE',    [$this, 'httpDelete']);
        $server->on('method:PROPFIND',  [$this, 'httpPropFind']);
        $server->on('method:PROPPATCH', [$this, 'httpPropPatch']);
        $server->on('method:PUT',       [$this, 'httpPut']);
        $server->on('method:MKCOL',     [$this, 'httpMkcol']);
        $server->on('method:MOVE',      [$this, 'httpMove']);
        $server->on('method:COPY',      [$this, 'httpCopy']);
        $server->on('method:REPORT',    [$this, 'httpReport']);

        $server->on('propPatch',        [$this, 'propPatchProtectedPropertyCheck'], 90);
        $server->on('propPatch',        [$this, 'propPatchNodeUpdate'], 200);
        $server->on('propFind',         [$this, 'propFind']);
        $server->on('propFind',         [$this, 'propFindNode'], 120);
        $server->on('propFind',         [$this, 'propFindLate'], 200);

        $server->on('exception',        [$this, 'exception']);

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'core';

    }

    /**
     * This is the default implementation for the GET method.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpGet(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();
        $node = $this->server->tree->getNodeForPath($path);

        if (!$node instanceof IFile) return;

        $body = $node->get();

        // Converting string into stream, if needed.
        if (is_string($body)) {
            $stream = fopen('php://temp', 'r+');
            fwrite($stream, $body);
            rewind($stream);
            $body = $stream;
        }

        /*
         * TODO: getetag, getlastmodified, getsize should also be used using
         * this method
         */
        $httpHeaders = $this->server->getHTTPHeaders($path);

        /* ContentType needs to get a default, because many webservers will otherwise
         * default to text/html, and we don't want this for security reasons.
         */
        if (!isset($httpHeaders['Content-Type'])) {
            $httpHeaders['Content-Type'] = 'application/octet-stream';
        }


        if (isset($httpHeaders['Content-Length'])) {

            $nodeSize = $httpHeaders['Content-Length'];

            // Need to unset Content-Length, because we'll handle that during figuring out the range
            unset($httpHeaders['Content-Length']);

        } else {
            $nodeSize = null;
        }

        $response->addHeaders($httpHeaders);

        $range = $this->server->getHTTPRange();
        $ifRange = $request->getHeader('If-Range');
        $ignoreRangeHeader = false;

        // If ifRange is set, and range is specified, we first need to check
        // the precondition.
        if ($nodeSize && $range && $ifRange) {

            // if IfRange is parsable as a date we'll treat it as a DateTime
            // otherwise, we must treat it as an etag.
            try {
                $ifRangeDate = new \DateTime($ifRange);

                // It's a date. We must check if the entity is modified since
                // the specified date.
                if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true;
                else {
                    $modified = new \DateTime($httpHeaders['Last-Modified']);
                    if ($modified > $ifRangeDate) $ignoreRangeHeader = true;
                }

            } catch (\Exception $e) {

                // It's an entity. We can do a simple comparison.
                if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true;
                elseif ($httpHeaders['ETag'] !== $ifRange) $ignoreRangeHeader = true;
            }
        }

        // We're only going to support HTTP ranges if the backend provided a filesize
        if (!$ignoreRangeHeader && $nodeSize && $range) {

            // Determining the exact byte offsets
            if (!is_null($range[0])) {

                $start = $range[0];
                $end = $range[1] ? $range[1] : $nodeSize - 1;
                if ($start >= $nodeSize)
                    throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')');

                if ($end < $start) throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')');
                if ($end >= $nodeSize) $end = $nodeSize - 1;

            } else {

                $start = $nodeSize - $range[1];
                $end = $nodeSize - 1;

                if ($start < 0) $start = 0;

            }

            // Streams may advertise themselves as seekable, but still not
            // actually allow fseek.  We'll manually go forward in the stream
            // if fseek failed.
            if (!stream_get_meta_data($body)['seekable'] || fseek($body, $start, SEEK_SET) === -1) {
                $consumeBlock = 8192;
                for ($consumed = 0; $start - $consumed > 0;){
                    if (feof($body)) throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')');
                    $consumed += strlen(fread($body, min($start - $consumed, $consumeBlock)));
                }
            }

            $response->setHeader('Content-Length', $end - $start + 1);
            $response->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $nodeSize);
            $response->setStatus(206);
            $response->setBody($body);

        } else {

            if ($nodeSize) $response->setHeader('Content-Length', $nodeSize);
            $response->setStatus(200);
            $response->setBody($body);

        }
        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * HTTP OPTIONS
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpOptions(RequestInterface $request, ResponseInterface $response) {

        $methods = $this->server->getAllowedMethods($request->getPath());

        $response->setHeader('Allow', strtoupper(implode(', ', $methods)));
        $features = ['1', '3', 'extended-mkcol'];

        foreach ($this->server->getPlugins() as $plugin) {
            $features = array_merge($features, $plugin->getFeatures());
        }

        $response->setHeader('DAV', implode(', ', $features));
        $response->setHeader('MS-Author-Via', 'DAV');
        $response->setHeader('Accept-Ranges', 'bytes');
        $response->setHeader('Content-Length', '0');
        $response->setStatus(200);

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * HTTP HEAD
     *
     * This method is normally used to take a peak at a url, and only get the
     * HTTP response headers, without the body. This is used by clients to
     * determine if a remote file was changed, so they can use a local cached
     * version, instead of downloading it again
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpHead(RequestInterface $request, ResponseInterface $response) {

        // This is implemented by changing the HEAD request to a GET request,
        // and dropping the response body.
        $subRequest = clone $request;
        $subRequest->setMethod('GET');

        try {
            $this->server->invokeMethod($subRequest, $response, false);
            $response->setBody('');
        } catch (Exception\NotImplemented $e) {
            // Some clients may do HEAD requests on collections, however, GET
            // requests and HEAD requests _may_ not be defined on a collection,
            // which would trigger a 501.
            // This breaks some clients though, so we're transforming these
            // 501s into 200s.
            $response->setStatus(200);
            $response->setBody('');
            $response->setHeader('Content-Type', 'text/plain');
            $response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode());
        }

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * HTTP Delete
     *
     * The HTTP delete method, deletes a given uri
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function httpDelete(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        if (!$this->server->emit('beforeUnbind', [$path])) return false;
        $this->server->tree->delete($path);
        $this->server->emit('afterUnbind', [$path]);

        $response->setStatus(204);
        $response->setHeader('Content-Length', '0');

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * WebDAV PROPFIND
     *
     * This WebDAV method requests information about an uri resource, or a list of resources
     * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value
     * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory)
     *
     * The request body contains an XML data structure that has a list of properties the client understands
     * The response body is also an xml document, containing information about every uri resource and the requested properties
     *
     * It has to return a HTTP 207 Multi-status status code
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function httpPropFind(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        $requestBody = $request->getBodyAsString();
        if (strlen($requestBody)) {
            try {
                $propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody);
            } catch (ParseException $e) {
                throw new BadRequest($e->getMessage(), null, $e);
            }
        } else {
            $propFindXml = new Xml\Request\PropFind();
            $propFindXml->allProp = true;
            $propFindXml->properties = [];
        }

        $depth = $this->server->getHTTPDepth(1);
        // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
        if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1;

        $newProperties = $this->server->getPropertiesIteratorForPath($path, $propFindXml->properties, $depth);

        // This is a multi-status response
        $response->setStatus(207);
        $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $response->setHeader('Vary', 'Brief,Prefer');

        // Normally this header is only needed for OPTIONS responses, however..
        // iCal seems to also depend on these being set for PROPFIND. Since
        // this is not harmful, we'll add it.
        $features = ['1', '3', 'extended-mkcol'];
        foreach ($this->server->getPlugins() as $plugin) {
            $features = array_merge($features, $plugin->getFeatures());
        }
        $response->setHeader('DAV', implode(', ', $features));

        $prefer = $this->server->getHTTPPrefer();
        $minimal = $prefer['return'] === 'minimal';

        $data = $this->server->generateMultiStatus($newProperties, $minimal);
        $response->setBody($data);

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * WebDAV PROPPATCH
     *
     * This method is called to update properties on a Node. The request is an XML body with all the mutations.
     * In this XML body it is specified which properties should be set/updated and/or deleted
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpPropPatch(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        try {
            $propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody());
        } catch (ParseException $e) {
            throw new BadRequest($e->getMessage(), null, $e);
        }
        $newProperties = $propPatch->properties;

        $result = $this->server->updateProperties($path, $newProperties);

        $prefer = $this->server->getHTTPPrefer();
        $response->setHeader('Vary', 'Brief,Prefer');

        if ($prefer['return'] === 'minimal') {

            // If return-minimal is specified, we only have to check if the
            // request was successful, and don't need to return the
            // multi-status.
            $ok = true;
            foreach ($result as $prop => $code) {
                if ((int)$code > 299) {
                    $ok = false;
                }
            }

            if ($ok) {

                $response->setStatus(204);
                return false;

            }

        }

        $response->setStatus(207);
        $response->setHeader('Content-Type', 'application/xml; charset=utf-8');


        // Reorganizing the result for generateMultiStatus
        $multiStatus = [];
        foreach ($result as $propertyName => $code) {
            if (isset($multiStatus[$code])) {
                $multiStatus[$code][$propertyName] = null;
            } else {
                $multiStatus[$code] = [$propertyName => null];
            }
        }
        $multiStatus['href'] = $path;

        $response->setBody(
            $this->server->generateMultiStatus([$multiStatus])
        );

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * HTTP PUT method
     *
     * This HTTP method updates a file, or creates a new one.
     *
     * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpPut(RequestInterface $request, ResponseInterface $response) {

        $body = $request->getBodyAsStream();
        $path = $request->getPath();

        // Intercepting Content-Range
        if ($request->getHeader('Content-Range')) {
            /*
               An origin server that allows PUT on a given target resource MUST send
               a 400 (Bad Request) response to a PUT request that contains a
               Content-Range header field.

               Reference: http://tools.ietf.org/html/rfc7231#section-4.3.4
            */
            throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.');
        }

        // Intercepting the Finder problem
        if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) {

            /*
            Many webservers will not cooperate well with Finder PUT requests,
            because it uses 'Chunked' transfer encoding for the request body.

            The symptom of this problem is that Finder sends files to the
            server, but they arrive as 0-length files in PHP.

            If we don't do anything, the user might think they are uploading
            files successfully, but they end up empty on the server. Instead,
            we throw back an error if we detect this.

            The reason Finder uses Chunked, is because it thinks the files
            might change as it's being uploaded, and therefore the
            Content-Length can vary.

            Instead it sends the X-Expected-Entity-Length header with the size
            of the file at the very start of the request. If this header is set,
            but we don't get a request body we will fail the request to
            protect the end-user.
            */

            // Only reading first byte
            $firstByte = fread($body, 1);
            if (strlen($firstByte) !== 1) {
                throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.');
            }

            // The body needs to stay intact, so we copy everything to a
            // temporary stream.

            $newBody = fopen('php://temp', 'r+');
            fwrite($newBody, $firstByte);
            stream_copy_to_stream($body, $newBody);
            rewind($newBody);

            $body = $newBody;

        }

        if ($this->server->tree->nodeExists($path)) {

            $node = $this->server->tree->getNodeForPath($path);

            // If the node is a collection, we'll deny it
            if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.');

            if (!$this->server->updateFile($path, $body, $etag)) {
                return false;
            }

            $response->setHeader('Content-Length', '0');
            if ($etag) $response->setHeader('ETag', $etag);
            $response->setStatus(204);

        } else {

            $etag = null;
            // If we got here, the resource didn't exist yet.
            if (!$this->server->createFile($path, $body, $etag)) {
                // For one reason or another the file was not created.
                return false;
            }

            $response->setHeader('Content-Length', '0');
            if ($etag) $response->setHeader('ETag', $etag);
            $response->setStatus(201);

        }

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }


    /**
     * WebDAV MKCOL
     *
     * The MKCOL method is used to create a new collection (directory) on the server
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpMkcol(RequestInterface $request, ResponseInterface $response) {

        $requestBody = $request->getBodyAsString();
        $path = $request->getPath();

        if ($requestBody) {

            $contentType = $request->getHeader('Content-Type');
            if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) {

                // We must throw 415 for unsupported mkcol bodies
                throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type');

            }

            try {
                $mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody);
            } catch (\Sabre\Xml\ParseException $e) {
                throw new Exception\BadRequest($e->getMessage(), null, $e);
            }

            $properties = $mkcol->getProperties();

            if (!isset($properties['{DAV:}resourcetype']))
                throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property');

            $resourceType = $properties['{DAV:}resourcetype']->getValue();
            unset($properties['{DAV:}resourcetype']);

        } else {

            $properties = [];
            $resourceType = ['{DAV:}collection'];

        }

        $mkcol = new MkCol($resourceType, $properties);

        $result = $this->server->createCollection($path, $mkcol);

        if (is_array($result)) {
            $response->setStatus(207);
            $response->setHeader('Content-Type', 'application/xml; charset=utf-8');

            $response->setBody(
                $this->server->generateMultiStatus([$result])
            );

        } else {
            $response->setHeader('Content-Length', '0');
            $response->setStatus(201);
        }

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * WebDAV HTTP MOVE method
     *
     * This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpMove(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        $moveInfo = $this->server->getCopyAndMoveInfo($request);

        if ($moveInfo['destinationExists']) {

            if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) return false;

        }
        if (!$this->server->emit('beforeUnbind', [$path])) return false;
        if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) return false;
        if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) return false;

        if ($moveInfo['destinationExists']) {

            $this->server->tree->delete($moveInfo['destination']);
            $this->server->emit('afterUnbind', [$moveInfo['destination']]);

        }

        $this->server->tree->move($path, $moveInfo['destination']);

        // Its important afterMove is called before afterUnbind, because it
        // allows systems to transfer data from one path to another.
        // PropertyStorage uses this. If afterUnbind was first, it would clean
        // up all the properties before it has a chance.
        $this->server->emit('afterMove', [$path, $moveInfo['destination']]);
        $this->server->emit('afterUnbind', [$path]);
        $this->server->emit('afterBind', [$moveInfo['destination']]);

        // If a resource was overwritten we should send a 204, otherwise a 201
        $response->setHeader('Content-Length', '0');
        $response->setStatus($moveInfo['destinationExists'] ? 204 : 201);

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * WebDAV HTTP COPY method
     *
     * This method copies one uri to a different uri, and works much like the MOVE request
     * A lot of the actual request processing is done in getCopyMoveInfo
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpCopy(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        $copyInfo = $this->server->getCopyAndMoveInfo($request);

        if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false;
        if ($copyInfo['destinationExists']) {
            if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false;
            $this->server->tree->delete($copyInfo['destination']);
        }

        $this->server->tree->copy($path, $copyInfo['destination']);
        $this->server->emit('afterBind', [$copyInfo['destination']]);

        // If a resource was overwritten we should send a 204, otherwise a 201
        $response->setHeader('Content-Length', '0');
        $response->setStatus($copyInfo['destinationExists'] ? 204 : 201);

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;


    }

    /**
     * HTTP REPORT method implementation
     *
     * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253)
     * It's used in a lot of extensions, so it made sense to implement it into the core.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpReport(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        $result = $this->server->xml->parse(
            $request->getBody(),
            $request->getUrl(),
            $rootElementName
        );

        if ($this->server->emit('report', [$rootElementName, $result, $path])) {

            // If emit returned true, it means the report was not supported
            throw new Exception\ReportNotSupported();

        }

        // Sending back false will interrupt the event chain and tell the server
        // we've handled this method.
        return false;

    }

    /**
     * This method is called during property updates.
     *
     * Here we check if a user attempted to update a protected property and
     * ensure that the process fails if this is the case.
     *
     * @param string $path
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatchProtectedPropertyCheck($path, PropPatch $propPatch) {

        // Comparing the mutation list to the list of protected properties.
        $mutations = $propPatch->getMutations();

        $protected = array_intersect(
            $this->server->protectedProperties,
            array_keys($mutations)
        );

        if ($protected) {
            $propPatch->setResultCode($protected, 403);
        }

    }

    /**
     * This method is called during property updates.
     *
     * Here we check if a node implements IProperties and let the node handle
     * updating of (some) properties.
     *
     * @param string $path
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatchNodeUpdate($path, PropPatch $propPatch) {

        // This should trigger a 404 if the node doesn't exist.
        $node = $this->server->tree->getNodeForPath($path);

        if ($node instanceof IProperties) {
            $node->propPatch($propPatch);
        }

    }

    /**
     * This method is called when properties are retrieved.
     *
     * Here we add all the default properties.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return void
     */
    function propFind(PropFind $propFind, INode $node) {

        $propFind->handle('{DAV:}getlastmodified', function() use ($node) {
            $lm = $node->getLastModified();
            if ($lm) {
                return new Xml\Property\GetLastModified($lm);
            }
        });

        if ($node instanceof IFile) {
            $propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']);
            $propFind->handle('{DAV:}getetag', [$node, 'getETag']);
            $propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']);
        }

        if ($node instanceof IQuota) {
            $quotaInfo = null;
            $propFind->handle('{DAV:}quota-used-bytes', function() use (&$quotaInfo, $node) {
                $quotaInfo = $node->getQuotaInfo();
                return $quotaInfo[0];
            });
            $propFind->handle('{DAV:}quota-available-bytes', function() use (&$quotaInfo, $node) {
                if (!$quotaInfo) {
                    $quotaInfo = $node->getQuotaInfo();
                }
                return $quotaInfo[1];
            });
        }

        $propFind->handle('{DAV:}supported-report-set', function() use ($propFind) {
            $reports = [];
            foreach ($this->server->getPlugins() as $plugin) {
                $reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath()));
            }
            return new Xml\Property\SupportedReportSet($reports);
        });
        $propFind->handle('{DAV:}resourcetype', function() use ($node) {
            return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node));
        });
        $propFind->handle('{DAV:}supported-method-set', function() use ($propFind) {
            return new Xml\Property\SupportedMethodSet(
                $this->server->getAllowedMethods($propFind->getPath())
            );
        });

    }

    /**
     * Fetches properties for a node.
     *
     * This event is called a bit later, so plugins have a chance first to
     * populate the result.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return void
     */
    function propFindNode(PropFind $propFind, INode $node) {

        if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) {

            $nodeProperties = $node->getProperties($propertyNames);
            foreach ($nodeProperties as $propertyName => $propertyValue) {
                $propFind->set($propertyName, $propertyValue, 200);
            }

        }

    }

    /**
     * This method is called when properties are retrieved.
     *
     * This specific handler is called very late in the process, because we
     * want other systems to first have a chance to handle the properties.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return void
     */
    function propFindLate(PropFind $propFind, INode $node) {

        $propFind->handle('{http://calendarserver.org/ns/}getctag', function() use ($propFind) {

            // If we already have a sync-token from the current propFind
            // request, we can re-use that.
            $val = $propFind->get('{http://sabredav.org/ns}sync-token');
            if ($val) return $val;

            $val = $propFind->get('{DAV:}sync-token');
            if ($val && is_scalar($val)) {
                return $val;
            }
            if ($val && $val instanceof Xml\Property\Href) {
                return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
            }

            // If we got here, the earlier two properties may simply not have
            // been part of the earlier request. We're going to fetch them.
            $result = $this->server->getProperties($propFind->getPath(), [
                '{http://sabredav.org/ns}sync-token',
                '{DAV:}sync-token',
            ]);

            if (isset($result['{http://sabredav.org/ns}sync-token'])) {
                return $result['{http://sabredav.org/ns}sync-token'];
            }
            if (isset($result['{DAV:}sync-token'])) {
                $val = $result['{DAV:}sync-token'];
                if (is_scalar($val)) {
                    return $val;
                } elseif ($val instanceof Xml\Property\Href) {
                    return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
                }
            }

        });

    }

    /**
     * Listens for exception events, and automatically logs them.
     *
     * @param Exception $e
     */
    function exception($e) {

        $logLevel = \Psr\Log\LogLevel::CRITICAL;
        if ($e instanceof \Sabre\DAV\Exception) {
            // If it's a standard sabre/dav exception, it means we have a http
            // status code available.
            $code = $e->getHTTPCode();

            if ($code >= 400 && $code < 500) {
                // user error
                $logLevel = \Psr\Log\LogLevel::INFO;
            } else {
                // Server-side error. We mark it's as an error, but it's not
                // critical.
                $logLevel = \Psr\Log\LogLevel::ERROR;
            }
        }

        $this->server->getLogger()->log(
            $logLevel,
            'Uncaught exception',
            [
                'exception' => $e,
            ]
        );
    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.',
            'link'        => null,
        ];

    }
}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * BadRequest
 *
 * The BadRequest is thrown when the user submitted an invalid HTTP request
 * BadRequest
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class BadRequest extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 400;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * Conflict
 *
 * A 409 Conflict is thrown when a user tried to make a directory over an existing
 * file or in a parent directory that doesn't exist.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Conflict extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 409;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * ConflictingLock
 *
 * Similar to  the Locked exception, this exception thrown when a LOCK request
 * was made, on a resource which was already locked
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ConflictingLock extends Locked {

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        if ($this->lock) {
            $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:no-conflicting-lock');
            $errorNode->appendChild($error);
            $error->appendChild($errorNode->ownerDocument->createElementNS('DAV:', 'd:href', $this->lock->uri));
        }

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * Forbidden
 *
 * This exception is thrown whenever a user tries to do an operation he's not allowed to
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Forbidden extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 403;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * InsufficientStorage
 *
 * This Exception can be thrown, when for example a harddisk is full or a quota is exceeded
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class InsufficientStorage extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 507;

    }

}
<?php

namespace Sabre\DAV\Exception;

/**
 * InvalidResourceType
 *
 * This exception is thrown when the user tried to create a new collection, with
 * a special resourcetype value that was not recognized by the server.
 *
 * See RFC5689 section 3.3
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class InvalidResourceType extends Forbidden {

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param \Sabre\DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(\Sabre\DAV\Server $server, \DOMElement $errorNode) {

        $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:valid-resourcetype');
        $errorNode->appendChild($error);

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * InvalidSyncToken
 *
 * This exception is emited for the {DAV:}valid-sync-token pre-condition, as
 * defined in rfc6578, section 3.2.
 *
 * http://tools.ietf.org/html/rfc6578#section-3.2
 *
 * This is emitted in cases where the the sync-token, supplied by a client is
 * either completely unknown, or has expired.
 *
 * @author Evert Pot (http://evertpot.com/)
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class InvalidSyncToken extends Forbidden {

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:valid-sync-token');
        $errorNode->appendChild($error);

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * LengthRequired
 *
 * This exception is thrown when a request was made that required a
 * Content-Length header, but did not contain one.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class LengthRequired extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 411;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * Locked
 *
 * The 423 is thrown when a client tried to access a resource that was locked, without supplying a valid lock token
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Locked extends DAV\Exception {

    /**
     * Lock information
     *
     * @var Sabre\DAV\Locks\LockInfo
     */
    protected $lock;

    /**
     * Creates the exception
     *
     * A LockInfo object should be passed if the user should be informed
     * which lock actually has the file locked.
     *
     * @param DAV\Locks\LockInfo $lock
     */
    function __construct(DAV\Locks\LockInfo $lock = null) {

        $this->lock = $lock;

    }

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 423;

    }

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        if ($this->lock) {
            $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-submitted');
            $errorNode->appendChild($error);

            $href = $errorNode->ownerDocument->createElementNS('DAV:', 'd:href');
            $href->appendChild($errorNode->ownerDocument->createTextNode($this->lock->uri));
            $error->appendChild(
                $href
            );
        }

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * LockTokenMatchesRequestUri
 *
 * This exception is thrown by UNLOCK if a supplied lock-token is invalid
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class LockTokenMatchesRequestUri extends Conflict {

    /**
     * Creates the exception
     */
    function __construct() {

        $this->message = 'The locktoken supplied does not match any locks on this entity';

    }

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-matches-request-uri');
        $errorNode->appendChild($error);

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * MethodNotAllowed
 *
 * The 405 is thrown when a client tried to create a directory on an already existing directory
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class MethodNotAllowed extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 405;

    }

    /**
     * This method allows the exception to return any extra HTTP response headers.
     *
     * The headers must be returned as an array.
     *
     * @param \Sabre\DAV\Server $server
     * @return array
     */
    function getHTTPHeaders(\Sabre\DAV\Server $server) {

        $methods = $server->getAllowedMethods($server->getRequestUri());

        return [
            'Allow' => strtoupper(implode(', ', $methods)),
        ];

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * NotAuthenticated
 *
 * This exception is thrown when the client did not provide valid
 * authentication credentials.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class NotAuthenticated extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 401;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * NotFound
 *
 * This Exception is thrown when a Node couldn't be found. It returns HTTP error code 404
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class NotFound extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 404;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * NotImplemented
 *
 * This exception is thrown when the client tried to call an unsupported HTTP method or other feature
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class NotImplemented extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 501;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * Payment Required
 *
 * The PaymentRequired exception may be thrown in a case where a user must pay
 * to access a certain resource or operation.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PaymentRequired extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 402;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * PreconditionFailed
 *
 * This exception is normally thrown when a client submitted a conditional request,
 * like for example an If, If-None-Match or If-Match header, which caused the HTTP
 * request to not execute (the condition of the header failed)
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PreconditionFailed extends DAV\Exception {

    /**
     * When this exception is thrown, the header-name might be set.
     *
     * This allows the exception-catching code to determine which HTTP header
     * caused the exception.
     *
     * @var string
     */
    public $header = null;

    /**
     * Create the exception
     *
     * @param string $message
     * @param string $header
     */
    function __construct($message, $header = null) {

        parent::__construct($message);
        $this->header = $header;

    }

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 412;

    }

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        if ($this->header) {
            $prop = $errorNode->ownerDocument->createElement('s:header');
            $prop->nodeValue = $this->header;
            $errorNode->appendChild($prop);
        }

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * ReportNotSupported
 *
 * This exception is thrown when the client requested an unknown report through the REPORT method
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ReportNotSupported extends UnsupportedMediaType {

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:supported-report');
        $errorNode->appendChild($error);

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * RequestedRangeNotSatisfiable
 *
 * This exception is normally thrown when the user
 * request a range that is out of the entity bounds.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class RequestedRangeNotSatisfiable extends DAV\Exception {

    /**
     * returns the http statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 416;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * ServiceUnavailable
 *
 * This exception is thrown in case the service
 * is currently not available (e.g. down for maintenance).
 *
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ServiceUnavailable extends DAV\Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 503;

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * TooManyMatches
 *
 * This exception is emited for the {DAV:}number-of-matches-within-limits
 * post-condition, as defined in rfc6578, section 3.2.
 *
 * http://tools.ietf.org/html/rfc6578#section-3.2
 *
 * This is emitted in cases where the response to a {DAV:}sync-collection would
 * generate more results than the implementation is willing to send back.
 *
 * @author Evert Pot (http://evertpot.com/)
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class TooManyMatches extends Forbidden {

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:number-of-matches-within-limits');
        $errorNode->appendChild($error);

    }

}
<?php

namespace Sabre\DAV\Exception;

use Sabre\DAV;

/**
 * UnSupportedMediaType
 *
 * The 415 Unsupported Media Type status code is generally sent back when the client
 * tried to call an HTTP method, with a body the server didn't understand
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class UnsupportedMediaType extends DAV\Exception {

    /**
     * returns the http statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 415;

    }

}
<?php

namespace Sabre\DAV;

/**
 * Main Exception class.
 *
 * This class defines a getHTTPCode method, which should return the appropriate HTTP code for the Exception occurred.
 * The default for this is 500.
 *
 * This class also allows you to generate custom xml data for your exceptions. This will be displayed
 * in the 'error' element in the failing response.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Exception extends \Exception {

    /**
     * Returns the HTTP statuscode for this exception
     *
     * @return int
     */
    function getHTTPCode() {

        return 500;

    }

    /**
     * This method allows the exception to include additional information into the WebDAV error response
     *
     * @param Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(Server $server, \DOMElement $errorNode) {


    }

    /**
     * This method allows the exception to return any extra HTTP response headers.
     *
     * The headers must be returned as an array.
     *
     * @param Server $server
     * @return array
     */
    function getHTTPHeaders(Server $server) {

        return [];

    }

}
<?php

namespace Sabre\DAV;

/**
 * File class
 *
 * This is a helper class, that should aid in getting file classes setup.
 * Most of its methods are implemented, and throw permission denied exceptions
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class File extends Node implements IFile {

    /**
     * Replaces the contents of the file.
     *
     * The data argument is a readable stream resource.
     *
     * After a successful put operation, you may choose to return an ETag. The
     * etag must always be surrounded by double-quotes. These quotes must
     * appear in the actual string you're returning.
     *
     * Clients may use the ETag from a PUT request to later on make sure that
     * when they update the file, the contents haven't changed in the mean
     * time.
     *
     * If you don't plan to store the file byte-by-byte, and you return a
     * different object on a subsequent GET you are strongly recommended to not
     * return an ETag, and just return null.
     *
     * @param string|resource $data
     * @return string|null
     */
    function put($data) {

        throw new Exception\Forbidden('Permission denied to change data');

    }

    /**
     * Returns the data
     *
     * This method may either return a string or a readable stream resource
     *
     * @return mixed
     */
    function get() {

        throw new Exception\Forbidden('Permission denied to read this file');

    }

    /**
     * Returns the size of the file, in bytes.
     *
     * @return int
     */
    function getSize() {

        return 0;

    }

    /**
     * Returns the ETag for a file
     *
     * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
     *
     * Return null if the ETag can not effectively be determined
     *
     * @return string|null
     */
    function getETag() {

        return null;

    }

    /**
     * Returns the mime-type for a file
     *
     * If null is returned, we'll assume application/octet-stream
     *
     * @return string|null
     */
    function getContentType() {

        return null;

    }

}
<?php

namespace Sabre\DAV\FS;

use Sabre\DAV;

/**
 * Directory class
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Directory extends Node implements DAV\ICollection, DAV\IQuota {

    /**
     * Creates a new file in the directory
     *
     * Data will either be supplied as a stream resource, or in certain cases
     * as a string. Keep in mind that you may have to support either.
     *
     * After successful creation of the file, you may choose to return the ETag
     * of the new file here.
     *
     * The returned ETag must be surrounded by double-quotes (The quotes should
     * be part of the actual string).
     *
     * If you cannot accurately determine the ETag, you should not return it.
     * If you don't store the file exactly as-is (you're transforming it
     * somehow) you should also not return an ETag.
     *
     * This means that if a subsequent GET to this new file does not exactly
     * return the same contents of what was submitted here, you are strongly
     * recommended to omit the ETag.
     *
     * @param string $name Name of the file
     * @param resource|string $data Initial payload
     * @return null|string
     */
    function createFile($name, $data = null) {

        $newPath = $this->path . '/' . $name;
        file_put_contents($newPath, $data);
        clearstatcache(true, $newPath);

    }

    /**
     * Creates a new subdirectory
     *
     * @param string $name
     * @return void
     */
    function createDirectory($name) {

        $newPath = $this->path . '/' . $name;
        mkdir($newPath);
        clearstatcache(true, $newPath);

    }

    /**
     * Returns a specific child node, referenced by its name
     *
     * This method must throw DAV\Exception\NotFound if the node does not
     * exist.
     *
     * @param string $name
     * @throws DAV\Exception\NotFound
     * @return DAV\INode
     */
    function getChild($name) {

        $path = $this->path . '/' . $name;

        if (!file_exists($path)) throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located');

        if (is_dir($path)) {

            return new self($path);

        } else {

            return new File($path);

        }

    }

    /**
     * Returns an array with all the child nodes
     *
     * @return DAV\INode[]
     */
    function getChildren() {

        $nodes = [];
        $iterator = new \FilesystemIterator(
            $this->path,
            \FilesystemIterator::CURRENT_AS_SELF
          | \FilesystemIterator::SKIP_DOTS
        );
        foreach ($iterator as $entry) {

            $nodes[] = $this->getChild($entry->getFilename());

        }
        return $nodes;

    }

    /**
     * Checks if a child exists.
     *
     * @param string $name
     * @return bool
     */
    function childExists($name) {

        $path = $this->path . '/' . $name;
        return file_exists($path);

    }

    /**
     * Deletes all files in this directory, and then itself
     *
     * @return void
     */
    function delete() {

        foreach ($this->getChildren() as $child) $child->delete();
        rmdir($this->path);

    }

    /**
     * Returns available diskspace information
     *
     * @return array
     */
    function getQuotaInfo() {
        $absolute = realpath($this->path);
        return [
            disk_total_space($absolute) - disk_free_space($absolute),
            disk_free_space($absolute)
        ];

    }

}
<?php

namespace Sabre\DAV\FS;

use Sabre\DAV;

/**
 * File class
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class File extends Node implements DAV\IFile {

    /**
     * Updates the data
     *
     * @param resource $data
     * @return void
     */
    function put($data) {

        file_put_contents($this->path, $data);
        clearstatcache(true, $this->path);

    }

    /**
     * Returns the data
     *
     * @return resource
     */
    function get() {

        return fopen($this->path, 'r');

    }

    /**
     * Delete the current file
     *
     * @return void
     */
    function delete() {

        unlink($this->path);

    }

    /**
     * Returns the size of the node, in bytes
     *
     * @return int
     */
    function getSize() {

        return filesize($this->path);

    }

    /**
     * Returns the ETag for a file
     *
     * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
     *
     * Return null if the ETag can not effectively be determined
     *
     * @return mixed
     */
    function getETag() {

        return '"' . sha1(
            fileinode($this->path) .
            filesize($this->path) .
            filemtime($this->path)
        ) . '"';

    }

    /**
     * Returns the mime-type for a file
     *
     * If null is returned, we'll assume application/octet-stream
     *
     * @return mixed
     */
    function getContentType() {

        return null;

    }

}
<?php

namespace Sabre\DAV\FS;

use Sabre\DAV;
use Sabre\HTTP\URLUtil;

/**
 * Base node-class
 *
 * The node class implements the method used by both the File and the Directory classes
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class Node implements DAV\INode {

    /**
     * The path to the current node
     *
     * @var string
     */
    protected $path;

    /**
     * Sets up the node, expects a full path name
     *
     * @param string $path
     */
    function __construct($path) {

        $this->path = $path;

    }



    /**
     * Returns the name of the node
     *
     * @return string
     */
    function getName() {

        list(, $name) = URLUtil::splitPath($this->path);
        return $name;

    }

    /**
     * Renames the node
     *
     * @param string $name The new name
     * @return void
     */
    function setName($name) {

        list($parentPath, ) = URLUtil::splitPath($this->path);
        list(, $newName) = URLUtil::splitPath($name);

        $newPath = $parentPath . '/' . $newName;
        rename($this->path, $newPath);

        $this->path = $newPath;

    }

    /**
     * Returns the last modification time, as a unix timestamp
     *
     * @return int
     */
    function getLastModified() {

        return filemtime($this->path);

    }

}
<?php

namespace Sabre\DAV\FSExt;

use Sabre\DAV;
use Sabre\DAV\FS\Node;

/**
 * Directory class
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Directory extends Node implements DAV\ICollection, DAV\IQuota, DAV\IMoveTarget {

    /**
     * Creates a new file in the directory
     *
     * Data will either be supplied as a stream resource, or in certain cases
     * as a string. Keep in mind that you may have to support either.
     *
     * After successful creation of the file, you may choose to return the ETag
     * of the new file here.
     *
     * The returned ETag must be surrounded by double-quotes (The quotes should
     * be part of the actual string).
     *
     * If you cannot accurately determine the ETag, you should not return it.
     * If you don't store the file exactly as-is (you're transforming it
     * somehow) you should also not return an ETag.
     *
     * This means that if a subsequent GET to this new file does not exactly
     * return the same contents of what was submitted here, you are strongly
     * recommended to omit the ETag.
     *
     * @param string $name Name of the file
     * @param resource|string $data Initial payload
     * @return null|string
     */
    function createFile($name, $data = null) {

        // We're not allowing dots
        if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..');
        $newPath = $this->path . '/' . $name;
        file_put_contents($newPath, $data);
        clearstatcache(true, $newPath);

        return '"' . sha1(
            fileinode($newPath) .
            filesize($newPath) .
            filemtime($newPath)
        ) . '"';

    }

    /**
     * Creates a new subdirectory
     *
     * @param string $name
     * @return void
     */
    function createDirectory($name) {

        // We're not allowing dots
        if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..');
        $newPath = $this->path . '/' . $name;
        mkdir($newPath);
        clearstatcache(true, $newPath);

    }

    /**
     * Returns a specific child node, referenced by its name
     *
     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
     * exist.
     *
     * @param string $name
     * @throws DAV\Exception\NotFound
     * @return DAV\INode
     */
    function getChild($name) {

        $path = $this->path . '/' . $name;

        if (!file_exists($path)) throw new DAV\Exception\NotFound('File could not be located');
        if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..');

        if (is_dir($path)) {

            return new self($path);

        } else {

            return new File($path);

        }

    }

    /**
     * Checks if a child exists.
     *
     * @param string $name
     * @return bool
     */
    function childExists($name) {

        if ($name == '.' || $name == '..')
            throw new DAV\Exception\Forbidden('Permission denied to . and ..');

        $path = $this->path . '/' . $name;
        return file_exists($path);

    }

    /**
     * Returns an array with all the child nodes
     *
     * @return DAV\INode[]
     */
    function getChildren() {

        $nodes = [];
        $iterator = new \FilesystemIterator(
            $this->path,
            \FilesystemIterator::CURRENT_AS_SELF
          | \FilesystemIterator::SKIP_DOTS
        );

        foreach ($iterator as $entry) {

            $nodes[] = $this->getChild($entry->getFilename());

        }
        return $nodes;

    }

    /**
     * Deletes all files in this directory, and then itself
     *
     * @return bool
     */
    function delete() {

        // Deleting all children
        foreach ($this->getChildren() as $child) $child->delete();

        // Removing the directory itself
        rmdir($this->path);

        return true;

    }

    /**
     * Returns available diskspace information
     *
     * @return array
     */
    function getQuotaInfo() {

        $total = disk_total_space(realpath($this->path));
        $free = disk_free_space(realpath($this->path));

        return [
            $total - $free,
            $free
        ];
    }

    /**
     * Moves a node into this collection.
     *
     * It is up to the implementors to:
     *   1. Create the new resource.
     *   2. Remove the old resource.
     *   3. Transfer any properties or other data.
     *
     * Generally you should make very sure that your collection can easily move
     * the move.
     *
     * If you don't, just return false, which will trigger sabre/dav to handle
     * the move itself. If you return true from this function, the assumption
     * is that the move was successful.
     *
     * @param string $targetName New local file/collection name.
     * @param string $sourcePath Full path to source node
     * @param DAV\INode $sourceNode Source node itself
     * @return bool
     */
    function moveInto($targetName, $sourcePath, DAV\INode $sourceNode) {

        // We only support FSExt\Directory or FSExt\File objects, so
        // anything else we want to quickly reject.
        if (!$sourceNode instanceof self && !$sourceNode instanceof File) {
            return false;
        }

        // PHP allows us to access protected properties from other objects, as
        // long as they are defined in a class that has a shared inheritence
        // with the current class.
        rename($sourceNode->path, $this->path . '/' . $targetName);

        return true;

    }

}
<?php

namespace Sabre\DAV\FSExt;

use Sabre\DAV;
use Sabre\DAV\FS\Node;

/**
 * File class
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class File extends Node implements DAV\PartialUpdate\IPatchSupport {

    /**
     * Updates the data
     *
     * Data is a readable stream resource.
     *
     * @param resource|string $data
     * @return string
     */
    function put($data) {

        file_put_contents($this->path, $data);
        clearstatcache(true, $this->path);
        return $this->getETag();

    }

    /**
     * Updates the file based on a range specification.
     *
     * The first argument is the data, which is either a readable stream
     * resource or a string.
     *
     * The second argument is the type of update we're doing.
     * This is either:
     * * 1. append
     * * 2. update based on a start byte
     * * 3. update based on an end byte
     *;
     * The third argument is the start or end byte.
     *
     * After a successful put operation, you may choose to return an ETag. The
     * ETAG must always be surrounded by double-quotes. These quotes must
     * appear in the actual string you're returning.
     *
     * Clients may use the ETag from a PUT request to later on make sure that
     * when they update the file, the contents haven't changed in the mean
     * time.
     *
     * @param resource|string $data
     * @param int $rangeType
     * @param int $offset
     * @return string|null
     */
    function patch($data, $rangeType, $offset = null) {

        switch ($rangeType) {
            case 1 :
                $f = fopen($this->path, 'a');
                break;
            case 2 :
                $f = fopen($this->path, 'c');
                fseek($f, $offset);
                break;
            case 3 :
                $f = fopen($this->path, 'c');
                fseek($f, $offset, SEEK_END);
                break;
        }
        if (is_string($data)) {
            fwrite($f, $data);
        } else {
            stream_copy_to_stream($data, $f);
        }
        fclose($f);
        clearstatcache(true, $this->path);
        return $this->getETag();

    }

    /**
     * Returns the data
     *
     * @return resource
     */
    function get() {

        return fopen($this->path, 'r');

    }

    /**
     * Delete the current file
     *
     * @return bool
     */
    function delete() {

        return unlink($this->path);

    }

    /**
     * Returns the ETag for a file
     *
     * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
     *
     * Return null if the ETag can not effectively be determined
     *
     * @return string|null
     */
    function getETag() {

        return '"' . sha1(
            fileinode($this->path) .
            filesize($this->path) .
            filemtime($this->path)
        ) . '"';

    }

    /**
     * Returns the mime-type for a file
     *
     * If null is returned, we'll assume application/octet-stream
     *
     * @return string|null
     */
    function getContentType() {

        return null;

    }

    /**
     * Returns the size of the file, in bytes
     *
     * @return int
     */
    function getSize() {

        return filesize($this->path);

    }

}
<?php

namespace Sabre\DAV;

/**
 * The ICollection Interface
 *
 * This interface should be implemented by each class that represents a collection
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ICollection extends INode {

    /**
     * Creates a new file in the directory
     *
     * Data will either be supplied as a stream resource, or in certain cases
     * as a string. Keep in mind that you may have to support either.
     *
     * After successful creation of the file, you may choose to return the ETag
     * of the new file here.
     *
     * The returned ETag must be surrounded by double-quotes (The quotes should
     * be part of the actual string).
     *
     * If you cannot accurately determine the ETag, you should not return it.
     * If you don't store the file exactly as-is (you're transforming it
     * somehow) you should also not return an ETag.
     *
     * This means that if a subsequent GET to this new file does not exactly
     * return the same contents of what was submitted here, you are strongly
     * recommended to omit the ETag.
     *
     * @param string $name Name of the file
     * @param resource|string $data Initial payload
     * @return null|string
     */
    function createFile($name, $data = null);

    /**
     * Creates a new subdirectory
     *
     * @param string $name
     * @return void
     */
    function createDirectory($name);

    /**
     * Returns a specific child node, referenced by its name
     *
     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
     * exist.
     *
     * @param string $name
     * @return INode
     */
    function getChild($name);

    /**
     * Returns an array with all the child nodes
     *
     * @return INode[]
     */
    function getChildren();

    /**
     * Checks if a child-node with the specified name exists
     *
     * @param string $name
     * @return bool
     */
    function childExists($name);

}
<?php

namespace Sabre\DAV;

/**
 * The IExtendedCollection interface.
 *
 * This interface can be used to create special-type of collection-resources
 * as defined by RFC 5689.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IExtendedCollection extends ICollection {

    /**
     * Creates a new collection.
     *
     * This method will receive a MkCol object with all the information about
     * the new collection that's being created.
     *
     * The MkCol object contains information about the resourceType of the new
     * collection. If you don't support the specified resourceType, you should
     * throw Exception\InvalidResourceType.
     *
     * The object also contains a list of WebDAV properties for the new
     * collection.
     *
     * You should call the handle() method on this object to specify exactly
     * which properties you are storing. This allows the system to figure out
     * exactly which properties you didn't store, which in turn allows other
     * plugins (such as the propertystorage plugin) to handle storing the
     * property for you.
     *
     * @param string $name
     * @param MkCol $mkCol
     * @throws Exception\InvalidResourceType
     * @return void
     */
    function createExtendedCollection($name, MkCol $mkCol);

}
<?php

namespace Sabre\DAV;

/**
 * This interface represents a file in the directory tree
 *
 * A file is a bit of a broad definition. In general it implies that on
 * this specific node a PUT or GET method may be performed, to either update,
 * or retrieve the contents of the file.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IFile extends INode {

    /**
     * Replaces the contents of the file.
     *
     * The data argument is a readable stream resource.
     *
     * After a successful put operation, you may choose to return an ETag. The
     * etag must always be surrounded by double-quotes. These quotes must
     * appear in the actual string you're returning.
     *
     * Clients may use the ETag from a PUT request to later on make sure that
     * when they update the file, the contents haven't changed in the mean
     * time.
     *
     * If you don't plan to store the file byte-by-byte, and you return a
     * different object on a subsequent GET you are strongly recommended to not
     * return an ETag, and just return null.
     *
     * @param resource|string $data
     * @return string|null
     */
    function put($data);

    /**
     * Returns the data
     *
     * This method may either return a string or a readable stream resource
     *
     * @return mixed
     */
    function get();

    /**
     * Returns the mime-type for a file
     *
     * If null is returned, we'll assume application/octet-stream
     *
     * @return string|null
     */
    function getContentType();

    /**
     * Returns the ETag for a file
     *
     * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
     *
     * Return null if the ETag can not effectively be determined.
     *
     * The ETag must be surrounded by double-quotes, so something like this
     * would make a valid ETag:
     *
     *   return '"someetag"';
     *
     * @return string|null
     */
    function getETag();

    /**
     * Returns the size of the node, in bytes
     *
     * @return int
     */
    function getSize();

}
<?php

namespace Sabre\DAV;

/**
 * By implementing this interface, a collection can effectively say "other
 * nodes may be moved into this collection".
 *
 * The benefit of this, is that sabre/dav will by default perform a move, by
 * transferring an entire directory tree, copying every collection, and deleting
 * every item.
 *
 * If a backend supports a better optimized move operation, this can trigger
 * some huge speed gains.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IMoveTarget extends ICollection {

    /**
     * Moves a node into this collection.
     *
     * It is up to the implementors to:
     *   1. Create the new resource.
     *   2. Remove the old resource.
     *   3. Transfer any properties or other data.
     *
     * Generally you should make very sure that your collection can easily move
     * the move.
     *
     * If you don't, just return false, which will trigger sabre/dav to handle
     * the move itself. If you return true from this function, the assumption
     * is that the move was successful.
     *
     * @param string $targetName New local file/collection name.
     * @param string $sourcePath Full path to source node
     * @param INode $sourceNode Source node itself
     * @return bool
     */
    function moveInto($targetName, $sourcePath, INode $sourceNode);

}
<?php

namespace Sabre\DAV;

/**
 * IMultiGet
 *
 * This interface adds a tiny bit of functionality to collections.
 *
 * There a certain situations, in particular in relation to WebDAV-Sync, CalDAV
 * and CardDAV, where information for a list of items will be requested.
 *
 * Because the getChild() call is the main abstraction method, this can in
 * reality result in many database calls, which could potentially be
 * optimized.
 *
 * The MultiGet interface is used by the server in these cases.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IMultiGet extends ICollection {

    /**
     * This method receives a list of paths in it's first argument.
     * It must return an array with Node objects.
     *
     * If any children are not found, you do not have to return them.
     *
     * @param string[] $paths
     * @return array
     */
    function getMultipleChildren(array $paths);

}
<?php

namespace Sabre\DAV;

/**
 * The INode interface is the base interface, and the parent class of both ICollection and IFile
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface INode {

    /**
     * Deleted the current node
     *
     * @return void
     */
    function delete();

    /**
     * Returns the name of the node.
     *
     * This is used to generate the url.
     *
     * @return string
     */
    function getName();

    /**
     * Renames the node
     *
     * @param string $name The new name
     * @return void
     */
    function setName($name);

    /**
     * Returns the last modification time, as a unix timestamp. Return null
     * if the information is not available.
     *
     * @return int|null
     */
    function getLastModified();

}
<?php

namespace Sabre\DAV;

/**
 * IProperties interface
 *
 * Implement this interface to support custom WebDAV properties requested and sent from clients.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IProperties extends INode {

    /**
     * Updates properties on this node.
     *
     * This method received a PropPatch object, which contains all the
     * information about the update.
     *
     * To update specific properties, call the 'handle' method on this object.
     * Read the PropPatch documentation for more information.
     *
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatch(PropPatch $propPatch);

    /**
     * Returns a list of properties for this nodes.
     *
     * The properties list is a list of propertynames the client requested,
     * encoded in clark-notation {xmlnamespace}tagname
     *
     * If the array is empty, it means 'all properties' were requested.
     *
     * Note that it's fine to liberally give properties back, instead of
     * conforming to the list of requested properties.
     * The Server class will filter out the extra.
     *
     * @param array $properties
     * @return array
     */
    function getProperties($properties);

}
<?php

namespace Sabre\DAV;

/**
 * IQuota interface
 *
 * Implement this interface to add the ability to return quota information. The ObjectTree
 * will check for quota information on any given node. If the information is not available it will
 * attempt to fetch the information from the root node.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IQuota extends ICollection {

    /**
     * Returns the quota information
     *
     * This method MUST return an array with 2 values, the first being the total used space,
     * the second the available space (in bytes)
     */
    function getQuotaInfo();

}
<?php

namespace Sabre\DAV\Locks\Backend;

/**
 * This is an Abstract clas for lock backends.
 *
 * Currently this backend has no function, but it exists for consistency, and
 * to ensure that if default code is required in the backend, there will be a
 * non-bc-breaking way to do so.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractBackend implements BackendInterface {

}
<?php

namespace Sabre\DAV\Locks\Backend;

use Sabre\DAV\Locks;

/**
 * If you are defining your own Locks backend, you must implement this
 * interface.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface BackendInterface {

    /**
     * Returns a list of Sabre\DAV\Locks\LockInfo objects
     *
     * This method should return all the locks for a particular uri, including
     * locks that might be set on a parent uri.
     *
     * If returnChildLocks is set to true, this method should also look for
     * any locks in the subtree of the uri for locks.
     *
     * @param string $uri
     * @param bool $returnChildLocks
     * @return array
     */
    function getLocks($uri, $returnChildLocks);

    /**
     * Locks a uri
     *
     * @param string $uri
     * @param Locks\LockInfo $lockInfo
     * @return bool
     */
    function lock($uri, Locks\LockInfo $lockInfo);

    /**
     * Removes a lock from a uri
     *
     * @param string $uri
     * @param Locks\LockInfo $lockInfo
     * @return bool
     */
    function unlock($uri, Locks\LockInfo $lockInfo);

}
<?php

namespace Sabre\DAV\Locks\Backend;

use Sabre\DAV\Locks\LockInfo;

/**
 * This Locks backend stores all locking information in a single file.
 *
 * Note that this is not nearly as robust as a database. If you are considering
 * using this backend, keep in mind that the PDO backend can work with SqLite,
 * which is designed to be a good file-based database.
 *
 * It literally solves the problem this class solves as well, but much better.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class File extends AbstractBackend {

    /**
     * The storage file
     *
     * @var string
     */
    private $locksFile;

    /**
     * Constructor
     *
     * @param string $locksFile path to file
     */
    function __construct($locksFile) {

        $this->locksFile = $locksFile;

    }

    /**
     * Returns a list of Sabre\DAV\Locks\LockInfo objects
     *
     * This method should return all the locks for a particular uri, including
     * locks that might be set on a parent uri.
     *
     * If returnChildLocks is set to true, this method should also look for
     * any locks in the subtree of the uri for locks.
     *
     * @param string $uri
     * @param bool $returnChildLocks
     * @return array
     */
    function getLocks($uri, $returnChildLocks) {

        $newLocks = [];

        $locks = $this->getData();

        foreach ($locks as $lock) {

            if ($lock->uri === $uri ||
                //deep locks on parents
                ($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) ||

                // locks on children
                ($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) {

                $newLocks[] = $lock;

            }

        }

        // Checking if we can remove any of these locks
        foreach ($newLocks as $k => $lock) {
            if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]);
        }
        return $newLocks;

    }

    /**
     * Locks a uri
     *
     * @param string $uri
     * @param LockInfo $lockInfo
     * @return bool
     */
    function lock($uri, LockInfo $lockInfo) {

        // We're making the lock timeout 30 minutes
        $lockInfo->timeout = 1800;
        $lockInfo->created = time();
        $lockInfo->uri = $uri;

        $locks = $this->getData();

        foreach ($locks as $k => $lock) {
            if (
                ($lock->token == $lockInfo->token) ||
                (time() > $lock->timeout + $lock->created)
            ) {
                unset($locks[$k]);
            }
        }
        $locks[] = $lockInfo;
        $this->putData($locks);
        return true;

    }

    /**
     * Removes a lock from a uri
     *
     * @param string $uri
     * @param LockInfo $lockInfo
     * @return bool
     */
    function unlock($uri, LockInfo $lockInfo) {

        $locks = $this->getData();
        foreach ($locks as $k => $lock) {

            if ($lock->token == $lockInfo->token) {

                unset($locks[$k]);
                $this->putData($locks);
                return true;

            }
        }
        return false;

    }

    /**
     * Loads the lockdata from the filesystem.
     *
     * @return array
     */
    protected function getData() {

        if (!file_exists($this->locksFile)) return [];

        // opening up the file, and creating a shared lock
        $handle = fopen($this->locksFile, 'r');
        flock($handle, LOCK_SH);

        // Reading data until the eof
        $data = stream_get_contents($handle);

        // We're all good
        flock($handle, LOCK_UN);
        fclose($handle);

        // Unserializing and checking if the resource file contains data for this file
        $data = unserialize($data);
        if (!$data) return [];
        return $data;

    }

    /**
     * Saves the lockdata
     *
     * @param array $newData
     * @return void
     */
    protected function putData(array $newData) {

        // opening up the file, and creating an exclusive lock
        $handle = fopen($this->locksFile, 'a+');
        flock($handle, LOCK_EX);

        // We can only truncate and rewind once the lock is acquired.
        ftruncate($handle, 0);
        rewind($handle);

        fwrite($handle, serialize($newData));
        flock($handle, LOCK_UN);
        fclose($handle);

    }

}
<?php

namespace Sabre\DAV\Locks\Backend;

use Sabre\DAV\Locks\LockInfo;

/**
 * The Lock manager allows you to handle all file-locks centrally.
 *
 * This Lock Manager stores all its data in a database. You must pass a PDO
 * connection object in the constructor.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PDO extends AbstractBackend {

    /**
     * The PDO tablename this backend uses.
     *
     * @var string
     */
    public $tableName = 'locks';

    /**
     * The PDO connection object
     *
     * @var pdo
     */
    protected $pdo;

    /**
     * Constructor
     *
     * @param \PDO $pdo
     */
    function __construct(\PDO $pdo) {

        $this->pdo = $pdo;

    }

    /**
     * Returns a list of Sabre\DAV\Locks\LockInfo objects
     *
     * This method should return all the locks for a particular uri, including
     * locks that might be set on a parent uri.
     *
     * If returnChildLocks is set to true, this method should also look for
     * any locks in the subtree of the uri for locks.
     *
     * @param string $uri
     * @param bool $returnChildLocks
     * @return array
     */
    function getLocks($uri, $returnChildLocks) {

        // NOTE: the following 10 lines or so could be easily replaced by
        // pure sql. MySQL's non-standard string concatenation prevents us
        // from doing this though.
        $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM ' . $this->tableName . ' WHERE (created > (? - timeout)) AND ((uri = ?)';
        $params = [time(),$uri];

        // We need to check locks for every part in the uri.
        $uriParts = explode('/', $uri);

        // We already covered the last part of the uri
        array_pop($uriParts);

        $currentPath = '';

        foreach ($uriParts as $part) {

            if ($currentPath) $currentPath .= '/';
            $currentPath .= $part;

            $query .= ' OR (depth!=0 AND uri = ?)';
            $params[] = $currentPath;

        }

        if ($returnChildLocks) {

            $query .= ' OR (uri LIKE ?)';
            $params[] = $uri . '/%';

        }
        $query .= ')';

        $stmt = $this->pdo->prepare($query);
        $stmt->execute($params);
        $result = $stmt->fetchAll();

        $lockList = [];
        foreach ($result as $row) {

            $lockInfo = new LockInfo();
            $lockInfo->owner = $row['owner'];
            $lockInfo->token = $row['token'];
            $lockInfo->timeout = $row['timeout'];
            $lockInfo->created = $row['created'];
            $lockInfo->scope = $row['scope'];
            $lockInfo->depth = $row['depth'];
            $lockInfo->uri = $row['uri'];
            $lockList[] = $lockInfo;

        }

        return $lockList;

    }

    /**
     * Locks a uri
     *
     * @param string $uri
     * @param LockInfo $lockInfo
     * @return bool
     */
    function lock($uri, LockInfo $lockInfo) {

        // We're making the lock timeout 30 minutes
        $lockInfo->timeout = 30 * 60;
        $lockInfo->created = time();
        $lockInfo->uri = $uri;

        $locks = $this->getLocks($uri, false);
        $exists = false;
        foreach ($locks as $lock) {
            if ($lock->token == $lockInfo->token) $exists = true;
        }

        if ($exists) {
            $stmt = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?');
            $stmt->execute([
                $lockInfo->owner,
                $lockInfo->timeout,
                $lockInfo->scope,
                $lockInfo->depth,
                $uri,
                $lockInfo->created,
                $lockInfo->token
            ]);
        } else {
            $stmt = $this->pdo->prepare('INSERT INTO ' . $this->tableName . ' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)');
            $stmt->execute([
                $lockInfo->owner,
                $lockInfo->timeout,
                $lockInfo->scope,
                $lockInfo->depth,
                $uri,
                $lockInfo->created,
                $lockInfo->token
            ]);
        }

        return true;

    }



    /**
     * Removes a lock from a uri
     *
     * @param string $uri
     * @param LockInfo $lockInfo
     * @return bool
     */
    function unlock($uri, LockInfo $lockInfo) {

        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->tableName . ' WHERE uri = ? AND token = ?');
        $stmt->execute([$uri, $lockInfo->token]);

        return $stmt->rowCount() === 1;

    }

}
<?php

namespace Sabre\DAV\Locks;

/**
 * LockInfo class
 *
 * An object of the LockInfo class holds all the information relevant to a
 * single lock.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class LockInfo {

    /**
     * A shared lock
     */
    const SHARED = 1;

    /**
     * An exclusive lock
     */
    const EXCLUSIVE = 2;

    /**
     * A never expiring timeout
     */
    const TIMEOUT_INFINITE = -1;

    /**
     * The owner of the lock
     *
     * @var string
     */
    public $owner;

    /**
     * The locktoken
     *
     * @var string
     */
    public $token;

    /**
     * How long till the lock is expiring
     *
     * @var int
     */
    public $timeout;

    /**
     * UNIX Timestamp of when this lock was created
     *
     * @var int
     */
    public $created;

    /**
     * Exclusive or shared lock
     *
     * @var int
     */
    public $scope = self::EXCLUSIVE;

    /**
     * Depth of lock, can be 0 or Sabre\DAV\Server::DEPTH_INFINITY
     */
    public $depth = 0;

    /**
     * The uri this lock locks
     *
     * TODO: This value is not always set
     * @var mixed
     */
    public $uri;

}
<?php

namespace Sabre\DAV\Locks;

use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * Locking plugin
 *
 * This plugin provides locking support to a WebDAV server.
 * The easiest way to get started, is by hooking it up as such:
 *
 * $lockBackend = new Sabre\DAV\Locks\Backend\File('./mylockdb');
 * $lockPlugin = new Sabre\DAV\Locks\Plugin($lockBackend);
 * $server->addPlugin($lockPlugin);
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends DAV\ServerPlugin {

    /**
     * locksBackend
     *
     * @var Backend\BackendInterface
     */
    protected $locksBackend;

    /**
     * server
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * __construct
     *
     * @param Backend\BackendInterface $locksBackend
     */
    function __construct(Backend\BackendInterface $locksBackend) {

        $this->locksBackend = $locksBackend;

    }

    /**
     * Initializes the plugin
     *
     * This method is automatically called by the Server class after addPlugin.
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;

        $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock';

        $server->on('method:LOCK',    [$this, 'httpLock']);
        $server->on('method:UNLOCK',  [$this, 'httpUnlock']);
        $server->on('validateTokens', [$this, 'validateTokens']);
        $server->on('propFind',       [$this, 'propFind']);
        $server->on('afterUnbind',    [$this, 'afterUnbind']);

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'locks';

    }

    /**
     * This method is called after most properties have been found
     * it allows us to add in any Lock-related properties
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @return void
     */
    function propFind(DAV\PropFind $propFind, DAV\INode $node) {

        $propFind->handle('{DAV:}supportedlock', function() {
            return new DAV\Xml\Property\SupportedLock();
        });
        $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) {
            return new DAV\Xml\Property\LockDiscovery(
                $this->getLocks($propFind->getPath())
            );
        });

    }

    /**
     * Use this method to tell the server this plugin defines additional
     * HTTP methods.
     *
     * This method is passed a uri. It should only return HTTP methods that are
     * available for the specified uri.
     *
     * @param string $uri
     * @return array
     */
    function getHTTPMethods($uri) {

        return ['LOCK','UNLOCK'];

    }

    /**
     * Returns a list of features for the HTTP OPTIONS Dav: header.
     *
     * In this case this is only the number 2. The 2 in the Dav: header
     * indicates the server supports locks.
     *
     * @return array
     */
    function getFeatures() {

        return [2];

    }

    /**
     * Returns all lock information on a particular uri
     *
     * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array.
     *
     * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree
     * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object
     * for any possible locks and return those as well.
     *
     * @param string $uri
     * @param bool $returnChildLocks
     * @return array
     */
    function getLocks($uri, $returnChildLocks = false) {

        return $this->locksBackend->getLocks($uri, $returnChildLocks);

    }

    /**
     * Locks an uri
     *
     * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock
     * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type
     * of lock (shared or exclusive) and the owner of the lock
     *
     * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock
     *
     * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpLock(RequestInterface $request, ResponseInterface $response) {

        $uri = $request->getPath();

        $existingLocks = $this->getLocks($uri);

        if ($body = $request->getBodyAsString()) {
            // This is a new lock request

            $existingLock = null;
            // Checking if there's already non-shared locks on the uri.
            foreach ($existingLocks as $existingLock) {
                if ($existingLock->scope === LockInfo::EXCLUSIVE) {
                    throw new DAV\Exception\ConflictingLock($existingLock);
                }
            }

            $lockInfo = $this->parseLockRequest($body);
            $lockInfo->depth = $this->server->getHTTPDepth();
            $lockInfo->uri = $uri;
            if ($existingLock && $lockInfo->scope != LockInfo::SHARED)
                throw new DAV\Exception\ConflictingLock($existingLock);

        } else {

            // Gonna check if this was a lock refresh.
            $existingLocks = $this->getLocks($uri);
            $conditions = $this->server->getIfConditions($request);
            $found = null;

            foreach ($existingLocks as $existingLock) {
                foreach ($conditions as $condition) {
                    foreach ($condition['tokens'] as $token) {
                        if ($token['token'] === 'opaquelocktoken:' . $existingLock->token) {
                            $found = $existingLock;
                            break 3;
                        }
                    }
                }
            }

            // If none were found, this request is in error.
            if (is_null($found)) {
                if ($existingLocks) {
                    throw new DAV\Exception\Locked(reset($existingLocks));
                } else {
                    throw new DAV\Exception\BadRequest('An xml body is required for lock requests');
                }

            }

            // This must have been a lock refresh
            $lockInfo = $found;

            // The resource could have been locked through another uri.
            if ($uri != $lockInfo->uri) $uri = $lockInfo->uri;

        }

        if ($timeout = $this->getTimeoutHeader()) $lockInfo->timeout = $timeout;

        $newFile = false;

        // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first
        try {
            $this->server->tree->getNodeForPath($uri);

            // We need to call the beforeWriteContent event for RFC3744
            // Edit: looks like this is not used, and causing problems now.
            //
            // See Issue 222
            // $this->server->emit('beforeWriteContent',array($uri));

        } catch (DAV\Exception\NotFound $e) {

            // It didn't, lets create it
            $this->server->createFile($uri, fopen('php://memory', 'r'));
            $newFile = true;

        }

        $this->lockNode($uri, $lockInfo);

        $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $response->setHeader('Lock-Token', '<opaquelocktoken:' . $lockInfo->token . '>');
        $response->setStatus($newFile ? 201 : 200);
        $response->setBody($this->generateLockResponse($lockInfo));

        // Returning false will interrupt the event chain and mark this method
        // as 'handled'.
        return false;

    }

    /**
     * Unlocks a uri
     *
     * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header
     * The server should return 204 (No content) on success
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function httpUnlock(RequestInterface $request, ResponseInterface $response) {

        $lockToken = $request->getHeader('Lock-Token');

        // If the locktoken header is not supplied, we need to throw a bad request exception
        if (!$lockToken) throw new DAV\Exception\BadRequest('No lock token was supplied');

        $path = $request->getPath();
        $locks = $this->getLocks($path);

        // Windows sometimes forgets to include < and > in the Lock-Token
        // header
        if ($lockToken[0] !== '<') $lockToken = '<' . $lockToken . '>';

        foreach ($locks as $lock) {

            if ('<opaquelocktoken:' . $lock->token . '>' == $lockToken) {

                $this->unlockNode($path, $lock);
                $response->setHeader('Content-Length', '0');
                $response->setStatus(204);

                // Returning false will break the method chain, and mark the
                // method as 'handled'.
                return false;

            }

        }

        // If we got here, it means the locktoken was invalid
        throw new DAV\Exception\LockTokenMatchesRequestUri();

    }

    /**
     * This method is called after a node is deleted.
     *
     * We use this event to clean up any locks that still exist on the node.
     *
     * @param string $path
     * @return void
     */
    function afterUnbind($path) {

        $locks = $this->getLocks($path, $includeChildren = true);
        foreach ($locks as $lock) {
            $this->unlockNode($path, $lock);
        }

    }

    /**
     * Locks a uri
     *
     * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored
     * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client
     *
     * @param string $uri
     * @param LockInfo $lockInfo
     * @return bool
     */
    function lockNode($uri, LockInfo $lockInfo) {

        if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) return;
        return $this->locksBackend->lock($uri, $lockInfo);

    }

    /**
     * Unlocks a uri
     *
     * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified
     *
     * @param string $uri
     * @param LockInfo $lockInfo
     * @return bool
     */
    function unlockNode($uri, LockInfo $lockInfo) {

        if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) return;
        return $this->locksBackend->unlock($uri, $lockInfo);

    }


    /**
     * Returns the contents of the HTTP Timeout header.
     *
     * The method formats the header into an integer.
     *
     * @return int
     */
    function getTimeoutHeader() {

        $header = $this->server->httpRequest->getHeader('Timeout');

        if ($header) {

            if (stripos($header, 'second-') === 0) $header = (int)(substr($header, 7));
            elseif (stripos($header, 'infinite') === 0) $header = LockInfo::TIMEOUT_INFINITE;
            else throw new DAV\Exception\BadRequest('Invalid HTTP timeout header');

        } else {

            $header = 0;

        }

        return $header;

    }

    /**
     * Generates the response for successful LOCK requests
     *
     * @param LockInfo $lockInfo
     * @return string
     */
    protected function generateLockResponse(LockInfo $lockInfo) {

        return $this->server->xml->write('{DAV:}prop', [
            '{DAV:}lockdiscovery' =>
                new DAV\Xml\Property\LockDiscovery([$lockInfo])
        ]);
    }

    /**
     * The validateTokens event is triggered before every request.
     *
     * It's a moment where this plugin can check all the supplied lock tokens
     * in the If: header, and check if they are valid.
     *
     * In addition, it will also ensure that it checks any missing lokens that
     * must be present in the request, and reject requests without the proper
     * tokens.
     *
     * @param RequestInterface $request
     * @param mixed $conditions
     * @return void
     */
    function validateTokens(RequestInterface $request, &$conditions) {

        // First we need to gather a list of locks that must be satisfied.
        $mustLocks = [];
        $method = $request->getMethod();

        // Methods not in that list are operations that doesn't alter any
        // resources, and we don't need to check the lock-states for.
        switch ($method) {

            case 'DELETE' :
                $mustLocks = array_merge($mustLocks, $this->getLocks(
                    $request->getPath(),
                    true
                ));
                break;
            case 'MKCOL' :
            case 'MKCALENDAR' :
            case 'PROPPATCH' :
            case 'PUT' :
            case 'PATCH' :
                $mustLocks = array_merge($mustLocks, $this->getLocks(
                    $request->getPath(),
                    false
                ));
                break;
            case 'MOVE' :
                $mustLocks = array_merge($mustLocks, $this->getLocks(
                    $request->getPath(),
                    true
                ));
                $mustLocks = array_merge($mustLocks, $this->getLocks(
                    $this->server->calculateUri($request->getHeader('Destination')),
                    false
                ));
                break;
            case 'COPY' :
                $mustLocks = array_merge($mustLocks, $this->getLocks(
                    $this->server->calculateUri($request->getHeader('Destination')),
                    false
                ));
                break;
            case 'LOCK' :
                //Temporary measure.. figure out later why this is needed
                // Here we basically ignore all incoming tokens...
                foreach ($conditions as $ii => $condition) {
                    foreach ($condition['tokens'] as $jj => $token) {
                        $conditions[$ii]['tokens'][$jj]['validToken'] = true;
                    }
                }
                return;

        }

        // It's possible that there's identical locks, because of shared
        // parents. We're removing the duplicates here.
        $tmp = [];
        foreach ($mustLocks as $lock) $tmp[$lock->token] = $lock;
        $mustLocks = array_values($tmp);

        foreach ($conditions as $kk => $condition) {

            foreach ($condition['tokens'] as $ii => $token) {

                // Lock tokens always start with opaquelocktoken:
                if (substr($token['token'], 0, 16) !== 'opaquelocktoken:') {
                    continue;
                }

                $checkToken = substr($token['token'], 16);
                // Looping through our list with locks.
                foreach ($mustLocks as $jj => $mustLock) {

                    if ($mustLock->token == $checkToken) {

                        // We have a match!
                        // Removing this one from mustlocks
                        unset($mustLocks[$jj]);

                        // Marking the condition as valid.
                        $conditions[$kk]['tokens'][$ii]['validToken'] = true;

                        // Advancing to the next token
                        continue 2;

                    }

                }

                // If we got here, it means that there was a
                // lock-token, but it was not in 'mustLocks'.
                //
                // This is an edge-case, as it could mean that token
                // was specified with a url that was not 'required' to
                // check. So we're doing one extra lookup to make sure
                // we really don't know this token.
                //
                // This also gets triggered when the user specified a
                // lock-token that was expired.
                $oddLocks = $this->getLocks($condition['uri']);
                foreach ($oddLocks as $oddLock) {

                    if ($oddLock->token === $checkToken) {

                        // We have a hit!
                        $conditions[$kk]['tokens'][$ii]['validToken'] = true;
                        continue 2;

                    }
                }

                // If we get all the way here, the lock-token was
                // really unknown.


            }

        }

        // If there's any locks left in the 'mustLocks' array, it means that
        // the resource was locked and we must block it.
        if ($mustLocks) {

            throw new DAV\Exception\Locked(reset($mustLocks));

        }

    }

    /**
     * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object
     *
     * @param string $body
     * @return LockInfo
     */
    protected function parseLockRequest($body) {

        $result = $this->server->xml->expect(
            '{DAV:}lockinfo',
            $body
        );

        $lockInfo = new LockInfo();

        $lockInfo->owner = $result->owner;
        $lockInfo->token = DAV\UUIDUtil::getUUID();
        $lockInfo->scope = $result->scope;

        return $lockInfo;

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK',
            'link'        => 'http://sabre.io/dav/locks/',
        ];

    }

}
<?php

namespace Sabre\DAV;

/**
 * This class represents a MKCOL operation.
 *
 * MKCOL creates a new collection. MKCOL comes in two flavours:
 *
 * 1. MKCOL with no body, signifies the creation of a simple collection.
 * 2. MKCOL with a request body. This can create a collection with a specific
 *    resource type, and a set of properties that should be set on the new
 *    collection. This can be used to create caldav calendars, carddav address
 *    books, etc.
 *
 * Property updates must always be atomic. This means that a property update
 * must either completely succeed, or completely fail.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class MkCol extends PropPatch {

    /**
     * A list of resource-types in clark-notation.
     *
     * @var array
     */
    protected $resourceType;

    /**
     * Creates the MKCOL object.
     *
     * @param string[] $resourceType List of resourcetype values.
     * @param array $mutations List of new properties values.
     */
    function __construct(array $resourceType, array $mutations) {

        $this->resourceType = $resourceType;
        parent::__construct($mutations);

    }

    /**
     * Returns the resourcetype of the new collection.
     *
     * @return string[]
     */
    function getResourceType() {

        return $this->resourceType;

    }

    /**
     * Returns true or false if the MKCOL operation has at least the specified
     * resource type.
     *
     * If the resourcetype is specified as an array, all resourcetypes are
     * checked.
     *
     * @param string|string[] $resourceType
     * @return bool
     */
    function hasResourceType($resourceType) {

        return count(array_diff((array)$resourceType, $this->resourceType)) === 0;

    }

}
<?php

namespace Sabre\DAV\Mount;

use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * This plugin provides support for RFC4709: Mounting WebDAV servers
 *
 * Simply append ?mount to any collection to generate the davmount response.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends DAV\ServerPlugin {

    /**
     * Reference to Server class
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * Initializes the plugin and registers event handles
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;
        $this->server->on('method:GET', [$this, 'httpGet'], 90);

    }

    /**
     * 'beforeMethod' event handles. This event handles intercepts GET requests ending
     * with ?mount
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpGet(RequestInterface $request, ResponseInterface $response) {

        $queryParams = $request->getQueryParameters();
        if (!array_key_exists('mount', $queryParams)) return;

        $currentUri = $request->getAbsoluteUrl();

        // Stripping off everything after the ?
        list($currentUri) = explode('?', $currentUri);

        $this->davMount($response, $currentUri);

        // Returning false to break the event chain
        return false;

    }

    /**
     * Generates the davmount response
     *
     * @param ResponseInterface $response
     * @param string $uri absolute uri
     * @return void
     */
    function davMount(ResponseInterface $response, $uri) {

        $response->setStatus(200);
        $response->setHeader('Content-Type', 'application/davmount+xml');
        ob_start();
        echo '<?xml version="1.0"?>', "\n";
        echo "<dm:mount xmlns:dm=\"http://purl.org/NET/webdav/mount\">\n";
        echo "  <dm:url>", htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "</dm:url>\n";
        echo "</dm:mount>";
        $response->setBody(ob_get_clean());

    }


}
<?php

namespace Sabre\DAV;

/**
 * Node class
 *
 * This is a helper class, that should aid in getting nodes setup.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class Node implements INode {

    /**
     * Returns the last modification time as a unix timestamp.
     *
     * If the information is not available, return null.
     *
     * @return int
     */
    function getLastModified() {

        return null;

    }

    /**
     * Deletes the current node
     *
     * @throws Exception\Forbidden
     * @return void
     */
    function delete() {

        throw new Exception\Forbidden('Permission denied to delete node');

    }

    /**
     * Renames the node
     *
     * @param string $name The new name
     * @throws Exception\Forbidden
     * @return void
     */
    function setName($name) {

        throw new Exception\Forbidden('Permission denied to rename file');

    }

}
<?php

namespace Sabre\DAV\PartialUpdate;

use Sabre\DAV;

/**
 * This interface provides a way to modify only part of a target resource
 * It may be used to update a file chunk, upload big a file into smaller
 * chunks or resume an upload
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IPatchSupport extends DAV\IFile {

    /**
     * Updates the file based on a range specification.
     *
     * The first argument is the data, which is either a readable stream
     * resource or a string.
     *
     * The second argument is the type of update we're doing.
     * This is either:
     * * 1. append
     * * 2. update based on a start byte
     * * 3. update based on an end byte
     *;
     * The third argument is the start or end byte.
     *
     * After a successful put operation, you may choose to return an ETag. The
     * etag must always be surrounded by double-quotes. These quotes must
     * appear in the actual string you're returning.
     *
     * Clients may use the ETag from a PUT request to later on make sure that
     * when they update the file, the contents haven't changed in the mean
     * time.
     *
     * @param resource|string $data
     * @param int $rangeType
     * @param int $offset
     * @return string|null
     */
    function patch($data, $rangeType, $offset = null);

}
<?php

namespace Sabre\DAV\PartialUpdate;

use Sabre\DAV;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * Partial update plugin (Patch method)
 *
 * This plugin provides a way to modify only part of a target resource
 * It may bu used to update a file chunk, upload big a file into smaller
 * chunks or resume an upload.
 *
 * $patchPlugin = new \Sabre\DAV\PartialUpdate\Plugin();
 * $server->addPlugin($patchPlugin);
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends DAV\ServerPlugin {

    const RANGE_APPEND = 1;
    const RANGE_START = 2;
    const RANGE_END = 3;

    /**
     * Reference to server
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * Initializes the plugin
     *
     * This method is automatically called by the Server class after addPlugin.
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;
        $server->on('method:PATCH', [$this, 'httpPatch']);

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'partialupdate';

    }

    /**
     * Use this method to tell the server this plugin defines additional
     * HTTP methods.
     *
     * This method is passed a uri. It should only return HTTP methods that are
     * available for the specified uri.
     *
     * We claim to support PATCH method (partirl update) if and only if
     *     - the node exist
     *     - the node implements our partial update interface
     *
     * @param string $uri
     * @return array
     */
    function getHTTPMethods($uri) {

        $tree = $this->server->tree;

        if ($tree->nodeExists($uri)) {
            $node = $tree->getNodeForPath($uri);
            if ($node instanceof IPatchSupport) {
                return ['PATCH'];
            }
        }
        return [];

    }

    /**
     * Returns a list of features for the HTTP OPTIONS Dav: header.
     *
     * @return array
     */
    function getFeatures() {

        return ['sabredav-partialupdate'];

    }

    /**
     * Patch an uri
     *
     * The WebDAV patch request can be used to modify only a part of an
     * existing resource. If the resource does not exist yet and the first
     * offset is not 0, the request fails
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function httpPatch(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();

        // Get the node. Will throw a 404 if not found
        $node = $this->server->tree->getNodeForPath($path);
        if (!$node instanceof IPatchSupport) {
            throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.');
        }

        $range = $this->getHTTPUpdateRange($request);

        if (!$range) {
            throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers');
        }

        $contentType = strtolower(
            $request->getHeader('Content-Type')
        );

        if ($contentType != 'application/x-sabredav-partialupdate') {
            throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"');
        }

        $len = $this->server->httpRequest->getHeader('Content-Length');
        if (!$len) throw new DAV\Exception\LengthRequired('A Content-Length header is required');

        switch ($range[0]) {
            case self::RANGE_START :
                // Calculate the end-range if it doesn't exist.
                if (!$range[2]) {
                    $range[2] = $range[1] + $len - 1;
                } else {
                    if ($range[2] < $range[1]) {
                        throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[2] . ') is lower than the start offset (' . $range[1] . ')');
                    }
                    if ($range[2] - $range[1] + 1 != $len) {
                        throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[1] . ') and end (' . $range[2] . ') offsets');
                    }
                }
                break;
        }

        if (!$this->server->emit('beforeWriteContent', [$path, $node, null]))
            return;

        $body = $this->server->httpRequest->getBody();


        $etag = $node->patch($body, $range[0], isset($range[1]) ? $range[1] : null);

        $this->server->emit('afterWriteContent', [$path, $node]);

        $response->setHeader('Content-Length', '0');
        if ($etag) $response->setHeader('ETag', $etag);
        $response->setStatus(204);

        // Breaks the event chain
        return false;

    }

    /**
     * Returns the HTTP custom range update header
     *
     * This method returns null if there is no well-formed HTTP range request
     * header. It returns array(1) if it was an append request, array(2,
     * $start, $end) if it's a start and end range, lastly it's array(3,
     * $endoffset) if the offset was negative, and should be calculated from
     * the end of the file.
     *
     * Examples:
     *
     * null - invalid
     * [1] - append
     * [2,10,15] - update bytes 10, 11, 12, 13, 14, 15
     * [2,10,null] - update bytes 10 until the end of the patch body
     * [3,-5] - update from 5 bytes from the end of the file.
     *
     * @param RequestInterface $request
     * @return array|null
     */
    function getHTTPUpdateRange(RequestInterface $request) {

        $range = $request->getHeader('X-Update-Range');
        if (is_null($range)) return null;

        // Matching "Range: bytes=1234-5678: both numbers are optional

        if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i', $range, $matches)) return null;

        if ($matches[1] === 'append') {
            return [self::RANGE_APPEND];
        } elseif (strlen($matches[2]) > 0) {
            return [self::RANGE_START, $matches[2], $matches[3] ?: null];
        } else {
            return [self::RANGE_END, $matches[4]];
        }

    }
}
<?php

namespace Sabre\DAV\PropertyStorage\Backend;

use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;

/**
 * Propertystorage backend interface.
 *
 * Propertystorage backends must implement this interface to be used by the
 * propertystorage plugin.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface BackendInterface {

    /**
     * Fetches properties for a path.
     *
     * This method received a PropFind object, which contains all the
     * information about the properties that need to be fetched.
     *
     * Usually you would just want to call 'get404Properties' on this object,
     * as this will give you the _exact_ list of properties that need to be
     * fetched, and haven't yet.
     *
     * However, you can also support the 'allprops' property here. In that
     * case, you should check for $propFind->isAllProps().
     *
     * @param string $path
     * @param PropFind $propFind
     * @return void
     */
    function propFind($path, PropFind $propFind);

    /**
     * Updates properties for a path
     *
     * This method received a PropPatch object, which contains all the
     * information about the update.
     *
     * Usually you would want to call 'handleRemaining' on this object, to get;
     * a list of all properties that need to be stored.
     *
     * @param string $path
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatch($path, PropPatch $propPatch);

    /**
     * This method is called after a node is deleted.
     *
     * This allows a backend to clean up all associated properties.
     *
     * The delete method will get called once for the deletion of an entire
     * tree.
     *
     * @param string $path
     * @return void
     */
    function delete($path);

    /**
     * This method is called after a successful MOVE
     *
     * This should be used to migrate all properties from one path to another.
     * Note that entire collections may be moved, so ensure that all properties
     * for children are also moved along.
     *
     * @param string $source
     * @param string $destination
     * @return void
     */
    function move($source, $destination);

}
<?php

namespace Sabre\DAV\PropertyStorage\Backend;

use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Xml\Property\Complex;

/**
 * PropertyStorage PDO backend.
 *
 * This backend class uses a PDO-enabled database to store webdav properties.
 * Both sqlite and mysql have been tested.
 *
 * The database structure can be found in the examples/sql/ directory.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PDO implements BackendInterface {

    /**
     * Value is stored as string.
     */
    const VT_STRING = 1;

    /**
     * Value is stored as XML fragment.
     */
    const VT_XML = 2;

    /**
     * Value is stored as a property object.
     */
    const VT_OBJECT = 3;

    /**
     * PDO
     *
     * @var \PDO
     */
    protected $pdo;

    /**
     * PDO table name we'll be using
     *
     * @var string
     */
    public $tableName = 'propertystorage';

    /**
     * Creates the PDO property storage engine
     *
     * @param \PDO $pdo
     */
    function __construct(\PDO $pdo) {

        $this->pdo = $pdo;

    }

    /**
     * Fetches properties for a path.
     *
     * This method received a PropFind object, which contains all the
     * information about the properties that need to be fetched.
     *
     * Usually you would just want to call 'get404Properties' on this object,
     * as this will give you the _exact_ list of properties that need to be
     * fetched, and haven't yet.
     *
     * However, you can also support the 'allprops' property here. In that
     * case, you should check for $propFind->isAllProps().
     *
     * @param string $path
     * @param PropFind $propFind
     * @return void
     */
    function propFind($path, PropFind $propFind) {

        if (!$propFind->isAllProps() && count($propFind->get404Properties()) === 0) {
            return;
        }

        $query = 'SELECT name, value, valuetype FROM ' . $this->tableName . ' WHERE path = ?';
        $stmt = $this->pdo->prepare($query);
        $stmt->execute([$path]);

        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            if (gettype($row['value']) === 'resource') {
                $row['value'] = stream_get_contents($row['value']);
            }
            switch ($row['valuetype']) {
                case null :
                case self::VT_STRING :
                    $propFind->set($row['name'], $row['value']);
                    break;
                case self::VT_XML :
                    $propFind->set($row['name'], new Complex($row['value']));
                    break;
                case self::VT_OBJECT :
                    $propFind->set($row['name'], unserialize($row['value']));
                    break;
            }
        }

    }

    /**
     * Updates properties for a path
     *
     * This method received a PropPatch object, which contains all the
     * information about the update.
     *
     * Usually you would want to call 'handleRemaining' on this object, to get;
     * a list of all properties that need to be stored.
     *
     * @param string $path
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatch($path, PropPatch $propPatch) {

        $propPatch->handleRemaining(function($properties) use ($path) {


            if ($this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'pgsql') {

                $updateSql = <<<SQL
INSERT INTO {$this->tableName} (path, name, valuetype, value)
VALUES (:path, :name, :valuetype, :value)
ON CONFLICT (path, name)
DO UPDATE SET valuetype = :valuetype, value = :value
SQL;


            } else {
                $updateSql = <<<SQL
REPLACE INTO {$this->tableName} (path, name, valuetype, value)
VALUES (:path, :name, :valuetype, :value)
SQL;

            }

            $updateStmt = $this->pdo->prepare($updateSql);
            $deleteStmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? AND name = ?");

            foreach ($properties as $name => $value) {

                if (!is_null($value)) {
                    if (is_scalar($value)) {
                        $valueType = self::VT_STRING;
                    } elseif ($value instanceof Complex) {
                        $valueType = self::VT_XML;
                        $value = $value->getXml();
                    } else {
                        $valueType = self::VT_OBJECT;
                        $value = serialize($value);
                    }

                    $updateStmt->bindParam('path', $path, \PDO::PARAM_STR);
                    $updateStmt->bindParam('name', $name, \PDO::PARAM_STR);
                    $updateStmt->bindParam('valuetype', $valueType, \PDO::PARAM_INT);
                    $updateStmt->bindParam('value', $value, \PDO::PARAM_LOB);

                    $updateStmt->execute();

                } else {
                    $deleteStmt->execute([$path, $name]);
                }

            }

            return true;

        });

    }

    /**
     * This method is called after a node is deleted.
     *
     * This allows a backend to clean up all associated properties.
     *
     * The delete method will get called once for the deletion of an entire
     * tree.
     *
     * @param string $path
     * @return void
     */
    function delete($path) {

        $stmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . "  WHERE path = ? OR path LIKE ? ESCAPE '='");
        $childPath = strtr(
            $path,
            [
                '=' => '==',
                '%' => '=%',
                '_' => '=_'
            ]
        ) . '/%';

        $stmt->execute([$path, $childPath]);

    }

    /**
     * This method is called after a successful MOVE
     *
     * This should be used to migrate all properties from one path to another.
     * Note that entire collections may be moved, so ensure that all properties
     * for children are also moved along.
     *
     * @param string $source
     * @param string $destination
     * @return void
     */
    function move($source, $destination) {

        // I don't know a way to write this all in a single sql query that's
        // also compatible across db engines, so we're letting PHP do all the
        // updates. Much slower, but it should still be pretty fast in most
        // cases.
        $select = $this->pdo->prepare('SELECT id, path FROM ' . $this->tableName . '  WHERE path = ? OR path LIKE ?');
        $select->execute([$source, $source . '/%']);

        $update = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET path = ? WHERE id = ?');
        while ($row = $select->fetch(\PDO::FETCH_ASSOC)) {

            // Sanity check. SQL may select too many records, such as records
            // with different cases.
            if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) continue;

            $trailingPart = substr($row['path'], strlen($source) + 1);
            $newPath = $destination;
            if ($trailingPart) {
                $newPath .= '/' . $trailingPart;
            }
            $update->execute([$newPath, $row['id']]);

        }

    }

}
<?php

namespace Sabre\DAV\PropertyStorage;

use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;

/**
 * PropertyStorage Plugin.
 *
 * Adding this plugin to your server allows clients to store any arbitrary
 * WebDAV property.
 *
 * See:
 *   http://sabre.io/dav/property-storage/
 *
 * for more information.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends ServerPlugin {

    /**
     * If you only want this plugin to store properties for a limited set of
     * paths, you can use a pathFilter to do this.
     *
     * The pathFilter should be a callable. The callable retrieves a path as
     * its argument, and should return true or false whether it allows
     * properties to be stored.
     *
     * @var callable
     */
    public $pathFilter;

    /**
     * @var Backend\BackendInterface
     */
    public $backend;

    /**
     * Creates the plugin
     *
     * @param Backend\BackendInterface $backend
     */
    function __construct(Backend\BackendInterface $backend) {

        $this->backend = $backend;

    }

    /**
     * This initializes the plugin.
     *
     * This function is called by Sabre\DAV\Server, after
     * addPlugin is called.
     *
     * This method should set up the required event subscriptions.
     *
     * @param Server $server
     * @return void
     */
    function initialize(Server $server) {

        $server->on('propFind',    [$this, 'propFind'], 130);
        $server->on('propPatch',   [$this, 'propPatch'], 300);
        $server->on('afterMove',   [$this, 'afterMove']);
        $server->on('afterUnbind', [$this, 'afterUnbind']);

    }

    /**
     * Called during PROPFIND operations.
     *
     * If there's any requested properties that don't have a value yet, this
     * plugin will look in the property storage backend to find them.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return void
     */
    function propFind(PropFind $propFind, INode $node) {

        $path = $propFind->getPath();
        $pathFilter = $this->pathFilter;
        if ($pathFilter && !$pathFilter($path)) return;
        $this->backend->propFind($propFind->getPath(), $propFind);

    }

    /**
     * Called during PROPPATCH operations
     *
     * If there's any updated properties that haven't been stored, the
     * propertystorage backend can handle it.
     *
     * @param string $path
     * @param PropPatch $propPatch
     * @return void
     */
    function propPatch($path, PropPatch $propPatch) {

        $pathFilter = $this->pathFilter;
        if ($pathFilter && !$pathFilter($path)) return;
        $this->backend->propPatch($path, $propPatch);

    }

    /**
     * Called after a node is deleted.
     *
     * This allows the backend to clean up any properties still in the
     * database.
     *
     * @param string $path
     * @return void
     */
    function afterUnbind($path) {

        $pathFilter = $this->pathFilter;
        if ($pathFilter && !$pathFilter($path)) return;
        $this->backend->delete($path);

    }

    /**
     * Called after a node is moved.
     *
     * This allows the backend to move all the associated properties.
     *
     * @param string $source
     * @param string $destination
     * @return void
     */
    function afterMove($source, $destination) {

        $pathFilter = $this->pathFilter;
        if ($pathFilter && !$pathFilter($source)) return;
        // If the destination is filtered, afterUnbind will handle cleaning up
        // the properties.
        if ($pathFilter && !$pathFilter($destination)) return;

        $this->backend->move($source, $destination);

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'property-storage';

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'This plugin allows any arbitrary WebDAV property to be set on any resource.',
            'link'        => 'http://sabre.io/dav/property-storage/',
        ];

    }
}
<?php

namespace Sabre\DAV;

/**
 * This class holds all the information about a PROPFIND request.
 *
 * It contains the type of PROPFIND request, which properties were requested
 * and also the returned items.
 */
class PropFind {

    /**
     * A normal propfind
     */
    const NORMAL = 0;

    /**
     * An allprops request.
     *
     * While this was originally intended for instructing the server to really
     * fetch every property, because it was used so often and it's so heavy
     * this turned into a small list of default properties after a while.
     *
     * So 'all properties' now means a hardcoded list.
     */
    const ALLPROPS = 1;

    /**
     * A propname request. This just returns a list of properties that are
     * defined on a node, without their values.
     */
    const PROPNAME = 2;

    /**
     * Creates the PROPFIND object
     *
     * @param string $path
     * @param array $properties
     * @param int $depth
     * @param int $requestType
     */
    function __construct($path, array $properties, $depth = 0, $requestType = self::NORMAL) {

        $this->path = $path;
        $this->properties = $properties;
        $this->depth = $depth;
        $this->requestType = $requestType;

        if ($requestType === self::ALLPROPS) {
            $this->properties = [
                '{DAV:}getlastmodified',
                '{DAV:}getcontentlength',
                '{DAV:}resourcetype',
                '{DAV:}quota-used-bytes',
                '{DAV:}quota-available-bytes',
                '{DAV:}getetag',
                '{DAV:}getcontenttype',
            ];
        }

        foreach ($this->properties as $propertyName) {

            // Seeding properties with 404's.
            $this->result[$propertyName] = [404, null];

        }
        $this->itemsLeft = count($this->result);

    }

    /**
     * Handles a specific property.
     *
     * This method checks whether the specified property was requested in this
     * PROPFIND request, and if so, it will call the callback and use the
     * return value for it's value.
     *
     * Example:
     *
     * $propFind->handle('{DAV:}displayname', function() {
     *      return 'hello';
     * });
     *
     * Note that handle will only work the first time. If null is returned, the
     * value is ignored.
     *
     * It's also possible to not pass a callback, but immediately pass a value
     *
     * @param string $propertyName
     * @param mixed $valueOrCallBack
     * @return void
     */
    function handle($propertyName, $valueOrCallBack) {

        if ($this->itemsLeft && isset($this->result[$propertyName]) && $this->result[$propertyName][0] === 404) {
            if (is_callable($valueOrCallBack)) {
                $value = $valueOrCallBack();
            } else {
                $value = $valueOrCallBack;
            }
            if (!is_null($value)) {
                $this->itemsLeft--;
                $this->result[$propertyName] = [200, $value];
            }
        }

    }

    /**
     * Sets the value of the property
     *
     * If status is not supplied, the status will default to 200 for non-null
     * properties, and 404 for null properties.
     *
     * @param string $propertyName
     * @param mixed $value
     * @param int $status
     * @return void
     */
    function set($propertyName, $value, $status = null) {

        if (is_null($status)) {
            $status = is_null($value) ? 404 : 200;
        }
        // If this is an ALLPROPS request and the property is
        // unknown, add it to the result; else ignore it:
        if (!isset($this->result[$propertyName])) {
            if ($this->requestType === self::ALLPROPS) {
                $this->result[$propertyName] = [$status, $value];
            }
            return;
        }
        if ($status !== 404 && $this->result[$propertyName][0] === 404) {
            $this->itemsLeft--;
        } elseif ($status === 404 && $this->result[$propertyName][0] !== 404) {
            $this->itemsLeft++;
        }
        $this->result[$propertyName] = [$status, $value];

    }

    /**
     * Returns the current value for a property.
     *
     * @param string $propertyName
     * @return mixed
     */
    function get($propertyName) {

        return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null;

    }

    /**
     * Returns the current status code for a property name.
     *
     * If the property does not appear in the list of requested properties,
     * null will be returned.
     *
     * @param string $propertyName
     * @return int|null
     */
    function getStatus($propertyName) {

        return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null;

    }

    /**
     * Updates the path for this PROPFIND.
     *
     * @param string $path
     * @return void
     */
    function setPath($path) {

        $this->path = $path;

    }

    /**
     * Returns the path this PROPFIND request is for.
     *
     * @return string
     */
    function getPath() {

        return $this->path;

    }

    /**
     * Returns the depth of this propfind request.
     *
     * @return int
     */
    function getDepth() {

        return $this->depth;

    }

    /**
     * Updates the depth of this propfind request.
     *
     * @param int $depth
     * @return void
     */
    function setDepth($depth) {

        $this->depth = $depth;

    }

    /**
     * Returns all propertynames that have a 404 status, and thus don't have a
     * value yet.
     *
     * @return array
     */
    function get404Properties() {

        if ($this->itemsLeft === 0) {
            return [];
        }
        $result = [];
        foreach ($this->result as $propertyName => $stuff) {
            if ($stuff[0] === 404) {
                $result[] = $propertyName;
            }
        }
        return $result;

    }

    /**
     * Returns the full list of requested properties.
     *
     * This returns just their names, not a status or value.
     *
     * @return array
     */
    function getRequestedProperties() {

        return $this->properties;

    }

    /**
     * Returns true if this was an '{DAV:}allprops' request.
     *
     * @return bool
     */
    function isAllProps() {

        return $this->requestType === self::ALLPROPS;

    }

    /**
     * Returns a result array that's often used in multistatus responses.
     *
     * The array uses status codes as keys, and property names and value pairs
     * as the value of the top array.. such as :
     *
     * [
     *  200 => [ '{DAV:}displayname' => 'foo' ],
     * ]
     *
     * @return array
     */
    function getResultForMultiStatus() {

        $r = [
            200 => [],
            404 => [],
        ];
        foreach ($this->result as $propertyName => $info) {
            if (!isset($r[$info[0]])) {
                $r[$info[0]] = [$propertyName => $info[1]];
            } else {
                $r[$info[0]][$propertyName] = $info[1];
            }
        }
        // Removing the 404's for multi-status requests.
        if ($this->requestType === self::ALLPROPS) unset($r[404]);
        return $r;

    }

    /**
     * The path that we're fetching properties for.
     *
     * @var string
     */
    protected $path;

    /**
     * The Depth of the request.
     *
     * 0 means only the current item. 1 means the current item + its children.
     * It can also be DEPTH_INFINITY if this is enabled in the server.
     *
     * @var int
     */
    protected $depth = 0;

    /**
     * The type of request. See the TYPE constants
     */
    protected $requestType;

    /**
     * A list of requested properties
     *
     * @var array
     */
    protected $properties = [];

    /**
     * The result of the operation.
     *
     * The keys in this array are property names.
     * The values are an array with two elements: the http status code and then
     * optionally a value.
     *
     * Example:
     *
     * [
     *    "{DAV:}owner" : [404],
     *    "{DAV:}displayname" : [200, "Admin"]
     * ]
     *
     * @var array
     */
    protected $result = [];

    /**
     * This is used as an internal counter for the number of properties that do
     * not yet have a value.
     *
     * @var int
     */
    protected $itemsLeft;

}
<?php

namespace Sabre\DAV;

use UnexpectedValueException;

/**
 * This class represents a set of properties that are going to be updated.
 *
 * Usually this is simply a PROPPATCH request, but it can also be used for
 * internal updates.
 *
 * Property updates must always be atomic. This means that a property update
 * must either completely succeed, or completely fail.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PropPatch {

    /**
     * Properties that are being updated.
     *
     * This is a key-value list. If the value is null, the property is supposed
     * to be deleted.
     *
     * @var array
     */
    protected $mutations;

    /**
     * A list of properties and the result of the update. The result is in the
     * form of a HTTP status code.
     *
     * @var array
     */
    protected $result = [];

    /**
     * This is the list of callbacks when we're performing the actual update.
     *
     * @var array
     */
    protected $propertyUpdateCallbacks = [];

    /**
     * This property will be set to true if the operation failed.
     *
     * @var bool
     */
    protected $failed = false;

    /**
     * Constructor
     *
     * @param array $mutations A list of updates
     */
    function __construct(array $mutations) {

        $this->mutations = $mutations;

    }

    /**
     * Call this function if you wish to handle updating certain properties.
     * For instance, your class may be responsible for handling updates for the
     * {DAV:}displayname property.
     *
     * In that case, call this method with the first argument
     * "{DAV:}displayname" and a second argument that's a method that does the
     * actual updating.
     *
     * It's possible to specify more than one property as an array.
     *
     * The callback must return a boolean or an it. If the result is true, the
     * operation was considered successful. If it's false, it's consided
     * failed.
     *
     * If the result is an integer, we'll use that integer as the http status
     * code associated with the operation.
     *
     * @param string|string[] $properties
     * @param callable $callback
     * @return void
     */
    function handle($properties, callable $callback) {

        $usedProperties = [];
        foreach ((array)$properties as $propertyName) {

            if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) {

                $usedProperties[] = $propertyName;
                // HTTP Accepted
                $this->result[$propertyName] = 202;
            }

        }

        // Only registering if there's any unhandled properties.
        if (!$usedProperties) {
            return;
        }
        $this->propertyUpdateCallbacks[] = [
            // If the original argument to this method was a string, we need
            // to also make sure that it stays that way, so the commit function
            // knows how to format the arguments to the callback.
            is_string($properties) ? $properties : $usedProperties,
            $callback
        ];

    }

    /**
     * Call this function if you wish to handle _all_ properties that haven't
     * been handled by anything else yet. Note that you effectively claim with
     * this that you promise to process _all_ properties that are coming in.
     *
     * @param callable $callback
     * @return void
     */
    function handleRemaining(callable $callback) {

        $properties = $this->getRemainingMutations();
        if (!$properties) {
            // Nothing to do, don't register callback
            return;
        }

        foreach ($properties as $propertyName) {
            // HTTP Accepted
            $this->result[$propertyName] = 202;

            $this->propertyUpdateCallbacks[] = [
                $properties,
                $callback
            ];
        }

    }

    /**
     * Sets the result code for one or more properties.
     *
     * @param string|string[] $properties
     * @param int $resultCode
     * @return void
     */
    function setResultCode($properties, $resultCode) {

        foreach ((array)$properties as $propertyName) {
            $this->result[$propertyName] = $resultCode;
        }

        if ($resultCode >= 400) {
            $this->failed = true;
        }

    }

    /**
     * Sets the result code for all properties that did not have a result yet.
     *
     * @param int $resultCode
     * @return void
     */
    function setRemainingResultCode($resultCode) {

        $this->setResultCode(
            $this->getRemainingMutations(),
            $resultCode
        );

    }

    /**
     * Returns the list of properties that don't have a result code yet.
     *
     * This method returns a list of property names, but not its values.
     *
     * @return string[]
     */
    function getRemainingMutations() {

        $remaining = [];
        foreach ($this->mutations as $propertyName => $propValue) {
            if (!isset($this->result[$propertyName])) {
                $remaining[] = $propertyName;
            }
        }

        return $remaining;

    }

    /**
     * Returns the list of properties that don't have a result code yet.
     *
     * This method returns list of properties and their values.
     *
     * @return array
     */
    function getRemainingValues() {

        $remaining = [];
        foreach ($this->mutations as $propertyName => $propValue) {
            if (!isset($this->result[$propertyName])) {
                $remaining[$propertyName] = $propValue;
            }
        }

        return $remaining;

    }

    /**
     * Performs the actual update, and calls all callbacks.
     *
     * This method returns true or false depending on if the operation was
     * successful.
     *
     * @return bool
     */
    function commit() {

        // First we validate if every property has a handler
        foreach ($this->mutations as $propertyName => $value) {

            if (!isset($this->result[$propertyName])) {
                $this->failed = true;
                $this->result[$propertyName] = 403;
            }

        }

        foreach ($this->propertyUpdateCallbacks as $callbackInfo) {

            if ($this->failed) {
                break;
            }
            if (is_string($callbackInfo[0])) {
                $this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]);
            } else {
                $this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]);
            }

        }

        /**
         * If anywhere in this operation updating a property failed, we must
         * update all other properties accordingly.
         */
        if ($this->failed) {

            foreach ($this->result as $propertyName => $status) {
                if ($status === 202) {
                    // Failed dependency
                    $this->result[$propertyName] = 424;
                }
            }

        }

        return !$this->failed;

    }

    /**
     * Executes a property callback with the single-property syntax.
     *
     * @param string $propertyName
     * @param callable $callback
     * @return void
     */
    private function doCallBackSingleProp($propertyName, callable $callback) {

        $result = $callback($this->mutations[$propertyName]);
        if (is_bool($result)) {
            if ($result) {
                if (is_null($this->mutations[$propertyName])) {
                    // Delete
                    $result = 204;
                } else {
                    // Update
                    $result = 200;
                }
            } else {
                // Fail
                $result = 403;
            }
        }
        if (!is_int($result)) {
            throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool');
        }
        $this->result[$propertyName] = $result;
        if ($result >= 400) {
            $this->failed = true;
        }

    }

    /**
     * Executes a property callback with the multi-property syntax.
     *
     * @param array $propertyList
     * @param callable $callback
     * @return void
     */
    private function doCallBackMultiProp(array $propertyList, callable $callback) {

        $argument = [];
        foreach ($propertyList as $propertyName) {
            $argument[$propertyName] = $this->mutations[$propertyName];
        }

        $result = $callback($argument);

        if (is_array($result)) {
            foreach ($propertyList as $propertyName) {
                if (!isset($result[$propertyName])) {
                    $resultCode = 500;
                } else {
                    $resultCode = $result[$propertyName];
                }
                if ($resultCode >= 400) {
                    $this->failed = true;
                }
                $this->result[$propertyName] = $resultCode;

            }
        } elseif ($result === true) {

            // Success
            foreach ($argument as $propertyName => $propertyValue) {
                $this->result[$propertyName] = is_null($propertyValue) ? 204 : 200;
            }

        } elseif ($result === false) {
            // Fail :(
            $this->failed = true;
            foreach ($propertyList as $propertyName) {
                $this->result[$propertyName] = 403;
            }
        } else {
            throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool');
        }

    }

    /**
     * Returns the result of the operation.
     *
     * @return array
     */
    function getResult() {

        return $this->result;

    }

    /**
     * Returns the full list of mutations
     *
     * @return array
     */
    function getMutations() {

        return $this->mutations;

    }

}
<?php

namespace Sabre\DAV;

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Sabre\Event\EventEmitter;
use Sabre\HTTP;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\HTTP\URLUtil;
use Sabre\Uri;

/**
 * Main DAV server class
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Server extends EventEmitter implements LoggerAwareInterface {

    use LoggerAwareTrait;

    /**
     * Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree
     */
    const DEPTH_INFINITY = -1;

    /**
     * XML namespace for all SabreDAV related elements
     */
    const NS_SABREDAV = 'http://sabredav.org/ns';

    /**
     * The tree object
     *
     * @var Tree
     */
    public $tree;

    /**
     * The base uri
     *
     * @var string
     */
    protected $baseUri = null;

    /**
     * httpResponse
     *
     * @var HTTP\Response
     */
    public $httpResponse;

    /**
     * httpRequest
     *
     * @var HTTP\Request
     */
    public $httpRequest;

    /**
     * PHP HTTP Sapi
     *
     * @var HTTP\Sapi
     */
    public $sapi;

    /**
     * The list of plugins
     *
     * @var array
     */
    protected $plugins = [];

    /**
     * This property will be filled with a unique string that describes the
     * transaction. This is useful for performance measuring and logging
     * purposes.
     *
     * By default it will just fill it with a lowercased HTTP method name, but
     * plugins override this. For example, the WebDAV-Sync sync-collection
     * report will set this to 'report-sync-collection'.
     *
     * @var string
     */
    public $transactionType;

    /**
     * This is a list of properties that are always server-controlled, and
     * must not get modified with PROPPATCH.
     *
     * Plugins may add to this list.
     *
     * @var string[]
     */
    public $protectedProperties = [

        // RFC4918
        '{DAV:}getcontentlength',
        '{DAV:}getetag',
        '{DAV:}getlastmodified',
        '{DAV:}lockdiscovery',
        '{DAV:}supportedlock',

        // RFC4331
        '{DAV:}quota-available-bytes',
        '{DAV:}quota-used-bytes',

        // RFC3744
        '{DAV:}supported-privilege-set',
        '{DAV:}current-user-privilege-set',
        '{DAV:}acl',
        '{DAV:}acl-restrictions',
        '{DAV:}inherited-acl-set',

        // RFC3253
        '{DAV:}supported-method-set',
        '{DAV:}supported-report-set',

        // RFC6578
        '{DAV:}sync-token',

        // calendarserver.org extensions
        '{http://calendarserver.org/ns/}ctag',

        // sabredav extensions
        '{http://sabredav.org/ns}sync-token',

    ];

    /**
     * This is a flag that allow or not showing file, line and code
     * of the exception in the returned XML
     *
     * @var bool
     */
    public $debugExceptions = false;

    /**
     * This property allows you to automatically add the 'resourcetype' value
     * based on a node's classname or interface.
     *
     * The preset ensures that {DAV:}collection is automatically added for nodes
     * implementing Sabre\DAV\ICollection.
     *
     * @var array
     */
    public $resourceTypeMapping = [
        'Sabre\\DAV\\ICollection' => '{DAV:}collection',
    ];

    /**
     * This property allows the usage of Depth: infinity on PROPFIND requests.
     *
     * By default Depth: infinity is treated as Depth: 1. Allowing Depth:
     * infinity is potentially risky, as it allows a single client to do a full
     * index of the webdav server, which is an easy DoS attack vector.
     *
     * Only turn this on if you know what you're doing.
     *
     * @var bool
     */
    public $enablePropfindDepthInfinity = false;

    /**
     * Reference to the XML utility object.
     *
     * @var Xml\Service
     */
    public $xml;

    /**
     * If this setting is turned off, SabreDAV's version number will be hidden
     * from various places.
     *
     * Some people feel this is a good security measure.
     *
     * @var bool
     */
    static $exposeVersion = true;

    /**
     * Sets up the server
     *
     * If a Sabre\DAV\Tree object is passed as an argument, it will
     * use it as the directory tree. If a Sabre\DAV\INode is passed, it
     * will create a Sabre\DAV\Tree and use the node as the root.
     *
     * If nothing is passed, a Sabre\DAV\SimpleCollection is created in
     * a Sabre\DAV\Tree.
     *
     * If an array is passed, we automatically create a root node, and use
     * the nodes in the array as top-level children.
     *
     * @param Tree|INode|array|null $treeOrNode The tree object
     */
    function __construct($treeOrNode = null) {

        if ($treeOrNode instanceof Tree) {
            $this->tree = $treeOrNode;
        } elseif ($treeOrNode instanceof INode) {
            $this->tree = new Tree($treeOrNode);
        } elseif (is_array($treeOrNode)) {

            // If it's an array, a list of nodes was passed, and we need to
            // create the root node.
            foreach ($treeOrNode as $node) {
                if (!($node instanceof INode)) {
                    throw new Exception('Invalid argument passed to constructor. If you\'re passing an array, all the values must implement Sabre\\DAV\\INode');
                }
            }

            $root = new SimpleCollection('root', $treeOrNode);
            $this->tree = new Tree($root);

        } elseif (is_null($treeOrNode)) {
            $root = new SimpleCollection('root');
            $this->tree = new Tree($root);
        } else {
            throw new Exception('Invalid argument passed to constructor. Argument must either be an instance of Sabre\\DAV\\Tree, Sabre\\DAV\\INode, an array or null');
        }

        $this->xml = new Xml\Service();
        $this->sapi = new HTTP\Sapi();
        $this->httpResponse = new HTTP\Response();
        $this->httpRequest = $this->sapi->getRequest();
        $this->addPlugin(new CorePlugin());

    }

    /**
     * Starts the DAV Server
     *
     * @return void
     */
    function exec() {

        try {

            // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an
            // origin, we must make sure we send back HTTP/1.0 if this was
            // requested.
            // This is mainly because nginx doesn't support Chunked Transfer
            // Encoding, and this forces the webserver SabreDAV is running on,
            // to buffer entire responses to calculate Content-Length.
            $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion());

            // Setting the base url
            $this->httpRequest->setBaseUrl($this->getBaseUri());
            $this->invokeMethod($this->httpRequest, $this->httpResponse);

        } catch (\Exception $e) {

            try {
                $this->emit('exception', [$e]);
            } catch (\Exception $ignore) {
            }
            $DOM = new \DOMDocument('1.0', 'utf-8');
            $DOM->formatOutput = true;

            $error = $DOM->createElementNS('DAV:', 'd:error');
            $error->setAttribute('xmlns:s', self::NS_SABREDAV);
            $DOM->appendChild($error);

            $h = function($v) {

                return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8');

            };

            if (self::$exposeVersion) {
                $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION)));
            }

            $error->appendChild($DOM->createElement('s:exception', $h(get_class($e))));
            $error->appendChild($DOM->createElement('s:message', $h($e->getMessage())));
            if ($this->debugExceptions) {
                $error->appendChild($DOM->createElement('s:file', $h($e->getFile())));
                $error->appendChild($DOM->createElement('s:line', $h($e->getLine())));
                $error->appendChild($DOM->createElement('s:code', $h($e->getCode())));
                $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString())));
            }

            if ($this->debugExceptions) {
                $previous = $e;
                while ($previous = $previous->getPrevious()) {
                    $xPrevious = $DOM->createElement('s:previous-exception');
                    $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous))));
                    $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage())));
                    $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile())));
                    $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine())));
                    $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode())));
                    $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString())));
                    $error->appendChild($xPrevious);
                }
            }


            if ($e instanceof Exception) {

                $httpCode = $e->getHTTPCode();
                $e->serialize($this, $error);
                $headers = $e->getHTTPHeaders($this);

            } else {

                $httpCode = 500;
                $headers = [];

            }
            $headers['Content-Type'] = 'application/xml; charset=utf-8';

            $this->httpResponse->setStatus($httpCode);
            $this->httpResponse->setHeaders($headers);
            $this->httpResponse->setBody($DOM->saveXML());
            $this->sapi->sendResponse($this->httpResponse);

        }

    }

    /**
     * Sets the base server uri
     *
     * @param string $uri
     * @return void
     */
    function setBaseUri($uri) {

        // If the baseUri does not end with a slash, we must add it
        if ($uri[strlen($uri) - 1] !== '/')
            $uri .= '/';

        $this->baseUri = $uri;

    }

    /**
     * Returns the base responding uri
     *
     * @return string
     */
    function getBaseUri() {

        if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri();
        return $this->baseUri;

    }

    /**
     * This method attempts to detect the base uri.
     * Only the PATH_INFO variable is considered.
     *
     * If this variable is not set, the root (/) is assumed.
     *
     * @return string
     */
    function guessBaseUri() {

        $pathInfo = $this->httpRequest->getRawServerValue('PATH_INFO');
        $uri = $this->httpRequest->getRawServerValue('REQUEST_URI');

        // If PATH_INFO is found, we can assume it's accurate.
        if (!empty($pathInfo)) {

            // We need to make sure we ignore the QUERY_STRING part
            if ($pos = strpos($uri, '?'))
                $uri = substr($uri, 0, $pos);

            // PATH_INFO is only set for urls, such as: /example.php/path
            // in that case PATH_INFO contains '/path'.
            // Note that REQUEST_URI is percent encoded, while PATH_INFO is
            // not, Therefore they are only comparable if we first decode
            // REQUEST_INFO as well.
            $decodedUri = URLUtil::decodePath($uri);

            // A simple sanity check:
            if (substr($decodedUri, strlen($decodedUri) - strlen($pathInfo)) === $pathInfo) {
                $baseUri = substr($decodedUri, 0, strlen($decodedUri) - strlen($pathInfo));
                return rtrim($baseUri, '/') . '/';
            }

            throw new Exception('The REQUEST_URI (' . $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.');

        }

        // The last fallback is that we're just going to assume the server root.
        return '/';

    }

    /**
     * Adds a plugin to the server
     *
     * For more information, console the documentation of Sabre\DAV\ServerPlugin
     *
     * @param ServerPlugin $plugin
     * @return void
     */
    function addPlugin(ServerPlugin $plugin) {

        $this->plugins[$plugin->getPluginName()] = $plugin;
        $plugin->initialize($this);

    }

    /**
     * Returns an initialized plugin by it's name.
     *
     * This function returns null if the plugin was not found.
     *
     * @param string $name
     * @return ServerPlugin
     */
    function getPlugin($name) {

        if (isset($this->plugins[$name]))
            return $this->plugins[$name];

        return null;

    }

    /**
     * Returns all plugins
     *
     * @return array
     */
    function getPlugins() {

        return $this->plugins;

    }

    /**
     * Returns the PSR-3 logger object.
     *
     * @return LoggerInterface
     */
    function getLogger() {

        if (!$this->logger) {
            $this->logger = new NullLogger();
        }
        return $this->logger;

    }

    /**
     * Handles a http request, and execute a method based on its name
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @param bool $sendResponse Whether to send the HTTP response to the DAV client.
     * @return void
     */
    function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true) {

        $method = $request->getMethod();

        if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return;
        if (!$this->emit('beforeMethod', [$request, $response])) return;

        if (self::$exposeVersion) {
            $response->setHeader('X-Sabre-Version', Version::VERSION);
        }

        $this->transactionType = strtolower($method);

        if (!$this->checkPreconditions($request, $response)) {
            $this->sapi->sendResponse($response);
            return;
        }

        if ($this->emit('method:' . $method, [$request, $response])) {
            if ($this->emit('method', [$request, $response])) {
                $exMessage = "There was no plugin in the system that was willing to handle this " . $method . " method.";
                if ($method === "GET") {
                    $exMessage .= " Enable the Browser plugin to get a better result here.";
                }

                // Unsupported method
                throw new Exception\NotImplemented($exMessage);
            }
        }

        if (!$this->emit('afterMethod:' . $method, [$request, $response])) return;
        if (!$this->emit('afterMethod', [$request, $response])) return;

        if ($response->getStatus() === null) {
            throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.');
        }
        if ($sendResponse) {
            $this->sapi->sendResponse($response);
            $this->emit('afterResponse', [$request, $response]);
        }

    }

    // {{{ HTTP/WebDAV protocol helpers

    /**
     * Returns an array with all the supported HTTP methods for a specific uri.
     *
     * @param string $path
     * @return array
     */
    function getAllowedMethods($path) {

        $methods = [
            'OPTIONS',
            'GET',
            'HEAD',
            'DELETE',
            'PROPFIND',
            'PUT',
            'PROPPATCH',
            'COPY',
            'MOVE',
            'REPORT'
        ];

        // The MKCOL is only allowed on an unmapped uri
        try {
            $this->tree->getNodeForPath($path);
        } catch (Exception\NotFound $e) {
            $methods[] = 'MKCOL';
        }

        // We're also checking if any of the plugins register any new methods
        foreach ($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($path));
        array_unique($methods);

        return $methods;

    }

    /**
     * Gets the uri for the request, keeping the base uri into consideration
     *
     * @return string
     */
    function getRequestUri() {

        return $this->calculateUri($this->httpRequest->getUrl());

    }

    /**
     * Turns a URI such as the REQUEST_URI into a local path.
     *
     * This method:
     *   * strips off the base path
     *   * normalizes the path
     *   * uri-decodes the path
     *
     * @param string $uri
     * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri
     * @return string
     */
    function calculateUri($uri) {

        if ($uri[0] != '/' && strpos($uri, '://')) {

            $uri = parse_url($uri, PHP_URL_PATH);

        }

        $uri = Uri\normalize(str_replace('//', '/', $uri));
        $baseUri = Uri\normalize($this->getBaseUri());

        if (strpos($uri, $baseUri) === 0) {

            return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/');

        // A special case, if the baseUri was accessed without a trailing
        // slash, we'll accept it as well.
        } elseif ($uri . '/' === $baseUri) {

            return '';

        } else {

            throw new Exception\Forbidden('Requested uri (' . $uri . ') is out of base uri (' . $this->getBaseUri() . ')');

        }

    }

    /**
     * Returns the HTTP depth header
     *
     * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object
     * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent
     *
     * @param mixed $default
     * @return int
     */
    function getHTTPDepth($default = self::DEPTH_INFINITY) {

        // If its not set, we'll grab the default
        $depth = $this->httpRequest->getHeader('Depth');

        if (is_null($depth)) return $default;

        if ($depth == 'infinity') return self::DEPTH_INFINITY;


        // If its an unknown value. we'll grab the default
        if (!ctype_digit($depth)) return $default;

        return (int)$depth;

    }

    /**
     * Returns the HTTP range header
     *
     * This method returns null if there is no well-formed HTTP range request
     * header or array($start, $end).
     *
     * The first number is the offset of the first byte in the range.
     * The second number is the offset of the last byte in the range.
     *
     * If the second offset is null, it should be treated as the offset of the last byte of the entity
     * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity
     *
     * @return array|null
     */
    function getHTTPRange() {

        $range = $this->httpRequest->getHeader('range');
        if (is_null($range)) return null;

        // Matching "Range: bytes=1234-5678: both numbers are optional

        if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) return null;

        if ($matches[1] === '' && $matches[2] === '') return null;

        return [
            $matches[1] !== '' ? $matches[1] : null,
            $matches[2] !== '' ? $matches[2] : null,
        ];

    }

    /**
     * Returns the HTTP Prefer header information.
     *
     * The prefer header is defined in:
     * http://tools.ietf.org/html/draft-snell-http-prefer-14
     *
     * This method will return an array with options.
     *
     * Currently, the following options may be returned:
     *  [
     *      'return-asynch'         => true,
     *      'return-minimal'        => true,
     *      'return-representation' => true,
     *      'wait'                  => 30,
     *      'strict'                => true,
     *      'lenient'               => true,
     *  ]
     *
     * This method also supports the Brief header, and will also return
     * 'return-minimal' if the brief header was set to 't'.
     *
     * For the boolean options, false will be returned if the headers are not
     * specified. For the integer options it will be 'null'.
     *
     * @return array
     */
    function getHTTPPrefer() {

        $result = [
            // can be true or false
            'respond-async' => false,
            // Could be set to 'representation' or 'minimal'.
            'return' => null,
            // Used as a timeout, is usually a number.
            'wait' => null,
            // can be 'strict' or 'lenient'.
            'handling' => false,
        ];

        if ($prefer = $this->httpRequest->getHeader('Prefer')) {

            $result = array_merge(
                $result,
                HTTP\parsePrefer($prefer)
            );

        } elseif ($this->httpRequest->getHeader('Brief') == 't') {
            $result['return'] = 'minimal';
        }

        return $result;

    }


    /**
     * Returns information about Copy and Move requests
     *
     * This function is created to help getting information about the source and the destination for the
     * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions
     *
     * The returned value is an array with the following keys:
     *   * destination - Destination path
     *   * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten)
     *
     * @param RequestInterface $request
     * @throws Exception\BadRequest upon missing or broken request headers
     * @throws Exception\UnsupportedMediaType when trying to copy into a
     *         non-collection.
     * @throws Exception\PreconditionFailed If overwrite is set to false, but
     *         the destination exists.
     * @throws Exception\Forbidden when source and destination paths are
     *         identical.
     * @throws Exception\Conflict When trying to copy a node into its own
     *         subtree.
     * @return array
     */
    function getCopyAndMoveInfo(RequestInterface $request) {

        // Collecting the relevant HTTP headers
        if (!$request->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied');
        $destination = $this->calculateUri($request->getHeader('Destination'));
        $overwrite = $request->getHeader('Overwrite');
        if (!$overwrite) $overwrite = 'T';
        if (strtoupper($overwrite) == 'T') $overwrite = true;
        elseif (strtoupper($overwrite) == 'F') $overwrite = false;
        // We need to throw a bad request exception, if the header was invalid
        else throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F');

        list($destinationDir) = URLUtil::splitPath($destination);

        try {
            $destinationParent = $this->tree->getNodeForPath($destinationDir);
            if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection');
        } catch (Exception\NotFound $e) {

            // If the destination parent node is not found, we throw a 409
            throw new Exception\Conflict('The destination node is not found');
        }

        try {

            $destinationNode = $this->tree->getNodeForPath($destination);

            // If this succeeded, it means the destination already exists
            // we'll need to throw precondition failed in case overwrite is false
            if (!$overwrite) throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite');

        } catch (Exception\NotFound $e) {

            // Destination didn't exist, we're all good
            $destinationNode = false;

        }

        $requestPath = $request->getPath();
        if ($destination === $requestPath) {
            throw new Exception\Forbidden('Source and destination uri are identical.');
        }
        if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath . '/') {
            throw new Exception\Conflict('The destination may not be part of the same subtree as the source path.');
        }

        // These are the three relevant properties we need to return
        return [
            'destination'       => $destination,
            'destinationExists' => !!$destinationNode,
            'destinationNode'   => $destinationNode,
        ];

    }

    /**
     * Returns a list of properties for a path
     *
     * This is a simplified version getPropertiesForPath. If you aren't
     * interested in status codes, but you just want to have a flat list of
     * properties, use this method.
     *
     * Please note though that any problems related to retrieving properties,
     * such as permission issues will just result in an empty array being
     * returned.
     *
     * @param string $path
     * @param array $propertyNames
     * @return array
     */
    function getProperties($path, $propertyNames) {

        $result = $this->getPropertiesForPath($path, $propertyNames, 0);
        if (isset($result[0][200])) {
            return $result[0][200];
        } else {
            return [];
        }

    }

    /**
     * A kid-friendly way to fetch properties for a node's children.
     *
     * The returned array will be indexed by the path of the of child node.
     * Only properties that are actually found will be returned.
     *
     * The parent node will not be returned.
     *
     * @param string $path
     * @param array $propertyNames
     * @return array
     */
    function getPropertiesForChildren($path, $propertyNames) {

        $result = [];
        foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) {

            // Skipping the parent path
            if ($k === 0) continue;

            $result[$row['href']] = $row[200];

        }
        return $result;

    }

    /**
     * Returns a list of HTTP headers for a particular resource
     *
     * The generated http headers are based on properties provided by the
     * resource. The method basically provides a simple mapping between
     * DAV property and HTTP header.
     *
     * The headers are intended to be used for HEAD and GET requests.
     *
     * @param string $path
     * @return array
     */
    function getHTTPHeaders($path) {

        $propertyMap = [
            '{DAV:}getcontenttype'   => 'Content-Type',
            '{DAV:}getcontentlength' => 'Content-Length',
            '{DAV:}getlastmodified'  => 'Last-Modified',
            '{DAV:}getetag'          => 'ETag',
        ];

        $properties = $this->getProperties($path, array_keys($propertyMap));

        $headers = [];
        foreach ($propertyMap as $property => $header) {
            if (!isset($properties[$property])) continue;

            if (is_scalar($properties[$property])) {
                $headers[$header] = $properties[$property];

            // GetLastModified gets special cased
            } elseif ($properties[$property] instanceof Xml\Property\GetLastModified) {
                $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime());
            }

        }

        return $headers;

    }

    /**
     * Small helper to support PROPFIND with DEPTH_INFINITY.
     *
     * @param PropFind $propFind
     * @param array $yieldFirst
     * @return \Iterator
     */
    private function generatePathNodes(PropFind $propFind, array $yieldFirst = null) {
        if ($yieldFirst !== null) {
            yield $yieldFirst;
        }
        $newDepth = $propFind->getDepth();
        $path = $propFind->getPath();

        if ($newDepth !== self::DEPTH_INFINITY) {
            $newDepth--;
        }

        foreach ($this->tree->getChildren($path) as $childNode) {
            $subPropFind = clone $propFind;
            $subPropFind->setDepth($newDepth);
            if ($path !== '') {
                $subPath = $path . '/' . $childNode->getName();
            } else {
                $subPath = $childNode->getName();
            }
            $subPropFind->setPath($subPath);

            yield [
                $subPropFind,
                $childNode
            ];

            if (($newDepth === self::DEPTH_INFINITY || $newDepth >= 1) && $childNode instanceof ICollection) {
                foreach ($this->generatePathNodes($subPropFind) as $subItem) {
                    yield $subItem;
                }
            }

        }
    }

    /**
     * Returns a list of properties for a given path
     *
     * The path that should be supplied should have the baseUrl stripped out
     * The list of properties should be supplied in Clark notation. If the list is empty
     * 'allprops' is assumed.
     *
     * If a depth of 1 is requested child elements will also be returned.
     *
     * @param string $path
     * @param array $propertyNames
     * @param int $depth
     * @return array
     *
     * @deprecated Use getPropertiesIteratorForPath() instead (as it's more memory efficient)
     * @see getPropertiesIteratorForPath()
     */
    function getPropertiesForPath($path, $propertyNames = [], $depth = 0) {

        return iterator_to_array($this->getPropertiesIteratorForPath($path, $propertyNames, $depth));

    }
    /**
     * Returns a list of properties for a given path
     *
     * The path that should be supplied should have the baseUrl stripped out
     * The list of properties should be supplied in Clark notation. If the list is empty
     * 'allprops' is assumed.
     *
     * If a depth of 1 is requested child elements will also be returned.
     *
     * @param string $path
     * @param array $propertyNames
     * @param int $depth
     * @return \Iterator
     */
    function getPropertiesIteratorForPath($path, $propertyNames = [], $depth = 0) {

        // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled
        if (!$this->enablePropfindDepthInfinity && $depth != 0) $depth = 1;

        $path = trim($path, '/');

        $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS;
        $propFind = new PropFind($path, (array)$propertyNames, $depth, $propFindType);

        $parentNode = $this->tree->getNodeForPath($path);

        $propFindRequests = [[
            $propFind,
            $parentNode
        ]];

        if (($depth > 0 || $depth === self::DEPTH_INFINITY) && $parentNode instanceof ICollection) {
            $propFindRequests = $this->generatePathNodes(clone $propFind, current($propFindRequests));
        }

        foreach ($propFindRequests as $propFindRequest) {

            list($propFind, $node) = $propFindRequest;
            $r = $this->getPropertiesByNode($propFind, $node);
            if ($r) {
                $result = $propFind->getResultForMultiStatus();
                $result['href'] = $propFind->getPath();

                // WebDAV recommends adding a slash to the path, if the path is
                // a collection.
                // Furthermore, iCal also demands this to be the case for
                // principals. This is non-standard, but we support it.
                $resourceType = $this->getResourceTypeForNode($node);
                if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
                    $result['href'] .= '/';
                }
                yield $result;
            }

        }

    }

    /**
     * Returns a list of properties for a list of paths.
     *
     * The path that should be supplied should have the baseUrl stripped out
     * The list of properties should be supplied in Clark notation. If the list is empty
     * 'allprops' is assumed.
     *
     * The result is returned as an array, with paths for it's keys.
     * The result may be returned out of order.
     *
     * @param array $paths
     * @param array $propertyNames
     * @return array
     */
    function getPropertiesForMultiplePaths(array $paths, array $propertyNames = []) {

        $result = [
        ];

        $nodes = $this->tree->getMultipleNodes($paths);

        foreach ($nodes as $path => $node) {

            $propFind = new PropFind($path, $propertyNames);
            $r = $this->getPropertiesByNode($propFind, $node);
            if ($r) {
                $result[$path] = $propFind->getResultForMultiStatus();
                $result[$path]['href'] = $path;

                $resourceType = $this->getResourceTypeForNode($node);
                if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
                    $result[$path]['href'] .= '/';
                }
            }

        }

        return $result;

    }


    /**
     * Determines all properties for a node.
     *
     * This method tries to grab all properties for a node. This method is used
     * internally getPropertiesForPath and a few others.
     *
     * It could be useful to call this, if you already have an instance of your
     * target node and simply want to run through the system to get a correct
     * list of properties.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return bool
     */
    function getPropertiesByNode(PropFind $propFind, INode $node) {

        return $this->emit('propFind', [$propFind, $node]);

    }

    /**
     * This method is invoked by sub-systems creating a new file.
     *
     * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin).
     * It was important to get this done through a centralized function,
     * allowing plugins to intercept this using the beforeCreateFile event.
     *
     * This method will return true if the file was actually created
     *
     * @param string   $uri
     * @param resource $data
     * @param string   $etag
     * @return bool
     */
    function createFile($uri, $data, &$etag = null) {

        list($dir, $name) = URLUtil::splitPath($uri);

        if (!$this->emit('beforeBind', [$uri])) return false;

        $parent = $this->tree->getNodeForPath($dir);
        if (!$parent instanceof ICollection) {
            throw new Exception\Conflict('Files can only be created as children of collections');
        }

        // It is possible for an event handler to modify the content of the
        // body, before it gets written. If this is the case, $modified
        // should be set to true.
        //
        // If $modified is true, we must not send back an ETag.
        $modified = false;
        if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) return false;

        $etag = $parent->createFile($name, $data);

        if ($modified) $etag = null;

        $this->tree->markDirty($dir . '/' . $name);

        $this->emit('afterBind', [$uri]);
        $this->emit('afterCreateFile', [$uri, $parent]);

        return true;
    }

    /**
     * This method is invoked by sub-systems updating a file.
     *
     * This method will return true if the file was actually updated
     *
     * @param string   $uri
     * @param resource $data
     * @param string   $etag
     * @return bool
     */
    function updateFile($uri, $data, &$etag = null) {

        $node = $this->tree->getNodeForPath($uri);

        // It is possible for an event handler to modify the content of the
        // body, before it gets written. If this is the case, $modified
        // should be set to true.
        //
        // If $modified is true, we must not send back an ETag.
        $modified = false;
        if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) return false;

        $etag = $node->put($data);
        if ($modified) $etag = null;
        $this->emit('afterWriteContent', [$uri, $node]);

        return true;
    }



    /**
     * This method is invoked by sub-systems creating a new directory.
     *
     * @param string $uri
     * @return void
     */
    function createDirectory($uri) {

        $this->createCollection($uri, new MkCol(['{DAV:}collection'], []));

    }

    /**
     * Use this method to create a new collection
     *
     * @param string $uri The new uri
     * @param MkCol $mkCol
     * @return array|null
     */
    function createCollection($uri, MkCol $mkCol) {

        list($parentUri, $newName) = URLUtil::splitPath($uri);

        // Making sure the parent exists
        try {
            $parent = $this->tree->getNodeForPath($parentUri);

        } catch (Exception\NotFound $e) {
            throw new Exception\Conflict('Parent node does not exist');

        }

        // Making sure the parent is a collection
        if (!$parent instanceof ICollection) {
            throw new Exception\Conflict('Parent node is not a collection');
        }

        // Making sure the child does not already exist
        try {
            $parent->getChild($newName);

            // If we got here.. it means there's already a node on that url, and we need to throw a 405
            throw new Exception\MethodNotAllowed('The resource you tried to create already exists');

        } catch (Exception\NotFound $e) {
            // NotFound is the expected behavior.
        }


        if (!$this->emit('beforeBind', [$uri])) return;

        if ($parent instanceof IExtendedCollection) {

            /**
             * If the parent is an instance of IExtendedCollection, it means that
             * we can pass the MkCol object directly as it may be able to store
             * properties immediately.
             */
            $parent->createExtendedCollection($newName, $mkCol);

        } else {

            /**
             * If the parent is a standard ICollection, it means only
             * 'standard' collections can be created, so we should fail any
             * MKCOL operation that carries extra resourcetypes.
             */
            if (count($mkCol->getResourceType()) > 1) {
                throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.');
            }

            $parent->createDirectory($newName);

        }

        // If there are any properties that have not been handled/stored,
        // we ask the 'propPatch' event to handle them. This will allow for
        // example the propertyStorage system to store properties upon MKCOL.
        if ($mkCol->getRemainingMutations()) {
            $this->emit('propPatch', [$uri, $mkCol]);
        }
        $success = $mkCol->commit();

        if (!$success) {
            $result = $mkCol->getResult();

            $formattedResult = [
                'href' => $uri,
            ];

            foreach ($result as $propertyName => $status) {

                if (!isset($formattedResult[$status])) {
                    $formattedResult[$status] = [];
                }
                $formattedResult[$status][$propertyName] = null;

            }
            return $formattedResult;
        }

        $this->tree->markDirty($parentUri);
        $this->emit('afterBind', [$uri]);

    }

    /**
     * This method updates a resource's properties
     *
     * The properties array must be a list of properties. Array-keys are
     * property names in clarknotation, array-values are it's values.
     * If a property must be deleted, the value should be null.
     *
     * Note that this request should either completely succeed, or
     * completely fail.
     *
     * The response is an array with properties for keys, and http status codes
     * as their values.
     *
     * @param string $path
     * @param array $properties
     * @return array
     */
    function updateProperties($path, array $properties) {

        $propPatch = new PropPatch($properties);
        $this->emit('propPatch', [$path, $propPatch]);
        $propPatch->commit();

        return $propPatch->getResult();

    }

    /**
     * This method checks the main HTTP preconditions.
     *
     * Currently these are:
     *   * If-Match
     *   * If-None-Match
     *   * If-Modified-Since
     *   * If-Unmodified-Since
     *
     * The method will return true if all preconditions are met
     * The method will return false, or throw an exception if preconditions
     * failed. If false is returned the operation should be aborted, and
     * the appropriate HTTP response headers are already set.
     *
     * Normally this method will throw 412 Precondition Failed for failures
     * related to If-None-Match, If-Match and If-Unmodified Since. It will
     * set the status to 304 Not Modified for If-Modified_since.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function checkPreconditions(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();
        $node = null;
        $lastMod = null;
        $etag = null;

        if ($ifMatch = $request->getHeader('If-Match')) {

            // If-Match contains an entity tag. Only if the entity-tag
            // matches we are allowed to make the request succeed.
            // If the entity-tag is '*' we are only allowed to make the
            // request succeed if a resource exists at that url.
            try {
                $node = $this->tree->getNodeForPath($path);
            } catch (Exception\NotFound $e) {
                throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist', 'If-Match');
            }

            // Only need to check entity tags if they are not *
            if ($ifMatch !== '*') {

                // There can be multiple ETags
                $ifMatch = explode(',', $ifMatch);
                $haveMatch = false;
                foreach ($ifMatch as $ifMatchItem) {

                    // Stripping any extra spaces
                    $ifMatchItem = trim($ifMatchItem, ' ');

                    $etag = $node instanceof IFile ? $node->getETag() : null;
                    if ($etag === $ifMatchItem) {
                        $haveMatch = true;
                    } else {
                        // Evolution has a bug where it sometimes prepends the "
                        // with a \. This is our workaround.
                        if (str_replace('\\"', '"', $ifMatchItem) === $etag) {
                            $haveMatch = true;
                        }
                    }

                }
                if (!$haveMatch) {
                    if ($etag) $response->setHeader('ETag', $etag);
                     throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match');
                }
            }
        }

        if ($ifNoneMatch = $request->getHeader('If-None-Match')) {

            // The If-None-Match header contains an ETag.
            // Only if the ETag does not match the current ETag, the request will succeed
            // The header can also contain *, in which case the request
            // will only succeed if the entity does not exist at all.
            $nodeExists = true;
            if (!$node) {
                try {
                    $node = $this->tree->getNodeForPath($path);
                } catch (Exception\NotFound $e) {
                    $nodeExists = false;
                }
            }
            if ($nodeExists) {
                $haveMatch = false;
                if ($ifNoneMatch === '*') $haveMatch = true;
                else {

                    // There might be multiple ETags
                    $ifNoneMatch = explode(',', $ifNoneMatch);
                    $etag = $node instanceof IFile ? $node->getETag() : null;

                    foreach ($ifNoneMatch as $ifNoneMatchItem) {

                        // Stripping any extra spaces
                        $ifNoneMatchItem = trim($ifNoneMatchItem, ' ');

                        if ($etag === $ifNoneMatchItem) $haveMatch = true;

                    }

                }

                if ($haveMatch) {
                    if ($etag) $response->setHeader('ETag', $etag);
                    if ($request->getMethod() === 'GET') {
                        $response->setStatus(304);
                        return false;
                    } else {
                        throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match');
                    }
                }
            }

        }

        if (!$ifNoneMatch && ($ifModifiedSince = $request->getHeader('If-Modified-Since'))) {

            // The If-Modified-Since header contains a date. We
            // will only return the entity if it has been changed since
            // that date. If it hasn't been changed, we return a 304
            // header
            // Note that this header only has to be checked if there was no If-None-Match header
            // as per the HTTP spec.
            $date = HTTP\Util::parseHTTPDate($ifModifiedSince);

            if ($date) {
                if (is_null($node)) {
                    $node = $this->tree->getNodeForPath($path);
                }
                $lastMod = $node->getLastModified();
                if ($lastMod) {
                    $lastMod = new \DateTime('@' . $lastMod);
                    if ($lastMod <= $date) {
                        $response->setStatus(304);
                        $response->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod));
                        return false;
                    }
                }
            }
        }

        if ($ifUnmodifiedSince = $request->getHeader('If-Unmodified-Since')) {

            // The If-Unmodified-Since will allow allow the request if the
            // entity has not changed since the specified date.
            $date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince);

            // We must only check the date if it's valid
            if ($date) {
                if (is_null($node)) {
                    $node = $this->tree->getNodeForPath($path);
                }
                $lastMod = $node->getLastModified();
                if ($lastMod) {
                    $lastMod = new \DateTime('@' . $lastMod);
                    if ($lastMod > $date) {
                        throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since');
                    }
                }
            }

        }

        // Now the hardest, the If: header. The If: header can contain multiple
        // urls, ETags and so-called 'state tokens'.
        //
        // Examples of state tokens include lock-tokens (as defined in rfc4918)
        // and sync-tokens (as defined in rfc6578).
        //
        // The only proper way to deal with these, is to emit events, that a
        // Sync and Lock plugin can pick up.
        $ifConditions = $this->getIfConditions($request);

        foreach ($ifConditions as $kk => $ifCondition) {
            foreach ($ifCondition['tokens'] as $ii => $token) {
                $ifConditions[$kk]['tokens'][$ii]['validToken'] = false;
            }
        }

        // Plugins are responsible for validating all the tokens.
        // If a plugin deemed a token 'valid', it will set 'validToken' to
        // true.
        $this->emit('validateTokens', [$request, &$ifConditions]);

        // Now we're going to analyze the result.

        // Every ifCondition needs to validate to true, so we exit as soon as
        // we have an invalid condition.
        foreach ($ifConditions as $ifCondition) {

            $uri = $ifCondition['uri'];
            $tokens = $ifCondition['tokens'];

            // We only need 1 valid token for the condition to succeed.
            foreach ($tokens as $token) {

                $tokenValid = $token['validToken'] || !$token['token'];

                $etagValid = false;
                if (!$token['etag']) {
                    $etagValid = true;
                }
                // Checking the ETag, only if the token was already deemed
                // valid and there is one.
                if ($token['etag'] && $tokenValid) {

                    // The token was valid, and there was an ETag. We must
                    // grab the current ETag and check it.
                    $node = $this->tree->getNodeForPath($uri);
                    $etagValid = $node instanceof IFile && $node->getETag() == $token['etag'];

                }


                if (($tokenValid && $etagValid) ^ $token['negate']) {
                    // Both were valid, so we can go to the next condition.
                    continue 2;
                }


            }

            // If we ended here, it means there was no valid ETag + token
            // combination found for the current condition. This means we fail!
            throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for ' . $uri, 'If');

        }

        return true;

    }

    /**
     * This method is created to extract information from the WebDAV HTTP 'If:' header
     *
     * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information
     * The function will return an array, containing structs with the following keys
     *
     *   * uri   - the uri the condition applies to.
     *   * tokens - The lock token. another 2 dimensional array containing 3 elements
     *
     * Example 1:
     *
     * If: (<opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)
     *
     * Would result in:
     *
     * [
     *    [
     *       'uri' => '/request/uri',
     *       'tokens' => [
     *          [
     *              [
     *                  'negate' => false,
     *                  'token'  => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
     *                  'etag'   => ""
     *              ]
     *          ]
     *       ],
     *    ]
     * ]
     *
     * Example 2:
     *
     * If: </path/> (Not <opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> ["Im An ETag"]) (["Another ETag"]) </path2/> (Not ["Path2 ETag"])
     *
     * Would result in:
     *
     * [
     *    [
     *       'uri' => 'path',
     *       'tokens' => [
     *          [
     *              [
     *                  'negate' => true,
     *                  'token'  => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2',
     *                  'etag'   => '"Im An ETag"'
     *              ],
     *              [
     *                  'negate' => false,
     *                  'token'  => '',
     *                  'etag'   => '"Another ETag"'
     *              ]
     *          ]
     *       ],
     *    ],
     *    [
     *       'uri' => 'path2',
     *       'tokens' => [
     *          [
     *              [
     *                  'negate' => true,
     *                  'token'  => '',
     *                  'etag'   => '"Path2 ETag"'
     *              ]
     *          ]
     *       ],
     *    ],
     * ]
     *
     * @param RequestInterface $request
     * @return array
     */
    function getIfConditions(RequestInterface $request) {

        $header = $request->getHeader('If');
        if (!$header) return [];

        $matches = [];

        $regex = '/(?:\<(?P<uri>.*?)\>\s)?\((?P<not>Not\s)?(?:\<(?P<token>[^\>]*)\>)?(?:\s?)(?:\[(?P<etag>[^\]]*)\])?\)/im';
        preg_match_all($regex, $header, $matches, PREG_SET_ORDER);

        $conditions = [];

        foreach ($matches as $match) {

            // If there was no uri specified in this match, and there were
            // already conditions parsed, we add the condition to the list of
            // conditions for the previous uri.
            if (!$match['uri'] && count($conditions)) {
                $conditions[count($conditions) - 1]['tokens'][] = [
                    'negate' => $match['not'] ? true : false,
                    'token'  => $match['token'],
                    'etag'   => isset($match['etag']) ? $match['etag'] : ''
                ];
            } else {

                if (!$match['uri']) {
                    $realUri = $request->getPath();
                } else {
                    $realUri = $this->calculateUri($match['uri']);
                }

                $conditions[] = [
                    'uri'    => $realUri,
                    'tokens' => [
                        [
                            'negate' => $match['not'] ? true : false,
                            'token'  => $match['token'],
                            'etag'   => isset($match['etag']) ? $match['etag'] : ''
                        ]
                    ],

                ];
            }

        }

        return $conditions;

    }

    /**
     * Returns an array with resourcetypes for a node.
     *
     * @param INode $node
     * @return array
     */
    function getResourceTypeForNode(INode $node) {

        $result = [];
        foreach ($this->resourceTypeMapping as $className => $resourceType) {
            if ($node instanceof $className) $result[] = $resourceType;
        }
        return $result;

    }

    // }}}
    // {{{ XML Readers & Writers


    /**
     * Generates a WebDAV propfind response body based on a list of nodes.
     *
     * If 'strip404s' is set to true, all 404 responses will be removed.
     *
     * @param array|\Traversable $fileProperties The list with nodes
     * @param bool $strip404s
     * @return string
     */
    function generateMultiStatus($fileProperties, $strip404s = false) {

        $w = $this->xml->getWriter();
        $w->openMemory();
        $w->contextUri = $this->baseUri;
        $w->startDocument();

        $w->startElement('{DAV:}multistatus');

        foreach ($fileProperties as $entry) {

            $href = $entry['href'];
            unset($entry['href']);
            if ($strip404s) {
                unset($entry[404]);
            }
            $response = new Xml\Element\Response(
                ltrim($href, '/'),
                $entry
            );
            $w->write([
                'name'  => '{DAV:}response',
                'value' => $response
            ]);
        }
        $w->endElement();

        return $w->outputMemory();

    }

}
<?php

namespace Sabre\DAV;

/**
 * The baseclass for all server plugins.
 *
 * Plugins can modify or extend the servers behaviour.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class ServerPlugin {

    /**
     * This initializes the plugin.
     *
     * This function is called by Sabre\DAV\Server, after
     * addPlugin is called.
     *
     * This method should set up the required event subscriptions.
     *
     * @param Server $server
     * @return void
     */
    abstract function initialize(Server $server);

    /**
     * This method should return a list of server-features.
     *
     * This is for example 'versioning' and is added to the DAV: header
     * in an OPTIONS response.
     *
     * @return array
     */
    function getFeatures() {

        return [];

    }

    /**
     * Use this method to tell the server this plugin defines additional
     * HTTP methods.
     *
     * This method is passed a uri. It should only return HTTP methods that are
     * available for the specified uri.
     *
     * @param string $path
     * @return array
     */
    function getHTTPMethods($path) {

        return [];

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return get_class($this);

    }

    /**
     * Returns a list of reports this plugin supports.
     *
     * This will be used in the {DAV:}supported-report-set property.
     * Note that you still need to subscribe to the 'report' event to actually
     * implement them
     *
     * @param string $uri
     * @return array
     */
    function getSupportedReportSet($uri) {

        return [];

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => null,
            'link'        => null,
        ];

    }

}
<?php

namespace Sabre\DAV\Sharing;

use Sabre\DAV\INode;

/**
 * This interface represents a resource that has sharing capabilities, either
 * because it's possible for an owner to share the resource, or because this is
 * an instance of a shared resource.
 *
 * @copyright Copyright (C) fruux GmbH. (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ISharedNode extends INode {

    /**
     * Returns the 'access level' for the instance of this shared resource.
     *
     * The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_
     * constants.
     *
     * @return int
     */
    function getShareAccess();

    /**
     * This function must return a URI that uniquely identifies the shared
     * resource. This URI should be identical across instances, and is
     * also used in several other XML bodies to connect invites to
     * resources.
     *
     * This may simply be a relative reference to the original shared instance,
     * but it could also be a urn. As long as it's a valid URI and unique.
     *
     * @return string
     */
    function getShareResourceUri();

    /**
     * Updates the list of sharees.
     *
     * Every item must be a Sharee object.
     *
     * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees
     * @return void
     */
    function updateInvites(array $sharees);

    /**
     * Returns the list of people whom this resource is shared with.
     *
     * Every item in the returned array must be a Sharee object with
     * at least the following properties set:
     *
     * * $href
     * * $shareAccess
     * * $inviteStatus
     *
     * and optionally:
     *
     * * $properties
     *
     * @return \Sabre\DAV\Xml\Element\Sharee[]
     */
    function getInvites();

}
<?php

namespace Sabre\DAV\Sharing;

use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\INode;
use Sabre\DAV\PropFind;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Xml\Element\Sharee;
use Sabre\DAV\Xml\Property;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * This plugin implements HTTP requests and properties related to:
 *
 * draft-pot-webdav-resource-sharing
 *
 * This specification allows people to share webdav resources with others.
 *
 * @copyright Copyright (C) 2007-2015 fruux GmbH. (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends ServerPlugin {

    const ACCESS_NOTSHARED = 0;
    const ACCESS_SHAREDOWNER = 1;
    const ACCESS_READ = 2;
    const ACCESS_READWRITE = 3;
    const ACCESS_NOACCESS = 4;

    const INVITE_NORESPONSE = 1;
    const INVITE_ACCEPTED = 2;
    const INVITE_DECLINED = 3;
    const INVITE_INVALID = 4;

    /**
     * Reference to SabreDAV server object.
     *
     * @var Server
     */
    protected $server;

    /**
     * This method should return a list of server-features.
     *
     * This is for example 'versioning' and is added to the DAV: header
     * in an OPTIONS response.
     *
     * @return array
     */
    function getFeatures() {

        return ['resource-sharing'];

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'sharing';

    }

    /**
     * This initializes the plugin.
     *
     * This function is called by Sabre\DAV\Server, after
     * addPlugin is called.
     *
     * This method should set up the required event subscriptions.
     *
     * @param Server $server
     * @return void
     */
    function initialize(Server $server) {

        $this->server = $server;

        $server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource';

        array_push(
            $server->protectedProperties,
            '{DAV:}share-mode'
        );

        $server->on('method:POST',              [$this, 'httpPost']);
        $server->on('propFind',                 [$this, 'propFind']);
        $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']);
        $server->on('onHTMLActionsPanel',       [$this, 'htmlActionsPanel']);
        $server->on('onBrowserPostAction',      [$this, 'browserPostAction']);

    }

    /**
     * Updates the list of sharees on a shared resource.
     *
     * The sharees  array is a list of people that are to be added modified
     * or removed in the list of shares.
     *
     * @param string $path
     * @param Sharee[] $sharees
     * @return void
     */
    function shareResource($path, array $sharees) {

        $node = $this->server->tree->getNodeForPath($path);

        if (!$node instanceof ISharedNode) {

            throw new Forbidden('Sharing is not allowed on this node');

        }

        // Getting ACL info
        $acl = $this->server->getPlugin('acl');

        // If there's no ACL support, we allow everything
        if ($acl) {
            $acl->checkPrivileges($path, '{DAV:}share');
        }

        foreach ($sharees as $sharee) {
            // We're going to attempt to get a local principal uri for a share
            // href by emitting the getPrincipalByUri event.
            $principal = null;
            $this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]);
            $sharee->principal = $principal;
        }
        $node->updateInvites($sharees);

    }

    /**
     * This event is triggered when properties are requested for nodes.
     *
     * This allows us to inject any sharings-specific properties.
     *
     * @param PropFind $propFind
     * @param INode $node
     * @return void
     */
    function propFind(PropFind $propFind, INode $node) {

        if ($node instanceof ISharedNode) {

            $propFind->handle('{DAV:}share-access', function() use ($node) {

                return new Property\ShareAccess($node->getShareAccess());

            });
            $propFind->handle('{DAV:}invite', function() use ($node) {

                return new Property\Invite($node->getInvites());

            });
            $propFind->handle('{DAV:}share-resource-uri', function() use ($node) {

                return new Property\Href($node->getShareResourceUri());

            });

        }

    }

    /**
     * We intercept this to handle POST requests on shared resources
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return null|bool
     */
    function httpPost(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();
        $contentType = $request->getHeader('Content-Type');

        // We're only interested in the davsharing content type.
        if (strpos($contentType, 'application/davsharing+xml') === false) {
            return;
        }

        $message = $this->server->xml->parse(
            $request->getBody(),
            $request->getUrl(),
            $documentType
        );

        switch ($documentType) {

            case '{DAV:}share-resource':

                $this->shareResource($path, $message->sharees);
                $response->setStatus(200);
                // Adding this because sending a response body may cause issues,
                // and I wanted some type of indicator the response was handled.
                $response->setHeader('X-Sabre-Status', 'everything-went-well');

                // Breaking the event chain
                return false;

            default :
                throw new BadRequest('Unexpected document type: ' . $documentType . ' for this Content-Type');

        }

    }

    /**
     * This method is triggered whenever a subsystem reqeuests the privileges
     * hat are supported on a particular node.
     *
     * We need to add a number of privileges for scheduling purposes.
     *
     * @param INode $node
     * @param array $supportedPrivilegeSet
     */
    function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) {

        if ($node instanceof ISharedNode) {
            $supportedPrivilegeSet['{DAV:}share'] = [
                'abstract'   => false,
                'aggregates' => [],
            ];
        }
    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'This plugin implements WebDAV resource sharing',
            'link'        => 'https://github.com/evert/webdav-sharing'
        ];

    }

    /**
     * This method is used to generate HTML output for the
     * DAV\Browser\Plugin.
     *
     * @param INode $node
     * @param string $output
     * @param string $path
     * @return bool|null
     */
    function htmlActionsPanel(INode $node, &$output, $path) {

        if (!$node instanceof ISharedNode) {
            return;
        }

        $aclPlugin = $this->server->getPlugin('acl');
        if ($aclPlugin) {
            if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) {
                // Sharing is not permitted, we will not draw this interface.
                return;
            }
        }

        $output .= '<tr><td colspan="2"><form method="post" action="">
            <h3>Share this resource</h3>
            <input type="hidden" name="sabreAction" value="share" />
            <label>Share with (uri):</label> <input type="text" name="href" placeholder="mailto:user@example.org"/><br />
            <label>Access</label>
                <select name="access">
                    <option value="readwrite">Read-write</option>
                    <option value="read">Read-only</option>
                    <option value="no-access">Revoke access</option>
                </select><br />
             <input type="submit" value="share" />
            </form>
            </td></tr>';

    }

    /**
     * This method is triggered for POST actions generated by the browser
     * plugin.
     *
     * @param string $path
     * @param string $action
     * @param array $postVars
     */
    function browserPostAction($path, $action, $postVars) {

        if ($action !== 'share') {
            return;
        }

        if (empty($postVars['href'])) {
            throw new BadRequest('The "href" POST parameter is required');
        }
        if (empty($postVars['access'])) {
            throw new BadRequest('The "access" POST parameter is required');
        }

        $accessMap = [
            'readwrite' => self::ACCESS_READWRITE,
            'read'      => self::ACCESS_READ,
            'no-access' => self::ACCESS_NOACCESS,
        ];

        if (!isset($accessMap[$postVars['access']])) {
            throw new BadRequest('The "access" POST must be readwrite, read or no-access');
        }
        $sharee = new Sharee([
            'href'   => $postVars['href'],
            'access' => $accessMap[$postVars['access']],
        ]);

        $this->shareResource(
            $path,
            [$sharee]
        );
        return false;

    }

}
<?php

namespace Sabre\DAV;

/**
 * SimpleCollection
 *
 * The SimpleCollection is used to quickly setup static directory structures.
 * Just create the object with a proper name, and add children to use it.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SimpleCollection extends Collection {

    /**
     * List of childnodes
     *
     * @var INode[]
     */
    protected $children = [];

    /**
     * Name of this resource
     *
     * @var string
     */
    protected $name;

    /**
     * Creates this node
     *
     * The name of the node must be passed, child nodes can also be passed.
     * This nodes must be instances of INode
     *
     * @param string $name
     * @param INode[] $children
     */
    function __construct($name, array $children = []) {

        $this->name = $name;
        foreach ($children as $child) {

            if (!($child instanceof INode)) throw new Exception('Only instances of Sabre\DAV\INode are allowed to be passed in the children argument');
            $this->addChild($child);

        }

    }

    /**
     * Adds a new childnode to this collection
     *
     * @param INode $child
     * @return void
     */
    function addChild(INode $child) {

        $this->children[$child->getName()] = $child;

    }

    /**
     * Returns the name of the collection
     *
     * @return string
     */
    function getName() {

        return $this->name;

    }

    /**
     * Returns a child object, by its name.
     *
     * This method makes use of the getChildren method to grab all the child nodes, and compares the name.
     * Generally its wise to override this, as this can usually be optimized
     *
     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
     * exist.
     *
     * @param string $name
     * @throws Exception\NotFound
     * @return INode
     */
    function getChild($name) {

        if (isset($this->children[$name])) return $this->children[$name];
        throw new Exception\NotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\'');

    }

    /**
     * Returns a list of children for this collection
     *
     * @return INode[]
     */
    function getChildren() {

        return array_values($this->children);

    }


}
<?php

namespace Sabre\DAV;

/**
 * SimpleFile
 *
 * The 'SimpleFile' class is used to easily add read-only immutable files to
 * the directory structure. One usecase would be to add a 'readme.txt' to a
 * root of a webserver with some standard content.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SimpleFile extends File {

    /**
     * File contents
     *
     * @var string
     */
    protected $contents = [];

    /**
     * Name of this resource
     *
     * @var string
     */
    protected $name;

    /**
     * A mimetype, such as 'text/plain' or 'text/html'
     *
     * @var string
     */
    protected $mimeType;

    /**
     * Creates this node
     *
     * The name of the node must be passed, as well as the contents of the
     * file.
     *
     * @param string $name
     * @param string $contents
     * @param string|null $mimeType
     */
    function __construct($name, $contents, $mimeType = null) {

        $this->name = $name;
        $this->contents = $contents;
        $this->mimeType = $mimeType;

    }

    /**
     * Returns the node name for this file.
     *
     * This name is used to construct the url.
     *
     * @return string
     */
    function getName() {

        return $this->name;

    }

    /**
     * Returns the data
     *
     * This method may either return a string or a readable stream resource
     *
     * @return mixed
     */
    function get() {

        return $this->contents;

    }

    /**
     * Returns the size of the file, in bytes.
     *
     * @return int
     */
    function getSize() {

        return strlen($this->contents);

    }

    /**
     * Returns the ETag for a file
     *
     * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change.
     * The ETag is an arbitrary string, but MUST be surrounded by double-quotes.
     *
     * Return null if the ETag can not effectively be determined
     * @return string
     */
    function getETag() {

        return '"' . sha1($this->contents) . '"';

    }

    /**
     * Returns the mime-type for a file
     *
     * If null is returned, we'll assume application/octet-stream
     * @return string
     */
    function getContentType() {

        return $this->mimeType;

    }

}
<?php

namespace Sabre\DAV;

/**
 * String utility
 *
 * This class is mainly used to implement the 'text-match' filter, used by both
 * the CalDAV calendar-query REPORT, and CardDAV addressbook-query REPORT.
 * Because they both need it, it was decided to put it in Sabre\DAV instead.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class StringUtil {

    /**
     * Checks if a needle occurs in a haystack ;)
     *
     * @param string $haystack
     * @param string $needle
     * @param string $collation
     * @param string $matchType
     * @return bool
     */
    static function textMatch($haystack, $needle, $collation, $matchType = 'contains') {

        switch ($collation) {

            case 'i;ascii-casemap' :
                // default strtolower takes locale into consideration
                // we don't want this.
                $haystack = str_replace(range('a', 'z'), range('A', 'Z'), $haystack);
                $needle = str_replace(range('a', 'z'), range('A', 'Z'), $needle);
                break;

            case 'i;octet' :
                // Do nothing
                break;

            case 'i;unicode-casemap' :
                $haystack = mb_strtoupper($haystack, 'UTF-8');
                $needle = mb_strtoupper($needle, 'UTF-8');
                break;

            default :
                throw new Exception\BadRequest('Collation type: ' . $collation . ' is not supported');

        }

        switch ($matchType) {

            case 'contains' :
                return strpos($haystack, $needle) !== false;
            case 'equals' :
                return $haystack === $needle;
            case 'starts-with' :
                return strpos($haystack, $needle) === 0;
            case 'ends-with' :
                return strrpos($haystack, $needle) === strlen($haystack) - strlen($needle);
            default :
                throw new Exception\BadRequest('Match-type: ' . $matchType . ' is not supported');

        }

    }

    /**
     * This method takes an input string, checks if it's not valid UTF-8 and
     * attempts to convert it to UTF-8 if it's not.
     *
     * Note that currently this can only convert ISO-8559-1 to UTF-8 (latin-1),
     * anything else will likely fail.
     *
     * @param string $input
     * @return string
     */
    static function ensureUTF8($input) {

        $encoding = mb_detect_encoding($input, ['UTF-8', 'ISO-8859-1'], true);

        if ($encoding === 'ISO-8859-1') {
            return utf8_encode($input);
        } else {
            return $input;
        }

    }

}
<?php

namespace Sabre\DAV\Sync;

use Sabre\DAV;

/**
 * If a class extends ISyncCollection, it supports WebDAV-sync.
 *
 * You are responsible for maintaining a changelist for this collection. This
 * means that if any child nodes in this collection was created, modified or
 * deleted in any way, you should maintain an updated changelist.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ISyncCollection extends DAV\ICollection {

    /**
     * This method returns the current sync-token for this collection.
     * This can be any string.
     *
     * If null is returned from this function, the plugin assumes there's no
     * sync information available.
     *
     * @return string|null
     */
    function getSyncToken();

    /**
     * The getChanges method returns all the changes that have happened, since
     * the specified syncToken and the current collection.
     *
     * This function should return an array, such as the following:
     *
     * [
     *   'syncToken' => 'The current synctoken',
     *   'added'   => [
     *      'new.txt',
     *   ],
     *   'modified'   => [
     *      'modified.txt',
     *   ],
     *   'deleted' => array(
     *      'foo.php.bak',
     *      'old.txt'
     *   )
     * ];
     *
     * The syncToken property should reflect the *current* syncToken of the
     * collection, as reported getSyncToken(). This is needed here too, to
     * ensure the operation is atomic.
     *
     * If the syncToken is specified as null, this is an initial sync, and all
     * members should be reported.
     *
     * The modified property is an array of nodenames that have changed since
     * the last token.
     *
     * The deleted property is an array with nodenames, that have been deleted
     * from collection.
     *
     * The second argument is basically the 'depth' of the report. If it's 1,
     * you only have to report changes that happened only directly in immediate
     * descendants. If it's 2, it should also include changes from the nodes
     * below the child collections. (grandchildren)
     *
     * The third (optional) argument allows a client to specify how many
     * results should be returned at most. If the limit is not specified, it
     * should be treated as infinite.
     *
     * If the limit (infinite or not) is higher than you're willing to return,
     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
     *
     * If the syncToken is expired (due to data cleanup) or unknown, you must
     * return null.
     *
     * The limit is 'suggestive'. You are free to ignore it.
     *
     * @param string $syncToken
     * @param int $syncLevel
     * @param int $limit
     * @return array
     */
    function getChanges($syncToken, $syncLevel, $limit = null);

}
<?php

namespace Sabre\DAV\Sync;

use Sabre\DAV;
use Sabre\DAV\Xml\Request\SyncCollectionReport;
use Sabre\HTTP\RequestInterface;

/**
 * This plugin all WebDAV-sync capabilities to the Server.
 *
 * WebDAV-sync is defined by rfc6578
 *
 * The sync capabilities only work with collections that implement
 * Sabre\DAV\Sync\ISyncCollection.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends DAV\ServerPlugin {

    /**
     * Reference to server object
     *
     * @var DAV\Server
     */
    protected $server;

    const SYNCTOKEN_PREFIX = 'http://sabre.io/ns/sync/';

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using \Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'sync';

    }

    /**
     * Initializes the plugin.
     *
     * This is when the plugin registers it's hooks.
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        $this->server = $server;
        $server->xml->elementMap['{DAV:}sync-collection'] = 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport';

        $self = $this;

        $server->on('report', function($reportName, $dom, $uri) use ($self) {

            if ($reportName === '{DAV:}sync-collection') {
                $this->server->transactionType = 'report-sync-collection';
                $self->syncCollection($uri, $dom);
                return false;
            }

        });

        $server->on('propFind',       [$this, 'propFind']);
        $server->on('validateTokens', [$this, 'validateTokens']);

    }

    /**
     * Returns a list of reports this plugin supports.
     *
     * This will be used in the {DAV:}supported-report-set property.
     * Note that you still need to subscribe to the 'report' event to actually
     * implement them
     *
     * @param string $uri
     * @return array
     */
    function getSupportedReportSet($uri) {

        $node = $this->server->tree->getNodeForPath($uri);
        if ($node instanceof ISyncCollection && $node->getSyncToken()) {
            return [
                '{DAV:}sync-collection',
            ];
        }

        return [];

    }


    /**
     * This method handles the {DAV:}sync-collection HTTP REPORT.
     *
     * @param string $uri
     * @param SyncCollectionReport $report
     * @return void
     */
    function syncCollection($uri, SyncCollectionReport $report) {

        // Getting the data
        $node = $this->server->tree->getNodeForPath($uri);
        if (!$node instanceof ISyncCollection) {
            throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.');
        }
        $token = $node->getSyncToken();
        if (!$token) {
            throw new DAV\Exception\ReportNotSupported('No sync information is available at this node');
        }

        $syncToken = $report->syncToken;
        if (!is_null($syncToken)) {
            // Sync-token must start with our prefix
            if (substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) {
                throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token');
            }

            $syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX));

        }
        $changeInfo = $node->getChanges($syncToken, $report->syncLevel, $report->limit);

        if (is_null($changeInfo)) {

            throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token');

        }

        // Encoding the response
        $this->sendSyncCollectionResponse(
            $changeInfo['syncToken'],
            $uri,
            $changeInfo['added'],
            $changeInfo['modified'],
            $changeInfo['deleted'],
            $report->properties
        );

    }

    /**
     * Sends the response to a sync-collection request.
     *
     * @param string $syncToken
     * @param string $collectionUrl
     * @param array $added
     * @param array $modified
     * @param array $deleted
     * @param array $properties
     * @return void
     */
    protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties) {


        $fullPaths = [];

        // Pre-fetching children, if this is possible.
        foreach (array_merge($added, $modified) as $item) {
            $fullPath = $collectionUrl . '/' . $item;
            $fullPaths[] = $fullPath;
        }

        $responses = [];
        foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) {

            // The 'Property_Response' class is responsible for generating a
            // single {DAV:}response xml element.
            $responses[] = new DAV\Xml\Element\Response($fullPath, $props);

        }



        // Deleted items also show up as 'responses'. They have no properties,
        // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'.
        foreach ($deleted as $item) {

            $fullPath = $collectionUrl . '/' . $item;
            $responses[] = new DAV\Xml\Element\Response($fullPath, [], 404);

        }
        $multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX . $syncToken);

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setBody(
            $this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri())
        );

    }

    /**
     * This method is triggered whenever properties are requested for a node.
     * We intercept this to see if we must return a {DAV:}sync-token.
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @return void
     */
    function propFind(DAV\PropFind $propFind, DAV\INode $node) {

        $propFind->handle('{DAV:}sync-token', function() use ($node) {
            if (!$node instanceof ISyncCollection || !$token = $node->getSyncToken()) {
                return;
            }
            return self::SYNCTOKEN_PREFIX . $token;
        });

    }

    /**
     * The validateTokens event is triggered before every request.
     *
     * It's a moment where this plugin can check all the supplied lock tokens
     * in the If: header, and check if they are valid.
     *
     * @param RequestInterface $request
     * @param array $conditions
     * @return void
     */
    function validateTokens(RequestInterface $request, &$conditions) {

        foreach ($conditions as $kk => $condition) {

            foreach ($condition['tokens'] as $ii => $token) {

                // Sync-tokens must always start with our designated prefix.
                if (substr($token['token'], 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) {
                    continue;
                }

                // Checking if the token is a match.
                $node = $this->server->tree->getNodeForPath($condition['uri']);

                if (
                    $node instanceof ISyncCollection &&
                    $node->getSyncToken() == substr($token['token'], strlen(self::SYNCTOKEN_PREFIX))
                ) {
                    $conditions[$kk]['tokens'][$ii]['validToken'] = true;
                }

            }

        }

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds support for WebDAV Collection Sync (rfc6578)',
            'link'        => 'http://sabre.io/dav/sync/',
        ];

    }

}
<?php

namespace Sabre\DAV;

use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\HTTP\URLUtil;

/**
 * Temporary File Filter Plugin
 *
 * The purpose of this filter is to intercept some of the garbage files
 * operation systems and applications tend to generate when mounting
 * a WebDAV share as a disk.
 *
 * It will intercept these files and place them in a separate directory.
 * these files are not deleted automatically, so it is advisable to
 * delete these after they are not accessed for 24 hours.
 *
 * Currently it supports:
 *   * OS/X style resource forks and .DS_Store
 *   * desktop.ini and Thumbs.db (windows)
 *   * .*.swp (vim temporary files)
 *   * .dat.* (smultron temporary files)
 *
 * Additional patterns can be added, by adding on to the
 * temporaryFilePatterns property.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class TemporaryFileFilterPlugin extends ServerPlugin {

    /**
     * This is the list of patterns we intercept.
     * If new patterns are added, they must be valid patterns for preg_match.
     *
     * @var array
     */
    public $temporaryFilePatterns = [
        '/^\._(.*)$/',     // OS/X resource forks
        '/^.DS_Store$/',   // OS/X custom folder settings
        '/^desktop.ini$/', // Windows custom folder settings
        '/^Thumbs.db$/',   // Windows thumbnail cache
        '/^.(.*).swp$/',   // ViM temporary files
        '/^\.dat(.*)$/',   // Smultron seems to create these
        '/^~lock.(.*)#$/', // Windows 7 lockfiles
    ];

    /**
     * A reference to the main Server class
     *
     * @var \Sabre\DAV\Server
     */
    protected $server;

    /**
     * This is the directory where this plugin
     * will store it's files.
     *
     * @var string
     */
    private $dataDir;

    /**
     * Creates the plugin.
     *
     * Make sure you specify a directory for your files. If you don't, we
     * will use PHP's directory for session-storage instead, and you might
     * not want that.
     *
     * @param string|null $dataDir
     */
    function __construct($dataDir = null) {

        if (!$dataDir) $dataDir = ini_get('session.save_path') . '/sabredav/';
        if (!is_dir($dataDir)) mkdir($dataDir);
        $this->dataDir = $dataDir;

    }

    /**
     * Initialize the plugin
     *
     * This is called automatically be the Server class after this plugin is
     * added with Sabre\DAV\Server::addPlugin()
     *
     * @param Server $server
     * @return void
     */
    function initialize(Server $server) {

        $this->server = $server;
        $server->on('beforeMethod',    [$this, 'beforeMethod']);
        $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);

    }

    /**
     * This method is called before any HTTP method handler
     *
     * This method intercepts any GET, DELETE, PUT and PROPFIND calls to
     * filenames that are known to match the 'temporary file' regex.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function beforeMethod(RequestInterface $request, ResponseInterface $response) {

        if (!$tempLocation = $this->isTempFile($request->getPath()))
            return;

        switch ($request->getMethod()) {
            case 'GET' :
                return $this->httpGet($request, $response, $tempLocation);
            case 'PUT' :
                return $this->httpPut($request, $response, $tempLocation);
            case 'PROPFIND' :
                return $this->httpPropfind($request, $response, $tempLocation);
            case 'DELETE' :
                return $this->httpDelete($request, $response, $tempLocation);
        }
        return;

    }

    /**
     * This method is invoked if some subsystem creates a new file.
     *
     * This is used to deal with HTTP LOCK requests which create a new
     * file.
     *
     * @param string $uri
     * @param resource $data
     * @param ICollection $parent
     * @param bool $modified Should be set to true, if this event handler
     *                       changed &$data.
     * @return bool
     */
    function beforeCreateFile($uri, $data, ICollection $parent, $modified) {

        if ($tempPath = $this->isTempFile($uri)) {

            $hR = $this->server->httpResponse;
            $hR->setHeader('X-Sabre-Temp', 'true');
            file_put_contents($tempPath, $data);
            return false;
        }
        return;

    }

    /**
     * This method will check if the url matches the temporary file pattern
     * if it does, it will return an path based on $this->dataDir for the
     * temporary file storage.
     *
     * @param string $path
     * @return bool|string
     */
    protected function isTempFile($path) {

        // We're only interested in the basename.
        list(, $tempPath) = URLUtil::splitPath($path);

        foreach ($this->temporaryFilePatterns as $tempFile) {

            if (preg_match($tempFile, $tempPath)) {
                return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile';
            }

        }

        return false;

    }


    /**
     * This method handles the GET method for temporary files.
     * If the file doesn't exist, it will return false which will kick in
     * the regular system for the GET method.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $hR
     * @param string $tempLocation
     * @return bool
     */
    function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) {

        if (!file_exists($tempLocation)) return;

        $hR->setHeader('Content-Type', 'application/octet-stream');
        $hR->setHeader('Content-Length', filesize($tempLocation));
        $hR->setHeader('X-Sabre-Temp', 'true');
        $hR->setStatus(200);
        $hR->setBody(fopen($tempLocation, 'r'));
        return false;

    }

    /**
     * This method handles the PUT method.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $hR
     * @param string $tempLocation
     * @return bool
     */
    function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation) {

        $hR->setHeader('X-Sabre-Temp', 'true');

        $newFile = !file_exists($tempLocation);

        if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) {
             throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied');
        }

        file_put_contents($tempLocation, $this->server->httpRequest->getBody());
        $hR->setStatus($newFile ? 201 : 200);
        return false;

    }

    /**
     * This method handles the DELETE method.
     *
     * If the file didn't exist, it will return false, which will make the
     * standard HTTP DELETE handler kick in.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $hR
     * @param string $tempLocation
     * @return bool
     */
    function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation) {

        if (!file_exists($tempLocation)) return;

        unlink($tempLocation);
        $hR->setHeader('X-Sabre-Temp', 'true');
        $hR->setStatus(204);
        return false;

    }

    /**
     * This method handles the PROPFIND method.
     *
     * It's a very lazy method, it won't bother checking the request body
     * for which properties were requested, and just sends back a default
     * set of properties.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $hR
     * @param string $tempLocation
     * @return bool
     */
    function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation) {

        if (!file_exists($tempLocation)) return;

        $hR->setHeader('X-Sabre-Temp', 'true');
        $hR->setStatus(207);
        $hR->setHeader('Content-Type', 'application/xml; charset=utf-8');

        $properties = [
            'href' => $request->getPath(),
            200    => [
                '{DAV:}getlastmodified'                 => new Xml\Property\GetLastModified(filemtime($tempLocation)),
                '{DAV:}getcontentlength'                => filesize($tempLocation),
                '{DAV:}resourcetype'                    => new Xml\Property\ResourceType(null),
                '{' . Server::NS_SABREDAV . '}tempFile' => true,

            ],
        ];

        $data = $this->server->generateMultiStatus([$properties]);
        $hR->setBody($data);
        return false;

    }


    /**
     * This method returns the directory where the temporary files should be stored.
     *
     * @return string
     */
    protected function getDataDir()
    {
        return $this->dataDir;
    }
}
<?php

namespace Sabre\DAV;

use Sabre\HTTP\URLUtil;

/**
 * The tree object is responsible for basic tree operations.
 *
 * It allows for fetching nodes by path, facilitates deleting, copying and
 * moving.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Tree {

    /**
     * The root node
     *
     * @var ICollection
     */
    protected $rootNode;

    /**
     * This is the node cache. Accessed nodes are stored here.
     * Arrays keys are path names, values are the actual nodes.
     *
     * @var array
     */
    protected $cache = [];

    /**
     * Creates the object
     *
     * This method expects the rootObject to be passed as a parameter
     *
     * @param ICollection $rootNode
     */
    function __construct(ICollection $rootNode) {

        $this->rootNode = $rootNode;

    }

    /**
     * Returns the INode object for the requested path
     *
     * @param string $path
     * @return INode
     */
    function getNodeForPath($path) {

        $path = trim($path, '/');
        if (isset($this->cache[$path])) return $this->cache[$path];

        // Is it the root node?
        if (!strlen($path)) {
            return $this->rootNode;
        }

        // Attempting to fetch its parent
        list($parentName, $baseName) = URLUtil::splitPath($path);

        // If there was no parent, we must simply ask it from the root node.
        if ($parentName === "") {
            $node = $this->rootNode->getChild($baseName);
        } else {
            // Otherwise, we recursively grab the parent and ask him/her.
            $parent = $this->getNodeForPath($parentName);

            if (!($parent instanceof ICollection))
                throw new Exception\NotFound('Could not find node at path: ' . $path);

            $node = $parent->getChild($baseName);

        }

        $this->cache[$path] = $node;
        return $node;

    }

    /**
     * This function allows you to check if a node exists.
     *
     * Implementors of this class should override this method to make
     * it cheaper.
     *
     * @param string $path
     * @return bool
     */
    function nodeExists($path) {

        try {

            // The root always exists
            if ($path === '') return true;

            list($parent, $base) = URLUtil::splitPath($path);

            $parentNode = $this->getNodeForPath($parent);
            if (!$parentNode instanceof ICollection) return false;
            return $parentNode->childExists($base);

        } catch (Exception\NotFound $e) {

            return false;

        }

    }

    /**
     * Copies a file from path to another
     *
     * @param string $sourcePath The source location
     * @param string $destinationPath The full destination path
     * @return void
     */
    function copy($sourcePath, $destinationPath) {

        $sourceNode = $this->getNodeForPath($sourcePath);

        // grab the dirname and basename components
        list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath);

        $destinationParent = $this->getNodeForPath($destinationDir);
        $this->copyNode($sourceNode, $destinationParent, $destinationName);

        $this->markDirty($destinationDir);

    }

    /**
     * Moves a file from one location to another
     *
     * @param string $sourcePath The path to the file which should be moved
     * @param string $destinationPath The full destination path, so not just the destination parent node
     * @return int
     */
    function move($sourcePath, $destinationPath) {

        list($sourceDir) = URLUtil::splitPath($sourcePath);
        list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath);

        if ($sourceDir === $destinationDir) {
            // If this is a 'local' rename, it means we can just trigger a rename.
            $sourceNode = $this->getNodeForPath($sourcePath);
            $sourceNode->setName($destinationName);
        } else {
            $newParentNode = $this->getNodeForPath($destinationDir);
            $moveSuccess = false;
            if ($newParentNode instanceof IMoveTarget) {
                // The target collection may be able to handle the move
                $sourceNode = $this->getNodeForPath($sourcePath);
                $moveSuccess = $newParentNode->moveInto($destinationName, $sourcePath, $sourceNode);
            }
            if (!$moveSuccess) {
                $this->copy($sourcePath, $destinationPath);
                $this->getNodeForPath($sourcePath)->delete();
            }
        }
        $this->markDirty($sourceDir);
        $this->markDirty($destinationDir);

    }

    /**
     * Deletes a node from the tree
     *
     * @param string $path
     * @return void
     */
    function delete($path) {

        $node = $this->getNodeForPath($path);
        $node->delete();

        list($parent) = URLUtil::splitPath($path);
        $this->markDirty($parent);

    }

    /**
     * Returns a list of childnodes for a given path.
     *
     * @param string $path
     * @return array
     */
    function getChildren($path) {

        $node = $this->getNodeForPath($path);
        $children = $node->getChildren();
        $basePath = trim($path, '/');
        if ($basePath !== '') $basePath .= '/';

        foreach ($children as $child) {

            $this->cache[$basePath . $child->getName()] = $child;

        }
        return $children;

    }

    /**
     * This method is called with every tree update
     *
     * Examples of tree updates are:
     *   * node deletions
     *   * node creations
     *   * copy
     *   * move
     *   * renaming nodes
     *
     * If Tree classes implement a form of caching, this will allow
     * them to make sure caches will be expired.
     *
     * If a path is passed, it is assumed that the entire subtree is dirty
     *
     * @param string $path
     * @return void
     */
    function markDirty($path) {

        // We don't care enough about sub-paths
        // flushing the entire cache
        $path = trim($path, '/');
        foreach ($this->cache as $nodePath => $node) {
            if ($path === '' || $nodePath == $path || strpos($nodePath, $path . '/') === 0)
                unset($this->cache[$nodePath]);

        }

    }

    /**
     * This method tells the tree system to pre-fetch and cache a list of
     * children of a single parent.
     *
     * There are a bunch of operations in the WebDAV stack that request many
     * children (based on uris), and sometimes fetching many at once can
     * optimize this.
     *
     * This method returns an array with the found nodes. It's keys are the
     * original paths. The result may be out of order.
     *
     * @param array $paths List of nodes that must be fetched.
     * @return array
     */
    function getMultipleNodes($paths) {

        // Finding common parents
        $parents = [];
        foreach ($paths as $path) {
            list($parent, $node) = URLUtil::splitPath($path);
            if (!isset($parents[$parent])) {
                $parents[$parent] = [$node];
            } else {
                $parents[$parent][] = $node;
            }
        }

        $result = [];

        foreach ($parents as $parent => $children) {

            $parentNode = $this->getNodeForPath($parent);
            if ($parentNode instanceof IMultiGet) {
                foreach ($parentNode->getMultipleChildren($children) as $childNode) {
                    $fullPath = $parent . '/' . $childNode->getName();
                    $result[$fullPath] = $childNode;
                    $this->cache[$fullPath] = $childNode;
                }
            } else {
                foreach ($children as $child) {
                    $fullPath = $parent . '/' . $child;
                    $result[$fullPath] = $this->getNodeForPath($fullPath);
                }
            }

        }

        return $result;

    }


    /**
     * copyNode
     *
     * @param INode $source
     * @param ICollection $destinationParent
     * @param string $destinationName
     * @return void
     */
    protected function copyNode(INode $source, ICollection $destinationParent, $destinationName = null) {

        if (!$destinationName) $destinationName = $source->getName();

        if ($source instanceof IFile) {

            $data = $source->get();

            // If the body was a string, we need to convert it to a stream
            if (is_string($data)) {
                $stream = fopen('php://temp', 'r+');
                fwrite($stream, $data);
                rewind($stream);
                $data = $stream;
            }
            $destinationParent->createFile($destinationName, $data);
            $destination = $destinationParent->getChild($destinationName);

        } elseif ($source instanceof ICollection) {

            $destinationParent->createDirectory($destinationName);

            $destination = $destinationParent->getChild($destinationName);
            foreach ($source->getChildren() as $child) {

                $this->copyNode($child, $destination);

            }

        }
        if ($source instanceof IProperties && $destination instanceof IProperties) {

            $props = $source->getProperties([]);
            $propPatch = new PropPatch($props);
            $destination->propPatch($propPatch);
            $propPatch->commit();

        }

    }

}
<?php

namespace Sabre\DAV;

/**
 * UUID Utility
 *
 * This class has static methods to generate and validate UUID's.
 * UUIDs are used a decent amount within various *DAV standards, so it made
 * sense to include it.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class UUIDUtil {

    /**
     * Returns a pseudo-random v4 UUID
     *
     * This function is based on a comment by Andrew Moore on php.net
     *
     * @see http://www.php.net/manual/en/function.uniqid.php#94959
     * @return string
     */
    static function getUUID() {

        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            // 32 bits for "time_low"
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),

            // 16 bits for "time_mid"
            mt_rand(0, 0xffff),

            // 16 bits for "time_hi_and_version",
            // four most significant bits holds version number 4
            mt_rand(0, 0x0fff) | 0x4000,

            // 16 bits, 8 bits for "clk_seq_hi_res",
            // 8 bits for "clk_seq_low",
            // two most significant bits holds zero and one for variant DCE1.1
            mt_rand(0, 0x3fff) | 0x8000,

            // 48 bits for "node"
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
    }

    /**
     * Checks if a string is a valid UUID.
     *
     * @param string $uuid
     * @return bool
     */
    static function validateUUID($uuid) {

        return preg_match(
            '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
            $uuid
        ) !== 0;

    }

}
<?php

namespace Sabre\DAV;

/**
 * This class contains the SabreDAV version constants.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Version {

    /**
     * Full version number
     */
    const VERSION = '3.2.2';

}
<?php

namespace Sabre\DAV\Xml\Element;

use Sabre\DAV\Xml\Property\Complex;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * This class is responsible for decoding the {DAV:}prop element as it appears
 * in {DAV:}property-update.
 *
 * This class doesn't return an instance of itself. It just returns a
 * key->value array.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Prop implements XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        // If there's no children, we don't do anything.
        if ($reader->isEmptyElement) {
            $reader->next();
            return [];
        }

        $values = [];

        $reader->read();
        do {

            if ($reader->nodeType === Reader::ELEMENT) {

                $clark = $reader->getClark();
                $values[$clark] = self::parseCurrentElement($reader)['value'];

            } else {
                $reader->read();
            }

        } while ($reader->nodeType !== Reader::END_ELEMENT);

        $reader->read();

        return $values;

    }

    /**
     * This function behaves similar to Sabre\Xml\Reader::parseCurrentElement,
     * but instead of creating deep xml array structures, it will turn any
     * top-level element it doesn't recognize into either a string, or an
     * XmlFragment class.
     *
     * This method returns arn array with 2 properties:
     *   * name - A clark-notation XML element name.
     *   * value - The parsed value.
     *
     * @param Reader $reader
     * @return array
     */
    private static function parseCurrentElement(Reader $reader) {

        $name = $reader->getClark();

        if (array_key_exists($name, $reader->elementMap)) {
            $deserializer = $reader->elementMap[$name];
            if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
                $value = call_user_func([$deserializer, 'xmlDeserialize'], $reader);
            } elseif (is_callable($deserializer)) {
                $value = call_user_func($deserializer, $reader);
            } else {
                $type = gettype($deserializer);
                if ($type === 'string') {
                    $type .= ' (' . $deserializer . ')';
                } elseif ($type === 'object') {
                    $type .= ' (' . get_class($deserializer) . ')';
                }
                throw new \LogicException('Could not use this type as a deserializer: ' . $type);
            }
        } else {
            $value = Complex::xmlDeserialize($reader);
        }

        return [
            'name'  => $name,
            'value' => $value,
        ];

    }

}
<?php

namespace Sabre\DAV\Xml\Element;

use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * WebDAV {DAV:}response parser
 *
 * This class parses the {DAV:}response element, as defined in:
 *
 * https://tools.ietf.org/html/rfc4918#section-14.24
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Response implements Element {

    /**
     * Url for the response
     *
     * @var string
     */
    protected $href;

    /**
     * Propertylist, ordered by HTTP status code
     *
     * @var array
     */
    protected $responseProperties;

    /**
     * The HTTP status for an entire response.
     *
     * This is currently only used in WebDAV-Sync
     *
     * @var string
     */
    protected $httpStatus;

    /**
     * The href argument is a url relative to the root of the server. This
     * class will calculate the full path.
     *
     * The responseProperties argument is a list of properties
     * within an array with keys representing HTTP status codes
     *
     * Besides specific properties, the entire {DAV:}response element may also
     * have a http status code.
     * In most cases you don't need it.
     *
     * This is currently used by the Sync extension to indicate that a node is
     * deleted.
     *
     * @param string $href
     * @param array $responseProperties
     * @param string $httpStatus
     */
    function __construct($href, array $responseProperties, $httpStatus = null) {

        $this->href = $href;
        $this->responseProperties = $responseProperties;
        $this->httpStatus = $httpStatus;

    }

    /**
     * Returns the url
     *
     * @return string
     */
    function getHref() {

        return $this->href;

    }

    /**
     * Returns the httpStatus value
     *
     * @return string
     */
    function getHttpStatus() {

        return $this->httpStatus;

    }

    /**
     * Returns the property list
     *
     * @return array
     */
    function getResponseProperties() {

        return $this->responseProperties;

    }


    /**
     * The serialize method is called during xml writing.
     *
     * It should use the $writer argument to encode this object into XML.
     *
     * Important note: it is not needed to create the parent element. The
     * parent element is already created, and we only have to worry about
     * attributes, child elements and text (if any).
     *
     * Important note 2: If you are writing any new elements, you are also
     * responsible for closing them.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        if ($status = $this->getHTTPStatus()) {
            $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]);
        }
        $writer->writeElement('{DAV:}href', $writer->contextUri . \Sabre\HTTP\encodePath($this->getHref()));

        $empty = true;

        foreach ($this->getResponseProperties() as $status => $properties) {

            // Skipping empty lists
            if (!$properties || (!ctype_digit($status) && !is_int($status))) {
                continue;
            }
            $empty = false;
            $writer->startElement('{DAV:}propstat');
            $writer->writeElement('{DAV:}prop', $properties);
            $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]);
            $writer->endElement(); // {DAV:}propstat

        }
        if ($empty) {
            /*
             * The WebDAV spec _requires_ at least one DAV:propstat to appear for
             * every DAV:response. In some circumstances however, there are no
             * properties to encode.
             *
             * In those cases we MUST specify at least one DAV:propstat anyway, with
             * no properties.
             */
            $writer->writeElement('{DAV:}propstat', [
                '{DAV:}prop'   => [],
                '{DAV:}status' => 'HTTP/1.1 418 ' . \Sabre\HTTP\Response::$statusCodes[418]
            ]);

        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $reader->pushContext();

        $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue';

        // We are overriding the parser for {DAV:}prop. This deserializer is
        // almost identical to the one for Sabre\Xml\Element\KeyValue.
        //
        // The difference is that if there are any child-elements inside of
        // {DAV:}prop, that have no value, normally any deserializers are
        // called. But we don't want this, because a singular element without
        // child-elements implies 'no value' in {DAV:}prop, so we want to skip
        // deserializers and just set null for those.
        $reader->elementMap['{DAV:}prop'] = function(Reader $reader) {

            if ($reader->isEmptyElement) {
                $reader->next();
                return [];
            }
            $values = [];
            $reader->read();
            do {
                if ($reader->nodeType === Reader::ELEMENT) {
                    $clark = $reader->getClark();

                    if ($reader->isEmptyElement) {
                        $values[$clark] = null;
                        $reader->next();
                    } else {
                        $values[$clark] = $reader->parseCurrentElement()['value'];
                    }
                } else {
                    $reader->read();
                }
            } while ($reader->nodeType !== Reader::END_ELEMENT);
            $reader->read();
            return $values;

        };
        $elems = $reader->parseInnerTree();
        $reader->popContext();

        $href = null;
        $propertyLists = [];
        $statusCode = null;

        foreach ($elems as $elem) {

            switch ($elem['name']) {

                case '{DAV:}href' :
                    $href = $elem['value'];
                    break;
                case '{DAV:}propstat' :
                    $status = $elem['value']['{DAV:}status'];
                    list(, $status, ) = explode(' ', $status, 3);
                    $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : [];
                    if ($properties) $propertyLists[$status] = $properties;
                    break;
                case '{DAV:}status' :
                    list(, $statusCode, ) = explode(' ', $elem['value'], 3);
                    break;

            }

        }

        return new self($href, $propertyLists, $statusCode);

    }

}
<?php

namespace Sabre\DAV\Xml\Element;

use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Sharing\Plugin;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAV\Xml\Property\ShareAccess;
use Sabre\Xml\Deserializer;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * This class represents the {DAV:}sharee element.
 *
 * @copyright Copyright (C) fruux GmbH. (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Sharee implements Element {

    /**
     * A URL. Usually a mailto: address, could also be a principal url.
     * This uniquely identifies the sharee.
     *
     * @var string
     */
    public $href;

    /**
     * A local principal path. The server will do its best to locate the
     * principal uri based on the given uri. If we could find a local matching
     * principal uri, this property will contain the value.
     *
     * @var string|null
     */
    public $principal;

    /**
     * A list of WebDAV properties that describe the sharee. This might for
     * example contain a {DAV:}displayname with the real name of the user.
     *
     * @var array
     */
    public $properties = [];

    /**
     * Share access level. One of the Sabre\DAV\Sharing\Plugin::ACCESS
     * constants.
     *
     * Can be one of:
     *
     * ACCESS_READ
     * ACCESS_READWRITE
     * ACCESS_SHAREDOWNER
     * ACCESS_NOACCESS
     *
     * depending on context.
     *
     * @var int
     */
    public $access;

    /**
     * When a sharee is originally invited to a share, the sharer may add
     * a comment. This will be placed in this property.
     *
     * @var string
     */
    public $comment;

    /**
     * The status of the invite, should be one of the
     * Sabre\DAV\Sharing\Plugin::INVITE constants.
     *
     * @var int
     */
    public $inviteStatus;

    /**
     * Creates the object
     *
     * $properties will be used to populate all internal properties.
     *
     * @param array $properties
     */
    function __construct(array $properties = []) {

        foreach ($properties as $k => $v) {

            if (property_exists($this, $k)) {
                $this->$k = $v;
            } else {
                throw new \InvalidArgumentException('Unknown property: ' . $k);
            }

        }

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {


        $writer->write([
            new Href($this->href),
            '{DAV:}prop'         => $this->properties,
            '{DAV:}share-access' => new ShareAccess($this->access),
        ]);
        switch ($this->inviteStatus) {
            case Plugin::INVITE_NORESPONSE :
                $writer->writeElement('{DAV:}invite-noresponse');
                break;
            case Plugin::INVITE_ACCEPTED :
                $writer->writeElement('{DAV:}invite-accepted');
                break;
            case Plugin::INVITE_DECLINED :
                $writer->writeElement('{DAV:}invite-declined');
                break;
            case Plugin::INVITE_INVALID :
                $writer->writeElement('{DAV:}invite-invalid');
                break;
        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        // Temporarily override configuration
        $reader->pushContext();
        $reader->elementMap['{DAV:}share-access'] = 'Sabre\DAV\Xml\Property\ShareAccess';
        $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\keyValue';

        $elems = Deserializer\keyValue($reader, 'DAV:');

        // Restore previous configuration
        $reader->popContext();

        $sharee = new self();
        if (!isset($elems['href'])) {
            throw new BadRequest('Every {DAV:}sharee must have a {DAV:}href child-element');
        }
        $sharee->href = $elems['href'];

        if (isset($elems['prop'])) {
            $sharee->properties = $elems['prop'];
        }
        if (isset($elems['comment'])) {
            $sharee->comment = $elems['comment'];
        }
        if (!isset($elems['share-access'])) {
            throw new BadRequest('Every {DAV:}sharee must have a {DAV:}share-access child element');
        }
        $sharee->access = $elems['share-access']->getValue();
        return $sharee;

    }

}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\Xml\Element\XmlFragment;
use Sabre\Xml\Reader;

/**
 * This class represents a 'complex' property that didn't have a default
 * decoder.
 *
 * It's basically a container for an xml snippet.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Complex extends XmlFragment {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $xml = $reader->readInnerXml();

        if ($reader->nodeType === Reader::ELEMENT && $reader->isEmptyElement) {
            // Easy!
            $reader->next();
            return null;
        }
        // Now we have a copy of the inner xml, we need to traverse it to get
        // all the strings. If there's no non-string data, we just return the
        // string, otherwise we return an instance of this class.
        $reader->read();

        $nonText = false;
        $text = '';

        while (true) {

            switch ($reader->nodeType) {
                case Reader::ELEMENT :
                    $nonText = true;
                    $reader->next();
                    continue 2;
                case Reader::TEXT :
                case Reader::CDATA :
                    $text .= $reader->value;
                    break;
                case Reader::END_ELEMENT :
                    break 2;
            }
            $reader->read();

        }

        // Make sure we advance the cursor one step further.
        $reader->read();

        if ($nonText) {
            $new = new self($xml);
            return $new;
        } else {
            return $text;
        }

    }


}
<?php

namespace Sabre\DAV\Xml\Property;

use DateTime;
use DateTimeZone;
use Sabre\HTTP;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * This property represents the {DAV:}getlastmodified property.
 *
 * Defined in:
 * http://tools.ietf.org/html/rfc4918#section-15.7
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class GetLastModified implements Element {

    /**
     * time
     *
     * @var DateTime
     */
    public $time;

    /**
     * Constructor
     *
     * @param int|DateTime $time
     */
    function __construct($time) {

        if ($time instanceof DateTime) {
            $this->time = clone $time;
        } else {
            $this->time = new DateTime('@' . $time);
        }

        // Setting timezone to UTC
        $this->time->setTimezone(new DateTimeZone('UTC'));

    }

    /**
     * getTime
     *
     * @return DateTime
     */
    function getTime() {

        return $this->time;

    }

    /**
     * The serialize method is called during xml writing.
     *
     * It should use the $writer argument to encode this object into XML.
     *
     * Important note: it is not needed to create the parent element. The
     * parent element is already created, and we only have to worry about
     * attributes, child elements and text (if any).
     *
     * Important note 2: If you are writing any new elements, you are also
     * responsible for closing them.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $writer->write(
            HTTP\Util::toHTTPDate($this->time)
        );

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        return
            new self(new DateTime($reader->parseInnerTree()));

    }
}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\DAV\Browser\HtmlOutput;
use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Uri;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * Href property
 *
 * This class represents any WebDAV property that contains a {DAV:}href
 * element, and there are many.
 *
 * It can support either 1 or more hrefs. If while unserializing no valid
 * {DAV:}href elements were found, this property will unserialize itself as
 * null.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Href implements Element, HtmlOutput {

    /**
     * List of uris
     *
     * @var array
     */
    protected $hrefs;

    /**
     * Constructor
     *
     * You must either pass a string for a single href, or an array of hrefs.
     *
     * If auto-prefix is set to false, the hrefs will be treated as absolute
     * and not relative to the servers base uri.
     *
     * @param string|string[] $hrefs
     */
    function __construct($hrefs) {

        if (is_string($hrefs)) {
            $hrefs = [$hrefs];
        }
        $this->hrefs = $hrefs;

    }

    /**
     * Returns the first Href.
     *
     * @return string
     */
    function getHref() {

        return $this->hrefs[0];

    }

    /**
     * Returns the hrefs as an array
     *
     * @return array
     */
    function getHrefs() {

        return $this->hrefs;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->getHrefs() as $href) {
            $href = Uri\resolve($writer->contextUri, $href);
            $writer->writeElement('{DAV:}href', $href);
        }

    }

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html) {

        $links = [];
        foreach ($this->getHrefs() as $href) {
            $links[] = $html->link($href);
        }
        return implode('<br />', $links);

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $hrefs = [];
        foreach ((array)$reader->parseInnerTree() as $elem) {
            if ($elem['name'] !== '{DAV:}href')
                continue;

            $hrefs[] = $elem['value'];

        }
        if ($hrefs) {
            return new self($hrefs, false);
        }

    }

}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\DAV\Xml\Element\Sharee;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * This class represents the {DAV:}invite property.
 *
 * This property is defined here:
 * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-03#section-4.4.2
 *
 * This property is used by clients to determine who currently has access to
 * a shared resource, what their access level is and what their invite status
 * is.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Invite implements XmlSerializable {

    /**
     * A list of sharees
     *
     * @var Sharee[]
     */
    public $sharees = [];

    /**
     * Creates the property.
     *
     * @param Sharee[] $sharees
     */
    function __construct(array $sharees) {

        $this->sharees = $sharees;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->sharees as $sharee) {
            $writer->writeElement('{DAV:}sharee', $sharee);
        }

    }

}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\HTTP;

/**
 * LocalHref property
 *
 * Like the Href property, this element represents {DAV:}href. The difference
 * is that this is used strictly for paths on the server. The LocalHref property
 * will prepare the path so it's a valid URI.
 *
 * These two objects behave identically:
 *    new LocalHref($path)
 *    new Href(\Sabre\HTTP\encodePath($path))
 *
 * LocalPath basically ensures that your spaces are %20, and everything that
 * needs to be is uri encoded.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class LocalHref extends Href {

    /**
     * Constructor
     *
     * You must either pass a string for a single href, or an array of hrefs.
     *
     * If auto-prefix is set to false, the hrefs will be treated as absolute
     * and not relative to the servers base uri.
     *
     * @param string|string[] $hrefs
     */
    function __construct($hrefs) {

        parent::__construct(array_map(
            function($href) {
                return \Sabre\HTTP\encodePath($href);
            },
            (array)$hrefs
        ));

    }

}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\DAV;
use Sabre\DAV\Locks\LockInfo;
use Sabre\Xml\Element\XmlFragment;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * Represents {DAV:}lockdiscovery property.
 *
 * This property is defined here:
 * http://tools.ietf.org/html/rfc4918#section-15.8
 *
 * This property contains all the open locks on a given resource
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class LockDiscovery implements XmlSerializable {

    /**
     * locks
     *
     * @var LockInfo[]
     */
    public $locks;

    /**
     * Hides the {DAV:}lockroot element from the response.
     *
     * It was reported that showing the lockroot in the response can break
     * Office 2000 compatibility.
     *
     * @var bool
     */
    static $hideLockRoot = false;

    /**
     * __construct
     *
     * @param LockInfo[] $locks
     */
    function __construct($locks) {

        $this->locks = $locks;

    }

    /**
     * The serialize method is called during xml writing.
     *
     * It should use the $writer argument to encode this object into XML.
     *
     * Important note: it is not needed to create the parent element. The
     * parent element is already created, and we only have to worry about
     * attributes, child elements and text (if any).
     *
     * Important note 2: If you are writing any new elements, you are also
     * responsible for closing them.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->locks as $lock) {

            $writer->startElement('{DAV:}activelock');

            $writer->startElement('{DAV:}lockscope');
            if ($lock->scope === LockInfo::SHARED) {
                $writer->writeElement('{DAV:}shared');
            } else {
                $writer->writeElement('{DAV:}exclusive');
            }

            $writer->endElement(); // {DAV:}lockscope

            $writer->startElement('{DAV:}locktype');
            $writer->writeElement('{DAV:}write');
            $writer->endElement(); // {DAV:}locktype

            if (!self::$hideLockRoot) {
                $writer->startElement('{DAV:}lockroot');
                $writer->writeElement('{DAV:}href', $writer->contextUri . $lock->uri);
                $writer->endElement(); // {DAV:}lockroot
            }
            $writer->writeElement('{DAV:}depth', ($lock->depth == DAV\Server::DEPTH_INFINITY ? 'infinity' : $lock->depth));
            $writer->writeElement('{DAV:}timeout', 'Second-' . $lock->timeout);

            $writer->startElement('{DAV:}locktoken');
            $writer->writeElement('{DAV:}href', 'opaquelocktoken:' . $lock->token);
            $writer->endElement(); // {DAV:}locktoken

            $writer->writeElement('{DAV:}owner', new XmlFragment($lock->owner));
            $writer->endElement(); // {DAV:}activelock

        }

    }

}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\DAV\Browser\HtmlOutput;
use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;

/**
 * {DAV:}resourcetype property
 *
 * This class represents the {DAV:}resourcetype property, as defined in:
 *
 * https://tools.ietf.org/html/rfc4918#section-15.9
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ResourceType extends Element\Elements implements HtmlOutput {

    /**
     * Constructor
     *
     * You can either pass null (for no resourcetype), a string (for a single
     * resourcetype) or an array (for multiple).
     *
     * The resourcetype must be specified in clark-notation
     *
     * @param array|string|null $resourceTypes
     */
    function __construct($resourceTypes = null) {

        parent::__construct((array)$resourceTypes);

    }

    /**
     * Returns the values in clark-notation
     *
     * For example array('{DAV:}collection')
     *
     * @return array
     */
    function getValue() {

        return $this->value;

    }

    /**
     * Checks if the principal contains a certain value
     *
     * @param string $type
     * @return bool
     */
    function is($type) {

        return in_array($type, $this->value);

    }

    /**
     * Adds a resourcetype value to this property
     *
     * @param string $type
     * @return void
     */
    function add($type) {

        $this->value[] = $type;
        $this->value = array_unique($this->value);

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        return
            new self(parent::xmlDeserialize($reader));

    }

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html) {

        return implode(
            ', ',
            array_map([$html, 'xmlName'], $this->getValue())
        );

    }

}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Sharing\Plugin as SharingPlugin;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * This class represents the {DAV:}share-access property.
 *
 * This property is defined here:
 * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-03#section-4.4.1
 *
 * This property is used to indicate if a resource is a shared resource, and
 * whether the instance of the shared resource is the original instance, or
 * an instance belonging to a sharee.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ShareAccess implements Element {

    /**
     * Either SHARED or SHAREDOWNER
     *
     * @var int
     */
    protected $value;

    /**
     * Creates the property.
     *
     * The constructor value must be one of the
     * \Sabre\DAV\Sharing\Plugin::ACCESS_ constants.
     *
     * @param int $shareAccess
     */
    function __construct($shareAccess) {

        $this->value = $shareAccess;

    }

    /**
     * Returns the current value.
     *
     * @return int
     */
    function getValue() {

        return $this->value;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        switch ($this->value) {

            case SharingPlugin::ACCESS_NOTSHARED :
                $writer->writeElement('{DAV:}not-shared');
                break;
            case SharingPlugin::ACCESS_SHAREDOWNER :
                $writer->writeElement('{DAV:}shared-owner');
                break;
            case SharingPlugin::ACCESS_READ :
                $writer->writeElement('{DAV:}read');
                break;
            case SharingPlugin::ACCESS_READWRITE :
                $writer->writeElement('{DAV:}read-write');
                break;
            case SharingPlugin::ACCESS_NOACCESS :
                $writer->writeElement('{DAV:}no-access');
                break;

        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = $reader->parseInnerTree();
        foreach ($elems as $elem) {
            switch ($elem['name']) {
                case '{DAV:}not-shared' :
                    return new self(SharingPlugin::ACCESS_NOTSHARED);
                case '{DAV:}shared-owner' :
                    return new self(SharingPlugin::ACCESS_SHAREDOWNER);
                case '{DAV:}read' :
                    return new self(SharingPlugin::ACCESS_READ);
                case '{DAV:}read-write' :
                    return new self(SharingPlugin::ACCESS_READWRITE);
                case '{DAV:}no-access' :
                    return new self(SharingPlugin::ACCESS_NOACCESS);
            }
        }
        throw new BadRequest('Invalid value for {DAV:}share-access element');

    }
}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * This class represents the {DAV:}supportedlock property.
 *
 * This property is defined here:
 * http://tools.ietf.org/html/rfc4918#section-15.10
 *
 * This property contains information about what kind of locks
 * this server supports.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedLock implements XmlSerializable {

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $writer->writeElement('{DAV:}lockentry', [
            '{DAV:}lockscope' => ['{DAV:}exclusive' => null],
            '{DAV:}locktype'  => ['{DAV:}write' => null],
        ]);
        $writer->writeElement('{DAV:}lockentry', [
            '{DAV:}lockscope' => ['{DAV:}shared' => null],
            '{DAV:}locktype'  => ['{DAV:}write' => null],
        ]);

    }
}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\DAV\Browser\HtmlOutput;
use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * supported-method-set property.
 *
 * This property is defined in RFC3253, but since it's
 * so common in other webdav-related specs, it is part of the core server.
 *
 * This property is defined here:
 * http://tools.ietf.org/html/rfc3253#section-3.1.3
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedMethodSet implements XmlSerializable, HtmlOutput {

    /**
     * List of methods
     *
     * @var string[]
     */
    protected $methods = [];

    /**
     * Creates the property
     *
     * @param string[] $methods
     */
    function __construct(array $methods) {

        $this->methods = $methods;

    }

    /**
     * Returns the list of supported http methods.
     *
     * @return string[]
     */
    function getValue() {

        return $this->methods;

    }

    /**
     * Returns true or false if the property contains a specific method.
     *
     * @param string $methodName
     * @return bool
     */
    function has($methodName) {

        return in_array(
            $methodName,
            $this->methods
        );

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->getValue() as $val) {
            $writer->startElement('{DAV:}supported-method');
            $writer->writeAttribute('name', $val);
            $writer->endElement();
        }

    }

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html) {

        return implode(
            ', ',
            array_map([$html, 'h'], $this->getValue())
        );

    }

}
<?php

namespace Sabre\DAV\Xml\Property;

use Sabre\DAV;
use Sabre\DAV\Browser\HtmlOutput;
use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * supported-report-set property.
 *
 * This property is defined in RFC3253, but since it's
 * so common in other webdav-related specs, it is part of the core server.
 *
 * This property is defined here:
 * http://tools.ietf.org/html/rfc3253#section-3.1.5
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedReportSet implements XmlSerializable, HtmlOutput {

    /**
     * List of reports
     *
     * @var array
     */
    protected $reports = [];

    /**
     * Creates the property
     *
     * Any reports passed in the constructor
     * should be valid report-types in clark-notation.
     *
     * Either a string or an array of strings must be passed.
     *
     * @param string|string[] $reports
     */
    function __construct($reports = null) {

        if (!is_null($reports))
            $this->addReport($reports);

    }

    /**
     * Adds a report to this property
     *
     * The report must be a string in clark-notation.
     * Multiple reports can be specified as an array.
     *
     * @param mixed $report
     * @return void
     */
    function addReport($report) {

        $report = (array)$report;

        foreach ($report as $r) {

            if (!preg_match('/^{([^}]*)}(.*)$/', $r))
                throw new DAV\Exception('Reportname must be in clark-notation');

            $this->reports[] = $r;

        }

    }

    /**
     * Returns the list of supported reports
     *
     * @return string[]
     */
    function getValue() {

        return $this->reports;

    }

    /**
     * Returns true or false if the property contains a specific report.
     *
     * @param string $reportName
     * @return bool
     */
    function has($reportName) {

        return in_array(
            $reportName,
            $this->reports
        );

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->getValue() as $val) {
            $writer->startElement('{DAV:}supported-report');
            $writer->startElement('{DAV:}report');
            $writer->writeElement($val);
            $writer->endElement();
            $writer->endElement();
        }

    }

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html) {

        return implode(
            ', ',
            array_map([$html, 'xmlName'], $this->getValue())
        );

    }

}
<?php

namespace Sabre\DAV\Xml\Request;

use Sabre\DAV\Locks\LockInfo;
use Sabre\Xml\Element\KeyValue;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * WebDAV LOCK request parser.
 *
 * This class parses the {DAV:}lockinfo request, as defined in:
 *
 * http://tools.ietf.org/html/rfc4918#section-9.10
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Lock implements XmlDeserializable {

    /**
     * Owner of the lock
     *
     * @var string
     */
    public $owner;

    /**
     * Scope of the lock.
     *
     * Either LockInfo::SHARED or LockInfo::EXCLUSIVE
     * @var int
     */
    public $scope;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $reader->pushContext();
        $reader->elementMap['{DAV:}owner'] = 'Sabre\\Xml\\Element\\XmlFragment';

        $values = KeyValue::xmlDeserialize($reader);

        $reader->popContext();

        $new = new self();
        $new->owner = !empty($values['{DAV:}owner']) ? $values['{DAV:}owner']->getXml() : null;
        $new->scope = LockInfo::SHARED;

        if (isset($values['{DAV:}lockscope'])) {
            foreach ($values['{DAV:}lockscope'] as $elem) {
                if ($elem['name'] === '{DAV:}exclusive') $new->scope = LockInfo::EXCLUSIVE;
            }
        }
        return $new;

    }

}
<?php

namespace Sabre\DAV\Xml\Request;

use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * WebDAV Extended MKCOL request parser.
 *
 * This class parses the {DAV:}mkol request, as defined in:
 *
 * https://tools.ietf.org/html/rfc5689#section-5.1
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class MkCol implements XmlDeserializable {

    /**
     * The list of properties that will be set.
     *
     * @var array
     */
    protected $properties = [];

    /**
     * Returns a key=>value array with properties that are supposed to get set
     * during creation of the new collection.
     *
     * @return array
     */
    function getProperties() {

        return $this->properties;

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $self = new self();

        $elementMap = $reader->elementMap;
        $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
        $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
        $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue';

        $elems = $reader->parseInnerTree($elementMap);

        foreach ($elems as $elem) {
            if ($elem['name'] === '{DAV:}set') {
                $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
            }
        }

        return $self;

    }

}
<?php

namespace Sabre\DAV\Xml\Request;

use Sabre\Xml\Element\KeyValue;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * WebDAV PROPFIND request parser.
 *
 * This class parses the {DAV:}propfind request, as defined in:
 *
 * https://tools.ietf.org/html/rfc4918#section-14.20
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PropFind implements XmlDeserializable {

    /**
     * If this is set to true, this was an 'allprop' request.
     *
     * @var bool
     */
    public $allProp = false;

    /**
     * The property list
     *
     * @var null|array
     */
    public $properties;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $self = new self();

        $reader->pushContext();
        $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements';

        foreach (KeyValue::xmlDeserialize($reader) as $k => $v) {

            switch ($k) {
                case '{DAV:}prop' :
                    $self->properties = $v;
                    break;
                case '{DAV:}allprop' :
                    $self->allProp = true;

            }

        }

        $reader->popContext();

        return $self;

    }

}
<?php

namespace Sabre\DAV\Xml\Request;

use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * WebDAV PROPPATCH request parser.
 *
 * This class parses the {DAV:}propertyupdate request, as defined in:
 *
 * https://tools.ietf.org/html/rfc4918#section-14.20
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PropPatch implements Element {

    /**
     * The list of properties that will be updated and removed.
     *
     * If a property will be removed, it's value will be set to null.
     *
     * @var array
     */
    public $properties = [];

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->properties as $propertyName => $propertyValue) {

            if (is_null($propertyValue)) {
                $writer->startElement("{DAV:}remove");
                $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]);
                $writer->endElement();
            } else {
                $writer->startElement("{DAV:}set");
                $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]);
                $writer->endElement();
            }

        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $self = new self();

        $elementMap = $reader->elementMap;
        $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop';
        $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue';
        $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue';

        $elems = $reader->parseInnerTree($elementMap);

        foreach ($elems as $elem) {
            if ($elem['name'] === '{DAV:}set') {
                $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']);
            }
            if ($elem['name'] === '{DAV:}remove') {

                // Ensuring there are no values.
                foreach ($elem['value']['{DAV:}prop'] as $remove => $value) {
                    $self->properties[$remove] = null;
                }

            }
        }

        return $self;

    }

}
<?php

namespace Sabre\DAV\Xml\Request;

use Sabre\DAV\Xml\Element\Sharee;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * ShareResource request parser.
 *
 * This class parses the {DAV:}share-resource POST request as defined in:
 *
 * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-01#section-5.3.2.1
 *
 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ShareResource implements XmlDeserializable {

    /**
     * The list of new people added or updated or removed from the share.
     *
     * @var Sharee[]
     */
    public $sharees = [];

    /**
     * Constructor
     *
     * @param Sharee[] $sharees
     */
    function __construct(array $sharees) {

        $this->sharees = $sharees;

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = $reader->parseInnerTree([
            '{DAV:}sharee'       => 'Sabre\DAV\Xml\Element\Sharee',
            '{DAV:}share-access' => 'Sabre\DAV\Xml\Property\ShareAccess',
            '{DAV:}prop'         => 'Sabre\Xml\Deserializer\keyValue',
        ]);

        $sharees = [];

        foreach ($elems as $elem) {
            if ($elem['name'] !== '{DAV:}sharee') continue;
            $sharees[] = $elem['value'];

        }

        return new self($sharees);

    }

}
<?php

namespace Sabre\DAV\Xml\Request;

use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Element\KeyValue;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * SyncCollection request parser.
 *
 * This class parses the {DAV:}sync-collection reprot, as defined in:
 *
 * http://tools.ietf.org/html/rfc6578#section-3.2
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://www.rooftopsolutions.nl/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SyncCollectionReport implements XmlDeserializable {

    /**
     * The sync-token the client supplied for the report.
     *
     * @var string|null
     */
    public $syncToken;

    /**
     * The 'depth' of the sync the client is interested in.
     *
     * @var int
     */
    public $syncLevel;

    /**
     * Maximum amount of items returned.
     *
     * @var int|null
     */
    public $limit;

    /**
     * The list of properties that are being requested for every change.
     *
     * @var null|array
     */
    public $properties;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $self = new self();

        $reader->pushContext();

        $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements';
        $elems = KeyValue::xmlDeserialize($reader);

        $reader->popContext();

        $required = [
            '{DAV:}sync-token',
            '{DAV:}prop',
            ];

        foreach ($required as $elem) {
            if (!array_key_exists($elem, $elems)) {
                throw new BadRequest('The ' . $elem . ' element in the {DAV:}sync-collection report is required');
            }
        }


        $self->properties = $elems['{DAV:}prop'];
        $self->syncToken = $elems['{DAV:}sync-token'];

        if (isset($elems['{DAV:}limit'])) {
            $nresults = null;
            foreach ($elems['{DAV:}limit'] as $child) {
                if ($child['name'] === '{DAV:}nresults') {
                    $nresults = (int)$child['value'];
                }
            }
            $self->limit = $nresults;
        }

        if (isset($elems['{DAV:}sync-level'])) {

            $value = $elems['{DAV:}sync-level'];
            if ($value === 'infinity') {
                $value = \Sabre\DAV\Server::DEPTH_INFINITY;
            }
            $self->syncLevel = $value;

        }

        return $self;

    }

}
<?php

namespace Sabre\DAV\Xml\Response;

use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * WebDAV MultiStatus parser
 *
 * This class parses the {DAV:}multistatus response, as defined in:
 * https://tools.ietf.org/html/rfc4918#section-14.16
 *
 * And it also adds the {DAV:}synctoken change from:
 * http://tools.ietf.org/html/rfc6578#section-6.4
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class MultiStatus implements Element {

    /**
     * The responses
     *
     * @var \Sabre\DAV\Xml\Element\Response[]
     */
    protected $responses;

    /**
     * A sync token (from RFC6578).
     *
     * @var string
     */
    protected $syncToken;

    /**
     * Constructor
     *
     * @param \Sabre\DAV\Xml\Element\Response[] $responses
     * @param string $syncToken
     */
    function __construct(array $responses, $syncToken = null) {

        $this->responses = $responses;
        $this->syncToken = $syncToken;

    }

    /**
     * Returns the response list.
     *
     * @return \Sabre\DAV\Xml\Element\Response[]
     */
    function getResponses() {

        return $this->responses;

    }

    /**
     * Returns the sync-token, if available.
     *
     * @return string|null
     */
    function getSyncToken() {

        return $this->syncToken;

    }

    /**
     * The serialize method is called during xml writing.
     *
     * It should use the $writer argument to encode this object into XML.
     *
     * Important note: it is not needed to create the parent element. The
     * parent element is already created, and we only have to worry about
     * attributes, child elements and text (if any).
     *
     * Important note 2: If you are writing any new elements, you are also
     * responsible for closing them.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->getResponses() as $response) {
            $writer->writeElement('{DAV:}response', $response);
        }
        if ($syncToken = $this->getSyncToken()) {
            $writer->writeElement('{DAV:}sync-token', $syncToken);
        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elementMap = $reader->elementMap;
        $elementMap['{DAV:}prop'] = 'Sabre\\DAV\\Xml\\Element\\Prop';
        $elements = $reader->parseInnerTree($elementMap);

        $responses = [];
        $syncToken = null;

        if ($elements) foreach ($elements as $elem) {
            if ($elem['name'] === '{DAV:}response') {
                $responses[] = $elem['value'];
            }
            if ($elem['name'] === '{DAV:}sync-token') {
                $syncToken = $elem['value'];
            }
        }

        return new self($responses, $syncToken);

    }

}
<?php

namespace Sabre\DAV\Xml;

/**
 * XML service for WebDAV
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Service extends \Sabre\Xml\Service {

    /**
     * This is a list of XML elements that we automatically map to PHP classes.
     *
     * For instance, this list may contain an entry `{DAV:}propfind` that would
     * be mapped to Sabre\DAV\Xml\Request\PropFind
     */
    public $elementMap = [
        '{DAV:}multistatus' => 'Sabre\\DAV\\Xml\\Response\\MultiStatus',
        '{DAV:}response'    => 'Sabre\\DAV\\Xml\\Element\\Response',

        // Requests
        '{DAV:}propfind'       => 'Sabre\\DAV\\Xml\\Request\\PropFind',
        '{DAV:}propertyupdate' => 'Sabre\\DAV\\Xml\\Request\\PropPatch',
        '{DAV:}mkcol'          => 'Sabre\\DAV\\Xml\\Request\\MkCol',

        // Properties
        '{DAV:}resourcetype' => 'Sabre\\DAV\\Xml\\Property\\ResourceType',

    ];

    /**
     * This is a default list of namespaces.
     *
     * If you are defining your own custom namespace, add it here to reduce
     * bandwidth and improve legibility of xml bodies.
     *
     * @var array
     */
    public $namespaceMap = [
        'DAV:'                   => 'd',
        'http://sabredav.org/ns' => 's',
    ];

}
<?php

namespace Sabre\DAVACL;

use Sabre\DAV;
use Sabre\HTTP\URLUtil;

/**
 * Principals Collection
 *
 * This is a helper class that easily allows you to create a collection that
 * has a childnode for every principal.
 *
 * To use this class, simply implement the getChildForPrincipal method.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractPrincipalCollection extends DAV\Collection implements IPrincipalCollection {

    /**
     * Principal backend
     *
     * @var PrincipalBackend\BackendInterface
     */
    protected $principalBackend;

    /**
     * The path to the principals we're listing from.
     *
     * @var string
     */
    protected $principalPrefix;

    /**
     * If this value is set to true, it effectively disables listing of users
     * it still allows user to find other users if they have an exact url.
     *
     * @var bool
     */
    public $disableListing = false;

    /**
     * Creates the object
     *
     * This object must be passed the principal backend. This object will
     * filter all principals from a specified prefix ($principalPrefix). The
     * default is 'principals', if your principals are stored in a different
     * collection, override $principalPrefix
     *
     *
     * @param PrincipalBackend\BackendInterface $principalBackend
     * @param string $principalPrefix
     */
    function __construct(PrincipalBackend\BackendInterface $principalBackend, $principalPrefix = 'principals') {

        $this->principalPrefix = $principalPrefix;
        $this->principalBackend = $principalBackend;

    }

    /**
     * This method returns a node for a principal.
     *
     * The passed array contains principal information, and is guaranteed to
     * at least contain a uri item. Other properties may or may not be
     * supplied by the authentication backend.
     *
     * @param array $principalInfo
     * @return IPrincipal
     */
    abstract function getChildForPrincipal(array $principalInfo);

    /**
     * Returns the name of this collection.
     *
     * @return string
     */
    function getName() {

        list(, $name) = URLUtil::splitPath($this->principalPrefix);
        return $name;

    }

    /**
     * Return the list of users
     *
     * @return array
     */
    function getChildren() {

        if ($this->disableListing)
            throw new DAV\Exception\MethodNotAllowed('Listing members of this collection is disabled');

        $children = [];
        foreach ($this->principalBackend->getPrincipalsByPrefix($this->principalPrefix) as $principalInfo) {

            $children[] = $this->getChildForPrincipal($principalInfo);


        }
        return $children;

    }

    /**
     * Returns a child object, by its name.
     *
     * @param string $name
     * @throws DAV\Exception\NotFound
     * @return DAV\INode
     */
    function getChild($name) {

        $principalInfo = $this->principalBackend->getPrincipalByPath($this->principalPrefix . '/' . $name);
        if (!$principalInfo) throw new DAV\Exception\NotFound('Principal with name ' . $name . ' not found');
        return $this->getChildForPrincipal($principalInfo);

    }

    /**
     * This method is used to search for principals matching a set of
     * properties.
     *
     * This search is specifically used by RFC3744's principal-property-search
     * REPORT. You should at least allow searching on
     * http://sabredav.org/ns}email-address.
     *
     * The actual search should be a unicode-non-case-sensitive search. The
     * keys in searchProperties are the WebDAV property names, while the values
     * are the property values to search on.
     *
     * By default, if multiple properties are submitted to this method, the
     * various properties should be combined with 'AND'. If $test is set to
     * 'anyof', it should be combined using 'OR'.
     *
     * This method should simply return a list of 'child names', which may be
     * used to call $this->getChild in the future.
     *
     * @param array $searchProperties
     * @param string $test
     * @return array
     */
    function searchPrincipals(array $searchProperties, $test = 'allof') {

        $result = $this->principalBackend->searchPrincipals($this->principalPrefix, $searchProperties, $test);
        $r = [];

        foreach ($result as $row) {
            list(, $r[]) = URLUtil::splitPath($row);
        }

        return $r;

    }

    /**
     * Finds a principal by its URI.
     *
     * This method may receive any type of uri, but mailto: addresses will be
     * the most common.
     *
     * Implementation of this API is optional. It is currently used by the
     * CalDAV system to find principals based on their email addresses. If this
     * API is not implemented, some features may not work correctly.
     *
     * This method must return a relative principal path, or null, if the
     * principal was not found or you refuse to find it.
     *
     * @param string $uri
     * @return string
     */
    function findByUri($uri) {

        return $this->principalBackend->findByUri($uri, $this->principalPrefix);

    }

}
<?php

namespace Sabre\DAVACL;

/**
 * This trait is a default implementation of the IACL interface.
 *
 * In many cases you only want to implement 1 or to of the IACL functions,
 * this trait allows you to be a bit lazier.
 *
 * By default this trait grants all privileges to the owner of the resource.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (https://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
trait ACLTrait {

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return null;

    }

    /**
     * Returns a group principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getGroup() {

        return null;

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        return [
            [
                'privilege' => '{DAV:}all',
                'principal' => '{DAV:}owner',
                'protected' => true,
            ]
        ];

    }

    /**
     * Updates the ACL
     *
     * This method will receive a list of new ACE's as an array argument.
     *
     * @param array $acl
     * @return void
     */
    function setACL(array $acl) {

        throw new \Sabre\DAV\Exception\Forbidden('Setting ACL is not supported on this node');
    }

    /**
     * Returns the list of supported privileges for this node.
     *
     * The returned data structure is a list of nested privileges.
     * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
     * standard structure.
     *
     * If null is returned from this method, the default privilege set is used,
     * which is fine for most common usecases.
     *
     * @return array|null
     */
    function getSupportedPrivilegeSet() {

        return null;

    }

}
<?php

namespace Sabre\DAVACL\Exception;

use Sabre\DAV;

/**
 * This exception is thrown when a client attempts to set conflicting
 * permissions.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AceConflict extends DAV\Exception\Conflict {

    /**
     * Adds in extra information in the xml response.
     *
     * This method adds the {DAV:}no-ace-conflict element as defined in rfc3744
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $doc = $errorNode->ownerDocument;

        $np = $doc->createElementNS('DAV:', 'd:no-ace-conflict');
        $errorNode->appendChild($np);

    }

}
<?php

namespace Sabre\DAVACL\Exception;

use Sabre\DAV;

/**
 * NeedPrivileges
 *
 * The 403-need privileges is thrown when a user didn't have the appropriate
 * permissions to perform an operation
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class NeedPrivileges extends DAV\Exception\Forbidden {

    /**
     * The relevant uri
     *
     * @var string
     */
    protected $uri;

    /**
     * The privileges the user didn't have.
     *
     * @var array
     */
    protected $privileges;

    /**
     * Constructor
     *
     * @param string $uri
     * @param array $privileges
     */
    function __construct($uri, array $privileges) {

        $this->uri = $uri;
        $this->privileges = $privileges;

        parent::__construct('User did not have the required privileges (' . implode(',', $privileges) . ') for path "' . $uri . '"');

    }

    /**
     * Adds in extra information in the xml response.
     *
     * This method adds the {DAV:}need-privileges element as defined in rfc3744
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $doc = $errorNode->ownerDocument;

        $np = $doc->createElementNS('DAV:', 'd:need-privileges');
        $errorNode->appendChild($np);

        foreach ($this->privileges as $privilege) {

            $resource = $doc->createElementNS('DAV:', 'd:resource');
            $np->appendChild($resource);

            $resource->appendChild($doc->createElementNS('DAV:', 'd:href', $server->getBaseUri() . $this->uri));

            $priv = $doc->createElementNS('DAV:', 'd:privilege');
            $resource->appendChild($priv);

            preg_match('/^{([^}]*)}(.*)$/', $privilege, $privilegeParts);
            $priv->appendChild($doc->createElementNS($privilegeParts[1], 'd:' . $privilegeParts[2]));


        }

    }

}
<?php

namespace Sabre\DAVACL\Exception;

use Sabre\DAV;

/**
 * This exception is thrown when a user tries to set a privilege that's marked
 * as abstract.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class NoAbstract extends DAV\Exception\PreconditionFailed {

    /**
     * Adds in extra information in the xml response.
     *
     * This method adds the {DAV:}no-abstract element as defined in rfc3744
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $doc = $errorNode->ownerDocument;

        $np = $doc->createElementNS('DAV:', 'd:no-abstract');
        $errorNode->appendChild($np);

    }

}
<?php

namespace Sabre\DAVACL\Exception;

use Sabre\DAV;

/**
 * If a client tried to set a privilege assigned to a non-existent principal,
 * this exception will be thrown.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class NotRecognizedPrincipal extends DAV\Exception\PreconditionFailed {

    /**
     * Adds in extra information in the xml response.
     *
     * This method adds the {DAV:}recognized-principal element as defined in rfc3744
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $doc = $errorNode->ownerDocument;

        $np = $doc->createElementNS('DAV:', 'd:recognized-principal');
        $errorNode->appendChild($np);

    }

}
<?php

namespace Sabre\DAVACL\Exception;

use Sabre\DAV;

/**
 * If a client tried to set a privilege that doesn't exist, this exception will
 * be thrown.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class NotSupportedPrivilege extends DAV\Exception\PreconditionFailed {

    /**
     * Adds in extra information in the xml response.
     *
     * This method adds the {DAV:}not-supported-privilege element as defined in rfc3744
     *
     * @param DAV\Server $server
     * @param \DOMElement $errorNode
     * @return void
     */
    function serialize(DAV\Server $server, \DOMElement $errorNode) {

        $doc = $errorNode->ownerDocument;

        $np = $doc->createElementNS('DAV:', 'd:not-supported-privilege');
        $errorNode->appendChild($np);

    }

}
<?php

namespace Sabre\DAVACL\FS;

use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\FSExt\Directory as BaseCollection;
use Sabre\DAVACL\ACLTrait;
use Sabre\DAVACL\IACL;

/**
 * This is an ACL-enabled collection.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Collection extends BaseCollection implements IACL {

    use ACLTrait;

    /**
     * A list of ACL rules.
     *
     * @var array
     */
    protected $acl;

    /**
     * Owner uri, or null for no owner.
     *
     * @var string|null
     */
    protected $owner;

    /**
     * Constructor
     *
     * @param string $path on-disk path.
     * @param array $acl ACL rules.
     * @param string|null $owner principal owner string.
     */
    function __construct($path, array $acl, $owner = null) {

        parent::__construct($path);
        $this->acl = $acl;
        $this->owner = $owner;

    }

    /**
     * Returns a specific child node, referenced by its name
     *
     * This method must throw Sabre\DAV\Exception\NotFound if the node does not
     * exist.
     *
     * @param string $name
     * @throws NotFound
     * @return \Sabre\DAV\INode
     */
    function getChild($name) {

        $path = $this->path . '/' . $name;

        if (!file_exists($path)) throw new NotFound('File could not be located');
        if ($name == '.' || $name == '..') throw new Forbidden('Permission denied to . and ..');

        if (is_dir($path)) {

            return new self($path, $this->acl, $this->owner);

        } else {

            return new File($path, $this->acl, $this->owner);

        }

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->owner;

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        return $this->acl;

    }

}
<?php

namespace Sabre\DAVACL\FS;

use Sabre\DAV\FSExt\File as BaseFile;
use Sabre\DAVACL\ACLTrait;
use Sabre\DAVACL\IACL;

/**
 * This is an ACL-enabled file node.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class File extends BaseFile implements IACL {

    use ACLTrait;

    /**
     * A list of ACL rules.
     *
     * @var array
     */
    protected $acl;

    /**
     * Owner uri, or null for no owner.
     *
     * @var string|null
     */
    protected $owner;

    /**
     * Constructor
     *
     * @param string $path on-disk path.
     * @param array $acl ACL rules.
     * @param string|null $owner principal owner string.
     */
    function __construct($path, array $acl, $owner = null) {

        parent::__construct($path);
        $this->acl = $acl;
        $this->owner = $owner;

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->owner;

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        return $this->acl;

    }

}
<?php

namespace Sabre\DAVACL\FS;

use Sabre\DAVACL\AbstractPrincipalCollection;
use Sabre\DAVACL\ACLTrait;
use Sabre\DAVACL\IACL;
use Sabre\DAVACL\PrincipalBackend\BackendInterface;
use Sabre\Uri;

/**
 * This collection contains a collection for every principal.
 * It is similar to /home on many unix systems.
 *
 * The per-user collections can only be accessed by the user who owns the
 * collection.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class HomeCollection extends AbstractPrincipalCollection implements IACL {

    use ACLTrait;

    /**
     * Name of this collection.
     *
     * @var string
     */
    public $collectionName = 'home';

    /**
     * Path to where the users' files are actually stored.
     *
     * @var string
     */
    protected $storagePath;

    /**
     * Creates the home collection.
     *
     * @param BackendInterface $principalBackend
     * @param string $storagePath Where the actual files are stored.
     * @param string $principalPrefix list of principals to iterate.
     */
    function __construct(BackendInterface $principalBackend, $storagePath, $principalPrefix = 'principals') {

        parent::__construct($principalBackend, $principalPrefix);
        $this->storagePath = $storagePath;

    }

    /**
     * Returns the name of the node.
     *
     * This is used to generate the url.
     *
     * @return string
     */
    function getName() {

        return $this->collectionName;

    }

    /**
     * Returns a principals' collection of files.
     *
     * The passed array contains principal information, and is guaranteed to
     * at least contain a uri item. Other properties may or may not be
     * supplied by the authentication backend.
     *
     * @param array $principalInfo
     * @return \Sabre\DAV\INode
     */
    function getChildForPrincipal(array $principalInfo) {

        $owner = $principalInfo['uri'];
        $acl = [
            [
                'privilege' => '{DAV:}all',
                'principal' => '{DAV:}owner',
                'protected' => true,
            ],
        ];

        list(, $principalBaseName) = Uri\split($owner);

        $path = $this->storagePath . '/' . $principalBaseName;

        if (!is_dir($path)) {
            mkdir($path, 0777, true);
        }
        return new Collection(
            $path,
            $acl,
            $owner
        );

    }


    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {

        return [
            [
                'principal' => '{DAV:}authenticated',
                'privilege' => '{DAV:}read',
                'protected' => true,
            ]
        ];

    }

}
<?php

namespace Sabre\DAVACL;

use Sabre\DAV;

/**
 * ACL-enabled node
 *
 * If you want to add WebDAV ACL to a node, you must implement this class
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IACL extends DAV\INode {

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner();

    /**
     * Returns a group principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getGroup();

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL();

    /**
     * Updates the ACL
     *
     * This method will receive a list of new ACE's as an array argument.
     *
     * @param array $acl
     * @return void
     */
    function setACL(array $acl);

    /**
     * Returns the list of supported privileges for this node.
     *
     * The returned data structure is a list of nested privileges.
     * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
     * standard structure.
     *
     * If null is returned from this method, the default privilege set is used,
     * which is fine for most common usecases.
     *
     * @return array|null
     */
    function getSupportedPrivilegeSet();

}
<?php

namespace Sabre\DAVACL;

use Sabre\DAV;

/**
 * IPrincipal interface
 *
 * Implement this interface to define your own principals
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IPrincipal extends DAV\INode {

    /**
     * Returns a list of alternative urls for a principal
     *
     * This can for example be an email address, or ldap url.
     *
     * @return array
     */
    function getAlternateUriSet();

    /**
     * Returns the full principal url
     *
     * @return string
     */
    function getPrincipalUrl();

    /**
     * Returns the list of group members
     *
     * If this principal is a group, this function should return
     * all member principal uri's for the group.
     *
     * @return array
     */
    function getGroupMemberSet();

    /**
     * Returns the list of groups this principal is member of
     *
     * If this principal is a member of a (list of) groups, this function
     * should return a list of principal uri's for it's members.
     *
     * @return array
     */
    function getGroupMembership();

    /**
     * Sets a list of group members
     *
     * If this principal is a group, this method sets all the group members.
     * The list of members is always overwritten, never appended to.
     *
     * This method should throw an exception if the members could not be set.
     *
     * @param array $principals
     * @return void
     */
    function setGroupMemberSet(array $principals);

    /**
     * Returns the displayname
     *
     * This should be a human readable name for the principal.
     * If none is available, return the nodename.
     *
     * @return string
     */
    function getDisplayName();

}
<?php

namespace Sabre\DAVACL;

use Sabre\DAV;

/**
 * Principal Collection interface.
 *
 * Implement this interface to ensure that your principal collection can be
 * searched using the principal-property-search REPORT.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface IPrincipalCollection extends DAV\ICollection {

    /**
     * This method is used to search for principals matching a set of
     * properties.
     *
     * This search is specifically used by RFC3744's principal-property-search
     * REPORT. You should at least allow searching on
     * http://sabredav.org/ns}email-address.
     *
     * The actual search should be a unicode-non-case-sensitive search. The
     * keys in searchProperties are the WebDAV property names, while the values
     * are the property values to search on.
     *
     * By default, if multiple properties are submitted to this method, the
     * various properties should be combined with 'AND'. If $test is set to
     * 'anyof', it should be combined using 'OR'.
     *
     * This method should simply return a list of 'child names', which may be
     * used to call $this->getChild in the future.
     *
     * @param array $searchProperties
     * @param string $test
     * @return array
     */
    function searchPrincipals(array $searchProperties, $test = 'allof');

    /**
     * Finds a principal by its URI.
     *
     * This method may receive any type of uri, but mailto: addresses will be
     * the most common.
     *
     * Implementation of this API is optional. It is currently used by the
     * CalDAV system to find principals based on their email addresses. If this
     * API is not implemented, some features may not work correctly.
     *
     * This method must return a relative principal path, or null, if the
     * principal was not found or you refuse to find it.
     *
     * @param string $uri
     * @return string
     */
    function findByUri($uri);

}
<?php

namespace Sabre\DAVACL;

use Sabre\DAV;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotAuthenticated;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\INode;
use Sabre\DAV\Xml\Property\Href;
use Sabre\DAVACL\Exception\NeedPrivileges;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\Uri;

/**
 * SabreDAV ACL Plugin
 *
 * This plugin provides functionality to enforce ACL permissions.
 * ACL is defined in RFC3744.
 *
 * In addition it also provides support for the {DAV:}current-user-principal
 * property, defined in RFC5397 and the {DAV:}expand-property report, as
 * defined in RFC3253.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Plugin extends DAV\ServerPlugin {

    /**
     * Recursion constants
     *
     * This only checks the base node
     */
    const R_PARENT = 1;

    /**
     * Recursion constants
     *
     * This checks every node in the tree
     */
    const R_RECURSIVE = 2;

    /**
     * Recursion constants
     *
     * This checks every parentnode in the tree, but not leaf-nodes.
     */
    const R_RECURSIVEPARENTS = 3;

    /**
     * Reference to server object.
     *
     * @var DAV\Server
     */
    protected $server;

    /**
     * List of urls containing principal collections.
     * Modify this if your principals are located elsewhere.
     *
     * @var array
     */
    public $principalCollectionSet = [
        'principals',
    ];

    /**
     * By default nodes that are inaccessible by the user, can still be seen
     * in directory listings (PROPFIND on parent with Depth: 1)
     *
     * In certain cases it's desirable to hide inaccessible nodes. Setting this
     * to true will cause these nodes to be hidden from directory listings.
     *
     * @var bool
     */
    public $hideNodesFromListings = false;

    /**
     * This list of properties are the properties a client can search on using
     * the {DAV:}principal-property-search report.
     *
     * The keys are the property names, values are descriptions.
     *
     * @var array
     */
    public $principalSearchPropertySet = [
        '{DAV:}displayname'                     => 'Display name',
        '{http://sabredav.org/ns}email-address' => 'Email address',
    ];

    /**
     * Any principal uri's added here, will automatically be added to the list
     * of ACL's. They will effectively receive {DAV:}all privileges, as a
     * protected privilege.
     *
     * @var array
     */
    public $adminPrincipals = [];

    /**
     * The ACL plugin allows privileges to be assigned to users that are not
     * logged in. To facilitate that, it modifies the auth plugin's behavior
     * to only require login when a privileged operation was denied.
     *
     * Unauthenticated access can be considered a security concern, so it's
     * possible to turn this feature off to harden the server's security.
     *
     * @var bool
     */
    public $allowUnauthenticatedAccess = true;

    /**
     * Returns a list of features added by this plugin.
     *
     * This list is used in the response of a HTTP OPTIONS request.
     *
     * @return array
     */
    function getFeatures() {

        return ['access-control', 'calendarserver-principal-property-search'];

    }

    /**
     * Returns a list of available methods for a given url
     *
     * @param string $uri
     * @return array
     */
    function getMethods($uri) {

        return ['ACL'];

    }

    /**
     * Returns a plugin name.
     *
     * Using this name other plugins will be able to access other plugins
     * using Sabre\DAV\Server::getPlugin
     *
     * @return string
     */
    function getPluginName() {

        return 'acl';

    }

    /**
     * Returns a list of reports this plugin supports.
     *
     * This will be used in the {DAV:}supported-report-set property.
     * Note that you still need to subscribe to the 'report' event to actually
     * implement them
     *
     * @param string $uri
     * @return array
     */
    function getSupportedReportSet($uri) {

        return [
            '{DAV:}expand-property',
            '{DAV:}principal-match',
            '{DAV:}principal-property-search',
            '{DAV:}principal-search-property-set',
        ];

    }


    /**
     * Checks if the current user has the specified privilege(s).
     *
     * You can specify a single privilege, or a list of privileges.
     * This method will throw an exception if the privilege is not available
     * and return true otherwise.
     *
     * @param string $uri
     * @param array|string $privileges
     * @param int $recursion
     * @param bool $throwExceptions if set to false, this method won't throw exceptions.
     * @throws NeedPrivileges
     * @throws NotAuthenticated
     * @return bool
     */
    function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) {

        if (!is_array($privileges)) $privileges = [$privileges];

        $acl = $this->getCurrentUserPrivilegeSet($uri);

        $failed = [];
        foreach ($privileges as $priv) {

            if (!in_array($priv, $acl)) {
                $failed[] = $priv;
            }

        }

        if ($failed) {
            if ($this->allowUnauthenticatedAccess && is_null($this->getCurrentUserPrincipal())) {
                // We are not authenticated. Kicking in the Auth plugin.
                $authPlugin = $this->server->getPlugin('auth');
                $reasons = $authPlugin->getLoginFailedReasons();
                $authPlugin->challenge(
                    $this->server->httpRequest,
                    $this->server->httpResponse
                );
                throw new notAuthenticated(implode(', ', $reasons) . '. Login was needed for privilege: ' . implode(', ', $failed) . ' on ' . $uri);
            }
            if ($throwExceptions) {

                throw new NeedPrivileges($uri, $failed);
            } else {
                return false;
            }
        }
        return true;

    }

    /**
     * Returns the standard users' principal.
     *
     * This is one authoritative principal url for the current user.
     * This method will return null if the user wasn't logged in.
     *
     * @return string|null
     */
    function getCurrentUserPrincipal() {

        /** @var $authPlugin \Sabre\DAV\Auth\Plugin */
        $authPlugin = $this->server->getPlugin('auth');
        if (!$authPlugin) {
            return null;
        }
        return $authPlugin->getCurrentPrincipal();

    }


    /**
     * Returns a list of principals that's associated to the current
     * user, either directly or through group membership.
     *
     * @return array
     */
    function getCurrentUserPrincipals() {

        $currentUser = $this->getCurrentUserPrincipal();

        if (is_null($currentUser)) return [];

        return array_merge(
            [$currentUser],
            $this->getPrincipalMembership($currentUser)
        );

    }

    /**
     * Sets the default ACL rules.
     *
     * These rules are used for all nodes that don't implement the IACL interface.
     *
     * @param array $acl
     * @return void
     */
    function setDefaultAcl(array $acl) {

        $this->defaultAcl = $acl;

    }

    /**
     * Returns the default ACL rules.
     *
     * These rules are used for all nodes that don't implement the IACL interface.
     *
     * @return array
     */
    function getDefaultAcl() {

        return $this->defaultAcl;

    }

    /**
     * The default ACL rules.
     *
     * These rules are used for nodes that don't implement IACL. These default
     * set of rules allow anyone to do anything, as long as they are
     * authenticated.
     *
     * @var array
     */
    protected $defaultAcl = [
        [
            'principal' => '{DAV:}authenticated',
            'protected' => true,
            'privilege' => '{DAV:}all',
        ],
    ];

    /**
     * This array holds a cache for all the principals that are associated with
     * a single principal.
     *
     * @var array
     */
    protected $principalMembershipCache = [];


    /**
     * Returns all the principal groups the specified principal is a member of.
     *
     * @param string $mainPrincipal
     * @return array
     */
    function getPrincipalMembership($mainPrincipal) {

        // First check our cache
        if (isset($this->principalMembershipCache[$mainPrincipal])) {
            return $this->principalMembershipCache[$mainPrincipal];
        }

        $check = [$mainPrincipal];
        $principals = [];

        while (count($check)) {

            $principal = array_shift($check);

            $node = $this->server->tree->getNodeForPath($principal);
            if ($node instanceof IPrincipal) {
                foreach ($node->getGroupMembership() as $groupMember) {

                    if (!in_array($groupMember, $principals)) {

                        $check[] = $groupMember;
                        $principals[] = $groupMember;

                    }

                }

            }

        }

        // Store the result in the cache
        $this->principalMembershipCache[$mainPrincipal] = $principals;

        return $principals;

    }

    /**
     * Find out of a principal equals another principal.
     *
     * This is a quick way to find out whether a principal URI is part of a
     * group, or any subgroups.
     *
     * The first argument is the principal URI you want to check against. For
     * example the principal group, and the second argument is the principal of
     * which you want to find out of it is the same as the first principal, or
     * in a member of the first principal's group or subgroups.
     *
     * So the arguments are not interchangeable. If principal A is in group B,
     * passing 'B', 'A' will yield true, but 'A', 'B' is false.
     *
     * If the second argument is not passed, we will use the current user
     * principal.
     *
     * @param string $checkPrincipal
     * @param string $currentPrincipal
     * @return bool
     */
    function principalMatchesPrincipal($checkPrincipal, $currentPrincipal = null) {

        if (is_null($currentPrincipal)) {
            $currentPrincipal = $this->getCurrentUserPrincipal();
        }
        if ($currentPrincipal === $checkPrincipal) {
            return true;
        }
        return in_array(
            $checkPrincipal,
            $this->getPrincipalMembership($currentPrincipal)
        );

    }


    /**
     * Returns a tree of supported privileges for a resource.
     *
     * The returned array structure should be in this form:
     *
     * [
     *    [
     *       'privilege' => '{DAV:}read',
     *       'abstract'  => false,
     *       'aggregates' => []
     *    ]
     * ]
     *
     * Privileges can be nested using "aggregates". Doing so means that
     * if you assign someone the aggregating privilege, all the
     * sub-privileges will automatically be granted.
     *
     * Marking a privilege as abstract means that the privilege cannot be
     * directly assigned, but must be assigned via the parent privilege.
     *
     * So a more complex version might look like this:
     *
     * [
     *    [
     *       'privilege' => '{DAV:}read',
     *       'abstract'  => false,
     *       'aggregates' => [
     *          [
     *              'privilege'  => '{DAV:}read-acl',
     *              'abstract'   => false,
     *              'aggregates' => [],
     *          ]
     *       ]
     *    ]
     * ]
     *
     * @param string|INode $node
     * @return array
     */
    function getSupportedPrivilegeSet($node) {

        if (is_string($node)) {
            $node = $this->server->tree->getNodeForPath($node);
        }

        $supportedPrivileges = null;
        if ($node instanceof IACL) {
            $supportedPrivileges = $node->getSupportedPrivilegeSet();
        }

        if (is_null($supportedPrivileges)) {

            // Default
            $supportedPrivileges = [
                '{DAV:}read' => [
                    'abstract'   => false,
                    'aggregates' => [
                        '{DAV:}read-acl' => [
                            'abstract'   => false,
                            'aggregates' => [],
                        ],
                        '{DAV:}read-current-user-privilege-set' => [
                            'abstract'   => false,
                            'aggregates' => [],
                        ],
                    ],
                ],
                '{DAV:}write' => [
                    'abstract'   => false,
                    'aggregates' => [
                        '{DAV:}write-properties' => [
                            'abstract'   => false,
                            'aggregates' => [],
                        ],
                        '{DAV:}write-content' => [
                            'abstract'   => false,
                            'aggregates' => [],
                        ],
                        '{DAV:}unlock' => [
                            'abstract'   => false,
                            'aggregates' => [],
                        ],
                    ],
                ],
            ];
            if ($node instanceof DAV\ICollection) {
                $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}bind'] = [
                    'abstract'   => false,
                    'aggregates' => [],
                ];
                $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}unbind'] = [
                    'abstract'   => false,
                    'aggregates' => [],
                ];
            }
            if ($node instanceof IACL) {
                $supportedPrivileges['{DAV:}write']['aggregates']['{DAV:}write-acl'] = [
                    'abstract'   => false,
                    'aggregates' => [],
                ];
            }

        }

        $this->server->emit(
            'getSupportedPrivilegeSet',
            [$node, &$supportedPrivileges]
        );

        return $supportedPrivileges;

    }

    /**
     * Returns the supported privilege set as a flat list
     *
     * This is much easier to parse.
     *
     * The returned list will be index by privilege name.
     * The value is a struct containing the following properties:
     *   - aggregates
     *   - abstract
     *   - concrete
     *
     * @param string|INode $node
     * @return array
     */
    final function getFlatPrivilegeSet($node) {

        $privs = [
            'abstract'   => false,
            'aggregates' => $this->getSupportedPrivilegeSet($node)
        ];

        $fpsTraverse = null;
        $fpsTraverse = function($privName, $privInfo, $concrete, &$flat) use (&$fpsTraverse) {

            $myPriv = [
                'privilege'  => $privName,
                'abstract'   => isset($privInfo['abstract']) && $privInfo['abstract'],
                'aggregates' => [],
                'concrete'   => isset($privInfo['abstract']) && $privInfo['abstract'] ? $concrete : $privName,
            ];

            if (isset($privInfo['aggregates'])) {

                foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {

                    $myPriv['aggregates'][] = $subPrivName;

                }

            }

            $flat[$privName] = $myPriv;

            if (isset($privInfo['aggregates'])) {

                foreach ($privInfo['aggregates'] as $subPrivName => $subPrivInfo) {

                    $fpsTraverse($subPrivName, $subPrivInfo, $myPriv['concrete'], $flat);

                }

            }

        };

        $flat = [];
        $fpsTraverse('{DAV:}all', $privs, null, $flat);

        return $flat;

    }

    /**
     * Returns the full ACL list.
     *
     * Either a uri or a INode may be passed.
     *
     * null will be returned if the node doesn't support ACLs.
     *
     * @param string|DAV\INode $node
     * @return array
     */
    function getAcl($node) {

        if (is_string($node)) {
            $node = $this->server->tree->getNodeForPath($node);
        }
        if (!$node instanceof IACL) {
            return $this->getDefaultAcl();
        }
        $acl = $node->getACL();
        foreach ($this->adminPrincipals as $adminPrincipal) {
            $acl[] = [
                'principal' => $adminPrincipal,
                'privilege' => '{DAV:}all',
                'protected' => true,
            ];
        }
        return $acl;

    }

    /**
     * Returns a list of privileges the current user has
     * on a particular node.
     *
     * Either a uri or a DAV\INode may be passed.
     *
     * null will be returned if the node doesn't support ACLs.
     *
     * @param string|DAV\INode $node
     * @return array
     */
    function getCurrentUserPrivilegeSet($node) {

        if (is_string($node)) {
            $node = $this->server->tree->getNodeForPath($node);
        }

        $acl = $this->getACL($node);

        $collected = [];

        $isAuthenticated = $this->getCurrentUserPrincipal() !== null;

        foreach ($acl as $ace) {

            $principal = $ace['principal'];

            switch ($principal) {

                case '{DAV:}owner' :
                    $owner = $node->getOwner();
                    if ($owner && $this->principalMatchesPrincipal($owner)) {
                        $collected[] = $ace;
                    }
                    break;


                // 'all' matches for every user
                case '{DAV:}all' :
                    $collected[] = $ace;
                    break;

                case '{DAV:}authenticated' :
                    // Authenticated users only
                    if ($isAuthenticated) {
                        $collected[] = $ace;
                    }
                    break;

                case '{DAV:}unauthenticated' :
                    // Unauthenticated users only
                    if (!$isAuthenticated) {
                        $collected[] = $ace;
                    }
                    break;

                default :
                    if ($this->principalMatchesPrincipal($ace['principal'])) {
                        $collected[] = $ace;
                    }
                    break;

            }


        }

        // Now we deduct all aggregated privileges.
        $flat = $this->getFlatPrivilegeSet($node);

        $collected2 = [];
        while (count($collected)) {

            $current = array_pop($collected);
            $collected2[] = $current['privilege'];

            if (!isset($flat[$current['privilege']])) {
                // Ignoring privileges that are not in the supported-privileges list.
                $this->server->getLogger()->debug('A node has the "' . $current['privilege'] . '" in its ACL list, but this privilege was not reported in the supportedPrivilegeSet list. This will be ignored.');
                continue;
            }
            foreach ($flat[$current['privilege']]['aggregates'] as $subPriv) {
                $collected2[] = $subPriv;
                $collected[] = $flat[$subPriv];
            }

        }

        return array_values(array_unique($collected2));

    }


    /**
     * Returns a principal based on its uri.
     *
     * Returns null if the principal could not be found.
     *
     * @param string $uri
     * @return null|string
     */
    function getPrincipalByUri($uri) {

        $result = null;
        $collections = $this->principalCollectionSet;
        foreach ($collections as $collection) {

            try {
                $principalCollection = $this->server->tree->getNodeForPath($collection);
            } catch (NotFound $e) {
                // Ignore and move on
                continue;
            }

            if (!$principalCollection instanceof IPrincipalCollection) {
                // Not a principal collection, we're simply going to ignore
                // this.
                continue;
            }

            $result = $principalCollection->findByUri($uri);
            if ($result) {
                return $result;
            }

        }

    }

    /**
     * Principal property search
     *
     * This method can search for principals matching certain values in
     * properties.
     *
     * This method will return a list of properties for the matched properties.
     *
     * @param array $searchProperties    The properties to search on. This is a
     *                                   key-value list. The keys are property
     *                                   names, and the values the strings to
     *                                   match them on.
     * @param array $requestedProperties This is the list of properties to
     *                                   return for every match.
     * @param string $collectionUri      The principal collection to search on.
     *                                   If this is ommitted, the standard
     *                                   principal collection-set will be used.
     * @param string $test               "allof" to use AND to search the
     *                                   properties. 'anyof' for OR.
     * @return array     This method returns an array structure similar to
     *                  Sabre\DAV\Server::getPropertiesForPath. Returned
     *                  properties are index by a HTTP status code.
     */
    function principalSearch(array $searchProperties, array $requestedProperties, $collectionUri = null, $test = 'allof') {

        if (!is_null($collectionUri)) {
            $uris = [$collectionUri];
        } else {
            $uris = $this->principalCollectionSet;
        }

        $lookupResults = [];
        foreach ($uris as $uri) {

            $principalCollection = $this->server->tree->getNodeForPath($uri);
            if (!$principalCollection instanceof IPrincipalCollection) {
                // Not a principal collection, we're simply going to ignore
                // this.
                continue;
            }

            $results = $principalCollection->searchPrincipals($searchProperties, $test);
            foreach ($results as $result) {
                $lookupResults[] = rtrim($uri, '/') . '/' . $result;
            }

        }

        $matches = [];

        foreach ($lookupResults as $lookupResult) {

            list($matches[]) = $this->server->getPropertiesForPath($lookupResult, $requestedProperties, 0);

        }

        return $matches;

    }

    /**
     * Sets up the plugin
     *
     * This method is automatically called by the server class.
     *
     * @param DAV\Server $server
     * @return void
     */
    function initialize(DAV\Server $server) {

        if ($this->allowUnauthenticatedAccess) {
            $authPlugin = $server->getPlugin('auth');
            if (!$authPlugin) {
                throw new \Exception('The Auth plugin must be loaded before the ACL plugin if you want to allow unauthenticated access.');
            }
            $authPlugin->autoRequireLogin = false;
        }

        $this->server = $server;
        $server->on('propFind',            [$this, 'propFind'], 20);
        $server->on('beforeMethod',        [$this, 'beforeMethod'], 20);
        $server->on('beforeBind',          [$this, 'beforeBind'], 20);
        $server->on('beforeUnbind',        [$this, 'beforeUnbind'], 20);
        $server->on('propPatch',           [$this, 'propPatch']);
        $server->on('beforeUnlock',        [$this, 'beforeUnlock'], 20);
        $server->on('report',              [$this, 'report']);
        $server->on('method:ACL',          [$this, 'httpAcl']);
        $server->on('onHTMLActionsPanel',  [$this, 'htmlActionsPanel']);
        $server->on('getPrincipalByUri',  function($principal, &$uri) {

            $uri = $this->getPrincipalByUri($principal);

            // Break event chain
            if ($uri) return false;

        });

        array_push($server->protectedProperties,
            '{DAV:}alternate-URI-set',
            '{DAV:}principal-URL',
            '{DAV:}group-membership',
            '{DAV:}principal-collection-set',
            '{DAV:}current-user-principal',
            '{DAV:}supported-privilege-set',
            '{DAV:}current-user-privilege-set',
            '{DAV:}acl',
            '{DAV:}acl-restrictions',
            '{DAV:}inherited-acl-set',
            '{DAV:}owner',
            '{DAV:}group'
        );

        // Automatically mapping nodes implementing IPrincipal to the
        // {DAV:}principal resourcetype.
        $server->resourceTypeMapping['Sabre\\DAVACL\\IPrincipal'] = '{DAV:}principal';

        // Mapping the group-member-set property to the HrefList property
        // class.
        $server->xml->elementMap['{DAV:}group-member-set'] = 'Sabre\\DAV\\Xml\\Property\\Href';
        $server->xml->elementMap['{DAV:}acl'] = 'Sabre\\DAVACL\\Xml\\Property\\Acl';
        $server->xml->elementMap['{DAV:}acl-principal-prop-set'] = 'Sabre\\DAVACL\\Xml\\Request\\AclPrincipalPropSetReport';
        $server->xml->elementMap['{DAV:}expand-property'] = 'Sabre\\DAVACL\\Xml\\Request\\ExpandPropertyReport';
        $server->xml->elementMap['{DAV:}principal-property-search'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalPropertySearchReport';
        $server->xml->elementMap['{DAV:}principal-search-property-set'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalSearchPropertySetReport';
        $server->xml->elementMap['{DAV:}principal-match'] = 'Sabre\\DAVACL\\Xml\\Request\\PrincipalMatchReport';

    }

    /* {{{ Event handlers */

    /**
     * Triggered before any method is handled
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return void
     */
    function beforeMethod(RequestInterface $request, ResponseInterface $response) {

        $method = $request->getMethod();
        $path = $request->getPath();

        $exists = $this->server->tree->nodeExists($path);

        // If the node doesn't exists, none of these checks apply
        if (!$exists) return;

        switch ($method) {

            case 'GET' :
            case 'HEAD' :
            case 'OPTIONS' :
                // For these 3 we only need to know if the node is readable.
                $this->checkPrivileges($path, '{DAV:}read');
                break;

            case 'PUT' :
            case 'LOCK' :
                // This method requires the write-content priv if the node
                // already exists, and bind on the parent if the node is being
                // created.
                // The bind privilege is handled in the beforeBind event.
                $this->checkPrivileges($path, '{DAV:}write-content');
                break;

            case 'UNLOCK' :
                // Unlock is always allowed at the moment.
                break;

            case 'PROPPATCH' :
                $this->checkPrivileges($path, '{DAV:}write-properties');
                break;

            case 'ACL' :
                $this->checkPrivileges($path, '{DAV:}write-acl');
                break;

            case 'COPY' :
            case 'MOVE' :
                // Copy requires read privileges on the entire source tree.
                // If the target exists write-content normally needs to be
                // checked, however, we're deleting the node beforehand and
                // creating a new one after, so this is handled by the
                // beforeUnbind event.
                //
                // The creation of the new node is handled by the beforeBind
                // event.
                //
                // If MOVE is used beforeUnbind will also be used to check if
                // the sourcenode can be deleted.
                $this->checkPrivileges($path, '{DAV:}read', self::R_RECURSIVE);
                break;

        }

    }

    /**
     * Triggered before a new node is created.
     *
     * This allows us to check permissions for any operation that creates a
     * new node, such as PUT, MKCOL, MKCALENDAR, LOCK, COPY and MOVE.
     *
     * @param string $uri
     * @return void
     */
    function beforeBind($uri) {

        list($parentUri) = Uri\split($uri);
        $this->checkPrivileges($parentUri, '{DAV:}bind');

    }

    /**
     * Triggered before a node is deleted
     *
     * This allows us to check permissions for any operation that will delete
     * an existing node.
     *
     * @param string $uri
     * @return void
     */
    function beforeUnbind($uri) {

        list($parentUri) = Uri\split($uri);
        $this->checkPrivileges($parentUri, '{DAV:}unbind', self::R_RECURSIVEPARENTS);

    }

    /**
     * Triggered before a node is unlocked.
     *
     * @param string $uri
     * @param DAV\Locks\LockInfo $lock
     * @TODO: not yet implemented
     * @return void
     */
    function beforeUnlock($uri, DAV\Locks\LockInfo $lock) {


    }

    /**
     * Triggered before properties are looked up in specific nodes.
     *
     * @param DAV\PropFind $propFind
     * @param DAV\INode $node
     * @TODO really should be broken into multiple methods, or even a class.
     * @return bool
     */
    function propFind(DAV\PropFind $propFind, DAV\INode $node) {

        $path = $propFind->getPath();

        // Checking the read permission
        if (!$this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false)) {
            // User is not allowed to read properties

            // Returning false causes the property-fetching system to pretend
            // that the node does not exist, and will cause it to be hidden
            // from listings such as PROPFIND or the browser plugin.
            if ($this->hideNodesFromListings) {
                return false;
            }

            // Otherwise we simply mark every property as 403.
            foreach ($propFind->getRequestedProperties() as $requestedProperty) {
                $propFind->set($requestedProperty, null, 403);
            }

            return;

        }

        /* Adding principal properties */
        if ($node instanceof IPrincipal) {

            $propFind->handle('{DAV:}alternate-URI-set', function() use ($node) {
                return new Href($node->getAlternateUriSet());
            });
            $propFind->handle('{DAV:}principal-URL', function() use ($node) {
                return new Href($node->getPrincipalUrl() . '/');
            });
            $propFind->handle('{DAV:}group-member-set', function() use ($node) {
                $members = $node->getGroupMemberSet();
                foreach ($members as $k => $member) {
                    $members[$k] = rtrim($member, '/') . '/';
                }
                return new Href($members);
            });
            $propFind->handle('{DAV:}group-membership', function() use ($node) {
                $members = $node->getGroupMembership();
                foreach ($members as $k => $member) {
                    $members[$k] = rtrim($member, '/') . '/';
                }
                return new Href($members);
            });
            $propFind->handle('{DAV:}displayname', [$node, 'getDisplayName']);

        }

        $propFind->handle('{DAV:}principal-collection-set', function() {

            $val = $this->principalCollectionSet;
            // Ensuring all collections end with a slash
            foreach ($val as $k => $v) $val[$k] = $v . '/';
            return new Href($val);

        });
        $propFind->handle('{DAV:}current-user-principal', function() {
            if ($url = $this->getCurrentUserPrincipal()) {
                return new Xml\Property\Principal(Xml\Property\Principal::HREF, $url . '/');
            } else {
                return new Xml\Property\Principal(Xml\Property\Principal::UNAUTHENTICATED);
            }
        });
        $propFind->handle('{DAV:}supported-privilege-set', function() use ($node) {
            return new Xml\Property\SupportedPrivilegeSet($this->getSupportedPrivilegeSet($node));
        });
        $propFind->handle('{DAV:}current-user-privilege-set', function() use ($node, $propFind, $path) {
            if (!$this->checkPrivileges($path, '{DAV:}read-current-user-privilege-set', self::R_PARENT, false)) {
                $propFind->set('{DAV:}current-user-privilege-set', null, 403);
            } else {
                $val = $this->getCurrentUserPrivilegeSet($node);
                return new Xml\Property\CurrentUserPrivilegeSet($val);
            }
        });
        $propFind->handle('{DAV:}acl', function() use ($node, $propFind, $path) {
            /* The ACL property contains all the permissions */
            if (!$this->checkPrivileges($path, '{DAV:}read-acl', self::R_PARENT, false)) {
                $propFind->set('{DAV:}acl', null, 403);
            } else {
                $acl = $this->getACL($node);
                return new Xml\Property\Acl($this->getACL($node));
            }
        });
        $propFind->handle('{DAV:}acl-restrictions', function() {
            return new Xml\Property\AclRestrictions();
        });

        /* Adding ACL properties */
        if ($node instanceof IACL) {
            $propFind->handle('{DAV:}owner', function() use ($node) {
                return new Href($node->getOwner() . '/');
            });
        }

    }

    /**
     * This method intercepts PROPPATCH methods and make sure the
     * group-member-set is updated correctly.
     *
     * @param string $path
     * @param DAV\PropPatch $propPatch
     * @return void
     */
    function propPatch($path, DAV\PropPatch $propPatch) {

        $propPatch->handle('{DAV:}group-member-set', function($value) use ($path) {
            if (is_null($value)) {
                $memberSet = [];
            } elseif ($value instanceof Href) {
                $memberSet = array_map(
                    [$this->server, 'calculateUri'],
                    $value->getHrefs()
                );
            } else {
                throw new DAV\Exception('The group-member-set property MUST be an instance of Sabre\DAV\Property\HrefList or null');
            }
            $node = $this->server->tree->getNodeForPath($path);
            if (!($node instanceof IPrincipal)) {
                // Fail
                return false;
            }

            $node->setGroupMemberSet($memberSet);
            // We must also clear our cache, just in case

            $this->principalMembershipCache = [];

            return true;
        });

    }

    /**
     * This method handles HTTP REPORT requests
     *
     * @param string $reportName
     * @param mixed $report
     * @param mixed $path
     * @return bool
     */
    function report($reportName, $report, $path) {

        switch ($reportName) {

            case '{DAV:}principal-property-search' :
                $this->server->transactionType = 'report-principal-property-search';
                $this->principalPropertySearchReport($path, $report);
                return false;
            case '{DAV:}principal-search-property-set' :
                $this->server->transactionType = 'report-principal-search-property-set';
                $this->principalSearchPropertySetReport($path, $report);
                return false;
            case '{DAV:}expand-property' :
                $this->server->transactionType = 'report-expand-property';
                $this->expandPropertyReport($path, $report);
                return false;
            case '{DAV:}principal-match' :
                $this->server->transactionType = 'report-principal-match';
                $this->principalMatchReport($path, $report);
                return false;
            case '{DAV:}acl-principal-prop-set' :
                $this->server->transactionType = 'acl-principal-prop-set';
                $this->aclPrincipalPropSetReport($path, $report);
                return false;

        }

    }

    /**
     * This method is responsible for handling the 'ACL' event.
     *
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return bool
     */
    function httpAcl(RequestInterface $request, ResponseInterface $response) {

        $path = $request->getPath();
        $body = $request->getBodyAsString();

        if (!$body) {
            throw new DAV\Exception\BadRequest('XML body expected in ACL request');
        }

        $acl = $this->server->xml->expect('{DAV:}acl', $body);
        $newAcl = $acl->getPrivileges();

        // Normalizing urls
        foreach ($newAcl as $k => $newAce) {
            $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']);
        }
        $node = $this->server->tree->getNodeForPath($path);

        if (!$node instanceof IACL) {
            throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method');
        }

        $oldAcl = $this->getACL($node);

        $supportedPrivileges = $this->getFlatPrivilegeSet($node);

        /* Checking if protected principals from the existing principal set are
           not overwritten. */
        foreach ($oldAcl as $oldAce) {

            if (!isset($oldAce['protected']) || !$oldAce['protected']) continue;

            $found = false;
            foreach ($newAcl as $newAce) {
                if (
                    $newAce['privilege'] === $oldAce['privilege'] &&
                    $newAce['principal'] === $oldAce['principal'] &&
                    $newAce['protected']
                )
                $found = true;
            }

            if (!$found)
                throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request');

        }

        foreach ($newAcl as $newAce) {

            // Do we recognize the privilege
            if (!isset($supportedPrivileges[$newAce['privilege']])) {
                throw new Exception\NotSupportedPrivilege('The privilege you specified (' . $newAce['privilege'] . ') is not recognized by this server');
            }

            if ($supportedPrivileges[$newAce['privilege']]['abstract']) {
                throw new Exception\NoAbstract('The privilege you specified (' . $newAce['privilege'] . ') is an abstract privilege');
            }

            // Looking up the principal
            try {
                $principal = $this->server->tree->getNodeForPath($newAce['principal']);
            } catch (NotFound $e) {
                throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist');
            }
            if (!($principal instanceof IPrincipal)) {
                throw new Exception\NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal');
            }

        }
        $node->setACL($newAcl);

        $response->setStatus(200);

        // Breaking the event chain, because we handled this method.
        return false;

    }

    /* }}} */

    /* Reports {{{ */

    /**
     * The principal-match report is defined in RFC3744, section 9.3.
     *
     * This report allows a client to figure out based on the current user,
     * or a principal URL, the principal URL and principal URLs of groups that
     * principal belongs to.
     *
     * @param string $path
     * @param Xml\Request\PrincipalMatchReport $report
     * @return void
     */
    protected function principalMatchReport($path, Xml\Request\PrincipalMatchReport $report) {

        $depth = $this->server->getHTTPDepth(0);
        if ($depth !== 0) {
            throw new BadRequest('The principal-match report is only defined on Depth: 0');
        }

        $currentPrincipals = $this->getCurrentUserPrincipals();

        $result = [];

        if ($report->type === Xml\Request\PrincipalMatchReport::SELF) {

            // Finding all principals under the request uri that match the
            // current principal.
            foreach ($currentPrincipals as $currentPrincipal) {

                if ($currentPrincipal === $path || strpos($currentPrincipal, $path . '/') === 0) {
                    $result[] = $currentPrincipal;
                }

            }

        } else {

            // We need to find all resources that have a property that matches
            // one of the current principals.
            $candidates = $this->server->getPropertiesForPath(
                $path,
                [$report->principalProperty],
                1
            );

            foreach ($candidates as $candidate) {

                if (!isset($candidate[200][$report->principalProperty])) {
                    continue;
                }

                $hrefs = $candidate[200][$report->principalProperty];

                if (!$hrefs instanceof Href) {
                    continue;
                }

                foreach ($hrefs->getHrefs() as $href) {
                    if (in_array(trim($href, '/'), $currentPrincipals)) {
                        $result[] = $candidate['href'];
                        continue 2;
                    }
                }
            }

        }

        $responses = [];

        foreach ($result as $item) {

            $properties = [];

            if ($report->properties) {

                $foo = $this->server->getPropertiesForPath($item, $report->properties);
                $foo = $foo[0];
                $item = $foo['href'];
                unset($foo['href']);
                $properties = $foo;

            }

            $responses[] = new DAV\Xml\Element\Response(
                $item,
                $properties,
                '200'
            );

        }

        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setBody(
            $this->server->xml->write(
                '{DAV:}multistatus',
                $responses,
                $this->server->getBaseUri()
            )
        );


    }

    /**
     * The expand-property report is defined in RFC3253 section 3.8.
     *
     * This report is very similar to a standard PROPFIND. The difference is
     * that it has the additional ability to look at properties containing a
     * {DAV:}href element, follow that property and grab additional elements
     * there.
     *
     * Other rfc's, such as ACL rely on this report, so it made sense to put
     * it in this plugin.
     *
     * @param string $path
     * @param Xml\Request\ExpandPropertyReport $report
     * @return void
     */
    protected function expandPropertyReport($path, $report) {

        $depth = $this->server->getHTTPDepth(0);

        $result = $this->expandProperties($path, $report->properties, $depth);

        $xml = $this->server->xml->write(
            '{DAV:}multistatus',
            new DAV\Xml\Response\MultiStatus($result),
            $this->server->getBaseUri()
        );
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setBody($xml);

    }

    /**
     * This method expands all the properties and returns
     * a list with property values
     *
     * @param array $path
     * @param array $requestedProperties the list of required properties
     * @param int $depth
     * @return array
     */
    protected function expandProperties($path, array $requestedProperties, $depth) {

        $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth);

        $result = [];

        foreach ($foundProperties as $node) {

            foreach ($requestedProperties as $propertyName => $childRequestedProperties) {

                // We're only traversing if sub-properties were requested
                if (count($childRequestedProperties) === 0) continue;

                // We only have to do the expansion if the property was found
                // and it contains an href element.
                if (!array_key_exists($propertyName, $node[200])) continue;

                if (!$node[200][$propertyName] instanceof DAV\Xml\Property\Href) {
                    continue;
                }

                $childHrefs = $node[200][$propertyName]->getHrefs();
                $childProps = [];

                foreach ($childHrefs as $href) {
                    // Gathering the result of the children
                    $childProps[] = [
                        'name'  => '{DAV:}response',
                        'value' => $this->expandProperties($href, $childRequestedProperties, 0)[0]
                    ];
                }

                // Replacing the property with its expanded form.
                $node[200][$propertyName] = $childProps;

            }
            $result[] = new DAV\Xml\Element\Response($node['href'], $node);

        }

        return $result;

    }

    /**
     * principalSearchPropertySetReport
     *
     * This method responsible for handing the
     * {DAV:}principal-search-property-set report. This report returns a list
     * of properties the client may search on, using the
     * {DAV:}principal-property-search report.
     *
     * @param string $path
     * @param Xml\Request\PrincipalSearchPropertySetReport $report
     * @return void
     */
    protected function principalSearchPropertySetReport($path, $report) {

        $httpDepth = $this->server->getHTTPDepth(0);
        if ($httpDepth !== 0) {
            throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0');
        }

        $writer = $this->server->xml->getWriter();
        $writer->openMemory();
        $writer->startDocument();

        $writer->startElement('{DAV:}principal-search-property-set');

        foreach ($this->principalSearchPropertySet as $propertyName => $description) {

            $writer->startElement('{DAV:}principal-search-property');
            $writer->startElement('{DAV:}prop');

            $writer->writeElement($propertyName);

            $writer->endElement(); // prop

            if ($description) {
                $writer->write([[
                    'name'       => '{DAV:}description',
                    'value'      => $description,
                    'attributes' => ['xml:lang' => 'en']
                ]]);
            }

            $writer->endElement(); // principal-search-property


        }

        $writer->endElement(); // principal-search-property-set

        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setStatus(200);
        $this->server->httpResponse->setBody($writer->outputMemory());

    }

    /**
     * principalPropertySearchReport
     *
     * This method is responsible for handing the
     * {DAV:}principal-property-search report. This report can be used for
     * clients to search for groups of principals, based on the value of one
     * or more properties.
     *
     * @param string $path
     * @param Xml\Request\PrincipalPropertySearchReport $report
     * @return void
     */
    protected function principalPropertySearchReport($path, Xml\Request\PrincipalPropertySearchReport $report) {

        if ($report->applyToPrincipalCollectionSet) {
            $path = null;
        }
        if ($this->server->getHttpDepth('0') !== 0) {
            throw new BadRequest('Depth must be 0');
        }
        $result = $this->principalSearch(
            $report->searchProperties,
            $report->properties,
            $path,
            $report->test
        );

        $prefer = $this->server->getHTTPPrefer();

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
        $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));

    }

    /**
     * aclPrincipalPropSet REPORT
     *
     * This method is responsible for handling the {DAV:}acl-principal-prop-set
     * REPORT, as defined in:
     *
     * https://tools.ietf.org/html/rfc3744#section-9.2
     *
     * This REPORT allows a user to quickly fetch information about all
     * principals specified in the access control list. Most commonly this
     * is used to for example generate a UI with ACL rules, allowing you
     * to show names for principals for every entry.
     *
     * @param string $path
     * @param Xml\Request\AclPrincipalPropSetReport $report
     * @return void
     */
    protected function aclPrincipalPropSetReport($path, Xml\Request\AclPrincipalPropSetReport $report) {

        if ($this->server->getHTTPDepth(0) !== 0) {
            throw new BadRequest('The {DAV:}acl-principal-prop-set REPORT only supports Depth 0');
        }

        // Fetching ACL rules for the given path. We're using the property
        // API and not the local getACL, because it will ensure that all
        // business rules and restrictions are applied.
        $acl = $this->server->getProperties($path, '{DAV:}acl');

        if (!$acl || !isset($acl['{DAV:}acl'])) {
            throw new Forbidden('Could not fetch ACL rules for this path');
        }

        $principals = [];
        foreach ($acl['{DAV:}acl']->getPrivileges() as $ace) {

            if ($ace['principal'][0] === '{') {
                // It's not a principal, it's one of the special rules such as {DAV:}authenticated
                continue;
            }

            $principals[] = $ace['principal'];

        }

        $properties = $this->server->getPropertiesForMultiplePaths(
            $principals,
            $report->properties
        );

        $this->server->httpResponse->setStatus(207);
        $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
        $this->server->httpResponse->setBody(
            $this->server->generateMultiStatus($properties)
        );

    }


    /* }}} */

    /**
     * This method is used to generate HTML output for the
     * DAV\Browser\Plugin. This allows us to generate an interface users
     * can use to create new calendars.
     *
     * @param DAV\INode $node
     * @param string $output
     * @return bool
     */
    function htmlActionsPanel(DAV\INode $node, &$output) {

        if (!$node instanceof PrincipalCollection)
            return;

        $output .= '<tr><td colspan="2"><form method="post" action="">
            <h3>Create new principal</h3>
            <input type="hidden" name="sabreAction" value="mkcol" />
            <input type="hidden" name="resourceType" value="{DAV:}principal" />
            <label>Name (uri):</label> <input type="text" name="name" /><br />
            <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
            <label>Email address:</label> <input type="text" name="{http://sabredav*DOT*org/ns}email-address" /><br />
            <input type="submit" value="create" />
            </form>
            </td></tr>';

        return false;

    }

    /**
     * Returns a bunch of meta-data about the plugin.
     *
     * Providing this information is optional, and is mainly displayed by the
     * Browser plugin.
     *
     * The description key in the returned array may contain html and will not
     * be sanitized.
     *
     * @return array
     */
    function getPluginInfo() {

        return [
            'name'        => $this->getPluginName(),
            'description' => 'Adds support for WebDAV ACL (rfc3744)',
            'link'        => 'http://sabre.io/dav/acl/',
        ];

    }
}
<?php

namespace Sabre\DAVACL;

use Sabre\DAV;
use Sabre\HTTP\URLUtil;

/**
 * Principal class
 *
 * This class is a representation of a simple principal
 *
 * Many WebDAV specs require a user to show up in the directory
 * structure.
 *
 * This principal also has basic ACL settings, only allowing the principal
 * access it's own principal.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Principal extends DAV\Node implements IPrincipal, DAV\IProperties, IACL {

    use ACLTrait;

    /**
     * Struct with principal information.
     *
     * @var array
     */
    protected $principalProperties;

    /**
     * Principal backend
     *
     * @var PrincipalBackend\BackendInterface
     */
    protected $principalBackend;

    /**
     * Creates the principal object
     *
     * @param PrincipalBackend\BackendInterface $principalBackend
     * @param array $principalProperties
     */
    function __construct(PrincipalBackend\BackendInterface $principalBackend, array $principalProperties = []) {

        if (!isset($principalProperties['uri'])) {
            throw new DAV\Exception('The principal properties must at least contain the \'uri\' key');
        }
        $this->principalBackend = $principalBackend;
        $this->principalProperties = $principalProperties;

    }

    /**
     * Returns the full principal url
     *
     * @return string
     */
    function getPrincipalUrl() {

        return $this->principalProperties['uri'];

    }

    /**
     * Returns a list of alternative urls for a principal
     *
     * This can for example be an email address, or ldap url.
     *
     * @return array
     */
    function getAlternateUriSet() {

        $uris = [];
        if (isset($this->principalProperties['{DAV:}alternate-URI-set'])) {

            $uris = $this->principalProperties['{DAV:}alternate-URI-set'];

        }

        if (isset($this->principalProperties['{http://sabredav.org/ns}email-address'])) {
            $uris[] = 'mailto:' . $this->principalProperties['{http://sabredav.org/ns}email-address'];
        }

        return array_unique($uris);

    }

    /**
     * Returns the list of group members
     *
     * If this principal is a group, this function should return
     * all member principal uri's for the group.
     *
     * @return array
     */
    function getGroupMemberSet() {

        return $this->principalBackend->getGroupMemberSet($this->principalProperties['uri']);

    }

    /**
     * Returns the list of groups this principal is member of
     *
     * If this principal is a member of a (list of) groups, this function
     * should return a list of principal uri's for it's members.
     *
     * @return array
     */
    function getGroupMembership() {

        return $this->principalBackend->getGroupMemberShip($this->principalProperties['uri']);

    }

    /**
     * Sets a list of group members
     *
     * If this principal is a group, this method sets all the group members.
     * The list of members is always overwritten, never appended to.
     *
     * This method should throw an exception if the members could not be set.
     *
     * @param array $groupMembers
     * @return void
     */
    function setGroupMemberSet(array $groupMembers) {

        $this->principalBackend->setGroupMemberSet($this->principalProperties['uri'], $groupMembers);

    }

    /**
     * Returns this principals name.
     *
     * @return string
     */
    function getName() {

        $uri = $this->principalProperties['uri'];
        list(, $name) = URLUtil::splitPath($uri);
        return $name;

    }

    /**
     * Returns the name of the user
     *
     * @return string
     */
    function getDisplayName() {

        if (isset($this->principalProperties['{DAV:}displayname'])) {
            return $this->principalProperties['{DAV:}displayname'];
        } else {
            return $this->getName();
        }

    }

    /**
     * Returns a list of properties
     *
     * @param array $requestedProperties
     * @return array
     */
    function getProperties($requestedProperties) {

        $newProperties = [];
        foreach ($requestedProperties as $propName) {

            if (isset($this->principalProperties[$propName])) {
                $newProperties[$propName] = $this->principalProperties[$propName];
            }

        }

        return $newProperties;

    }

    /**
     * Updates properties on this node.
     *
     * This method received a PropPatch object, which contains all the
     * information about the update.
     *
     * To update specific properties, call the 'handle' method on this object.
     * Read the PropPatch documentation for more information.
     *
     * @param DAV\PropPatch $propPatch
     * @return void
     */
    function propPatch(DAV\PropPatch $propPatch) {

        return $this->principalBackend->updatePrincipal(
            $this->principalProperties['uri'],
            $propPatch
        );

    }

    /**
     * Returns the owner principal
     *
     * This must be a url to a principal, or null if there's no owner
     *
     * @return string|null
     */
    function getOwner() {

        return $this->principalProperties['uri'];


    }

}
<?php

namespace Sabre\DAVACL\PrincipalBackend;

/**
 * Abstract Principal Backend
 *
 * Currently this class has no function. It's here for consistency and so we
 * have a non-bc-breaking way to add a default generic implementation to
 * functions we may add in the future.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractBackend implements BackendInterface {

    /**
     * Finds a principal by its URI.
     *
     * This method may receive any type of uri, but mailto: addresses will be
     * the most common.
     *
     * Implementation of this API is optional. It is currently used by the
     * CalDAV system to find principals based on their email addresses. If this
     * API is not implemented, some features may not work correctly.
     *
     * This method must return a relative principal path, or null, if the
     * principal was not found or you refuse to find it.
     *
     * @param string $uri
     * @param string $principalPrefix
     * @return string
     */
    function findByUri($uri, $principalPrefix) {

        // Note that the default implementation here is a bit slow and could
        // likely be optimized.
        if (substr($uri, 0, 7) !== 'mailto:') {
            return;
        }
        $result = $this->searchPrincipals(
            $principalPrefix,
            ['{http://sabredav.org/ns}email-address' => substr($uri, 7)]
        );

        if ($result) {
            return $result[0];
        }

    }

}
<?php

namespace Sabre\DAVACL\PrincipalBackend;

/**
 * Implement this interface to create your own principal backends.
 *
 * Creating backends for principals is entirely optional. You can also
 * implement Sabre\DAVACL\IPrincipal directly. This interface is used solely by
 * Sabre\DAVACL\AbstractPrincipalCollection.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface BackendInterface {

    /**
     * Returns a list of principals based on a prefix.
     *
     * This prefix will often contain something like 'principals'. You are only
     * expected to return principals that are in this base path.
     *
     * You are expected to return at least a 'uri' for every user, you can
     * return any additional properties if you wish so. Common properties are:
     *   {DAV:}displayname
     *   {http://sabredav.org/ns}email-address - This is a custom SabreDAV
     *     field that's actually injected in a number of other properties. If
     *     you have an email address, use this property.
     *
     * @param string $prefixPath
     * @return array
     */
    function getPrincipalsByPrefix($prefixPath);

    /**
     * Returns a specific principal, specified by it's path.
     * The returned structure should be the exact same as from
     * getPrincipalsByPrefix.
     *
     * @param string $path
     * @return array
     */
    function getPrincipalByPath($path);

    /**
     * Updates one ore more webdav properties on a principal.
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param string $path
     * @param \Sabre\DAV\PropPatch $propPatch
     * @return void
     */
    function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch);

    /**
     * This method is used to search for principals matching a set of
     * properties.
     *
     * This search is specifically used by RFC3744's principal-property-search
     * REPORT.
     *
     * The actual search should be a unicode-non-case-sensitive search. The
     * keys in searchProperties are the WebDAV property names, while the values
     * are the property values to search on.
     *
     * By default, if multiple properties are submitted to this method, the
     * various properties should be combined with 'AND'. If $test is set to
     * 'anyof', it should be combined using 'OR'.
     *
     * This method should simply return an array with full principal uri's.
     *
     * If somebody attempted to search on a property the backend does not
     * support, you should simply return 0 results.
     *
     * You can also just return 0 results if you choose to not support
     * searching at all, but keep in mind that this may stop certain features
     * from working.
     *
     * @param string $prefixPath
     * @param array $searchProperties
     * @param string $test
     * @return array
     */
    function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof');

    /**
     * Finds a principal by its URI.
     *
     * This method may receive any type of uri, but mailto: addresses will be
     * the most common.
     *
     * Implementation of this API is optional. It is currently used by the
     * CalDAV system to find principals based on their email addresses. If this
     * API is not implemented, some features may not work correctly.
     *
     * This method must return a relative principal path, or null, if the
     * principal was not found or you refuse to find it.
     *
     * @param string $uri
     * @param string $principalPrefix
     * @return string
     */
    function findByUri($uri, $principalPrefix);

    /**
     * Returns the list of members for a group-principal
     *
     * @param string $principal
     * @return array
     */
    function getGroupMemberSet($principal);

    /**
     * Returns the list of groups a principal is a member of
     *
     * @param string $principal
     * @return array
     */
    function getGroupMembership($principal);

    /**
     * Updates the list of group members for a group principal.
     *
     * The principals should be passed as a list of uri's.
     *
     * @param string $principal
     * @param array $members
     * @return void
     */
    function setGroupMemberSet($principal, array $members);

}
<?php

namespace Sabre\DAVACL\PrincipalBackend;

use Sabre\DAV\MkCol;

/**
 * Implement this interface to add support for creating new principals to your
 * principal backend.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface CreatePrincipalSupport extends BackendInterface {

    /**
     * Creates a new principal.
     *
     * This method receives a full path for the new principal. The mkCol object
     * contains any additional webdav properties specified during the creation
     * of the principal.
     *
     * @param string $path
     * @param MkCol $mkCol
     * @return void
     */
    function createPrincipal($path, MkCol $mkCol);

}
<?php

namespace Sabre\DAVACL\PrincipalBackend;

use Sabre\DAV;
use Sabre\DAV\MkCol;
use Sabre\HTTP\URLUtil;

/**
 * PDO principal backend
 *
 *
 * This backend assumes all principals are in a single collection. The default collection
 * is 'principals/', but this can be overridden.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PDO extends AbstractBackend implements CreatePrincipalSupport {

    /**
     * PDO table name for 'principals'
     *
     * @var string
     */
    public $tableName = 'principals';

    /**
     * PDO table name for 'group members'
     *
     * @var string
     */
    public $groupMembersTableName = 'groupmembers';

    /**
     * pdo
     *
     * @var PDO
     */
    protected $pdo;

    /**
     * A list of additional fields to support
     *
     * @var array
     */
    protected $fieldMap = [

        /**
         * This property can be used to display the users' real name.
         */
        '{DAV:}displayname' => [
            'dbField' => 'displayname',
        ],

        /**
         * This is the users' primary email-address.
         */
        '{http://sabredav.org/ns}email-address' => [
            'dbField' => 'email',
        ],
    ];

    /**
     * Sets up the backend.
     *
     * @param \PDO $pdo
     */
    function __construct(\PDO $pdo) {

        $this->pdo = $pdo;

    }

    /**
     * Returns a list of principals based on a prefix.
     *
     * This prefix will often contain something like 'principals'. You are only
     * expected to return principals that are in this base path.
     *
     * You are expected to return at least a 'uri' for every user, you can
     * return any additional properties if you wish so. Common properties are:
     *   {DAV:}displayname
     *   {http://sabredav.org/ns}email-address - This is a custom SabreDAV
     *     field that's actualy injected in a number of other properties. If
     *     you have an email address, use this property.
     *
     * @param string $prefixPath
     * @return array
     */
    function getPrincipalsByPrefix($prefixPath) {

        $fields = [
            'uri',
        ];

        foreach ($this->fieldMap as $key => $value) {
            $fields[] = $value['dbField'];
        }
        $result = $this->pdo->query('SELECT ' . implode(',', $fields) . '  FROM ' . $this->tableName);

        $principals = [];

        while ($row = $result->fetch(\PDO::FETCH_ASSOC)) {

            // Checking if the principal is in the prefix
            list($rowPrefix) = URLUtil::splitPath($row['uri']);
            if ($rowPrefix !== $prefixPath) continue;

            $principal = [
                'uri' => $row['uri'],
            ];
            foreach ($this->fieldMap as $key => $value) {
                if ($row[$value['dbField']]) {
                    $principal[$key] = $row[$value['dbField']];
                }
            }
            $principals[] = $principal;

        }

        return $principals;

    }

    /**
     * Returns a specific principal, specified by it's path.
     * The returned structure should be the exact same as from
     * getPrincipalsByPrefix.
     *
     * @param string $path
     * @return array
     */
    function getPrincipalByPath($path) {

        $fields = [
            'id',
            'uri',
        ];

        foreach ($this->fieldMap as $key => $value) {
            $fields[] = $value['dbField'];
        }
        $stmt = $this->pdo->prepare('SELECT ' . implode(',', $fields) . '  FROM ' . $this->tableName . ' WHERE uri = ?');
        $stmt->execute([$path]);

        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
        if (!$row) return;

        $principal = [
            'id'  => $row['id'],
            'uri' => $row['uri'],
        ];
        foreach ($this->fieldMap as $key => $value) {
            if ($row[$value['dbField']]) {
                $principal[$key] = $row[$value['dbField']];
            }
        }
        return $principal;

    }

    /**
     * Updates one ore more webdav properties on a principal.
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param string $path
     * @param DAV\PropPatch $propPatch
     */
    function updatePrincipal($path, DAV\PropPatch $propPatch) {

        $propPatch->handle(array_keys($this->fieldMap), function($properties) use ($path) {

            $query = "UPDATE " . $this->tableName . " SET ";
            $first = true;

            $values = [];

            foreach ($properties as $key => $value) {

                $dbField = $this->fieldMap[$key]['dbField'];

                if (!$first) {
                    $query .= ', ';
                }
                $first = false;
                $query .= $dbField . ' = :' . $dbField;
                $values[$dbField] = $value;

            }

            $query .= " WHERE uri = :uri";
            $values['uri'] = $path;

            $stmt = $this->pdo->prepare($query);
            $stmt->execute($values);

            return true;

        });

    }

    /**
     * This method is used to search for principals matching a set of
     * properties.
     *
     * This search is specifically used by RFC3744's principal-property-search
     * REPORT.
     *
     * The actual search should be a unicode-non-case-sensitive search. The
     * keys in searchProperties are the WebDAV property names, while the values
     * are the property values to search on.
     *
     * By default, if multiple properties are submitted to this method, the
     * various properties should be combined with 'AND'. If $test is set to
     * 'anyof', it should be combined using 'OR'.
     *
     * This method should simply return an array with full principal uri's.
     *
     * If somebody attempted to search on a property the backend does not
     * support, you should simply return 0 results.
     *
     * You can also just return 0 results if you choose to not support
     * searching at all, but keep in mind that this may stop certain features
     * from working.
     *
     * @param string $prefixPath
     * @param array $searchProperties
     * @param string $test
     * @return array
     */
    function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
        if (count($searchProperties) == 0) return [];    //No criteria

        $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE ';
        $values = [];
        foreach ($searchProperties as $property => $value) {
            switch ($property) {
                case '{DAV:}displayname' :
                    $column = "displayname";
                    break;
                case '{http://sabredav.org/ns}email-address' :
                    $column = "email";
                    break;
                default :
                    // Unsupported property
                    return [];
            }
            if (count($values) > 0) $query .= (strcmp($test, "anyof") == 0 ? " OR " : " AND ");
            $query .= 'lower(' . $column . ') LIKE lower(?)';
            $values[] = '%' . $value . '%';

        }
        $stmt = $this->pdo->prepare($query);
        $stmt->execute($values);

        $principals = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {

            // Checking if the principal is in the prefix
            list($rowPrefix) = URLUtil::splitPath($row['uri']);
            if ($rowPrefix !== $prefixPath) continue;

            $principals[] = $row['uri'];

        }

        return $principals;

    }

    /**
     * Finds a principal by its URI.
     *
     * This method may receive any type of uri, but mailto: addresses will be
     * the most common.
     *
     * Implementation of this API is optional. It is currently used by the
     * CalDAV system to find principals based on their email addresses. If this
     * API is not implemented, some features may not work correctly.
     *
     * This method must return a relative principal path, or null, if the
     * principal was not found or you refuse to find it.
     *
     * @param string $uri
     * @param string $principalPrefix
     * @return string
     */
    function findByUri($uri, $principalPrefix) {
        $value = null;
        $scheme = null;
        list($scheme, $value) = explode(":", $uri, 2);
        if (empty($value)) return null;

        $uri = null;
        switch ($scheme){
            case "mailto":
                $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE lower(email)=lower(?)';
                $stmt = $this->pdo->prepare($query);
                $stmt->execute([$value]);
            
                while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
                    // Checking if the principal is in the prefix
                    list($rowPrefix) = URLUtil::splitPath($row['uri']);
                    if ($rowPrefix !== $principalPrefix) continue;
                    
                    $uri = $row['uri'];
                    break; //Stop on first match
                }
                break;
            default:
                //unsupported uri scheme
                return null;
        }
        return $uri;
    }

    /**
     * Returns the list of members for a group-principal
     *
     * @param string $principal
     * @return array
     */
    function getGroupMemberSet($principal) {

        $principal = $this->getPrincipalByPath($principal);
        if (!$principal) throw new DAV\Exception('Principal not found');

        $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM ' . $this->groupMembersTableName . ' AS groupmembers LEFT JOIN ' . $this->tableName . ' AS principals ON groupmembers.member_id = principals.id WHERE groupmembers.principal_id = ?');
        $stmt->execute([$principal['id']]);

        $result = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $result[] = $row['uri'];
        }
        return $result;

    }

    /**
     * Returns the list of groups a principal is a member of
     *
     * @param string $principal
     * @return array
     */
    function getGroupMembership($principal) {

        $principal = $this->getPrincipalByPath($principal);
        if (!$principal) throw new DAV\Exception('Principal not found');

        $stmt = $this->pdo->prepare('SELECT principals.uri as uri FROM ' . $this->groupMembersTableName . ' AS groupmembers LEFT JOIN ' . $this->tableName . ' AS principals ON groupmembers.principal_id = principals.id WHERE groupmembers.member_id = ?');
        $stmt->execute([$principal['id']]);

        $result = [];
        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            $result[] = $row['uri'];
        }
        return $result;

    }

    /**
     * Updates the list of group members for a group principal.
     *
     * The principals should be passed as a list of uri's.
     *
     * @param string $principal
     * @param array $members
     * @return void
     */
    function setGroupMemberSet($principal, array $members) {

        // Grabbing the list of principal id's.
        $stmt = $this->pdo->prepare('SELECT id, uri FROM ' . $this->tableName . ' WHERE uri IN (? ' . str_repeat(', ? ', count($members)) . ');');
        $stmt->execute(array_merge([$principal], $members));

        $memberIds = [];
        $principalId = null;

        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            if ($row['uri'] == $principal) {
                $principalId = $row['id'];
            } else {
                $memberIds[] = $row['id'];
            }
        }
        if (!$principalId) throw new DAV\Exception('Principal not found');

        // Wiping out old members
        $stmt = $this->pdo->prepare('DELETE FROM ' . $this->groupMembersTableName . ' WHERE principal_id = ?;');
        $stmt->execute([$principalId]);

        foreach ($memberIds as $memberId) {

            $stmt = $this->pdo->prepare('INSERT INTO ' . $this->groupMembersTableName . ' (principal_id, member_id) VALUES (?, ?);');
            $stmt->execute([$principalId, $memberId]);

        }

    }

    /**
     * Creates a new principal.
     *
     * This method receives a full path for the new principal. The mkCol object
     * contains any additional webdav properties specified during the creation
     * of the principal.
     *
     * @param string $path
     * @param MkCol $mkCol
     * @return void
     */
    function createPrincipal($path, MkCol $mkCol) {

        $stmt = $this->pdo->prepare('INSERT INTO ' . $this->tableName . ' (uri) VALUES (?)');
        $stmt->execute([$path]);
        $this->updatePrincipal($path, $mkCol);

    }

}
<?php

namespace Sabre\DAVACL;

use Sabre\DAV\Exception\InvalidResourceType;
use Sabre\DAV\IExtendedCollection;
use Sabre\DAV\MkCol;

/**
 * Principals Collection
 *
 * This collection represents a list of users.
 * The users are instances of Sabre\DAVACL\Principal
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PrincipalCollection extends AbstractPrincipalCollection implements IExtendedCollection, IACL {

    use ACLTrait;

    /**
     * This method returns a node for a principal.
     *
     * The passed array contains principal information, and is guaranteed to
     * at least contain a uri item. Other properties may or may not be
     * supplied by the authentication backend.
     *
     * @param array $principal
     * @return \Sabre\DAV\INode
     */
    function getChildForPrincipal(array $principal) {

        return new Principal($this->principalBackend, $principal);

    }

    /**
     * Creates a new collection.
     *
     * This method will receive a MkCol object with all the information about
     * the new collection that's being created.
     *
     * The MkCol object contains information about the resourceType of the new
     * collection. If you don't support the specified resourceType, you should
     * throw Exception\InvalidResourceType.
     *
     * The object also contains a list of WebDAV properties for the new
     * collection.
     *
     * You should call the handle() method on this object to specify exactly
     * which properties you are storing. This allows the system to figure out
     * exactly which properties you didn't store, which in turn allows other
     * plugins (such as the propertystorage plugin) to handle storing the
     * property for you.
     *
     * @param string $name
     * @param MkCol $mkCol
     * @throws InvalidResourceType
     * @return void
     */
    function createExtendedCollection($name, MkCol $mkCol) {

        if (!$mkCol->hasResourceType('{DAV:}principal')) {
            throw new InvalidResourceType('Only resources of type {DAV:}principal may be created here');
        }

        $this->principalBackend->createPrincipal(
            $this->principalPrefix . '/' . $name,
            $mkCol
        );

    }

    /**
     * Returns a list of ACE's for this node.
     *
     * Each ACE has the following properties:
     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
     *     currently the only supported privileges
     *   * 'principal', a url to the principal who owns the node
     *   * 'protected' (optional), indicating that this ACE is not allowed to
     *      be updated.
     *
     * @return array
     */
    function getACL() {
        return [
            [
                'principal' => '{DAV:}authenticated',
                'privilege' => '{DAV:}read',
                'protected' => true,
            ],
        ];
    }

}
<?php

namespace Sabre\DAVACL\Xml\Property;

use Sabre\DAV;
use Sabre\DAV\Browser\HtmlOutput;
use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * This class represents the {DAV:}acl property.
 *
 * The {DAV:}acl property is a full list of access control entries for a
 * resource.
 *
 * {DAV:}acl is used as a WebDAV property, but it is also used within the body
 * of the ACL request.
 *
 * See:
 * http://tools.ietf.org/html/rfc3744#section-5.5
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Acl implements Element, HtmlOutput {

    /**
     * List of privileges
     *
     * @var array
     */
    protected $privileges;

    /**
     * Whether or not the server base url is required to be prefixed when
     * serializing the property.
     *
     * @var bool
     */
    protected $prefixBaseUrl;

    /**
     * Constructor
     *
     * This object requires a structure similar to the return value from
     * Sabre\DAVACL\Plugin::getACL().
     *
     * Each privilege is a an array with at least a 'privilege' property, and a
     * 'principal' property. A privilege may have a 'protected' property as
     * well.
     *
     * The prefixBaseUrl should be set to false, if the supplied principal urls
     * are already full urls. If this is kept to true, the servers base url
     * will automatically be prefixed.
     *
     * @param array $privileges
     * @param bool $prefixBaseUrl
     */
    function __construct(array $privileges, $prefixBaseUrl = true) {

        $this->privileges = $privileges;
        $this->prefixBaseUrl = $prefixBaseUrl;

    }

    /**
     * Returns the list of privileges for this property
     *
     * @return array
     */
    function getPrivileges() {

        return $this->privileges;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->privileges as $ace) {

            $this->serializeAce($writer, $ace);

        }

    }

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html) {

        ob_start();
        echo "<table>";
        echo "<tr><th>Principal</th><th>Privilege</th><th></th></tr>";
        foreach ($this->privileges as $privilege) {

            echo '<tr>';
            // if it starts with a {, it's a special principal
            if ($privilege['principal'][0] === '{') {
                echo '<td>', $html->xmlName($privilege['principal']), '</td>';
            } else {
                echo '<td>', $html->link($privilege['principal']), '</td>';
            }
            echo '<td>', $html->xmlName($privilege['privilege']), '</td>';
            echo '<td>';
            if (!empty($privilege['protected'])) echo '(protected)';
            echo '</td>';
            echo '</tr>';

        }
        echo "</table>";
        return ob_get_clean();

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elementMap = [
            '{DAV:}ace'       => 'Sabre\Xml\Element\KeyValue',
            '{DAV:}privilege' => 'Sabre\Xml\Element\Elements',
            '{DAV:}principal' => 'Sabre\DAVACL\Xml\Property\Principal',
        ];

        $privileges = [];

        foreach ((array)$reader->parseInnerTree($elementMap) as $element) {

            if ($element['name'] !== '{DAV:}ace') {
                continue;
            }
            $ace = $element['value'];

            if (empty($ace['{DAV:}principal'])) {
                throw new DAV\Exception\BadRequest('Each {DAV:}ace element must have one {DAV:}principal element');
            }
            $principal = $ace['{DAV:}principal'];

            switch ($principal->getType()) {
                case Principal::HREF :
                    $principal = $principal->getHref();
                    break;
                case Principal::AUTHENTICATED :
                    $principal = '{DAV:}authenticated';
                    break;
                case Principal::UNAUTHENTICATED :
                    $principal = '{DAV:}unauthenticated';
                    break;
                case Principal::ALL :
                    $principal = '{DAV:}all';
                    break;

            }

            $protected = array_key_exists('{DAV:}protected', $ace);

            if (!isset($ace['{DAV:}grant'])) {
                throw new DAV\Exception\NotImplemented('Every {DAV:}ace element must have a {DAV:}grant element. {DAV:}deny is not yet supported');
            }
            foreach ($ace['{DAV:}grant'] as $elem) {
                if ($elem['name'] !== '{DAV:}privilege') {
                    continue;
                }

                foreach ($elem['value'] as $priv) {
                    $privileges[] = [
                        'principal' => $principal,
                        'protected' => $protected,
                        'privilege' => $priv,
                    ];
                }

            }

        }

        return new self($privileges);

    }

    /**
     * Serializes a single access control entry.
     *
     * @param Writer $writer
     * @param array $ace
     * @return void
     */
    private function serializeAce(Writer $writer, array $ace) {

        $writer->startElement('{DAV:}ace');

        switch ($ace['principal']) {
            case '{DAV:}authenticated' :
                $principal = new Principal(Principal::AUTHENTICATED);
                break;
            case '{DAV:}unauthenticated' :
                $principal = new Principal(Principal::UNAUTHENTICATED);
                break;
            case '{DAV:}all' :
                $principal = new Principal(Principal::ALL);
                break;
            default:
                $principal = new Principal(Principal::HREF, $ace['principal']);
                break;
        }

        $writer->writeElement('{DAV:}principal', $principal);
        $writer->startElement('{DAV:}grant');
        $writer->startElement('{DAV:}privilege');

        $writer->writeElement($ace['privilege']);

        $writer->endElement(); // privilege
        $writer->endElement(); // grant

        if (!empty($ace['protected'])) {
            $writer->writeElement('{DAV:}protected');
        }

        $writer->endElement(); // ace

    }

}
<?php

namespace Sabre\DAVACL\Xml\Property;

use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * AclRestrictions property
 *
 * This property represents {DAV:}acl-restrictions, as defined in RFC3744.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AclRestrictions implements XmlSerializable {

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $writer->writeElement('{DAV:}grant-only');
        $writer->writeElement('{DAV:}no-invert');

    }

}
<?php

namespace Sabre\DAVACL\Xml\Property;

use Sabre\DAV\Browser\HtmlOutput;
use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * CurrentUserPrivilegeSet
 *
 * This class represents the current-user-privilege-set property. When
 * requested, it contain all the privileges a user has on a specific node.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CurrentUserPrivilegeSet implements Element, HtmlOutput {

    /**
     * List of privileges
     *
     * @var array
     */
    private $privileges;

    /**
     * Creates the object
     *
     * Pass the privileges in clark-notation
     *
     * @param array $privileges
     */
    function __construct(array $privileges) {

        $this->privileges = $privileges;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        foreach ($this->privileges as $privName) {

            $writer->startElement('{DAV:}privilege');
            $writer->writeElement($privName);
            $writer->endElement();

        }


    }

    /**
     * Returns true or false, whether the specified principal appears in the
     * list.
     *
     * @param string $privilegeName
     * @return bool
     */
    function has($privilegeName) {

        return in_array($privilegeName, $this->privileges);

    }

    /**
     * Returns the list of privileges.
     *
     * @return array
     */
    function getValue() {

        return $this->privileges;

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = [];

        $tree = $reader->parseInnerTree(['{DAV:}privilege' => 'Sabre\\Xml\\Element\\Elements']);
        foreach ($tree as $element) {
            if ($element['name'] !== '{DAV:}privilege') {
                continue;
            }
            $result[] = $element['value'][0];
        }
        return new self($result);

    }

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html) {

        return implode(
            ', ',
            array_map([$html, 'xmlName'], $this->getValue())
        );

    }


}
<?php

namespace Sabre\DAVACL\Xml\Property;

use Sabre\DAV;
use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * Principal property
 *
 * The principal property represents a principal from RFC3744 (ACL).
 * The property can be used to specify a principal or pseudo principals.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Principal extends DAV\Xml\Property\Href {

    /**
     * To specify a not-logged-in user, use the UNAUTHENTICATED principal
     */
    const UNAUTHENTICATED = 1;

    /**
     * To specify any principal that is logged in, use AUTHENTICATED
     */
    const AUTHENTICATED = 2;

    /**
     * Specific principals can be specified with the HREF
     */
    const HREF = 3;

    /**
     * Everybody, basically
     */
    const ALL = 4;

    /**
     * Principal-type
     *
     * Must be one of the UNAUTHENTICATED, AUTHENTICATED or HREF constants.
     *
     * @var int
     */
    protected $type;

    /**
     * Creates the property.
     *
     * The 'type' argument must be one of the type constants defined in this class.
     *
     * 'href' is only required for the HREF type.
     *
     * @param int $type
     * @param string|null $href
     */
    function __construct($type, $href = null) {

        $this->type = $type;
        if ($type === self::HREF && is_null($href)) {
            throw new DAV\Exception('The href argument must be specified for the HREF principal type.');
        }
        if ($href) {
            $href = rtrim($href, '/') . '/';
            parent::__construct($href);
        }

    }

    /**
     * Returns the principal type
     *
     * @return int
     */
    function getType() {

        return $this->type;

    }


    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        switch ($this->type) {

            case self::UNAUTHENTICATED :
                $writer->writeElement('{DAV:}unauthenticated');
                break;
            case self::AUTHENTICATED :
                $writer->writeElement('{DAV:}authenticated');
                break;
            case self::HREF :
                parent::xmlSerialize($writer);
                break;
            case self::ALL :
                $writer->writeElement('{DAV:}all');
                break;
        }

    }

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html) {

        switch ($this->type) {

            case self::UNAUTHENTICATED :
                return '<em>unauthenticated</em>';
            case self::AUTHENTICATED :
                return '<em>authenticated</em>';
            case self::HREF :
                return parent::toHtml($html);
            case self::ALL :
                return '<em>all</em>';
        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called staticly, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $tree = $reader->parseInnerTree()[0];

        switch ($tree['name']) {
            case '{DAV:}unauthenticated' :
                return new self(self::UNAUTHENTICATED);
            case '{DAV:}authenticated' :
                return new self(self::AUTHENTICATED);
            case '{DAV:}href':
                return new self(self::HREF, $tree['value']);
            case '{DAV:}all':
                return new self(self::ALL);
            default :
                throw new BadRequest('Unknown or unsupported principal type: ' . $tree['name']);
        }

    }

}
<?php

namespace Sabre\DAVACL\Xml\Property;

use Sabre\DAV\Browser\HtmlOutput;
use Sabre\DAV\Browser\HtmlOutputHelper;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * SupportedPrivilegeSet property
 *
 * This property encodes the {DAV:}supported-privilege-set property, as defined
 * in rfc3744. Please consult the rfc for details about it's structure.
 *
 * This class expects a structure like the one given from
 * Sabre\DAVACL\Plugin::getSupportedPrivilegeSet as the argument in its
 * constructor.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SupportedPrivilegeSet implements XmlSerializable, HtmlOutput {

    /**
     * privileges
     *
     * @var array
     */
    protected $privileges;

    /**
     * Constructor
     *
     * @param array $privileges
     */
    function __construct(array $privileges) {

        $this->privileges = $privileges;

    }

    /**
     * Returns the privilege value.
     *
     * @return array
     */
    function getValue() {

        return $this->privileges;

    }

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $this->serializePriv($writer, '{DAV:}all', ['aggregates' => $this->privileges]);

    }

    /**
     * Generate html representation for this value.
     *
     * The html output is 100% trusted, and no effort is being made to sanitize
     * it. It's up to the implementor to sanitize user provided values.
     *
     * The output must be in UTF-8.
     *
     * The baseUri parameter is a url to the root of the application, and can
     * be used to construct local links.
     *
     * @param HtmlOutputHelper $html
     * @return string
     */
    function toHtml(HtmlOutputHelper $html) {

        $traverse = function($privName, $priv) use (&$traverse, $html) {
            echo "<li>";
            echo $html->xmlName($privName);
            if (isset($priv['abstract']) && $priv['abstract']) {
                echo " <i>(abstract)</i>";
            }
            if (isset($priv['description'])) {
                echo " " . $html->h($priv['description']);
            }
            if (isset($priv['aggregates'])) {
                echo "\n<ul>\n";
                foreach ($priv['aggregates'] as $subPrivName => $subPriv) {
                    $traverse($subPrivName, $subPriv);
                }
                echo "</ul>";
            }
            echo "</li>\n";
        };

        ob_start();
        echo "<ul class=\"tree\">";
        $traverse('{DAV:}all', ['aggregates' => $this->getValue()]);
        echo "</ul>\n";

        return ob_get_clean();

    }



    /**
     * Serializes a property
     *
     * This is a recursive function.
     *
     * @param Writer $writer
     * @param string $privName
     * @param array $privilege
     * @return void
     */
    private function serializePriv(Writer $writer, $privName, $privilege) {

        $writer->startElement('{DAV:}supported-privilege');

        $writer->startElement('{DAV:}privilege');
        $writer->writeElement($privName);
        $writer->endElement(); // privilege

        if (!empty($privilege['abstract'])) {
            $writer->writeElement('{DAV:}abstract');
        }
        if (!empty($privilege['description'])) {
            $writer->writeElement('{DAV:}description', $privilege['description']);
        }
        if (isset($privilege['aggregates'])) {
            foreach ($privilege['aggregates'] as $subPrivName => $subPrivilege) {
                $this->serializePriv($writer, $subPrivName, $subPrivilege);
            }
        }

        $writer->endElement(); // supported-privilege

    }

}
<?php

namespace Sabre\DAVACL\Xml\Request;

use Sabre\Xml\Deserializer;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * AclPrincipalPropSet request parser.
 *
 * This class parses the {DAV:}acl-principal-prop-set REPORT, as defined in:
 *
 * https://tools.ietf.org/html/rfc3744#section-9.2
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (https://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AclPrincipalPropSetReport implements XmlDeserializable {

    public $properties = [];

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {
       
        $reader->pushContext();
        $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum';

        $elems = Deserializer\keyValue(
            $reader,
            'DAV:'
        );

        $reader->popContext();

        $report = new self();

        if (!empty($elems['prop'])) {
            $report->properties = $elems['prop'];
        }

        return $report;

    }

}
<?php

namespace Sabre\DAVACL\Xml\Request;

use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * ExpandProperty request parser.
 *
 * This class parses the {DAV:}expand-property REPORT, as defined in:
 *
 * http://tools.ietf.org/html/rfc3253#section-3.8
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ExpandPropertyReport implements XmlDeserializable {

    /**
     * An array with requested properties.
     *
     * The requested properties will be used as keys in this array. The value
     * is normally null.
     *
     * If the value is an array though, it means the property must be expanded.
     * Within the array, the sub-properties, which themselves may be null or
     * arrays.
     *
     * @var array
     */
    public $properties;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $elems = $reader->parseInnerTree();

        $obj = new self();
        $obj->properties = self::traverse($elems);

        return $obj;

    }

    /**
     * This method is used by deserializeXml, to recursively parse the
     * {DAV:}property elements.
     *
     * @param array $elems
     * @return void
     */
    private static function traverse($elems) {

        $result = [];

        foreach ($elems as $elem) {

            if ($elem['name'] !== '{DAV:}property') {
                continue;
            }

            $namespace = isset($elem['attributes']['namespace']) ?
                $elem['attributes']['namespace'] :
                'DAV:';

            $propName = '{' . $namespace . '}' . $elem['attributes']['name'];

            $value = null;
            if (is_array($elem['value'])) {
                $value = self::traverse($elem['value']);
            }

            $result[$propName] = $value;

        }

        return $result;

    }

}
<?php

namespace Sabre\DAVACL\Xml\Request;

use Sabre\Xml\Deserializer;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * PrincipalMatchReport request parser.
 *
 * This class parses the {DAV:}principal-match REPORT, as defined
 * in:
 *
 * https://tools.ietf.org/html/rfc3744#section-9.3
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PrincipalMatchReport implements XmlDeserializable {

    /**
     * Report on a list of principals that match the current principal.
     */
    const SELF = 1;

    /**
     * Report on a property on resources, such as {DAV:}owner, that match the current principal.
     */
    const PRINCIPAL_PROPERTY = 2;

    /**
     * Must be SELF or PRINCIPAL_PROPERTY
     *
     * @var int
     */
    public $type;

    /**
     * List of properties that are being requested for matching resources.
     *
     * @var string[]
     */
    public $properties = [];

    /**
     * If $type = PRINCIPAL_PROPERTY, which WebDAV property we should compare
     * to the current principal.
     *
     * @var string
     */
    public $principalProperty;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $reader->pushContext();
        $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\enum';

        $elems = Deserializer\keyValue(
            $reader,
            'DAV:'
        );

        $reader->popContext();

        $principalMatch = new self();

        if (array_key_exists('self', $elems)) {
            $principalMatch->type = self::SELF;
        }

        if (array_key_exists('principal-property', $elems)) {
            $principalMatch->type = self::PRINCIPAL_PROPERTY;
            $principalMatch->principalProperty = $elems['principal-property'][0]['name'];
        }

        if (!empty($elems['prop'])) {
            $principalMatch->properties = $elems['prop'];
        }

        return $principalMatch;

    }

}
<?php

namespace Sabre\DAVACL\Xml\Request;

use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * PrincipalSearchPropertySetReport request parser.
 *
 * This class parses the {DAV:}principal-property-search REPORT, as defined
 * in:
 *
 * https://tools.ietf.org/html/rfc3744#section-9.4
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PrincipalPropertySearchReport implements XmlDeserializable {

    /**
     * The requested properties.
     *
     * @var array|null
     */
    public $properties;

    /**
     * searchProperties
     *
     * @var array
     */
    public $searchProperties = [];

    /**
     * By default the property search will be conducted on the url of the http
     * request. If this is set to true, it will be applied to the principal
     * collection set instead.
     *
     * @var bool
     */
    public $applyToPrincipalCollectionSet = false;

    /**
     * Search for principals matching ANY of the properties (OR) or a ALL of
     * the properties (AND).
     *
     * This property is either "anyof" or "allof".
     *
     * @var string
     */
    public $test;

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $self = new self();

        $foundSearchProp = false;
        $self->test = 'allof';
        if ($reader->getAttribute('test') === 'anyof') {
            $self->test = 'anyof';
        }

        $elemMap = [
            '{DAV:}property-search' => 'Sabre\\Xml\\Element\\KeyValue',
            '{DAV:}prop'            => 'Sabre\\Xml\\Element\\KeyValue',
        ];
        
        foreach ($reader->parseInnerTree($elemMap) as $elem) {

            switch ($elem['name']) {

                case '{DAV:}prop' :
                    $self->properties = array_keys($elem['value']);
                    break;
                case '{DAV:}property-search' :
                    $foundSearchProp = true;
                    // This property has two sub-elements:
                    //   {DAV:}prop - The property to be searched on. This may
                    //                also be more than one
                    //   {DAV:}match - The value to match with
                    if (!isset($elem['value']['{DAV:}prop']) || !isset($elem['value']['{DAV:}match'])) {
                        throw new BadRequest('The {DAV:}property-search element must contain one {DAV:}match and one {DAV:}prop element');
                    }
                    foreach ($elem['value']['{DAV:}prop'] as $propName => $discard) {
                        $self->searchProperties[$propName] = $elem['value']['{DAV:}match'];
                    }
                    break;
                case '{DAV:}apply-to-principal-collection-set' :
                    $self->applyToPrincipalCollectionSet = true;
                    break;

            }

        }
        if (!$foundSearchProp) {
            throw new BadRequest('The {DAV:}principal-property-search report must contain at least 1 {DAV:}property-search element');
        }

        return $self;

    }

}
<?php

namespace Sabre\DAVACL\Xml\Request;

use Sabre\DAV\Exception\BadRequest;
use Sabre\Xml\Reader;
use Sabre\Xml\XmlDeserializable;

/**
 * PrincipalSearchPropertySetReport request parser.
 *
 * This class parses the {DAV:}principal-search-property-set REPORT, as defined
 * in:
 *
 * https://tools.ietf.org/html/rfc3744#section-9.5
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PrincipalSearchPropertySetReport implements XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        if (!$reader->isEmptyElement) {
            throw new BadRequest('The {DAV:}principal-search-property-set element must be empty');
        }

        // The element is actually empty, so there's not much to do.
        $reader->next();

        $self = new self();
        return $self;

    }

}
Copyright (C) 2007-2016 fruux GmbH (https://fruux.com/).

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * 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.
    * Neither the name of SabreDAV 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
![sabre's logo](http://sabre.io/img/logo.png) sabre/dav
=======================================================

Introduction
------------

sabre/dav is the most popular WebDAV framework for PHP. Use it to create WebDAV, CalDAV and CardDAV servers.

Full documentation can be found on the website:

http://sabre.io/


Build status
------------

| branch       | status | minimum PHP version |
| ------------ | ------ | ------------------- |
| master       | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=master)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.5 |
| 3.1          | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.5 |
| 3.0          | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 2.1          | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 2.0          | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.4 |
| 1.8          | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.8)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |
| 1.7          | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.7)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |
| 1.6          | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.6)](https://travis-ci.org/fruux/sabre-dav) | PHP 5.3 |

Documentation
-------------

* [Introduction](http://sabre.io/dav/).
* [Installation](http://sabre.io/dav/install/).


Made at fruux
-------------

SabreDAV is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.
#composer
vendor
composer.lock

#binaries
bin/sabre-cs-fixer
bin/php-cs-fixer
bin/phpunit

#vim lock files
.*.swp

#development stuff
tests/cov
language: php
php:
  - 5.5
  - 5.6
  - 7
  - hhvm

matrix:
  allow_failures:
    - php: hhvm

env:
  matrix:
    - LOWEST_DEPS=""
    - LOWEST_DEPS="--prefer-lowest"

before_script:
  - composer update --prefer-source $LOWEST_DEPS

script:
  - ./bin/phpunit
  - ./bin/sabre-cs-fixer fix . --dry-run --diff

sudo: false

cache: vendor
ChangeLog
=========

3.0.0 (2015-11-05)
------------------

* Now requires PHP 5.5!
* `Promise::all()` is moved to `Promise\all()`.
* Aside from the `Promise\all()` function, there's now also `Promise\race()`.
* `Promise\reject()` and `Promise\resolve()` have also been added.
* Now 100% compatible with the Ecmascript 6 Promise.


3.0.0-alpha1 (2015-10-23)
-------------------------

* This package now requires PHP 5.5.
* #26: Added an event loop implementation. Also knows as the Reactor Pattern.
* Renamed `Promise::error` to `Promise::otherwise` to be consistent with
  ReactPHP and Guzzle. The `error` method is kept for BC but will be removed
  in a future version.
* #27: Support for Promise-based coroutines via the `Sabre\Event\coroutine`
  function.
* BC Break: Promises now use the EventLoop to run "then"-events in a separate
  execution context. In practise that means you need to run the event loop to
  wait for any `then`/`otherwise` callbacks to trigger.
* Promises now have a `wait()` method. Allowing you to make a promise
  synchronous and simply wait for a result (or exception) to happen.


2.0.2 (2015-05-19)
------------------

* This release has no functional changes. It's just been brought up to date
  with the latest coding standards.


2.0.1 (2014-10-06)
------------------

* Fixed: `$priority` was ignored in `EventEmitter::once` method.
* Fixed: Breaking the event chain was not possible in `EventEmitter::once`.


2.0.0 (2014-06-21)
------------------

* Added: When calling emit, it's now possible to specify a callback that will be
  triggered after each method handled. This is dubbed the 'continueCallback' and
  can be used to implement strategy patterns.
* Added: Promise object!
* Changed: EventEmitter::listeners now returns just the callbacks for an event,
  and no longer returns the list by reference. The list is now automatically
  sorted by priority.
* Update: Speed improvements.
* Updated: It's now possible to remove all listeners for every event.
* Changed: Now uses psr-4 autoloading.


1.0.1 (2014-06-12)
------------------

* hhvm compatible!
* Fixed: Issue #4. Compatiblitiy for PHP < 5.4.14.


1.0.0 (2013-07-19)
------------------

* Added: removeListener, removeAllListeners
* Added: once, to only listen to an event emitting once.
* Added README.md.


0.0.1-alpha (2013-06-29)
------------------------

* First version!
{
    "name": "sabre/event",
    "description": "sabre/event is a library for lightweight event-based programming",
    "keywords": [
        "Events",
        "EventEmitter",
        "Promise",
        "Hooks",
        "Plugin",
        "Signal",
        "Async"
    ],
    "homepage": "http://sabre.io/event/",
    "license": "BSD-3-Clause",
    "require": {
        "php": ">=5.5"
    },
    "authors": [
        {
            "name": "Evert Pot",
            "email": "me@evertpot.com",
            "homepage": "http://evertpot.com/",
            "role": "Developer"
        }
    ],
    "support": {
        "forum": "https://groups.google.com/group/sabredav-discuss",
        "source": "https://github.com/fruux/sabre-event"
    },
    "autoload": {
        "psr-4": {
            "Sabre\\Event\\": "lib/"
        },
        "files" : [
            "lib/coroutine.php",
            "lib/Loop/functions.php",
            "lib/Promise/functions.php"
        ]
    },
    "require-dev": {
        "sabre/cs": "~0.0.4",
        "phpunit/phpunit" : "*"
    },
    "config" : {
        "bin-dir" : "bin/"
    }
}
#!/usr/bin/env php
<?php

use Sabre\Event\Promise;
use Sabre\Event\Loop;
use function Sabre\Event\coroutine;

require __DIR__ . '/../vendor/autoload.php';

/**
 * This example shows demonstrates the Promise api.
 */


/* Creating a new promise */
$promise = new Promise();

/* After 2 seconds we fulfill it */
Loop\setTimeout(function() use ($promise) {

    echo "Step 1\n";
    $promise->fulfill("hello");

}, 2);


/* Callback chain */

$result = $promise
    ->then(function($value) {

        echo "Step 2\n";
        // Immediately returning a new value.
        return $value . " world";

    })
    ->then(function($value) {

        echo "Step 3\n";
        // This 'then' returns a new promise which we resolve later.
        $promise = new Promise();

        // Resolving after 2 seconds
        Loop\setTimeout(function() use ($promise, $value) {

            $promise->fulfill($value . ", how are ya?");

        }, 2);

        return $promise;
    })
    ->then(function($value) {

        echo "Step 4\n";
        // This is the final event handler.
        return $value . " you rock!";
         
    })
    // Making all async calls synchronous by waiting for the final result.
    ->wait();

echo $result, "\n";

/* Now an identical example, this time with coroutines. */

$result = coroutine(function() {

    $promise = new Promise();

    /* After 2 seconds we fulfill it */
    Loop\setTimeout(function() use ($promise) {

        echo "Step 1\n";
        $promise->fulfill("hello");

    }, 2);

    $value = (yield $promise);

    echo "Step 2\n";
    $value .= ' world';

    echo "Step 3\n";
    $promise = new Promise();
    Loop\setTimeout(function() use ($promise, $value) {

        $promise->fulfill($value . ", how are ya?");

    }, 2);

    $value = (yield $promise);

    echo "Step 4\n";

    // This is the final event handler.
    yield $value . " you rock!";

})->wait();

echo $result, "\n";
#!/usr/bin/env php
<?php

/**
 * This example can be used to logfile processing and basically wraps the tail
 * command.
 *
 * The benefit of using this, is that it allows you to tail multiple logs at
 * the same time
 *
 * To stop this application, hit CTRL-C
 */
if ($argc < 2) {
    echo "Usage: " . $argv[0] . " filename\n";
    exit(1);
}

require __DIR__ . '/../vendor/autoload.php';

$tail = popen('tail -fn0 ' . escapeshellarg($argv[1]), 'r');

\Sabre\Event\Loop\addReadStream($tail, function() use ($tail) {

    echo fread($tail, 4096);

});

$loop->run();
<?php

namespace Sabre\Event;

use Generator;
use Exception;

/**
 * Turn asynchronous promise-based code into something that looks synchronous
 * again, through the use of generators.
 *
 * Example without coroutines:
 *
 * $promise = $httpClient->request('GET', '/foo');
 * $promise->then(function($value) {
 *
 *   return $httpClient->request('DELETE','/foo');
 *
 * })->then(function($value) {
 *
 *   return $httpClient->request('PUT', '/foo');
 *
 * })->error(function($reason) {
 *
 *   echo "Failed because: $reason\n";
 *
 * });
 *
 * Example with coroutines:
 *
 * coroutine(function() {
 *
 *   try {
 *     yield $httpClient->request('GET', '/foo');
 *     yield $httpClient->request('DELETE', /foo');
 *     yield $httpClient->request('PUT', '/foo');
 *   } catch(\Exception $reason) {
 *     echo "Failed because: $reason\n";
 *   }
 *
 * });
 *
 * @copyright Copyright (C) 2013-2015 fruux GmbH. All rights reserved.
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
function coroutine(callable $gen) {

    $generator = $gen();
    if (!$generator instanceof Generator) {
        throw new \InvalidArgumentException('You must pass a generator function');
    }

    // This is the value we're returning.
    $promise = new Promise();

    $lastYieldResult = null;

    /**
     * So tempted to use the mythical y-combinator here, but it's not needed in
     * PHP.
     */
    $advanceGenerator = function() use (&$advanceGenerator, $generator, $promise, &$lastYieldResult) {

        while ($generator->valid()) {

            $yieldedValue = $generator->current();
            if ($yieldedValue instanceof Promise) {
                $yieldedValue->then(
                    function($value) use ($generator, &$advanceGenerator, &$lastYieldResult) {
                        $lastYieldResult = $value;
                        $generator->send($value);
                        $advanceGenerator();
                    },
                    function($reason) use ($generator, $advanceGenerator) {
                        if ($reason instanceof Exception) {
                            $generator->throw($reason);
                        } elseif (is_scalar($reason)) {
                            $generator->throw(new Exception($reason));
                        } else {
                            $type = is_object($reason) ? get_class($reason) : gettype($reason);
                            $generator->throw(new Exception('Promise was rejected with reason of type: ' . $type));
                        }
                        $advanceGenerator();
                    }
                )->error(function($reason) use ($promise) {
                    // This error handler would be called, if something in the
                    // generator throws an exception, and it's not caught
                    // locally.
                    $promise->reject($reason);
                });
                // We need to break out of the loop, because $advanceGenerator
                // will be called asynchronously when the promise has a result.
                break;
            } else {
                // If the value was not a promise, we'll just let it pass through.
                $lastYieldResult = $yieldedValue;
                $generator->send($yieldedValue);
            }

        }

        // If the generator is at the end, and we didn't run into an exception,
        // we can fullfill the promise with the last thing that was yielded to
        // us.
        if (!$generator->valid() && $promise->state === Promise::PENDING) {
            $promise->fulfill($lastYieldResult);
        }

    };

    try {
        $advanceGenerator();
    } catch (Exception $e) {
        $promise->reject($e);
    }

    return $promise;

}
<?php

namespace Sabre\Event;

/**
 * EventEmitter object.
 *
 * Instantiate this class, or subclass it for easily creating event emitters.
 *
 * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class EventEmitter implements EventEmitterInterface {

    use EventEmitterTrait;

}
<?php

namespace Sabre\Event;

/**
 * Event Emitter Interface
 *
 * Anything that accepts listeners and emits events should implement this
 * interface.
 *
 * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface EventEmitterInterface {

    /**
     * Subscribe to an event.
     *
     * @param string $eventName
     * @param callable $callBack
     * @param int $priority
     * @return void
     */
    function on($eventName, callable $callBack, $priority = 100);

    /**
     * Subscribe to an event exactly once.
     *
     * @param string $eventName
     * @param callable $callBack
     * @param int $priority
     * @return void
     */
    function once($eventName, callable $callBack, $priority = 100);

    /**
     * Emits an event.
     *
     * This method will return true if 0 or more listeners were succesfully
     * handled. false is returned if one of the events broke the event chain.
     *
     * If the continueCallBack is specified, this callback will be called every
     * time before the next event handler is called.
     *
     * If the continueCallback returns false, event propagation stops. This
     * allows you to use the eventEmitter as a means for listeners to implement
     * functionality in your application, and break the event loop as soon as
     * some condition is fulfilled.
     *
     * Note that returning false from an event subscriber breaks propagation
     * and returns false, but if the continue-callback stops propagation, this
     * is still considered a 'successful' operation and returns true.
     *
     * Lastly, if there are 5 event handlers for an event. The continueCallback
     * will be called at most 4 times.
     *
     * @param string $eventName
     * @param array $arguments
     * @param callback $continueCallBack
     * @return bool
     */
    function emit($eventName, array $arguments = [], callable $continueCallBack = null);

    /**
     * Returns the list of listeners for an event.
     *
     * The list is returned as an array, and the list of events are sorted by
     * their priority.
     *
     * @param string $eventName
     * @return callable[]
     */
    function listeners($eventName);

    /**
     * Removes a specific listener from an event.
     *
     * If the listener could not be found, this method will return false. If it
     * was removed it will return true.
     *
     * @param string $eventName
     * @param callable $listener
     * @return bool
     */
    function removeListener($eventName, callable $listener);

    /**
     * Removes all listeners.
     *
     * If the eventName argument is specified, all listeners for that event are
     * removed. If it is not specified, every listener for every event is
     * removed.
     *
     * @param string $eventName
     * @return void
     */
    function removeAllListeners($eventName = null);

}
<?php

namespace Sabre\Event;

/**
 * Event Emitter Trait
 *
 * This trait contains all the basic functions to implement an
 * EventEmitterInterface.
 *
 * Using the trait + interface allows you to add EventEmitter capabilities
 * without having to change your base-class.
 *
 * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
trait EventEmitterTrait {

    /**
     * The list of listeners
     *
     * @var array
     */
    protected $listeners = [];

    /**
     * Subscribe to an event.
     *
     * @param string $eventName
     * @param callable $callBack
     * @param int $priority
     * @return void
     */
    function on($eventName, callable $callBack, $priority = 100) {

        if (!isset($this->listeners[$eventName])) {
            $this->listeners[$eventName] = [
                true,  // If there's only one item, it's sorted
                [$priority],
                [$callBack]
            ];
        } else {
            $this->listeners[$eventName][0] = false; // marked as unsorted
            $this->listeners[$eventName][1][] = $priority;
            $this->listeners[$eventName][2][] = $callBack;
        }

    }

    /**
     * Subscribe to an event exactly once.
     *
     * @param string $eventName
     * @param callable $callBack
     * @param int $priority
     * @return void
     */
    function once($eventName, callable $callBack, $priority = 100) {

        $wrapper = null;
        $wrapper = function() use ($eventName, $callBack, &$wrapper) {

            $this->removeListener($eventName, $wrapper);
            return call_user_func_array($callBack, func_get_args());

        };

        $this->on($eventName, $wrapper, $priority);

    }

    /**
     * Emits an event.
     *
     * This method will return true if 0 or more listeners were succesfully
     * handled. false is returned if one of the events broke the event chain.
     *
     * If the continueCallBack is specified, this callback will be called every
     * time before the next event handler is called.
     *
     * If the continueCallback returns false, event propagation stops. This
     * allows you to use the eventEmitter as a means for listeners to implement
     * functionality in your application, and break the event loop as soon as
     * some condition is fulfilled.
     *
     * Note that returning false from an event subscriber breaks propagation
     * and returns false, but if the continue-callback stops propagation, this
     * is still considered a 'successful' operation and returns true.
     *
     * Lastly, if there are 5 event handlers for an event. The continueCallback
     * will be called at most 4 times.
     *
     * @param string $eventName
     * @param array $arguments
     * @param callback $continueCallBack
     * @return bool
     */
    function emit($eventName, array $arguments = [], callable $continueCallBack = null) {

        if (is_null($continueCallBack)) {

            foreach ($this->listeners($eventName) as $listener) {

                $result = call_user_func_array($listener, $arguments);
                if ($result === false) {
                    return false;
                }
            }

        } else {

            $listeners = $this->listeners($eventName);
            $counter = count($listeners);

            foreach ($listeners as $listener) {

                $counter--;
                $result = call_user_func_array($listener, $arguments);
                if ($result === false) {
                    return false;
                }

                if ($counter > 0) {
                    if (!$continueCallBack()) break;
                }

            }

        }

        return true;

    }

    /**
     * Returns the list of listeners for an event.
     *
     * The list is returned as an array, and the list of events are sorted by
     * their priority.
     *
     * @param string $eventName
     * @return callable[]
     */
    function listeners($eventName) {

        if (!isset($this->listeners[$eventName])) {
            return [];
        }

        // The list is not sorted
        if (!$this->listeners[$eventName][0]) {

            // Sorting
            array_multisort($this->listeners[$eventName][1], SORT_NUMERIC, $this->listeners[$eventName][2]);

            // Marking the listeners as sorted
            $this->listeners[$eventName][0] = true;
        }

        return $this->listeners[$eventName][2];

    }

    /**
     * Removes a specific listener from an event.
     *
     * If the listener could not be found, this method will return false. If it
     * was removed it will return true.
     *
     * @param string $eventName
     * @param callable $listener
     * @return bool
     */
    function removeListener($eventName, callable $listener) {

        if (!isset($this->listeners[$eventName])) {
            return false;
        }
        foreach ($this->listeners[$eventName][2] as $index => $check) {
            if ($check === $listener) {
                unset($this->listeners[$eventName][1][$index]);
                unset($this->listeners[$eventName][2][$index]);
                return true;
            }
        }
        return false;

    }

    /**
     * Removes all listeners.
     *
     * If the eventName argument is specified, all listeners for that event are
     * removed. If it is not specified, every listener for every event is
     * removed.
     *
     * @param string $eventName
     * @return void
     */
    function removeAllListeners($eventName = null) {

        if (!is_null($eventName)) {
            unset($this->listeners[$eventName]);
        } else {
            $this->listeners = [];
        }

    }

}
<?php

namespace Sabre\Event\Loop;

/**
 * Executes a function after x seconds.
 *
 * @param callable $cb
 * @param float $timeout timeout in seconds
 * @return void
 */
function setTimeout(callable $cb, $timeout) {

    instance()->setTimeout($cb, $timeout);

}

/**
 * Executes a function every x seconds.
 *
 * The value this function returns can be used to stop the interval with
 * clearInterval.
 *
 * @param callable $cb
 * @param float $timeout
 * @return array
 */
function setInterval(callable $cb, $timeout) {

    return instance()->setInterval($cb, $timeout);

}

/**
 * Stops a running internval.
 *
 * @param array $intervalId
 * @return void
 */
function clearInterval($intervalId) {

    instance()->clearInterval($intervalId);

}

/**
 * Runs a function immediately at the next iteration of the loop.
 *
 * @param callable $cb
 * @return void
 */
function nextTick(callable $cb) {

    instance()->nextTick($cb);

}


/**
 * Adds a read stream.
 *
 * The callback will be called as soon as there is something to read from
 * the stream.
 *
 * You MUST call removeReadStream after you are done with the stream, to
 * prevent the eventloop from never stopping.
 *
 * @param resource $stream
 * @param callable $cb
 * @return void
 */
function addReadStream($stream, callable $cb) {

    instance()->addReadStream($stream, $cb);

}

/**
 * Adds a write stream.
 *
 * The callback will be called as soon as the system reports it's ready to
 * receive writes on the stream.
 *
 * You MUST call removeWriteStream after you are done with the stream, to
 * prevent the eventloop from never stopping.
 *
 * @param resource $stream
 * @param callable $cb
 * @return void
 */
function addWriteStream($stream, callable $cb) {

    instance()->addWriteStream($stream, $cb);

}

/**
 * Stop watching a stream for reads.
 *
 * @param resource $stream
 * @return void
 */
function removeReadStream($stream) {

    instance()->removeReadStream($stream);

}

/**
 * Stop watching a stream for writes.
 *
 * @param resource $stream
 * @return void
 */
function removeWriteStream($stream) {

    instance()->removeWriteStream($stream);

}


/**
 * Runs the loop.
 *
 * This function will run continiously, until there's no more events to
 * handle.
 *
 * @return void
 */
function run() {

    instance()->run();

}

/**
 * Executes all pending events.
 *
 * If $block is turned true, this function will block until any event is
 * triggered.
 *
 * If there are now timeouts, nextTick callbacks or events in the loop at
 * all, this function will exit immediately.
 *
 * This function will return true if there are _any_ events left in the
 * loop after the tick.
 *
 * @param bool $block
 * @return bool
 */
function tick($block = false) {

    return instance()->tick($block);

}

/**
 * Stops a running eventloop
 *
 * @return void
 */
function stop() {

    instance()->stop();

}

/**
 * Retrieves or sets the global Loop object.
 *
 * @param Loop $newLoop
 */
function instance(Loop $newLoop = null) {

    static $loop;
    if ($newLoop) {
        $loop = $newLoop;
    } elseif (!$loop) {
        $loop = new Loop();
    }
    return $loop;

}
<?php

namespace Sabre\Event\Loop;

/**
 * A simple eventloop implementation.
 *
 * This eventloop supports:
 *   * nextTick
 *   * setTimeout for delayed functions
 *   * setInterval for repeating functions
 *   * stream events using stream_select
 *
 * @copyright Copyright (C) 2007-2015 fruux GmbH. (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Loop {

    /**
     * Executes a function after x seconds.
     *
     * @param callable $cb
     * @param float $timeout timeout in seconds
     * @return void
     */
    function setTimeout(callable $cb, $timeout) {

        $triggerTime = microtime(true) + ($timeout);

        if (!$this->timers) {
            // Special case when the timers array was empty.
            $this->timers[] = [$triggerTime, $cb];
            return;
        }

        // We need to insert these values in the timers array, but the timers
        // array must be in reverse-order of trigger times.
        //
        // So here we search the array for the insertion point.
        $index = count($this->timers) - 1;
        while (true) {
            if ($triggerTime < $this->timers[$index][0]) {
                array_splice(
                    $this->timers,
                    $index + 1,
                    0,
                    [[$triggerTime, $cb]]
                );
                break;
            } elseif ($index === 0) {
                array_unshift($this->timers, [$triggerTime, $cb]);
                break;
            }
            $index--;

        }

    }

    /**
     * Executes a function every x seconds.
     *
     * The value this function returns can be used to stop the interval with
     * clearInterval.
     *
     * @param callable $cb
     * @param float $timeout
     * @return array
     */
    function setInterval(callable $cb, $timeout) {

        $keepGoing = true;
        $f = null;

        $f = function() use ($cb, &$f, $timeout, &$keepGoing) {
            if ($keepGoing) {
                $cb();
                $this->setTimeout($f, $timeout);
            }
        };
        $this->setTimeout($f, $timeout);

        // Really the only thing that matters is returning the $keepGoing
        // boolean value.
        //
        // We need to pack it in an array to allow returning by reference.
        // Because I'm worried people will be confused by using a boolean as a
        // sort of identifier, I added an extra string.
        return ['I\'m an implementation detail', &$keepGoing];

    }

    /**
     * Stops a running internval.
     *
     * @param array $intervalId
     * @return void
     */
    function clearInterval($intervalId) {

        $intervalId[1] = false;

    }

    /**
     * Runs a function immediately at the next iteration of the loop.
     *
     * @param callable $cb
     * @return void
     */
    function nextTick(callable $cb) {

        $this->nextTick[] = $cb;

    }


    /**
     * Adds a read stream.
     *
     * The callback will be called as soon as there is something to read from
     * the stream.
     *
     * You MUST call removeReadStream after you are done with the stream, to
     * prevent the eventloop from never stopping.
     *
     * @param resource $stream
     * @param callable $cb
     * @return void
     */
    function addReadStream($stream, callable $cb) {

        $this->readStreams[(int)$stream] = $stream;
        $this->readCallbacks[(int)$stream] = $cb;

    }

    /**
     * Adds a write stream.
     *
     * The callback will be called as soon as the system reports it's ready to
     * receive writes on the stream.
     *
     * You MUST call removeWriteStream after you are done with the stream, to
     * prevent the eventloop from never stopping.
     *
     * @param resource $stream
     * @param callable $cb
     * @return void
     */
    function addWriteStream($stream, callable $cb) {

        $this->writeStreams[(int)$stream] = $stream;
        $this->writeCallbacks[(int)$stream] = $cb;

    }

    /**
     * Stop watching a stream for reads.
     *
     * @param resource $stream
     * @return void
     */
    function removeReadStream($stream) {

        unset(
            $this->readStreams[(int)$stream],
            $this->readCallbacks[(int)$stream]
        );

    }

    /**
     * Stop watching a stream for writes.
     *
     * @param resource $stream
     * @return void
     */
    function removeWriteStream($stream) {

        unset(
            $this->writeStreams[(int)$stream],
            $this->writeCallbacks[(int)$stream]
        );

    }


    /**
     * Runs the loop.
     *
     * This function will run continiously, until there's no more events to
     * handle.
     *
     * @return void
     */
    function run() {

        $this->running = true;

        do {

            $hasEvents = $this->tick(true);

        } while ($this->running && $hasEvents);
        $this->running = false;

    }

    /**
     * Executes all pending events.
     *
     * If $block is turned true, this function will block until any event is
     * triggered.
     *
     * If there are now timeouts, nextTick callbacks or events in the loop at
     * all, this function will exit immediately.
     *
     * This function will return true if there are _any_ events left in the
     * loop after the tick.
     *
     * @param bool $block
     * @return bool
     */
    function tick($block = false) {

        $this->runNextTicks();
        $nextTimeout = $this->runTimers();

        // Calculating how long runStreams should at most wait.
        if (!$block) {
            // Don't wait
            $streamWait = 0;
        } elseif ($this->nextTick) {
            // There's a pending 'nextTick'. Don't wait.
            $streamWait = 0;
        } elseif (is_numeric($nextTimeout)) {
            // Wait until the next Timeout should trigger.
            $streamWait = $nextTimeout;
        } else {
            // Wait indefinitely
            $streamWait = null;
        }

        $this->runStreams($streamWait);

        return ($this->readStreams || $this->writeStreams || $this->nextTick || $this->timers);

    }

    /**
     * Stops a running eventloop
     *
     * @return void
     */
    function stop() {

        $this->running = false;

    }

    /**
     * Executes all 'nextTick' callbacks.
     *
     * return void
     */
    protected function runNextTicks() {

        $nextTick = $this->nextTick;
        $this->nextTick = [];

        foreach ($nextTick as $cb) {
            $cb();
        }

    }

    /**
     * Runs all pending timers.
     *
     * After running the timer callbacks, this function returns the number of
     * seconds until the next timer should be executed.
     *
     * If there's no more pending timers, this function returns null.
     *
     * @return float
     */
    protected function runTimers() {

        $now = microtime(true);
        while (($timer = array_pop($this->timers)) && $timer[0] < $now) {
            $timer[1]();
        }
        // Add the last timer back to the array.
        if ($timer) {
            $this->timers[] = $timer;
            return $timer[0] - microtime(true);
        }

    }

    /**
     * Runs all pending stream events.
     *
     * @param float $timeout
     */
    protected function runStreams($timeout) {

        if ($this->readStreams || $this->writeStreams) {

            $read = $this->readStreams;
            $write = $this->writeStreams;
            $except = null;
            if (stream_select($read, $write, $except, null, $timeout)) {

                // See PHP Bug https://bugs.php.net/bug.php?id=62452
                // Fixed in PHP7
                foreach ($read as $readStream) {
                    $readCb = $this->readCallbacks[(int)$readStream];
                    $readCb();
                }
                foreach ($write as $writeStream) {
                    $writeCb = $this->writeCallbacks[(int)$writeStream];
                    $writeCb();
                }

            }

        } elseif ($this->running && ($this->nextTick || $this->timers)) {
            usleep($timeout !== null ? $timeout * 1000000 : 200000);
        }

    }

    /**
     * Is the main loop active
     *
     * @var bool
     */
    protected $running = false;

    /**
     * A list of timers, added by setTimeout.
     *
     * @var array
     */
    protected $timers = [];

    /**
     * A list of 'nextTick' callbacks.
     *
     * @var callable[]
     */
    protected $nextTick = [];

    /**
     * List of readable streams for stream_select, indexed by stream id.
     *
     * @var resource[]
     */
    protected $readStreams = [];

    /**
     * List of writable streams for stream_select, indexed by stream id.
     *
     * @var resource[]
     */
    protected $writeStreams = [];

    /**
     * List of read callbacks, indexed by stream id.
     *
     * @var callback[]
     */
    protected $readCallbacks = [];

    /**
     * List of write callbacks, indexed by stream id.
     *
     * @var callback[]
     */
    protected $writeCallbacks = [];


}
<?php

namespace Sabre\Event\Promise;

use Sabre\Event\Promise;

/**
 * This file contains a set of functions that are useful for dealing with the
 * Promise object.
 *
 * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */


/**
 * This function takes an array of Promises, and returns a Promise that
 * resolves when all of the given arguments have resolved.
 *
 * The returned Promise will resolve with a value that's an array of all the
 * values the given promises have been resolved with.
 *
 * This array will be in the exact same order as the array of input promises.
 *
 * If any of the given Promises fails, the returned promise will immidiately
 * fail with the first Promise that fails, and its reason.
 *
 * @param Promise[] $promises
 * @return Promise
 */
function all(array $promises) {

    return new Promise(function($success, $fail) use ($promises) {

        $successCount = 0;
        $completeResult = [];

        foreach ($promises as $promiseIndex => $subPromise) {

            $subPromise->then(
                function($result) use ($promiseIndex, &$completeResult, &$successCount, $success, $promises) {
                    $completeResult[$promiseIndex] = $result;
                    $successCount++;
                    if ($successCount === count($promises)) {
                        $success($completeResult);
                    }
                    return $result;
                }
            )->error(
                function($reason) use ($fail) {
                    $fail($reason);
                }
            );

        }
    });

}

/**
 * The race function returns a promise that resolves or rejects as soon as
 * one of the promises in the argument resolves or rejects.
 *
 * The returned promise will resolve or reject with the value or reason of
 * that first promise.
 *
 * @param Promise[] $promises
 * @return Promise
 */
function race(array $promises) {

    return new Promise(function($success, $fail) use ($promises) {

        $alreadyDone = false;
        foreach ($promises as $promise) {

            $promise->then(
                function($result) use ($success, &$alreadyDone) {
                    if ($alreadyDone) {
                        return;
                    }
                    $alreadyDone = true;
                    $success($result);
                },
                function($reason) use ($fail, &$alreadyDone) {
                    if ($alreadyDone) {
                        return;
                    }
                    $alreadyDone = true;
                    $fail($reason);
                }
            );

        }

    });

}


/**
 * Returns a Promise that resolves with the given value.
 *
 * If the value is a promise, the returned promise will attach itself to that
 * promise and eventually get the same state as the followed promise.
 *
 * @param mixed $value
 * @return Promise
 */
function resolve($value) {

    if ($value instanceof Promise) {
        return $value->then();
    } else {
        $promise = new Promise();
        $promise->fulfill($value);
        return $promise;
    }

}

/**
 * Returns a Promise that will reject with the given reason.
 *
 * @param mixed $reason
 * @return Promise
 */
function reject($reason) {

    $promise = new Promise();
    $promise->reject($reason);
    return $promise;

}
<?php

namespace Sabre\Event;

use Exception;

/**
 * An implementation of the Promise pattern.
 *
 * A promise represents the result of an asynchronous operation.
 * At any given point a promise can be in one of three states:
 *
 * 1. Pending (the promise does not have a result yet).
 * 2. Fulfilled (the asynchronous operation has completed with a result).
 * 3. Rejected (the asynchronous operation has completed with an error).
 *
 * To get a callback when the operation has finished, use the `then` method.
 *
 * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Promise {

    /**
     * The asynchronous operation is pending.
     */
    const PENDING = 0;

    /**
     * The asynchronous operation has completed, and has a result.
     */
    const FULFILLED = 1;

    /**
     * The asynchronous operation has completed with an error.
     */
    const REJECTED = 2;

    /**
     * The current state of this promise.
     *
     * @var int
     */
    public $state = self::PENDING;

    /**
     * Creates the promise.
     *
     * The passed argument is the executor. The executor is automatically
     * called with two arguments.
     *
     * Each are callbacks that map to $this->fulfill and $this->reject.
     * Using the executor is optional.
     *
     * @param callable $executor
     */
    function __construct(callable $executor = null) {

        if ($executor) {
            $executor(
                [$this, 'fulfill'],
                [$this, 'reject']
            );
        }

    }

    /**
     * This method allows you to specify the callback that will be called after
     * the promise has been fulfilled or rejected.
     *
     * Both arguments are optional.
     *
     * This method returns a new promise, which can be used for chaining.
     * If either the onFulfilled or onRejected callback is called, you may
     * return a result from this callback.
     *
     * If the result of this callback is yet another promise, the result of
     * _that_ promise will be used to set the result of the returned promise.
     *
     * If either of the callbacks return any other value, the returned promise
     * is automatically fulfilled with that value.
     *
     * If either of the callbacks throw an exception, the returned promise will
     * be rejected and the exception will be passed back.
     *
     * @param callable $onFulfilled
     * @param callable $onRejected
     * @return Promise
     */
    function then(callable $onFulfilled = null, callable $onRejected = null) {

        // This new subPromise will be returned from this function, and will
        // be fulfilled with the result of the onFulfilled or onRejected event
        // handlers.
        $subPromise = new self();

        switch ($this->state) {
            case self::PENDING :
                // The operation is pending, so we keep a reference to the
                // event handlers so we can call them later.
                $this->subscribers[] = [$subPromise, $onFulfilled, $onRejected];
                break;
            case self::FULFILLED :
                // The async operation is already fulfilled, so we trigger the
                // onFulfilled callback asap.
                $this->invokeCallback($subPromise, $onFulfilled);
                break;
            case self::REJECTED :
                // The async operation failed, so we call teh onRejected
                // callback asap.
                $this->invokeCallback($subPromise, $onRejected);
                break;
        }
        return $subPromise;

    }

    /**
     * Add a callback for when this promise is rejected.
     *
     * Its usage is identical to then(). However, the otherwise() function is
     * preferred.
     *
     * @param callable $onRejected
     * @return Promise
     */
    function otherwise(callable $onRejected) {

        return $this->then(null, $onRejected);

    }

    /**
     * Marks this promise as fulfilled and sets its return value.
     *
     * @param mixed $value
     * @return void
     */
    function fulfill($value = null) {
        if ($this->state !== self::PENDING) {
            throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once');
        }
        $this->state = self::FULFILLED;
        $this->value = $value;
        foreach ($this->subscribers as $subscriber) {
            $this->invokeCallback($subscriber[0], $subscriber[1]);
        }
    }

    /**
     * Marks this promise as rejected, and set it's rejection reason.
     *
     * While it's possible to use any PHP value as the reason, it's highly
     * recommended to use an Exception for this.
     *
     * @param mixed $reason
     * @return void
     */
    function reject($reason = null) {
        if ($this->state !== self::PENDING) {
            throw new PromiseAlreadyResolvedException('This promise is already resolved, and you\'re not allowed to resolve a promise more than once');
        }
        $this->state = self::REJECTED;
        $this->value = $reason;
        foreach ($this->subscribers as $subscriber) {
            $this->invokeCallback($subscriber[0], $subscriber[2]);
        }

    }

    /**
     * Stops execution until this promise is resolved.
     *
     * This method stops exection completely. If the promise is successful with
     * a value, this method will return this value. If the promise was
     * rejected, this method will throw an exception.
     *
     * This effectively turns the asynchronous operation into a synchronous
     * one. In PHP it might be useful to call this on the last promise in a
     * chain.
     *
     * @throws Exception
     * @return mixed
     */
    function wait() {

        $hasEvents = true;
        while ($this->state === self::PENDING) {

            if (!$hasEvents) {
                throw new \LogicException('There were no more events in the loop. This promise will never be fulfilled.');
            }

            // As long as the promise is not fulfilled, we tell the event loop
            // to handle events, and to block.
            $hasEvents = Loop\tick(true);

        }

        if ($this->state === self::FULFILLED) {
            // If the state of this promise is fulfilled, we can return the value.
            return $this->value;
        } else {
            // If we got here, it means that the asynchronous operation
            // errored. Therefore we need to throw an exception.
            $reason = $this->value;
            if ($reason instanceof Exception) {
                throw $reason;
            } elseif (is_scalar($reason)) {
                throw new Exception($reason);
            } else {
                $type = is_object($reason) ? get_class($reason) : gettype($reason);
                throw new Exception('Promise was rejected with reason of type: ' . $type);
            }
        }


    }


    /**
     * A list of subscribers. Subscribers are the callbacks that want us to let
     * them know if the callback was fulfilled or rejected.
     *
     * @var array
     */
    protected $subscribers = [];

    /**
     * The result of the promise.
     *
     * If the promise was fulfilled, this will be the result value. If the
     * promise was rejected, this property hold the rejection reason.
     *
     * @var mixed
     */
    protected $value = null;

    /**
     * This method is used to call either an onFulfilled or onRejected callback.
     *
     * This method makes sure that the result of these callbacks are handled
     * correctly, and any chained promises are also correctly fulfilled or
     * rejected.
     *
     * @param Promise $subPromise
     * @param callable $callBack
     * @return void
     */
    private function invokeCallback(Promise $subPromise, callable $callBack = null) {

        // We use 'nextTick' to ensure that the event handlers are always
        // triggered outside of the calling stack in which they were originally
        // passed to 'then'.
        //
        // This makes the order of execution more predictable.
        Loop\nextTick(function() use ($callBack, $subPromise) {
            if (is_callable($callBack)) {
                try {

                    $result = $callBack($this->value);
                    if ($result instanceof self) {
                        // If the callback (onRejected or onFulfilled)
                        // returned a promise, we only fulfill or reject the
                        // chained promise once that promise has also been
                        // resolved.
                        $result->then([$subPromise, 'fulfill'], [$subPromise, 'reject']);
                    } else {
                        // If the callback returned any other value, we
                        // immediately fulfill the chained promise.
                        $subPromise->fulfill($result);
                    }
                } catch (Exception $e) {
                    // If the event handler threw an exception, we need to make sure that
                    // the chained promise is rejected as well.
                    $subPromise->reject($e);
                }
            } else {
                if ($this->state === self::FULFILLED) {
                    $subPromise->fulfill($this->value);
                } else {
                    $subPromise->reject($this->value);
                }
            }
        });
    }

    /**
     * Alias for 'otherwise'.
     *
     * This function is now deprecated and will be removed in a future version.
     *
     * @param callable $onRejected
     * @deprecated
     * @return Promise
     */
    function error(callable $onRejected) {

        return $this->otherwise($onRejected);

    }

    /**
     * Deprecated.
     *
     * Please use Sabre\Event\Promise::all
     *
     * @param Promise[] $promises
     * @deprecated
     * @return Promise
     */
    static function all(array $promises) {

        return Promise\all($promises);

    }

}
<?php

namespace Sabre\Event;

/**
 * This exception is thrown when the user tried to reject or fulfill a promise,
 * after either of these actions were already performed.
 *
 * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class PromiseAlreadyResolvedException extends \LogicException {

}
<?php

namespace Sabre\Event;

/**
 * This class contains the version number for this package.
 *
 * @copyright Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Version {

    /**
     * Full version number
     */
    const VERSION = '3.0.0';

}
Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/)

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * 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.
    * Neither the name Sabre 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
<phpunit
  colors="true"
  bootstrap="vendor/autoload.php"
  convertErrorsToExceptions="true"
  convertNoticesToExceptions="true"
  convertWarningsToExceptions="true"
  strict="true"
  >
  <testsuite name="sabre-event">
    <directory>tests/</directory>
  </testsuite>

  <filter>
    <whitelist addUncoveredFilesFromWhitelist="true">
        <directory suffix=".php">./lib/</directory>
   </whitelist>
  </filter>
</phpunit>
sabre/event
===========

A lightweight library for event-based development in PHP.

This library provides the following event-based concepts:

1. EventEmitter.
2. Promises.
3. An event loop.
4. Co-routines.

Full documentation can be found on [the website][1].

Installation
------------

Make sure you have [composer][3] installed, and then run:

    composer require sabre/event "~3.0.0"

This package requires PHP 5.5. The 2.0 branch is still maintained as well, and
supports PHP 5.4.

Build status
------------

| branch | status |
| ------ | ------ |
| master | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=master)](https://travis-ci.org/fruux/sabre-event) |
| 2.0    | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-event) |
| 1.0    | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=1.0)](https://travis-ci.org/fruux/sabre-event) |
| php53  | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=php53)](https://travis-ci.org/fruux/sabre-event) |


Questions?
----------

Head over to the [sabre/dav mailinglist][4], or you can also just open a ticket
on [GitHub][5].

Made at fruux
-------------

This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.

[1]: http://sabre.io/event/
[3]: http://getcomposer.org/
[4]: http://groups.google.com/group/sabredav-discuss
[5]: https://github.com/fruux/sabre-event/issues/
<?php

use Sabre\Event\EventEmitter;

include __DIR__ . '/../../vendor/autoload.php';

abstract class BenchMark {

    protected $startTime;
    protected $iterations = 10000;
    protected $totalTime;

    function setUp() {

    }

    abstract function test();

    function go() {

        $this->setUp();
        $this->startTime = microtime(true);
        $this->test();
        $this->totalTime = microtime(true) - $this->startTime;
        return $this->totalTime;

    }

}

class OneCallBack extends BenchMark {

    protected $emitter;
    protected $iterations = 100000;

    function setUp() {

        $this->emitter = new EventEmitter();
        $this->emitter->on('foo', function() {
            // NOOP
        });

    }

    function test() {

        for ($i = 0;$i < $this->iterations;$i++) {
            $this->emitter->emit('foo', []);
        }

    }

}

class ManyCallBacks extends BenchMark {

    protected $emitter;

    function setUp() {

        $this->emitter = new EventEmitter();
        for ($i = 0;$i < 100;$i++) {
            $this->emitter->on('foo', function() {
                // NOOP
            });
        }

    }

    function test() {

        for ($i = 0;$i < $this->iterations;$i++) {
            $this->emitter->emit('foo', []);
        }

    }

}

class ManyPrioritizedCallBacks extends BenchMark {

    protected $emitter;

    function setUp() {

        $this->emitter = new EventEmitter();
        for ($i = 0;$i < 100;$i++) {
            $this->emitter->on('foo', function() {
            }, 1000 - $i);
        }

    }

    function test() {

        for ($i = 0;$i < $this->iterations;$i++) {
            $this->emitter->emit('foo', []);
        }

    }

}

$tests = [
    'OneCallBack',
    'ManyCallBacks',
    'ManyPrioritizedCallBacks',
];

foreach ($tests as $test) {

    $testObj = new $test();
    $result = $testObj->go();
    echo $test . " " . $result . "\n";

}
<?php

namespace Sabre\Event;

class ContinueCallbackTest extends \PHPUnit_Framework_TestCase {

    function testContinueCallBack() {

        $ee = new EventEmitter();

        $handlerCounter = 0;
        $bla = function() use (&$handlerCounter) {
            $handlerCounter++;
        };
        $ee->on('foo', $bla);
        $ee->on('foo', $bla);
        $ee->on('foo', $bla);

        $continueCounter = 0;
        $r = $ee->emit('foo', [], function() use (&$continueCounter) {
            $continueCounter++;
            return true;
        });
        $this->assertTrue($r);
        $this->assertEquals(3, $handlerCounter);
        $this->assertEquals(2, $continueCounter);

    }

    function testContinueCallBackBreak() {

        $ee = new EventEmitter();

        $handlerCounter = 0;
        $bla = function() use (&$handlerCounter) {
            $handlerCounter++;
        };
        $ee->on('foo', $bla);
        $ee->on('foo', $bla);
        $ee->on('foo', $bla);

        $continueCounter = 0;
        $r = $ee->emit('foo', [], function() use (&$continueCounter) {
            $continueCounter++;
            return false;
        });
        $this->assertTrue($r);
        $this->assertEquals(1, $handlerCounter);
        $this->assertEquals(1, $continueCounter);

    }

    function testContinueCallBackBreakByHandler() {

        $ee = new EventEmitter();

        $handlerCounter = 0;
        $bla = function() use (&$handlerCounter) {
            $handlerCounter++;
            return false;
        };
        $ee->on('foo', $bla);
        $ee->on('foo', $bla);
        $ee->on('foo', $bla);

        $continueCounter = 0;
        $r = $ee->emit('foo', [], function() use (&$continueCounter) {
            $continueCounter++;
            return false;
        });
        $this->assertFalse($r);
        $this->assertEquals(1, $handlerCounter);
        $this->assertEquals(0, $continueCounter);

    }
}
<?php

namespace Sabre\Event;

class CoroutineTest extends \PHPUnit_Framework_TestCase {

    /**
     * @expectedException \InvalidArgumentException
     */
    function testNonGenerator() {

        coroutine(function() {});

    }

    function testBasicCoroutine() {

        $start = 0;

        coroutine(function() use (&$start) {

            $start += 1;
            yield;

        });

        $this->assertEquals(1, $start);

    }

    function testFulfilledPromise() {

        $start = 0;
        $promise = new Promise(function($fulfill, $reject) {
            $fulfill(2);
        });

        coroutine(function() use (&$start, $promise) {

            $start += 1;
            $start += (yield $promise);

        });

        Loop\run();
        $this->assertEquals(3, $start);

    }

    function testRejectedPromise() {

        $start = 0;
        $promise = new Promise(function($fulfill, $reject) {
            $reject(2);
        });

        coroutine(function() use (&$start, $promise) {

            $start += 1;
            try {
                $start += (yield $promise);
                // This line is unreachable, but it's our control
                $start += 4;
            } catch (\Exception $e) {
                $start += $e->getMessage();
            }

        });

        Loop\run();
        $this->assertEquals(3, $start);

    }

    function testRejectedPromiseException() {

        $start = 0;
        $promise = new Promise(function($fulfill, $reject) {
            $reject(new \LogicException('2'));
        });

        coroutine(function() use (&$start, $promise) {

            $start += 1;
            try {
                $start += (yield $promise);
                // This line is unreachable, but it's our control
                $start += 4;
            } catch (\LogicException $e) {
                $start += $e->getMessage();
            }

        });

        Loop\run();
        $this->assertEquals(3, $start);

    }

    function testRejectedPromiseArray() {

        $start = 0;
        $promise = new Promise(function($fulfill, $reject) {
            $reject([]);
        });

        coroutine(function() use (&$start, $promise) {

            $start += 1;
            try {
                $start += (yield $promise);
                // This line is unreachable, but it's our control
                $start += 4;
            } catch (\Exception $e) {
                $this->assertTrue(strpos($e->getMessage(), 'Promise was rejected with') === 0);
                $start += 2;
            }

        })->wait();

        $this->assertEquals(3, $start);

    }

    function testFulfilledPromiseAsync() {

        $start = 0;
        $promise = new Promise();
        coroutine(function() use (&$start, $promise) {

            $start += 1;
            $start += (yield $promise);

        });
        Loop\run();

        $this->assertEquals(1, $start);

        $promise->fulfill(2);
        Loop\run();

        $this->assertEquals(3, $start);

    }

    function testRejectedPromiseAsync() {

        $start = 0;
        $promise = new Promise();
        coroutine(function() use (&$start, $promise) {

            $start += 1;
            try {
                $start += (yield $promise);
                // This line is unreachable, but it's our control
                $start += 4;
            } catch (\Exception $e) {
                $start += $e->getMessage();
            }

        });

        $this->assertEquals(1, $start);

        $promise->reject(new \Exception(2));
        Loop\run();

        $this->assertEquals(3, $start);

    }

    function testCoroutineException() {

        $start = 0;
        coroutine(function() use (&$start) {

            $start += 1;
            $start += (yield 2);

            throw new \Exception('4');

        })->error(function($e) use (&$start) {

            $start += $e->getMessage();

        });
        Loop\run();

        $this->assertEquals(7, $start);

    }

    function testDeepException() {

        $start = 0;
        $promise = new Promise();
        coroutine(function() use (&$start, $promise) {

            $start += 1;
            $start += (yield $promise);

        })->error(function($e) use (&$start) {

            $start += $e->getMessage();

        });

        $this->assertEquals(1, $start);

        $promise->reject(new \Exception(2));
        Loop\run();

        $this->assertEquals(3, $start);

    }

    function testResolveToLastYield() {

        $ok = false;
        coroutine(function() {

            yield 1;
            yield 2;
            $hello = 'hi';

        })->then(function($value) use (&$ok) {
            $this->assertEquals(2, $value);
            $ok = true;
        })->error(function($reason) {
            $this->fail($reason);
        });
        Loop\run();

        $this->assertTrue($ok);

    }

    function testResolveToLastYieldPromise() {

        $ok = false;

        $promise = new Promise();

        coroutine(function() use ($promise) {

            yield 'fail';
            yield $promise;
            $hello = 'hi';

        })->then(function($value) use (&$ok) {
            $ok = $value;
            $this->fail($reason);
        });

        $promise->fulfill('omg it worked');
        Loop\run();

        $this->assertEquals('omg it worked', $ok);

    }

}
<?php

namespace Sabre\Event;

class EventEmitterTest extends \PHPUnit_Framework_TestCase {

    function testInit() {

        $ee = new EventEmitter();
        $this->assertInstanceOf('Sabre\\Event\\EventEmitter', $ee);

    }

    function testListeners() {

        $ee = new EventEmitter();

        $callback1 = function() { };
        $callback2 = function() { };
        $ee->on('foo', $callback1, 200);
        $ee->on('foo', $callback2, 100);

        $this->assertEquals([$callback2, $callback1], $ee->listeners('foo'));

    }

    /**
     * @depends testInit
     */
    function testHandleEvent() {

        $argResult = null;

        $ee = new EventEmitter();
        $ee->on('foo', function($arg) use (&$argResult) {

            $argResult = $arg;

        });

        $this->assertTrue(
            $ee->emit('foo', ['bar'])
        );

        $this->assertEquals('bar', $argResult);

    }

    /**
     * @depends testHandleEvent
     */
    function testCancelEvent() {

        $argResult = 0;

        $ee = new EventEmitter();
        $ee->on('foo', function($arg) use (&$argResult) {

            $argResult = 1;
            return false;

        });
        $ee->on('foo', function($arg) use (&$argResult) {

            $argResult = 2;

        });

        $this->assertFalse(
            $ee->emit('foo', ['bar'])
        );

        $this->assertEquals(1, $argResult);

    }

    /**
     * @depends testCancelEvent
     */
    function testPriority() {

        $argResult = 0;

        $ee = new EventEmitter();
        $ee->on('foo', function($arg) use (&$argResult) {

            $argResult = 1;
            return false;

        });
        $ee->on('foo', function($arg) use (&$argResult) {

            $argResult = 2;
            return false;

        }, 1);

        $this->assertFalse(
            $ee->emit('foo', ['bar'])
        );

        $this->assertEquals(2, $argResult);

    }

    /**
     * @depends testPriority
     */
    function testPriority2() {

        $result = [];
        $ee = new EventEmitter();

        $ee->on('foo', function() use (&$result) {

            $result[] = 'a';

        }, 200);
        $ee->on('foo', function() use (&$result) {

            $result[] = 'b';

        }, 50);
        $ee->on('foo', function() use (&$result) {

            $result[] = 'c';

        }, 300);
        $ee->on('foo', function() use (&$result) {

            $result[] = 'd';

        });

        $ee->emit('foo');
        $this->assertEquals(['b', 'd', 'a', 'c'], $result);

    }

    function testRemoveListener() {

        $result = false;

        $callBack = function() use (&$result) {

            $result = true;

        };

        $ee = new EventEmitter();

        $ee->on('foo', $callBack);

        $ee->emit('foo');
        $this->assertTrue($result);
        $result = false;

        $this->assertTrue(
            $ee->removeListener('foo', $callBack)
        );

        $ee->emit('foo');
        $this->assertFalse($result);

    }

    function testRemoveUnknownListener() {

        $result = false;

        $callBack = function() use (&$result) {

            $result = true;

        };

        $ee = new EventEmitter();

        $ee->on('foo', $callBack);

        $ee->emit('foo');
        $this->assertTrue($result);
        $result = false;

        $this->assertFalse($ee->removeListener('bar', $callBack));

        $ee->emit('foo');
        $this->assertTrue($result);

    }

    function testRemoveListenerTwice() {

        $result = false;

        $callBack = function() use (&$result) {

            $result = true;

        };

        $ee = new EventEmitter();

        $ee->on('foo', $callBack);

        $ee->emit('foo');
        $this->assertTrue($result);
        $result = false;

        $this->assertTrue(
            $ee->removeListener('foo', $callBack)
        );
        $this->assertFalse(
            $ee->removeListener('foo', $callBack)
        );

        $ee->emit('foo');
        $this->assertFalse($result);

    }

    function testRemoveAllListeners() {

        $result = false;
        $callBack = function() use (&$result) {

            $result = true;

        };

        $ee = new EventEmitter();
        $ee->on('foo', $callBack);

        $ee->emit('foo');
        $this->assertTrue($result);
        $result = false;

        $ee->removeAllListeners('foo');

        $ee->emit('foo');
        $this->assertFalse($result);

    }

    function testRemoveAllListenersNoArg() {

        $result = false;

        $callBack = function() use (&$result) {

            $result = true;

        };


        $ee = new EventEmitter();
        $ee->on('foo', $callBack);

        $ee->emit('foo');
        $this->assertTrue($result);
        $result = false;

        $ee->removeAllListeners();

        $ee->emit('foo');
        $this->assertFalse($result);

    }

    function testOnce() {

        $result = 0;

        $callBack = function() use (&$result) {

            $result++;

        };

        $ee = new EventEmitter();
        $ee->once('foo', $callBack);

        $ee->emit('foo');
        $ee->emit('foo');

        $this->assertEquals(1, $result);

    }

    /**
     * @depends testCancelEvent
     */
    function testPriorityOnce() {

        $argResult = 0;

        $ee = new EventEmitter();
        $ee->once('foo', function($arg) use (&$argResult) {

            $argResult = 1;
            return false;

        });
        $ee->once('foo', function($arg) use (&$argResult) {

            $argResult = 2;
            return false;

        }, 1);

        $this->assertFalse(
            $ee->emit('foo', ['bar'])
        );

        $this->assertEquals(2, $argResult);

    }
}
<?php

namespace Sabre\Event\Loop;

class FunctionsTest extends \PHPUnit_Framework_TestCase {

    function setUp() {

        // Always creating a fresh loop object.
        instance(new Loop());

    }

    function tearDown() {

        // Removing the global loop object.
        instance(null);

    }

    function testNextTick() {

        $check  = 0;
        nextTick(function() use (&$check) {

            $check++;

        });

        run();

        $this->assertEquals(1, $check);

    }

    function testTimeout() {

        $check  = 0;
        setTimeout(function() use (&$check) {

            $check++;

        }, 0.02);

        run();

        $this->assertEquals(1, $check);

    }

    function testTimeoutOrder() {

        $check  = [];
        setTimeout(function() use (&$check) {

            $check[] = 'a';

        }, 0.2);
        setTimeout(function() use (&$check) {

            $check[] = 'b';

        }, 0.1);
        setTimeout(function() use (&$check) {

            $check[] = 'c';

        }, 0.3);

        run();

        $this->assertEquals(['b', 'a', 'c'], $check);

    }

    function testSetInterval() {

        $check = 0;
        $intervalId = null;
        $intervalId = setInterval(function() use (&$check, &$intervalId) {

            $check++;
            if ($check > 5) {
                clearInterval($intervalId);
            }

        }, 0.02);

        run();
        $this->assertEquals(6, $check);

    }

    function testAddWriteStream() {

        $h = fopen('php://temp', 'r+');
        addWriteStream($h, function() use ($h) {

            fwrite($h, 'hello world');
            removeWriteStream($h);

        });
        run();
        rewind($h);
        $this->assertEquals('hello world', stream_get_contents($h));

    }

    function testAddReadStream() {

        $h = fopen('php://temp', 'r+');
        fwrite($h, 'hello world');
        rewind($h);

        $result = null;

        addReadStream($h, function() use ($h, &$result) {

            $result = fgets($h);
            removeReadStream($h);

        });
        run();
        $this->assertEquals('hello world', $result);

    }

    function testStop() {

        $check = 0;
        setTimeout(function() use (&$check) {
            $check++;
        }, 200);

        nextTick(function() {
            stop();
        });
        run();

        $this->assertEquals(0, $check);

    }

    function testTick() {

        $check = 0;
        setTimeout(function() use (&$check) {
            $check++;
        }, 1);

        nextTick(function() use (&$check) {
            $check++;
        });
        tick();

        $this->assertEquals(1, $check);

    }

}
<?php

namespace Sabre\Event\Loop;

class LoopTest extends \PHPUnit_Framework_TestCase {

    function testNextTick() {

        $loop = new Loop();
        $check  = 0;
        $loop->nextTick(function() use (&$check) {

            $check++;

        });

        $loop->run();

        $this->assertEquals(1, $check);

    }

    function testTimeout() {

        $loop = new Loop();
        $check  = 0;
        $loop->setTimeout(function() use (&$check) {

            $check++;

        }, 0.02);

        $loop->run();

        $this->assertEquals(1, $check);

    }

    function testTimeoutOrder() {

        $loop = new Loop();
        $check  = [];
        $loop->setTimeout(function() use (&$check) {

            $check[] = 'a';

        }, 0.2);
        $loop->setTimeout(function() use (&$check) {

            $check[] = 'b';

        }, 0.1);
        $loop->setTimeout(function() use (&$check) {

            $check[] = 'c';

        }, 0.3);

        $loop->run();

        $this->assertEquals(['b', 'a', 'c'], $check);

    }

    function testSetInterval() {

        $loop = new Loop();
        $check = 0;
        $intervalId = null;
        $intervalId = $loop->setInterval(function() use (&$check, &$intervalId, $loop) {

            $check++;
            if ($check > 5) {
                $loop->clearInterval($intervalId);
            }

        }, 0.02);

        $loop->run();
        $this->assertEquals(6, $check);

    }

    function testAddWriteStream() {

        $h = fopen('php://temp', 'r+');
        $loop = new Loop();
        $loop->addWriteStream($h, function() use ($h, $loop) {

            fwrite($h, 'hello world');
            $loop->removeWriteStream($h);

        });
        $loop->run();
        rewind($h);
        $this->assertEquals('hello world', stream_get_contents($h));

    }

    function testAddReadStream() {

        $h = fopen('php://temp', 'r+');
        fwrite($h, 'hello world');
        rewind($h);

        $loop = new Loop();

        $result = null;

        $loop->addReadStream($h, function() use ($h, $loop, &$result) {

            $result = fgets($h);
            $loop->removeReadStream($h);

        });
        $loop->run();
        $this->assertEquals('hello world', $result);

    }

    function testStop() {

        $check = 0;
        $loop = new Loop();
        $loop->setTimeout(function() use (&$check) {
            $check++;
        }, 200);

        $loop->nextTick(function() use ($loop) {
            $loop->stop();
        });
        $loop->run();

        $this->assertEquals(0, $check);

    }

    function testTick() {

        $check = 0;
        $loop = new Loop();
        $loop->setTimeout(function() use (&$check) {
            $check++;
        }, 1);

        $loop->nextTick(function() use ($loop, &$check) {
            $check++;
        });
        $loop->tick();

        $this->assertEquals(1, $check);

    }

    /**
     * Here we add a new nextTick function as we're in the middle of a current
     * nextTick.
     */
    function testNextTickStacking() {

        $loop = new Loop();
        $check  = 0;
        $loop->nextTick(function() use (&$check, $loop) {

            $loop->nextTick(function() use (&$check) {

                $check++;

            });
            $check++;

        });

        $loop->run();

        $this->assertEquals(2, $check);

    }

}
<?php

namespace Sabre\Event\Promise;

use Sabre\Event\Loop;
use Sabre\Event\Promise;

class FunctionsTest extends \PHPUnit_Framework_TestCase {

    function testAll() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise\all([$promise1, $promise2])->then(function($value) use (&$finalValue) {

            $finalValue = $value;

        });

        $promise1->fulfill(1);
        Loop\run();
        $this->assertEquals(0, $finalValue);

        $promise2->fulfill(2);
        Loop\run();
        $this->assertEquals([1, 2], $finalValue);

    }

    function testAllReject() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise\all([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = 'foo';
                return 'test';
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->reject(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->reject(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testAllRejectThenResolve() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise\all([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = 'foo';
                return 'test';
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->reject(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->fulfill(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testRace() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise\race([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = $value;
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->fulfill(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->fulfill(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testRaceReject() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise\race([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = $value;
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->reject(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->reject(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testResolve() {

        $finalValue = 0;

        $promise = resolve(1);
        $promise->then(function($value) use (&$finalValue) {

            $finalValue = $value;

        });

        $this->assertEquals(0, $finalValue);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    /**
     * @expectedException \Exception
     */
    function testResolvePromise() {

        $finalValue = 0;

        $promise = new Promise();
        $promise->reject(new \Exception('uh oh'));

        $newPromise = resolve($promise);
        $newPromise->wait();

    }

    function testReject() {

        $finalValue = 0;

        $promise = reject(1);
        $promise->then(function($value) use (&$finalValue) {

            $finalValue = 'im broken';

        }, function($reason) use (&$finalValue) {

            $finalValue = $reason;
        
        });

        $this->assertEquals(0, $finalValue);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }


}
<?php

namespace Sabre\Event\Promise;

use Sabre\Event\Loop;
use Sabre\Event\Promise;

class PromiseTest extends \PHPUnit_Framework_TestCase {

    function testSuccess() {

        $finalValue = 0;
        $promise = new Promise();
        $promise->fulfill(1);

        $promise->then(function($value) use (&$finalValue) {
            $finalValue = $value + 2;
        });
        Loop\run();

        $this->assertEquals(3, $finalValue);

    }

    function testFail() {

        $finalValue = 0;
        $promise = new Promise();
        $promise->reject(1);

        $promise->then(null, function($value) use (&$finalValue) {
            $finalValue = $value + 2;
        });
        Loop\run();

        $this->assertEquals(3, $finalValue);

    }

    function testChain() {

        $finalValue = 0;
        $promise = new Promise();
        $promise->fulfill(1);

        $promise->then(function($value) use (&$finalValue) {
            $finalValue = $value + 2;
            return $finalValue;
        })->then(function($value) use (&$finalValue) {
            $finalValue = $value + 4;
            return $finalValue;
        });
        Loop\run();

        $this->assertEquals(7, $finalValue);

    }
    function testChainPromise() {

        $finalValue = 0;
        $promise = new Promise();
        $promise->fulfill(1);

        $subPromise = new Promise();

        $promise->then(function($value) use ($subPromise) {
            return $subPromise;
        })->then(function($value) use (&$finalValue) {
            $finalValue = $value + 4;
            return $finalValue;
        });

        $subPromise->fulfill(2);
        Loop\run();

        $this->assertEquals(6, $finalValue);

    }

    function testPendingResult() {

        $finalValue = 0;
        $promise = new Promise();

        $promise->then(function($value) use (&$finalValue) {
            $finalValue = $value + 2;
        });

        $promise->fulfill(4);
        Loop\run();

        $this->assertEquals(6, $finalValue);

    }

    function testPendingFail() {

        $finalValue = 0;
        $promise = new Promise();

        $promise->then(null, function($value) use (&$finalValue) {
            $finalValue = $value + 2;
        });

        $promise->reject(4);
        Loop\run();

        $this->assertEquals(6, $finalValue);

    }

    function testExecutorSuccess() {

        $promise = (new Promise(function($success, $fail) {

            $success('hi');

        }))->then(function($result) use (&$realResult) {

            $realResult = $result;

        });
        Loop\run();

        $this->assertEquals('hi', $realResult);

    }

    function testExecutorFail() {

        $promise = (new Promise(function($success, $fail) {

            $fail('hi');

        }))->then(function($result) use (&$realResult) {

            $realResult = 'incorrect';

        }, function($reason) use (&$realResult) {

            $realResult = $reason;

        });
        Loop\run();

        $this->assertEquals('hi', $realResult);

    }

    /**
     * @expectedException \Sabre\Event\PromiseAlreadyResolvedException
     */
    function testFulfillTwice() {

        $promise = new Promise();
        $promise->fulfill(1);
        $promise->fulfill(1);

    }

    /**
     * @expectedException \Sabre\Event\PromiseAlreadyResolvedException
     */
    function testRejectTwice() {

        $promise = new Promise();
        $promise->reject(1);
        $promise->reject(1);

    }

    function testFromFailureHandler() {

        $ok = 0;
        $promise = new Promise();
        $promise->otherwise(function($reason) {

            $this->assertEquals('foo', $reason);
            throw new \Exception('hi');

        })->then(function() use (&$ok) {

            $ok = -1;

        }, function() use (&$ok) {

            $ok = 1;

        });

        $this->assertEquals(0, $ok);
        $promise->reject('foo');
        Loop\run();

        $this->assertEquals(1, $ok);

    }

    function testAll() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise::all([$promise1, $promise2])->then(function($value) use (&$finalValue) {

            $finalValue = $value;

        });

        $promise1->fulfill(1);
        Loop\run();
        $this->assertEquals(0, $finalValue);

        $promise2->fulfill(2);
        Loop\run();
        $this->assertEquals([1, 2], $finalValue);

    }

    function testAllReject() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise::all([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = 'foo';
                return 'test';
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->reject(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->reject(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testAllRejectThenResolve() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise::all([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = 'foo';
                return 'test';
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->reject(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->fulfill(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testWaitResolve() {

        $promise = new Promise();
        Loop\nextTick(function() use ($promise) {
            $promise->fulfill(1);
        });
        $this->assertEquals(
            1,
            $promise->wait()
        );

    }

    /**
     * @expectedException \LogicException
     */
    function testWaitWillNeverResolve() {

        $promise = new Promise();
        $promise->wait();

    }

    function testWaitRejectedException() {

        $promise = new Promise();
        Loop\nextTick(function() use ($promise) {
            $promise->reject(new \OutOfBoundsException('foo'));
        });
        try {
            $promise->wait();
            $this->fail('We did not get the expected exception');
        } catch (\Exception $e) {
            $this->assertInstanceOf('OutOfBoundsException', $e);
            $this->assertEquals('foo', $e->getMessage());
        }

    }

    function testWaitRejectedScalar() {

        $promise = new Promise();
        Loop\nextTick(function() use ($promise) {
            $promise->reject('foo');
        });
        try {
            $promise->wait();
            $this->fail('We did not get the expected exception');
        } catch (\Exception $e) {
            $this->assertInstanceOf('Exception', $e);
            $this->assertEquals('foo', $e->getMessage());
        }

    }

    function testWaitRejectedNonScalar() {

        $promise = new Promise();
        Loop\nextTick(function() use ($promise) {
            $promise->reject([]);
        });
        try {
            $promise->wait();
            $this->fail('We did not get the expected exception');
        } catch (\Exception $e) {
            $this->assertInstanceOf('Exception', $e);
            $this->assertEquals('Promise was rejected with reason of type: array', $e->getMessage());
        }

    }
}
<?php

namespace Sabre\Event;

class PromiseTest extends \PHPUnit_Framework_TestCase {

    function testSuccess() {

        $finalValue = 0;
        $promise = new Promise();
        $promise->fulfill(1);

        $promise->then(function($value) use (&$finalValue) {
            $finalValue = $value + 2;
        });
        Loop\run();

        $this->assertEquals(3, $finalValue);

    }

    function testFail() {

        $finalValue = 0;
        $promise = new Promise();
        $promise->reject(1);

        $promise->then(null, function($value) use (&$finalValue) {
            $finalValue = $value + 2;
        });
        Loop\run();

        $this->assertEquals(3, $finalValue);

    }

    function testChain() {

        $finalValue = 0;
        $promise = new Promise();
        $promise->fulfill(1);

        $promise->then(function($value) use (&$finalValue) {
            $finalValue = $value + 2;
            return $finalValue;
        })->then(function($value) use (&$finalValue) {
            $finalValue = $value + 4;
            return $finalValue;
        });
        Loop\run();

        $this->assertEquals(7, $finalValue);

    }
    function testChainPromise() {

        $finalValue = 0;
        $promise = new Promise();
        $promise->fulfill(1);

        $subPromise = new Promise();

        $promise->then(function($value) use ($subPromise) {
            return $subPromise;
        })->then(function($value) use (&$finalValue) {
            $finalValue = $value + 4;
            return $finalValue;
        });

        $subPromise->fulfill(2);
        Loop\run();

        $this->assertEquals(6, $finalValue);

    }

    function testPendingResult() {

        $finalValue = 0;
        $promise = new Promise();

        $promise->then(function($value) use (&$finalValue) {
            $finalValue = $value + 2;
        });

        $promise->fulfill(4);
        Loop\run();

        $this->assertEquals(6, $finalValue);

    }

    function testPendingFail() {

        $finalValue = 0;
        $promise = new Promise();

        $promise->then(null, function($value) use (&$finalValue) {
            $finalValue = $value + 2;
        });

        $promise->reject(4);
        Loop\run();

        $this->assertEquals(6, $finalValue);

    }

    function testExecutorSuccess() {

        $promise = (new Promise(function($success, $fail) {

            $success('hi');

        }))->then(function($result) use (&$realResult) {

            $realResult = $result;

        });
        Loop\run();

        $this->assertEquals('hi', $realResult);

    }

    function testExecutorFail() {

        $promise = (new Promise(function($success, $fail) {

            $fail('hi');

        }))->then(function($result) use (&$realResult) {

            $realResult = 'incorrect';

        }, function($reason) use (&$realResult) {

            $realResult = $reason;

        });
        Loop\run();

        $this->assertEquals('hi', $realResult);

    }

    /**
     * @expectedException \Sabre\Event\PromiseAlreadyResolvedException
     */
    function testFulfillTwice() {

        $promise = new Promise();
        $promise->fulfill(1);
        $promise->fulfill(1);

    }

    /**
     * @expectedException \Sabre\Event\PromiseAlreadyResolvedException
     */
    function testRejectTwice() {

        $promise = new Promise();
        $promise->reject(1);
        $promise->reject(1);

    }

    function testFromFailureHandler() {

        $ok = 0;
        $promise = new Promise();
        $promise->otherwise(function($reason) {

            $this->assertEquals('foo', $reason);
            throw new \Exception('hi');

        })->then(function() use (&$ok) {

            $ok = -1;

        }, function() use (&$ok) {

            $ok = 1;

        });

        $this->assertEquals(0, $ok);
        $promise->reject('foo');
        Loop\run();

        $this->assertEquals(1, $ok);

    }

    function testAll() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise::all([$promise1, $promise2])->then(function($value) use (&$finalValue) {

            $finalValue = $value;

        });

        $promise1->fulfill(1);
        Loop\run();
        $this->assertEquals(0, $finalValue);

        $promise2->fulfill(2);
        Loop\run();
        $this->assertEquals([1, 2], $finalValue);

    }

    function testAllReject() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise::all([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = 'foo';
                return 'test';
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->reject(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->reject(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testAllRejectThenResolve() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise::all([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = 'foo';
                return 'test';
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->reject(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->fulfill(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testRace() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise\race([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = $value;
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->fulfill(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->fulfill(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testRaceReject() {

        $promise1 = new Promise();
        $promise2 = new Promise();

        $finalValue = 0;
        Promise\race([$promise1, $promise2])->then(
            function($value) use (&$finalValue) {
                $finalValue = $value;
            },
            function($value) use (&$finalValue) {
                $finalValue = $value;
            }
        );

        $promise1->reject(1);
        Loop\run();
        $this->assertEquals(1, $finalValue);
        $promise2->reject(2);
        Loop\run();
        $this->assertEquals(1, $finalValue);

    }

    function testWaitResolve() {

        $promise = new Promise();
        Loop\nextTick(function() use ($promise) {
            $promise->fulfill(1);
        });
        $this->assertEquals(
            1,
            $promise->wait()
        );

    }

    /**
     * @expectedException \LogicException
     */
    function testWaitWillNeverResolve() {

        $promise = new Promise();
        $promise->wait();

    }

    function testWaitRejectedException() {

        $promise = new Promise();
        Loop\nextTick(function() use ($promise) {
            $promise->reject(new \OutOfBoundsException('foo'));
        });
        try {
            $promise->wait();
            $this->fail('We did not get the expected exception');
        } catch (\Exception $e) {
            $this->assertInstanceOf('OutOfBoundsException', $e);
            $this->assertEquals('foo', $e->getMessage());
        }

    }

    function testWaitRejectedScalar() {

        $promise = new Promise();
        Loop\nextTick(function() use ($promise) {
            $promise->reject('foo');
        });
        try {
            $promise->wait();
            $this->fail('We did not get the expected exception');
        } catch (\Exception $e) {
            $this->assertInstanceOf('Exception', $e);
            $this->assertEquals('foo', $e->getMessage());
        }

    }

    function testWaitRejectedNonScalar() {

        $promise = new Promise();
        Loop\nextTick(function() use ($promise) {
            $promise->reject([]);
        });
        try {
            $promise->wait();
            $this->fail('We did not get the expected exception');
        } catch (\Exception $e) {
            $this->assertInstanceOf('Exception', $e);
            $this->assertEquals('Promise was rejected with reason of type: array', $e->getMessage());
        }

    }
}
# Composer
vendor/
composer.lock

# Tests
tests/cov/

# Composer binaries
bin/phpunit
bin/phpcs
bin/php-cs-fixer
bin/sabre-cs-fixer

# Vim
.*.swp
language: php
php:
  - 5.4
  - 5.5
  - 5.6
  - 7
  - 7.1
  - hhvm

matrix:
  fast_finish: true

env:
  matrix:
    - PREFER_LOWEST=""
    - PREFER_LOWEST="--prefer-lowest"


before_script:
    - rm -f ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini
#    - composer self-update
    - composer update --prefer-source $PREFER_LOWEST

script:
  - ./bin/phpunit --configuration tests/phpunit.xml
  - ./bin/sabre-cs-fixer fix . --dry-run --diff
ChangeLog
=========

4.2.2 (2017-01-02)
------------------

* #72: Handling clients that send invalid `Content-Length` headers.


4.2.1 (2016-01-06)
------------------

* #56: `getBodyAsString` now returns at most as many bytes as the contents of
  the `Content-Length` header. This allows users to pass much larger strings
  without having to copy and truncate them.
* The client now sets a default `User-Agent` header identifying this library.


4.2.0 (2016-01-04)
------------------

* This package now supports sabre/event 3.0.


4.1.0 (2015-09-04)
------------------

* The async client wouldn't `wait()` for new http requests being started
  after the (previous) last request in the queue was resolved.
* Added `Sabre\HTTP\Auth\Bearer`, to easily extract a OAuth2 bearer token.


4.0.0 (2015-05-20)
------------------

* Deprecated: All static functions from `Sabre\HTTP\URLUtil` and
  `Sabre\HTTP\Util` moved to a separate `functions.php`, which is also
  autoloaded. The old functions are still there, but will be removed in a
  future version. (#49)


4.0.0-alpha3 (2015-05-19)
-------------------------

* Added a parser for the HTTP `Prefer` header, as defined in [RFC7240][rfc7240].
* Deprecated `Sabre\HTTP\Util::parseHTTPDate`, use `Sabre\HTTP\parseDate()`.
* Deprecated `Sabre\HTTP\Util::toHTTPDate` use `Sabre\HTTP\toDate()`.


4.0.0-alpha2 (2015-05-18)
-------------------------

* #45: Don't send more data than what is promised in the HTTP content-length.
  (@dratini0).
* #43: `getCredentials` returns null if incomplete. (@Hywan)
* #48: Now using php-cs-fixer to make our CS consistent (yay!)
* This includes fixes released in version 3.0.5.


4.0.0-alpha1 (2015-02-25)
-------------------------

* #41: Fixing bugs related to comparing URLs in `Request::getPath()`.
* #41: This library now uses the `sabre/uri` package for uri handling.
* Added `421 Misdirected Request` from the HTTP/2.0 spec.


3.0.5 (2015-05-11)
------------------

* #47 #35: When re-using the client and doing any request after a `HEAD`
  request, the client discards the body.


3.0.4 (2014-12-10)
------------------

* #38: The Authentication helpers no longer overwrite any existing
  `WWW-Authenticate` headers, but instead append new headers. This ensures
  that multiple authentication systems can exist in the same environment.


3.0.3 (2014-12-03)
------------------

* Hiding `Authorization` header value from `Request::__toString`.


3.0.2 (2014-10-09)
------------------

* When parsing `Accept:` headers, we're ignoring invalid parts. Before we
  would throw a PHP E_NOTICE.


3.0.1 (2014-09-29)
------------------

* Minor change in unittests.


3.0.0 (2014-09-23)
------------------

* `getHeaders()` now returns header values as an array, just like psr/http.
* Added `hasHeader()`.


2.1.0-alpha1 (2014-09-15)
-------------------------

* Changed: Copied most of the header-semantics for the PSR draft for
  representing HTTP messages. [Reference here][psr-http].
* This means that `setHeaders()` does not wipe out every existing header
  anymore.
* We also support multiple headers with the same name.
* Use `Request::getHeaderAsArray()` and `Response::getHeaderAsArray()` to
  get a hold off multiple headers with the same name.
* If you use `getHeader()`, and there's more than 1 header with that name, we
  concatenate all these with a comma.
* `addHeader()` will now preserve an existing header with that name, and add a
  second header with the same name.
* The message class should be a lot faster now for looking up headers. No more
  array traversal, because we maintain a tiny index.
* Added: `URLUtil::resolve()` to make resolving relative urls super easy.
* Switched to PSR-4.
* #12: Circumventing CURL's FOLLOW_LOCATION and doing it in PHP instead. This
  fixes compatibility issues with people that have open_basedir turned on.
* Added: Content negotiation now correctly support mime-type parameters such as
  charset.
* Changed: `Util::negotiate()` is now deprecated. Use
  `Util::negotiateContentType()` instead.
* #14: The client now only follows http and https urls.


2.0.4 (2014-07-14)
------------------

* Changed: No longer escaping @ in urls when it's not needed.
* Fixed: #7: Client now correctly deals with responses without a body.


2.0.3 (2014-04-17)
------------------

* Now works on hhvm!
* Fixed: Now throwing an error when a Request object is being created with
  arguments that were valid for sabre/http 1.0. Hopefully this will aid with
  debugging for upgraders.


2.0.2 (2014-02-09)
------------------

* Fixed: Potential security problem in the client.


2.0.1 (2014-01-09)
------------------

* Fixed: getBodyAsString on an empty body now works.
* Fixed: Version string


2.0.0 (2014-01-08)
------------------

* Removed: Request::createFromPHPRequest. This is now handled by
  Sapi::getRequest.


2.0.0alpha6 (2014-01-03)
------------------------

* Added: Asynchronous HTTP client. See examples/asyncclient.php.
* Fixed: Issue #4: Don't escape colon (:) when it's not needed.
* Fixed: Fixed a bug in the content negotation script.
* Fixed: Fallback for when CURLOPT_POSTREDIR is not defined (mainly for hhvm).
* Added: The Request and Response object now have a `__toString()` method that
  serializes the objects into a standard HTTP message. This is mainly for
  debugging purposes.
* Changed: Added Response::getStatusText(). This method returns the
  human-readable HTTP status message. This part has been removed from
  Response::getStatus(), which now always returns just the status code as an
  int.
* Changed: Response::send() is now Sapi::sendResponse($response).
* Changed: Request::createFromPHPRequest is now Sapi::getRequest().
* Changed: Message::getBodyAsStream and Message::getBodyAsString were added. The
  existing Message::getBody changed it's behavior, so be careful.


2.0.0alpha5 (2013-11-07)
------------------------

* Added: HTTP Status 451 Unavailable For Legal Reasons. Fight government
  censorship!
* Added: Ability to catch and retry http requests in the client when a curl
  error occurs.
* Changed: Request::getPath does not return the query part of the url, so
  everything after the ? is stripped.
* Added: a reverse proxy example.


2.0.0alpha4 (2013-08-07)
------------------------

* Fixed: Doing a GET request with the client uses the last used HTTP method
  instead.
* Added: HttpException
* Added: The Client class can now automatically emit exceptions when HTTP errors
  occurred.


2.0.0alpha3 (2013-07-24)
------------------------

* Changed: Now depends on sabre/event package.
* Changed: setHeaders() now overwrites any existing http headers.
* Added: getQueryParameters to RequestInterface.
* Added: Util::negotiate.
* Added: RequestDecorator, ResponseDecorator.
* Added: A very simple HTTP client.
* Added: addHeaders() to append a list of new headers.
* Fixed: Not erroring on unknown HTTP status codes.
* Fixed: Throwing exceptions on invalid HTTP status codes (not 3 digits).
* Fixed: Much better README.md
* Changed: getBody() now uses a bitfield to specify what type to return.


2.0.0alpha2 (2013-07-02)
------------------------

* Added: Digest & AWS Authentication.
* Added: Message::getHttpVersion and Message::setHttpVersion.
* Added: Request::setRawServerArray, getRawServerValue.
* Added: Request::createFromPHPRequest
* Added: Response::send
* Added: Request::getQueryParameters
* Added: Utility for dealing with HTTP dates.
* Added: Request::setPostData and Request::getPostData.
* Added: Request::setAbsoluteUrl and Request::getAbsoluteUrl.
* Added: URLUtil, methods for calculation relative and base urls.
* Removed: Response::sendBody


2.0.0alpha1 (2012-10-07)
------------------------

* Fixed: Lots of small naming improvements
* Added: Introduction of Message, MessageInterface, Response, ResponseInterface.

Before 2.0.0, this package was built-into SabreDAV, where it first appeared in
January 2009.

[psr-http]: https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md
[rfc-7240]: http://tools.ietf.org/html/rfc7240
{
    "name": "sabre/http",
    "description" : "The sabre/http library provides utilities for dealing with http requests and responses. ",
    "keywords" : [ "HTTP" ],
    "homepage" : "https://github.com/fruux/sabre-http",
    "license" : "BSD-3-Clause",
    "require" : {
        "php"          : ">=5.4",
        "ext-mbstring" : "*",
        "ext-ctype"    : "*",
        "sabre/event"  : ">=1.0.0,<4.0.0",
        "sabre/uri"    : "~1.0"
    },
    "require-dev" : {
        "phpunit/phpunit" : "~4.3",
        "sabre/cs" : "~0.0.1"
    },
    "suggest" : {
        "ext-curl" : " to make http requests with the Client class"
    },
    "authors" : [
        {
            "name" : "Evert Pot",
            "email" : "me@evertpot.com",
            "homepage" : "http://evertpot.com/",
            "role" : "Developer"
        }
    ],
    "support" : {
        "forum" : "https://groups.google.com/group/sabredav-discuss",
        "source" : "https://github.com/fruux/sabre-http"
    },
    "autoload" : {
        "files" : [
            "lib/functions.php"
        ],
        "psr-4" : {
            "Sabre\\HTTP\\" : "lib/"
        }
    },
    "config" : {
        "bin-dir" : "bin/"
    }
}
<?php

/**
 * This example demonstrates the ability for clients to work asynchronously.
 *
 * By default up to 10 requests will be executed in paralel. HTTP connections
 * are re-used and DNS is cached, all thanks to the power of curl.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
use Sabre\HTTP\Client;
use Sabre\HTTP\Request;

// Find the autoloader
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
    __DIR__ . '/vendor/autoload.php',

];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

// This is the request we're repeating a 1000 times.
$request = new Request('GET', 'http://localhost/');
$client = new Client();

for ($i = 0; $i < 1000; $i++) {

    echo "$i sending\n";
    $client->sendAsync(
        $request,

        // This is the 'success' callback
        function($response) use ($i) {
            echo "$i -> " . $response->getStatus() . "\n";
        },

        // This is the 'error' callback. It is called for general connection
        // problems (such as not being able to connect to a host, dns errors,
        // etc.) and also cases where a response was returned, but it had a
        // status code of 400 or higher.
        function($error) use ($i) {

            if ($error['status'] === Client::STATUS_CURLERROR) {
                // Curl errors
                echo "$i -> curl error: " . $error['curl_errmsg'] . "\n";
            } else {
                // HTTP errors
                echo "$i -> " . $error['response']->getStatus() . "\n";
            }
        }
    );
}

// After everything is done, we call 'wait'. This causes the client to wait for
// all outstanding http requests to complete.
$client->wait();
<?php

/**
 * This example shows how to do Basic authentication.
 * *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
$userList = [
    "user1" => "password",
    "user2" => "password",
];

use Sabre\HTTP\Auth;
use Sabre\HTTP\Response;
use Sabre\HTTP\Sapi;

// Find the autoloader
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
    __DIR__ . '/vendor/autoload.php',

];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

$request = Sapi::getRequest();
$response = new Response();

$basicAuth = new Auth\Basic("Locked down area", $request, $response);
if (!$userPass = $basicAuth->getCredentials()) {

    // No username or password given
    $basicAuth->requireLogin();

} elseif (!isset($userList[$userPass[0]]) || $userList[$userPass[0]] !== $userPass[1]) {

    // Username or password are incorrect
    $basicAuth->requireLogin();
} else {

    // Success !
    $response->setBody('You are logged in!');

}

// Sending the response
Sapi::sendResponse($response);
<?php

/**
 * This example shows how to make a HTTP request with the Request and Response
 * objects.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
use Sabre\HTTP\Client;
use Sabre\HTTP\Request;

// Find the autoloader
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
    __DIR__ . '/vendor/autoload.php',

];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

// Constructing the request.
$request = new Request('GET', 'http://localhost/');

$client = new Client();
//$client->addCurlSetting(CURLOPT_PROXY,'localhost:8888');
$response = $client->send($request);

echo "Response:\n";

echo (string)$response;
<?php

/**
 * This example shows how to do Digest authentication.
 * *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Markus Staab
 * @license http://sabre.io/license/ Modified BSD License
 */
$userList = [
    "user1" => "password",
    "user2" => "password",
];

use Sabre\HTTP\Auth;
use Sabre\HTTP\Response;
use Sabre\HTTP\Sapi;

// Find the autoloader
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
    __DIR__ . '/vendor/autoload.php',

];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

$request = Sapi::getRequest();
$response = new Response();

$digestAuth = new Auth\Digest("Locked down area", $request, $response);
$digestAuth->init();
if (!$userName = $digestAuth->getUsername()) {

    // No username given
    $digestAuth->requireLogin();

} elseif (!isset($userList[$userName]) || !$digestAuth->validatePassword($userList[$userName])) {

    // Username or password are incorrect
    $digestAuth->requireLogin();
} else {

    // Success !
    $response->setBody('You are logged in!');

}

// Sending the response
Sapi::sendResponse($response);
<?php

// The url we're proxying to.
$remoteUrl = 'http://example.org/';

// The url we're proxying from. Please note that this must be a relative url,
// and basically acts as the base url.
//
// If your $remoteUrl doesn't end with a slash, this one probably shouldn't
// either.
$myBaseUrl = '/reverseproxy.php';
// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/';

use Sabre\HTTP\Client;
use Sabre\HTTP\Sapi;

// Find the autoloader
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
    __DIR__ . '/vendor/autoload.php',

];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}


$request = Sapi::getRequest();
$request->setBaseUrl($myBaseUrl);

$subRequest = clone $request;

// Removing the Host header.
$subRequest->removeHeader('Host');

// Rewriting the url.
$subRequest->setUrl($remoteUrl . $request->getPath());

$client = new Client();

// Sends the HTTP request to the server
$response = $client->send($subRequest);

// Sends the response back to the client that connected to the proxy.
Sapi::sendResponse($response);
<?php

/**
 * This simple example shows the capability of Request and Response objects to
 * serialize themselves as strings.
 *
 * This is mainly useful for debugging purposes.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
use Sabre\HTTP\Request;
use Sabre\HTTP\Response;

// Find the autoloader
$paths = [
    __DIR__ . '/../vendor/autoload.php',
    __DIR__ . '/../../../autoload.php',
    __DIR__ . '/vendor/autoload.php',

];
foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

$request = new Request('POST', '/foo');
$request->setHeaders([
    'Host'         => 'example.org',
    'Content-Type' => 'application/json'
    ]);

$request->setBody(json_encode(['foo' => 'bar']));

echo $request;
echo "\r\n\r\n";

$response = new Response(424);
$response->setHeaders([
    'Content-Type' => 'text/plain',
    'Connection'   => 'close',
    ]);

$response->setBody("ABORT! ABORT!");

echo $response;

echo "\r\n";
<?php

namespace Sabre\HTTP\Auth;

use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * HTTP Authentication base class.
 *
 * This class provides some common functionality for the various base classes.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class AbstractAuth {

    /**
     * Authentication realm
     *
     * @var string
     */
    protected $realm;

    /**
     * Request object
     *
     * @var RequestInterface
     */
    protected $request;

    /**
     * Response object
     *
     * @var ResponseInterface
     */
    protected $response;

    /**
     * Creates the object
     *
     * @param string $realm
     * @return void
     */
    function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) {

        $this->realm = $realm;
        $this->request = $request;
        $this->response = $response;

    }

    /**
     * This method sends the needed HTTP header and statuscode (401) to force
     * the user to login.
     *
     * @return void
     */
    abstract function requireLogin();

    /**
     * Returns the HTTP realm
     *
     * @return string
     */
    function getRealm() {

        return $this->realm;

    }

}
<?php

namespace Sabre\HTTP\Auth;

use Sabre\HTTP\Util;

/**
 * HTTP AWS Authentication handler
 *
 * Use this class to leverage amazon's AWS authentication header
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class AWS extends AbstractAuth {

    /**
     * The signature supplied by the HTTP client
     *
     * @var string
     */
    private $signature = null;

    /**
     * The accesskey supplied by the HTTP client
     *
     * @var string
     */
    private $accessKey = null;

    /**
     * An error code, if any
     *
     * This value will be filled with one of the ERR_* constants
     *
     * @var int
     */
    public $errorCode = 0;

    const ERR_NOAWSHEADER = 1;
    const ERR_MD5CHECKSUMWRONG = 2;
    const ERR_INVALIDDATEFORMAT = 3;
    const ERR_REQUESTTIMESKEWED = 4;
    const ERR_INVALIDSIGNATURE = 5;

    /**
     * Gathers all information from the headers
     *
     * This method needs to be called prior to anything else.
     *
     * @return bool
     */
    function init() {

        $authHeader = $this->request->getHeader('Authorization');
        $authHeader = explode(' ', $authHeader);

        if ($authHeader[0] != 'AWS' || !isset($authHeader[1])) {
            $this->errorCode = self::ERR_NOAWSHEADER;
             return false;
        }

        list($this->accessKey, $this->signature) = explode(':', $authHeader[1]);

        return true;

    }

    /**
     * Returns the username for the request
     *
     * @return string
     */
    function getAccessKey() {

        return $this->accessKey;

    }

    /**
     * Validates the signature based on the secretKey
     *
     * @param string $secretKey
     * @return bool
     */
    function validate($secretKey) {

        $contentMD5 = $this->request->getHeader('Content-MD5');

        if ($contentMD5) {
            // We need to validate the integrity of the request
            $body = $this->request->getBody();
            $this->request->setBody($body);

            if ($contentMD5 != base64_encode(md5($body, true))) {
                // content-md5 header did not match md5 signature of body
                $this->errorCode = self::ERR_MD5CHECKSUMWRONG;
                return false;
            }

        }

        if (!$requestDate = $this->request->getHeader('x-amz-date'))
            $requestDate = $this->request->getHeader('Date');

        if (!$this->validateRFC2616Date($requestDate))
            return false;

        $amzHeaders = $this->getAmzHeaders();

        $signature = base64_encode(
            $this->hmacsha1($secretKey,
                $this->request->getMethod() . "\n" .
                $contentMD5 . "\n" .
                $this->request->getHeader('Content-type') . "\n" .
                $requestDate . "\n" .
                $amzHeaders .
                $this->request->getUrl()
            )
        );

        if ($this->signature != $signature) {

            $this->errorCode = self::ERR_INVALIDSIGNATURE;
            return false;

        }

        return true;

    }


    /**
     * Returns an HTTP 401 header, forcing login
     *
     * This should be called when username and password are incorrect, or not supplied at all
     *
     * @return void
     */
    function requireLogin() {

        $this->response->addHeader('WWW-Authenticate', 'AWS');
        $this->response->setStatus(401);

    }

    /**
     * Makes sure the supplied value is a valid RFC2616 date.
     *
     * If we would just use strtotime to get a valid timestamp, we have no way of checking if a
     * user just supplied the word 'now' for the date header.
     *
     * This function also makes sure the Date header is within 15 minutes of the operating
     * system date, to prevent replay attacks.
     *
     * @param string $dateHeader
     * @return bool
     */
    protected function validateRFC2616Date($dateHeader) {

        $date = Util::parseHTTPDate($dateHeader);

        // Unknown format
        if (!$date) {
            $this->errorCode = self::ERR_INVALIDDATEFORMAT;
            return false;
        }

        $min = new \DateTime('-15 minutes');
        $max = new \DateTime('+15 minutes');

        // We allow 15 minutes around the current date/time
        if ($date > $max || $date < $min) {
            $this->errorCode = self::ERR_REQUESTTIMESKEWED;
            return false;
        }

        return $date;

    }

    /**
     * Returns a list of AMZ headers
     *
     * @return string
     */
    protected function getAmzHeaders() {

        $amzHeaders = [];
        $headers = $this->request->getHeaders();
        foreach ($headers as $headerName => $headerValue) {
            if (strpos(strtolower($headerName), 'x-amz-') === 0) {
                $amzHeaders[strtolower($headerName)] = str_replace(["\r\n"], [' '], $headerValue[0]) . "\n";
            }
        }
        ksort($amzHeaders);

        $headerStr = '';
        foreach ($amzHeaders as $h => $v) {
            $headerStr .= $h . ':' . $v;
        }

        return $headerStr;

    }

    /**
     * Generates an HMAC-SHA1 signature
     *
     * @param string $key
     * @param string $message
     * @return string
     */
    private function hmacsha1($key, $message) {

        if (function_exists('hash_hmac')) {
            return hash_hmac('sha1', $message, $key, true);
        }

        $blocksize = 64;
        if (strlen($key) > $blocksize) {
            $key = pack('H*', sha1($key));
        }
        $key = str_pad($key, $blocksize, chr(0x00));
        $ipad = str_repeat(chr(0x36), $blocksize);
        $opad = str_repeat(chr(0x5c), $blocksize);
        $hmac = pack('H*', sha1(($key ^ $opad) . pack('H*', sha1(($key ^ $ipad) . $message))));
        return $hmac;

    }

}
<?php

namespace Sabre\HTTP\Auth;

/**
 * HTTP Basic authentication utility.
 *
 * This class helps you setup basic auth. The process is fairly simple:
 *
 * 1. Instantiate the class.
 * 2. Call getCredentials (this will return null or a user/pass pair)
 * 3. If you didn't get valid credentials, call 'requireLogin'
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Basic extends AbstractAuth {

    /**
     * This method returns a numeric array with a username and password as the
     * only elements.
     *
     * If no credentials were found, this method returns null.
     *
     * @return null|array
     */
    function getCredentials() {

        $auth = $this->request->getHeader('Authorization');

        if (!$auth) {
            return null;
        }

        if (strtolower(substr($auth, 0, 6)) !== 'basic ') {
            return null;
        }

        $credentials = explode(':', base64_decode(substr($auth, 6)), 2);

        if (2 !== count($credentials)) {
            return null;
        }

        return $credentials;

    }

    /**
     * This method sends the needed HTTP header and statuscode (401) to force
     * the user to login.
     *
     * @return void
     */
    function requireLogin() {

        $this->response->addHeader('WWW-Authenticate', 'Basic realm="' . $this->realm . '"');
        $this->response->setStatus(401);

    }

}
<?php

namespace Sabre\HTTP\Auth;

/**
 * HTTP Bearer authentication utility.
 *
 * This class helps you setup bearer auth. The process is fairly simple:
 *
 * 1. Instantiate the class.
 * 2. Call getToken (this will return null or a token as string)
 * 3. If you didn't get a valid token, call 'requireLogin'
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author François Kooman (fkooman@tuxed.net)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Bearer extends AbstractAuth {

    /**
     * This method returns a string with an access token.
     *
     * If no token was found, this method returns null.
     *
     * @return null|string
     */
    function getToken() {

        $auth = $this->request->getHeader('Authorization');

        if (!$auth) {
            return null;
        }

        if (strtolower(substr($auth, 0, 7)) !== 'bearer ') {
            return null;
        }

        return substr($auth, 7);

    }

    /**
     * This method sends the needed HTTP header and statuscode (401) to force
     * authentication.
     *
     * @return void
     */
    function requireLogin() {

        $this->response->addHeader('WWW-Authenticate', 'Bearer realm="' . $this->realm . '"');
        $this->response->setStatus(401);

    }

}
<?php

namespace Sabre\HTTP\Auth;

use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

/**
 * HTTP Digest Authentication handler
 *
 * Use this class for easy http digest authentication.
 * Instructions:
 *
 *  1. Create the object
 *  2. Call the setRealm() method with the realm you plan to use
 *  3. Call the init method function.
 *  4. Call the getUserName() function. This function may return null if no
 *     authentication information was supplied. Based on the username you
 *     should check your internal database for either the associated password,
 *     or the so-called A1 hash of the digest.
 *  5. Call either validatePassword() or validateA1(). This will return true
 *     or false.
 *  6. To make sure an authentication prompt is displayed, call the
 *     requireLogin() method.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Digest extends AbstractAuth {

    /**
     * These constants are used in setQOP();
     */
    const QOP_AUTH = 1;
    const QOP_AUTHINT = 2;

    protected $nonce;
    protected $opaque;
    protected $digestParts;
    protected $A1;
    protected $qop = self::QOP_AUTH;

    /**
     * Initializes the object
     */
    function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) {

        $this->nonce = uniqid();
        $this->opaque = md5($realm);
        parent::__construct($realm, $request, $response);

    }

    /**
     * Gathers all information from the headers
     *
     * This method needs to be called prior to anything else.
     *
     * @return void
     */
    function init() {

        $digest = $this->getDigest();
        $this->digestParts = $this->parseDigest($digest);

    }

    /**
     * Sets the quality of protection value.
     *
     * Possible values are:
     *   Sabre\HTTP\DigestAuth::QOP_AUTH
     *   Sabre\HTTP\DigestAuth::QOP_AUTHINT
     *
     * Multiple values can be specified using logical OR.
     *
     * QOP_AUTHINT ensures integrity of the request body, but this is not
     * supported by most HTTP clients. QOP_AUTHINT also requires the entire
     * request body to be md5'ed, which can put strains on CPU and memory.
     *
     * @param int $qop
     * @return void
     */
    function setQOP($qop) {

        $this->qop = $qop;

    }

    /**
     * Validates the user.
     *
     * The A1 parameter should be md5($username . ':' . $realm . ':' . $password);
     *
     * @param string $A1
     * @return bool
     */
    function validateA1($A1) {

        $this->A1 = $A1;
        return $this->validate();

    }

    /**
     * Validates authentication through a password. The actual password must be provided here.
     * It is strongly recommended not store the password in plain-text and use validateA1 instead.
     *
     * @param string $password
     * @return bool
     */
    function validatePassword($password) {

        $this->A1 = md5($this->digestParts['username'] . ':' . $this->realm . ':' . $password);
        return $this->validate();

    }

    /**
     * Returns the username for the request
     *
     * @return string
     */
    function getUsername() {

        return $this->digestParts['username'];

    }

    /**
     * Validates the digest challenge
     *
     * @return bool
     */
    protected function validate() {

        $A2 = $this->request->getMethod() . ':' . $this->digestParts['uri'];

        if ($this->digestParts['qop'] == 'auth-int') {
            // Making sure we support this qop value
            if (!($this->qop & self::QOP_AUTHINT)) return false;
            // We need to add an md5 of the entire request body to the A2 part of the hash
            $body = $this->request->getBody($asString = true);
            $this->request->setBody($body);
            $A2 .= ':' . md5($body);
        } else {

            // We need to make sure we support this qop value
            if (!($this->qop & self::QOP_AUTH)) return false;
        }

        $A2 = md5($A2);

        $validResponse = md5("{$this->A1}:{$this->digestParts['nonce']}:{$this->digestParts['nc']}:{$this->digestParts['cnonce']}:{$this->digestParts['qop']}:{$A2}");

        return $this->digestParts['response'] == $validResponse;


    }

    /**
     * Returns an HTTP 401 header, forcing login
     *
     * This should be called when username and password are incorrect, or not supplied at all
     *
     * @return void
     */
    function requireLogin() {

        $qop = '';
        switch ($this->qop) {
            case self::QOP_AUTH    :
                $qop = 'auth';
                break;
            case self::QOP_AUTHINT :
                $qop = 'auth-int';
                break;
            case self::QOP_AUTH | self::QOP_AUTHINT :
                $qop = 'auth,auth-int';
                break;
        }

        $this->response->addHeader('WWW-Authenticate', 'Digest realm="' . $this->realm . '",qop="' . $qop . '",nonce="' . $this->nonce . '",opaque="' . $this->opaque . '"');
        $this->response->setStatus(401);

    }


    /**
     * This method returns the full digest string.
     *
     * It should be compatibile with mod_php format and other webservers.
     *
     * If the header could not be found, null will be returned
     *
     * @return mixed
     */
    function getDigest() {

        return $this->request->getHeader('Authorization');

    }


    /**
     * Parses the different pieces of the digest string into an array.
     *
     * This method returns false if an incomplete digest was supplied
     *
     * @param string $digest
     * @return mixed
     */
    protected function parseDigest($digest) {

        // protect against missing data
        $needed_parts = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1];
        $data = [];

        preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $digest, $matches, PREG_SET_ORDER);

        foreach ($matches as $m) {
            $data[$m[1]] = $m[2] ? $m[2] : $m[3];
            unset($needed_parts[$m[1]]);
        }

        return $needed_parts ? false : $data;

    }

}
<?php

namespace Sabre\HTTP;

use Sabre\Event\EventEmitter;
use Sabre\Uri;

/**
 * A rudimentary HTTP client.
 *
 * This object wraps PHP's curl extension and provides an easy way to send it a
 * Request object, and return a Response object.
 *
 * This is by no means intended as the next best HTTP client, but it does the
 * job and provides a simple integration with the rest of sabre/http.
 *
 * This client emits the following events:
 *   beforeRequest(RequestInterface $request)
 *   afterRequest(RequestInterface $request, ResponseInterface $response)
 *   error(RequestInterface $request, ResponseInterface $response, bool &$retry, int $retryCount)
 *   exception(RequestInterface $request, ClientException $e, bool &$retry, int $retryCount)
 *
 * The beforeRequest event allows you to do some last minute changes to the
 * request before it's done, such as adding authentication headers.
 *
 * The afterRequest event will be emitted after the request is completed
 * succesfully.
 *
 * If a HTTP error is returned (status code higher than 399) the error event is
 * triggered. It's possible using this event to retry the request, by setting
 * retry to true.
 *
 * The amount of times a request has retried is passed as $retryCount, which
 * can be used to avoid retrying indefinitely. The first time the event is
 * called, this will be 0.
 *
 * It's also possible to intercept specific http errors, by subscribing to for
 * example 'error:401'.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Client extends EventEmitter {

    /**
     * List of curl settings
     *
     * @var array
     */
    protected $curlSettings = [];

    /**
     * Wether or not exceptions should be thrown when a HTTP error is returned.
     *
     * @var bool
     */
    protected $throwExceptions = false;

    /**
     * The maximum number of times we'll follow a redirect.
     *
     * @var int
     */
    protected $maxRedirects = 5;

    /**
     * Initializes the client.
     *
     * @return void
     */
    function __construct() {

        $this->curlSettings = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => true,
            CURLOPT_NOBODY         => false,
            CURLOPT_USERAGENT      => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
        ];

    }

    /**
     * Sends a request to a HTTP server, and returns a response.
     *
     * @param RequestInterface $request
     * @return ResponseInterface
     */
    function send(RequestInterface $request) {

        $this->emit('beforeRequest', [$request]);

        $retryCount = 0;
        $redirects = 0;

        do {

            $doRedirect = false;
            $retry = false;

            try {

                $response = $this->doRequest($request);

                $code = (int)$response->getStatus();

                // We are doing in-PHP redirects, because curl's
                // FOLLOW_LOCATION throws errors when PHP is configured with
                // open_basedir.
                //
                // https://github.com/fruux/sabre-http/issues/12
                if (in_array($code, [301, 302, 307, 308]) && $redirects < $this->maxRedirects) {

                    $oldLocation = $request->getUrl();

                    // Creating a new instance of the request object.
                    $request = clone $request;

                    // Setting the new location
                    $request->setUrl(Uri\resolve(
                        $oldLocation,
                        $response->getHeader('Location')
                    ));

                    $doRedirect = true;
                    $redirects++;

                }

                // This was a HTTP error
                if ($code >= 400) {

                    $this->emit('error', [$request, $response, &$retry, $retryCount]);
                    $this->emit('error:' . $code, [$request, $response, &$retry, $retryCount]);

                }

            } catch (ClientException $e) {

                $this->emit('exception', [$request, $e, &$retry, $retryCount]);

                // If retry was still set to false, it means no event handler
                // dealt with the problem. In this case we just re-throw the
                // exception.
                if (!$retry) {
                    throw $e;
                }

            }

            if ($retry) {
                $retryCount++;
            }

        } while ($retry || $doRedirect);

        $this->emit('afterRequest', [$request, $response]);

        if ($this->throwExceptions && $code >= 400) {
            throw new ClientHttpException($response);
        }

        return $response;

    }

    /**
     * Sends a HTTP request asynchronously.
     *
     * Due to the nature of PHP, you must from time to time poll to see if any
     * new responses came in.
     *
     * After calling sendAsync, you must therefore occasionally call the poll()
     * method, or wait().
     *
     * @param RequestInterface $request
     * @param callable $success
     * @param callable $error
     * @return void
     */
    function sendAsync(RequestInterface $request, callable $success = null, callable $error = null) {

        $this->emit('beforeRequest', [$request]);
        $this->sendAsyncInternal($request, $success, $error);
        $this->poll();

    }


    /**
     * This method checks if any http requests have gotten results, and if so,
     * call the appropriate success or error handlers.
     *
     * This method will return true if there are still requests waiting to
     * return, and false if all the work is done.
     *
     * @return bool
     */
    function poll() {

        // nothing to do?
        if (!$this->curlMultiMap) {
            return false;
        }

        do {
            $r = curl_multi_exec(
                $this->curlMultiHandle,
                $stillRunning
            );
        } while ($r === CURLM_CALL_MULTI_PERFORM);

        do {

            messageQueue:

            $status = curl_multi_info_read(
                $this->curlMultiHandle,
                $messagesInQueue
            );

            if ($status && $status['msg'] === CURLMSG_DONE) {

                $resourceId = intval($status['handle']);
                list(
                    $request,
                    $successCallback,
                    $errorCallback,
                    $retryCount,
                ) = $this->curlMultiMap[$resourceId];
                unset($this->curlMultiMap[$resourceId]);
                $curlResult = $this->parseCurlResult(curl_multi_getcontent($status['handle']), $status['handle']);
                $retry = false;

                if ($curlResult['status'] === self::STATUS_CURLERROR) {

                    $e = new ClientException($curlResult['curl_errmsg'], $curlResult['curl_errno']);
                    $this->emit('exception', [$request, $e, &$retry, $retryCount]);

                    if ($retry) {
                        $retryCount++;
                        $this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
                        goto messageQueue;
                    }

                    $curlResult['request'] = $request;

                    if ($errorCallback) {
                        $errorCallback($curlResult);
                    }

                } elseif ($curlResult['status'] === self::STATUS_HTTPERROR) {

                    $this->emit('error', [$request, $curlResult['response'], &$retry, $retryCount]);
                    $this->emit('error:' . $curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]);

                    if ($retry) {

                        $retryCount++;
                        $this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount);
                        goto messageQueue;

                    }

                    $curlResult['request'] = $request;

                    if ($errorCallback) {
                        $errorCallback($curlResult);
                    }

                } else {

                    $this->emit('afterRequest', [$request, $curlResult['response']]);

                    if ($successCallback) {
                        $successCallback($curlResult['response']);
                    }

                }
            }

        } while ($messagesInQueue > 0);

        return count($this->curlMultiMap) > 0;

    }

    /**
     * Processes every HTTP request in the queue, and waits till they are all
     * completed.
     *
     * @return void
     */
    function wait() {

        do {
            curl_multi_select($this->curlMultiHandle);
            $stillRunning = $this->poll();
        } while ($stillRunning);

    }

    /**
     * If this is set to true, the Client will automatically throw exceptions
     * upon HTTP errors.
     *
     * This means that if a response came back with a status code greater than
     * or equal to 400, we will throw a ClientHttpException.
     *
     * This only works for the send() method. Throwing exceptions for
     * sendAsync() is not supported.
     *
     * @param bool $throwExceptions
     * @return void
     */
    function setThrowExceptions($throwExceptions) {

        $this->throwExceptions = $throwExceptions;

    }

    /**
     * Adds a CURL setting.
     *
     * These settings will be included in every HTTP request.
     *
     * @param int $name
     * @param mixed $value
     * @return void
     */
    function addCurlSetting($name, $value) {

        $this->curlSettings[$name] = $value;

    }

    /**
     * This method is responsible for performing a single request.
     *
     * @param RequestInterface $request
     * @return ResponseInterface
     */
    protected function doRequest(RequestInterface $request) {

        $settings = $this->createCurlSettingsArray($request);

        if (!$this->curlHandle) {
            $this->curlHandle = curl_init();
        }

        curl_setopt_array($this->curlHandle, $settings);
        $response = $this->curlExec($this->curlHandle);
        $response = $this->parseCurlResult($response, $this->curlHandle);

        if ($response['status'] === self::STATUS_CURLERROR) {
            throw new ClientException($response['curl_errmsg'], $response['curl_errno']);
        }

        return $response['response'];

    }

    /**
     * Cached curl handle.
     *
     * By keeping this resource around for the lifetime of this object, things
     * like persistent connections are possible.
     *
     * @var resource
     */
    private $curlHandle;

    /**
     * Handler for curl_multi requests.
     *
     * The first time sendAsync is used, this will be created.
     *
     * @var resource
     */
    private $curlMultiHandle;

    /**
     * Has a list of curl handles, as well as their associated success and
     * error callbacks.
     *
     * @var array
     */
    private $curlMultiMap = [];

    /**
     * Turns a RequestInterface object into an array with settings that can be
     * fed to curl_setopt
     *
     * @param RequestInterface $request
     * @return array
     */
    protected function createCurlSettingsArray(RequestInterface $request) {

        $settings = $this->curlSettings;

        switch ($request->getMethod()) {
            case 'HEAD' :
                $settings[CURLOPT_NOBODY] = true;
                $settings[CURLOPT_CUSTOMREQUEST] = 'HEAD';
                $settings[CURLOPT_POSTFIELDS] = '';
                $settings[CURLOPT_PUT] = false;
                break;
            case 'GET' :
                $settings[CURLOPT_CUSTOMREQUEST] = 'GET';
                $settings[CURLOPT_POSTFIELDS] = '';
                $settings[CURLOPT_PUT] = false;
                break;
            default :
                $body = $request->getBody();
                if (is_resource($body)) {
                    // This needs to be set to PUT, regardless of the actual
                    // method used. Without it, INFILE will be ignored for some
                    // reason.
                    $settings[CURLOPT_PUT] = true;
                    $settings[CURLOPT_INFILE] = $request->getBody();
                } else {
                    // For security we cast this to a string. If somehow an array could
                    // be passed here, it would be possible for an attacker to use @ to
                    // post local files.
                    $settings[CURLOPT_POSTFIELDS] = (string)$body;
                }
                $settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod();
                break;

        }

        $nHeaders = [];
        foreach ($request->getHeaders() as $key => $values) {

            foreach ($values as $value) {
                $nHeaders[] = $key . ': ' . $value;
            }

        }
        $settings[CURLOPT_HTTPHEADER] = $nHeaders;
        $settings[CURLOPT_URL] = $request->getUrl();
        // FIXME: CURLOPT_PROTOCOLS is currently unsupported by HHVM
        if (defined('CURLOPT_PROTOCOLS')) {
            $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }
        // FIXME: CURLOPT_REDIR_PROTOCOLS is currently unsupported by HHVM
        if (defined('CURLOPT_REDIR_PROTOCOLS')) {
            $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }

        return $settings;

    }

    const STATUS_SUCCESS = 0;
    const STATUS_CURLERROR = 1;
    const STATUS_HTTPERROR = 2;

    /**
     * Parses the result of a curl call in a format that's a bit more
     * convenient to work with.
     *
     * The method returns an array with the following elements:
     *   * status - one of the 3 STATUS constants.
     *   * curl_errno - A curl error number. Only set if status is
     *                  STATUS_CURLERROR.
     *   * curl_errmsg - A current error message. Only set if status is
     *                   STATUS_CURLERROR.
     *   * response - Response object. Only set if status is STATUS_SUCCESS, or
     *                STATUS_HTTPERROR.
     *   * http_code - HTTP status code, as an int. Only set if Only set if
     *                 status is STATUS_SUCCESS, or STATUS_HTTPERROR
     *
     * @param string $response
     * @param resource $curlHandle
     * @return Response
     */
    protected function parseCurlResult($response, $curlHandle) {

        list(
            $curlInfo,
            $curlErrNo,
            $curlErrMsg
        ) = $this->curlStuff($curlHandle);

        if ($curlErrNo) {
            return [
                'status'      => self::STATUS_CURLERROR,
                'curl_errno'  => $curlErrNo,
                'curl_errmsg' => $curlErrMsg,
            ];
        }

        $headerBlob = substr($response, 0, $curlInfo['header_size']);
        // In the case of 204 No Content, strlen($response) == $curlInfo['header_size].
        // This will cause substr($response, $curlInfo['header_size']) return FALSE instead of NULL
        // An exception will be thrown when calling getBodyAsString then
        $responseBody = substr($response, $curlInfo['header_size']) ?: null;

        unset($response);

        // In the case of 100 Continue, or redirects we'll have multiple lists
        // of headers for each separate HTTP response. We can easily split this
        // because they are separated by \r\n\r\n
        $headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n"));

        // We only care about the last set of headers
        $headerBlob = $headerBlob[count($headerBlob) - 1];

        // Splitting headers
        $headerBlob = explode("\r\n", $headerBlob);

        $response = new Response();
        $response->setStatus($curlInfo['http_code']);

        foreach ($headerBlob as $header) {
            $parts = explode(':', $header, 2);
            if (count($parts) == 2) {
                $response->addHeader(trim($parts[0]), trim($parts[1]));
            }
        }

        $response->setBody($responseBody);

        $httpCode = intval($response->getStatus());

        return [
            'status'    => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS,
            'response'  => $response,
            'http_code' => $httpCode,
        ];

    }

    /**
     * Sends an asynchronous HTTP request.
     *
     * We keep this in a separate method, so we can call it without triggering
     * the beforeRequest event and don't do the poll().
     *
     * @param RequestInterface $request
     * @param callable $success
     * @param callable $error
     * @param int $retryCount
     */
    protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, $retryCount = 0) {

        if (!$this->curlMultiHandle) {
            $this->curlMultiHandle = curl_multi_init();
        }
        $curl = curl_init();
        curl_setopt_array(
            $curl,
            $this->createCurlSettingsArray($request)
        );
        curl_multi_add_handle($this->curlMultiHandle, $curl);
        $this->curlMultiMap[intval($curl)] = [
            $request,
            $success,
            $error,
            $retryCount
        ];

    }

    // @codeCoverageIgnoreStart

    /**
     * Calls curl_exec
     *
     * This method exists so it can easily be overridden and mocked.
     *
     * @param resource $curlHandle
     * @return string
     */
    protected function curlExec($curlHandle) {

        return curl_exec($curlHandle);

    }

    /**
     * Returns a bunch of information about a curl request.
     *
     * This method exists so it can easily be overridden and mocked.
     *
     * @param resource $curlHandle
     * @return array
     */
    protected function curlStuff($curlHandle) {

        return [
            curl_getinfo($curlHandle),
            curl_errno($curlHandle),
            curl_error($curlHandle),
        ];

    }
    // @codeCoverageIgnoreEnd

}
<?php

namespace Sabre\HTTP;

/**
 * This exception may be emitted by the HTTP\Client class, in case there was a
 * problem emitting the request.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ClientException extends \Exception {

}
<?php

namespace Sabre\HTTP;

/**
 * This exception represents a HTTP error coming from the Client.
 *
 * By default the Client will not emit these, this has to be explicitly enabled
 * with the setThrowExceptions method.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ClientHttpException extends \Exception implements HttpException {

    /**
     * Response object
     *
     * @var ResponseInterface
     */
    protected $response;

    /**
     * Constructor
     *
     * @param ResponseInterface $response
     */
    function __construct(ResponseInterface $response) {

        $this->response = $response;
        parent::__construct($response->getStatusText(), $response->getStatus());

    }

    /**
     * The http status code for the error.
     *
     * @return int
     */
    function getHttpStatus() {

        return $this->response->getStatus();

    }

    /**
     * Returns the full response object.
     *
     * @return ResponseInterface
     */
    function getResponse() {

        return $this->response;

    }

}
<?php

namespace Sabre\HTTP;

use DateTime;

/**
 * A collection of useful helpers for parsing or generating various HTTP
 * headers.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */

/**
 * Parses a HTTP date-string.
 *
 * This method returns false if the date is invalid.
 *
 * The following formats are supported:
 *    Sun, 06 Nov 1994 08:49:37 GMT    ; IMF-fixdate
 *    Sunday, 06-Nov-94 08:49:37 GMT   ; obsolete RFC 850 format
 *    Sun Nov  6 08:49:37 1994         ; ANSI C's asctime() format
 *
 * See:
 *   http://tools.ietf.org/html/rfc7231#section-7.1.1.1
 *
 * @param string $dateString
 * @return bool|DateTime
 */
function parseDate($dateString) {

    // Only the format is checked, valid ranges are checked by strtotime below
    $month = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)';
    $weekday = '(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)';
    $wkday = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)';
    $time = '([0-1]\d|2[0-3])(\:[0-5]\d){2}';
    $date3 = $month . ' ([12]\d|3[01]| [1-9])';
    $date2 = '(0[1-9]|[12]\d|3[01])\-' . $month . '\-\d{2}';
    // 4-digit year cannot begin with 0 - unix timestamp begins in 1970
    $date1 = '(0[1-9]|[12]\d|3[01]) ' . $month . ' [1-9]\d{3}';

    // ANSI C's asctime() format
    // 4-digit year cannot begin with 0 - unix timestamp begins in 1970
    $asctime_date = $wkday . ' ' . $date3 . ' ' . $time . ' [1-9]\d{3}';
    // RFC 850, obsoleted by RFC 1036
    $rfc850_date = $weekday . ', ' . $date2 . ' ' . $time . ' GMT';
    // RFC 822, updated by RFC 1123
    $rfc1123_date = $wkday . ', ' . $date1 . ' ' . $time . ' GMT';
    // allowed date formats by RFC 2616
    $HTTP_date = "($rfc1123_date|$rfc850_date|$asctime_date)";

    // allow for space around the string and strip it
    $dateString = trim($dateString, ' ');
    if (!preg_match('/^' . $HTTP_date . '$/', $dateString))
        return false;

    // append implicit GMT timezone to ANSI C time format
    if (strpos($dateString, ' GMT') === false)
        $dateString .= ' GMT';

    try {
        return new DateTime($dateString, new \DateTimeZone('UTC'));
    } catch (\Exception $e) {
        return false;
    }

}

/**
 * Transforms a DateTime object to a valid HTTP/1.1 Date header value
 *
 * @param DateTime $dateTime
 * @return string
 */
function toDate(DateTime $dateTime) {

    // We need to clone it, as we don't want to affect the existing
    // DateTime.
    $dateTime = clone $dateTime;
    $dateTime->setTimezone(new \DateTimeZone('GMT'));
    return $dateTime->format('D, d M Y H:i:s \G\M\T');

}

/**
 * This function can be used to aid with content negotiation.
 *
 * It takes 2 arguments, the $acceptHeaderValue, which usually comes from
 * an Accept header, and $availableOptions, which contains an array of
 * items that the server can support.
 *
 * The result of this function will be the 'best possible option'. If no
 * best possible option could be found, null is returned.
 *
 * When it's null you can according to the spec either return a default, or
 * you can choose to emit 406 Not Acceptable.
 *
 * The method also accepts sending 'null' for the $acceptHeaderValue,
 * implying that no accept header was sent.
 *
 * @param string|null $acceptHeaderValue
 * @param array $availableOptions
 * @return string|null
 */
function negotiateContentType($acceptHeaderValue, array $availableOptions) {

    if (!$acceptHeaderValue) {
        // Grabbing the first in the list.
        return reset($availableOptions);
    }

    $proposals = array_map(
        'Sabre\HTTP\parseMimeType',
        explode(',', $acceptHeaderValue)
    );

    // Ensuring array keys are reset.
    $availableOptions = array_values($availableOptions);

    $options = array_map(
        'Sabre\HTTP\parseMimeType',
        $availableOptions
    );

    $lastQuality = 0;
    $lastSpecificity = 0;
    $lastOptionIndex = 0;
    $lastChoice = null;

    foreach ($proposals as $proposal) {

        // Ignoring broken values.
        if (is_null($proposal)) continue;

        // If the quality is lower we don't have to bother comparing.
        if ($proposal['quality'] < $lastQuality) {
            continue;
        }

        foreach ($options as $optionIndex => $option) {

            if ($proposal['type'] !== '*' && $proposal['type'] !== $option['type']) {
                // no match on type.
                continue;
            }
            if ($proposal['subType'] !== '*' && $proposal['subType'] !== $option['subType']) {
                // no match on subtype.
                continue;
            }

            // Any parameters appearing on the options must appear on
            // proposals.
            foreach ($option['parameters'] as $paramName => $paramValue) {
                if (!array_key_exists($paramName, $proposal['parameters'])) {
                    continue 2;
                }
                if ($paramValue !== $proposal['parameters'][$paramName]) {
                    continue 2;
                }
            }

            // If we got here, we have a match on parameters, type and
            // subtype. We need to calculate a score for how specific the
            // match was.
            $specificity =
                ($proposal['type'] !== '*' ? 20 : 0) +
                ($proposal['subType'] !== '*' ? 10 : 0) +
                count($option['parameters']);


            // Does this entry win?
            if (
                ($proposal['quality'] > $lastQuality) ||
                ($proposal['quality'] === $lastQuality && $specificity > $lastSpecificity) ||
                ($proposal['quality'] === $lastQuality && $specificity === $lastSpecificity && $optionIndex < $lastOptionIndex)
            ) {

                $lastQuality = $proposal['quality'];
                $lastSpecificity = $specificity;
                $lastOptionIndex = $optionIndex;
                $lastChoice = $availableOptions[$optionIndex];

            }

        }

    }

    return $lastChoice;

}

/**
 * Parses the Prefer header, as defined in RFC7240.
 *
 * Input can be given as a single header value (string) or multiple headers
 * (array of string).
 *
 * This method will return a key->value array with the various Prefer
 * parameters.
 *
 * Prefer: return=minimal will result in:
 *
 * [ 'return' => 'minimal' ]
 *
 * Prefer: foo, wait=10 will result in:
 *
 * [ 'foo' => true, 'wait' => '10']
 *
 * This method also supports the formats from older drafts of RFC7240, and
 * it will automatically map them to the new values, as the older values
 * are still pretty common.
 *
 * Parameters are currently discarded. There's no known prefer value that
 * uses them.
 *
 * @param string|string[] $input
 * @return array
 */
function parsePrefer($input) {

    $token = '[!#$%&\'*+\-.^_`~A-Za-z0-9]+';

    // Work in progress
    $word = '(?: [a-zA-Z0-9]+ | "[a-zA-Z0-9]*" )';

    $regex = <<<REGEX
/
^
(?<name> $token)      # Prefer property name
\s*                   # Optional space
(?: = \s*             # Prefer property value
   (?<value> $word)
)?
(?: \s* ; (?: .*))?   # Prefer parameters (ignored)
$
/x
REGEX;

    $output = [];
    foreach (getHeaderValues($input) as $value) {

        if (!preg_match($regex, $value, $matches)) {
            // Ignore
            continue;
        }

        // Mapping old values to their new counterparts
        switch ($matches['name']) {
            case 'return-asynch' :
                $output['respond-async'] = true;
                break;
            case 'return-representation' :
                $output['return'] = 'representation';
                break;
            case 'return-minimal' :
                $output['return'] = 'minimal';
                break;
            case 'strict' :
                $output['handling'] = 'strict';
                break;
            case 'lenient' :
                $output['handling'] = 'lenient';
                break;
            default :
                if (isset($matches['value'])) {
                    $value = trim($matches['value'], '"');
                } else {
                    $value = true;
                }
                $output[strtolower($matches['name'])] = empty($value) ? true : $value;
                break;
        }

    }

    return $output;

}

/**
 * This method splits up headers into all their individual values.
 *
 * A HTTP header may have more than one header, such as this:
 *   Cache-Control: private, no-store
 *
 * Header values are always split with a comma.
 *
 * You can pass either a string, or an array. The resulting value is always
 * an array with each spliced value.
 *
 * If the second headers argument is set, this value will simply be merged
 * in. This makes it quicker to merge an old list of values with a new set.
 *
 * @param string|string[] $values
 * @param string|string[] $values2
 * @return string[]
 */
function getHeaderValues($values, $values2 = null) {

    $values = (array)$values;
    if ($values2) {
        $values = array_merge($values, (array)$values2);
    }
    foreach ($values as $l1) {
        foreach (explode(',', $l1) as $l2) {
            $result[] = trim($l2);
        }
    }
    return $result;

}

/**
 * Parses a mime-type and splits it into:
 *
 * 1. type
 * 2. subtype
 * 3. quality
 * 4. parameters
 *
 * @param string $str
 * @return array
 */
function parseMimeType($str) {

    $parameters = [];
    // If no q= parameter appears, then quality = 1.
    $quality = 1;

    $parts = explode(';', $str);

    // The first part is the mime-type.
    $mimeType = array_shift($parts);

    $mimeType = explode('/', trim($mimeType));
    if (count($mimeType) !== 2) {
        // Illegal value
        return null;
    }
    list($type, $subType) = $mimeType;

    foreach ($parts as $part) {

        $part = trim($part);
        if (strpos($part, '=')) {
            list($partName, $partValue) =
                explode('=', $part, 2);
        } else {
            $partName = $part;
            $partValue = null;
        }

        // The quality parameter, if it appears, also marks the end of
        // the parameter list. Anything after the q= counts as an
        // 'accept extension' and could introduce new semantics in
        // content-negotation.
        if ($partName !== 'q') {
            $parameters[$partName] = $part;
        } else {
            $quality = (float)$partValue;
            break; // Stop parsing parts
        }

    }

    return [
        'type'       => $type,
        'subType'    => $subType,
        'quality'    => $quality,
        'parameters' => $parameters,
    ];

}

/**
 * Encodes the path of a url.
 *
 * slashes (/) are treated as path-separators.
 *
 * @param string $path
 * @return string
 */
function encodePath($path) {

    return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/:@])/', function($match) {

        return '%' . sprintf('%02x', ord($match[0]));

    }, $path);

}

/**
 * Encodes a 1 segment of a path
 *
 * Slashes are considered part of the name, and are encoded as %2f
 *
 * @param string $pathSegment
 * @return string
 */
function encodePathSegment($pathSegment) {

    return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\):@])/', function($match) {

        return '%' . sprintf('%02x', ord($match[0]));

    }, $pathSegment);
}

/**
 * Decodes a url-encoded path
 *
 * @param string $path
 * @return string
 */
function decodePath($path) {

    return decodePathSegment($path);

}

/**
 * Decodes a url-encoded path segment
 *
 * @param string $path
 * @return string
 */
function decodePathSegment($path) {

    $path = rawurldecode($path);
    $encoding = mb_detect_encoding($path, ['UTF-8', 'ISO-8859-1']);

    switch ($encoding) {

        case 'ISO-8859-1' :
            $path = utf8_encode($path);

    }

    return $path;

}
<?php

namespace Sabre\HTTP;

/**
 * An exception representing a HTTP error.
 *
 * This can be used as a generic exception in your application, if you'd like
 * to map HTTP errors to exceptions.
 *
 * If you'd like to use this, create a new exception class, extending Exception
 * and implementing this interface.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface HttpException {

    /**
     * The http status code for the error.
     *
     * This may either be just the number, or a number and a human-readable
     * message, separated by a space.
     *
     * @return string|null
     */
    function getHttpStatus();

}
<?php

namespace Sabre\HTTP;

/**
 * This is the abstract base class for both the Request and Response objects.
 *
 * This object contains a few simple methods that are shared by both.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class Message implements MessageInterface {

    /**
     * Request body
     *
     * This should be a stream resource
     *
     * @var resource
     */
    protected $body;

    /**
     * Contains the list of HTTP headers
     *
     * @var array
     */
    protected $headers = [];

    /**
     * HTTP message version (1.0 or 1.1)
     *
     * @var string
     */
    protected $httpVersion = '1.1';

    /**
     * Returns the body as a readable stream resource.
     *
     * Note that the stream may not be rewindable, and therefore may only be
     * read once.
     *
     * @return resource
     */
    function getBodyAsStream() {

        $body = $this->getBody();
        if (is_string($body) || is_null($body)) {
            $stream = fopen('php://temp', 'r+');
            fwrite($stream, $body);
            rewind($stream);
            return $stream;
        }
        return $body;

    }

    /**
     * Returns the body as a string.
     *
     * Note that because the underlying data may be based on a stream, this
     * method could only work correctly the first time.
     *
     * @return string
     */
    function getBodyAsString() {

        $body = $this->getBody();
        if (is_string($body)) {
            return $body;
        }
        if (is_null($body)) {
            return '';
        }
        $contentLength = $this->getHeader('Content-Length');
        if (is_int($contentLength) || ctype_digit($contentLength)) {
            return stream_get_contents($body, $contentLength);
        } else {
            return stream_get_contents($body);
        }
    }

    /**
     * Returns the message body, as it's internal representation.
     *
     * This could be either a string or a stream.
     *
     * @return resource|string
     */
    function getBody() {

        return $this->body;

    }

    /**
     * Replaces the body resource with a new stream or string.
     *
     * @param resource|string $body
     */
    function setBody($body) {

        $this->body = $body;

    }

    /**
     * Returns all the HTTP headers as an array.
     *
     * Every header is returned as an array, with one or more values.
     *
     * @return array
     */
    function getHeaders() {

        $result = [];
        foreach ($this->headers as $headerInfo) {
            $result[$headerInfo[0]] = $headerInfo[1];
        }
        return $result;

    }

    /**
     * Will return true or false, depending on if a HTTP header exists.
     *
     * @param string $name
     * @return bool
     */
    function hasHeader($name) {

        return isset($this->headers[strtolower($name)]);

    }

    /**
     * Returns a specific HTTP header, based on it's name.
     *
     * The name must be treated as case-insensitive.
     * If the header does not exist, this method must return null.
     *
     * If a header appeared more than once in a HTTP request, this method will
     * concatenate all the values with a comma.
     *
     * Note that this not make sense for all headers. Some, such as
     * `Set-Cookie` cannot be logically combined with a comma. In those cases
     * you *should* use getHeaderAsArray().
     *
     * @param string $name
     * @return string|null
     */
    function getHeader($name) {

        $name = strtolower($name);

        if (isset($this->headers[$name])) {
            return implode(',', $this->headers[$name][1]);
        }
        return null;

    }

    /**
     * Returns a HTTP header as an array.
     *
     * For every time the HTTP header appeared in the request or response, an
     * item will appear in the array.
     *
     * If the header did not exists, this method will return an empty array.
     *
     * @param string $name
     * @return string[]
     */
    function getHeaderAsArray($name) {

        $name = strtolower($name);

        if (isset($this->headers[$name])) {
            return $this->headers[$name][1];
        }

        return [];

    }

    /**
     * Updates a HTTP header.
     *
     * The case-sensitivity of the name value must be retained as-is.
     *
     * If the header already existed, it will be overwritten.
     *
     * @param string $name
     * @param string|string[] $value
     * @return void
     */
    function setHeader($name, $value) {

        $this->headers[strtolower($name)] = [$name, (array)$value];

    }

    /**
     * Sets a new set of HTTP headers.
     *
     * The headers array should contain headernames for keys, and their value
     * should be specified as either a string or an array.
     *
     * Any header that already existed will be overwritten.
     *
     * @param array $headers
     * @return void
     */
    function setHeaders(array $headers) {

        foreach ($headers as $name => $value) {
            $this->setHeader($name, $value);
        }

    }

    /**
     * Adds a HTTP header.
     *
     * This method will not overwrite any existing HTTP header, but instead add
     * another value. Individual values can be retrieved with
     * getHeadersAsArray.
     *
     * @param string $name
     * @param string $value
     * @return void
     */
    function addHeader($name, $value) {

        $lName = strtolower($name);
        if (isset($this->headers[$lName])) {
            $this->headers[$lName][1] = array_merge(
                $this->headers[$lName][1],
                (array)$value
            );
        } else {
            $this->headers[$lName] = [
                $name,
                (array)$value
            ];
        }

    }

    /**
     * Adds a new set of HTTP headers.
     *
     * Any existing headers will not be overwritten.
     *
     * @param array $headers
     * @return void
     */
    function addHeaders(array $headers) {

        foreach ($headers as $name => $value) {
            $this->addHeader($name, $value);
        }

    }


    /**
     * Removes a HTTP header.
     *
     * The specified header name must be treated as case-insensitive.
     * This method should return true if the header was successfully deleted,
     * and false if the header did not exist.
     *
     * @param string $name
     * @return bool
     */
    function removeHeader($name) {

        $name = strtolower($name);
        if (!isset($this->headers[$name])) {
            return false;
        }
        unset($this->headers[$name]);
        return true;

    }

    /**
     * Sets the HTTP version.
     *
     * Should be 1.0 or 1.1.
     *
     * @param string $version
     * @return void
     */
    function setHttpVersion($version) {

        $this->httpVersion = $version;

    }

    /**
     * Returns the HTTP version.
     *
     * @return string
     */
    function getHttpVersion() {

        return $this->httpVersion;

    }
}
<?php

namespace Sabre\HTTP;

/**
 * This trait contains a bunch of methods, shared by both the RequestDecorator
 * and the ResponseDecorator.
 *
 * Didn't seem needed to create a full class for this, so we're just
 * implementing it as a trait.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
trait MessageDecoratorTrait {

    /**
     * The inner request object.
     *
     * All method calls will be forwarded here.
     *
     * @var MessageInterface
     */
    protected $inner;

    /**
     * Returns the body as a readable stream resource.
     *
     * Note that the stream may not be rewindable, and therefore may only be
     * read once.
     *
     * @return resource
     */
    function getBodyAsStream() {

        return $this->inner->getBodyAsStream();

    }

    /**
     * Returns the body as a string.
     *
     * Note that because the underlying data may be based on a stream, this
     * method could only work correctly the first time.
     *
     * @return string
     */
    function getBodyAsString() {

        return $this->inner->getBodyAsString();

    }

    /**
     * Returns the message body, as it's internal representation.
     *
     * This could be either a string or a stream.
     *
     * @return resource|string
     */
    function getBody() {

        return $this->inner->getBody();

    }

    /**
     * Updates the body resource with a new stream.
     *
     * @param resource $body
     * @return void
     */
    function setBody($body) {

        $this->inner->setBody($body);

    }

    /**
     * Returns all the HTTP headers as an array.
     *
     * Every header is returned as an array, with one or more values.
     *
     * @return array
     */
    function getHeaders() {

        return $this->inner->getHeaders();

    }

    /**
     * Will return true or false, depending on if a HTTP header exists.
     *
     * @param string $name
     * @return bool
     */
    function hasHeader($name) {

        return $this->inner->hasHeader($name);

    }

    /**
     * Returns a specific HTTP header, based on it's name.
     *
     * The name must be treated as case-insensitive.
     * If the header does not exist, this method must return null.
     *
     * If a header appeared more than once in a HTTP request, this method will
     * concatenate all the values with a comma.
     *
     * Note that this not make sense for all headers. Some, such as
     * `Set-Cookie` cannot be logically combined with a comma. In those cases
     * you *should* use getHeaderAsArray().
     *
     * @param string $name
     * @return string|null
     */
    function getHeader($name) {

        return $this->inner->getHeader($name);

    }

    /**
     * Returns a HTTP header as an array.
     *
     * For every time the HTTP header appeared in the request or response, an
     * item will appear in the array.
     *
     * If the header did not exists, this method will return an empty array.
     *
     * @param string $name
     * @return string[]
     */
    function getHeaderAsArray($name) {

        return $this->inner->getHeaderAsArray($name);

    }

    /**
     * Updates a HTTP header.
     *
     * The case-sensitivity of the name value must be retained as-is.
     *
     * If the header already existed, it will be overwritten.
     *
     * @param string $name
     * @param string|string[] $value
     * @return void
     */
    function setHeader($name, $value) {

        $this->inner->setHeader($name, $value);

    }

    /**
     * Sets a new set of HTTP headers.
     *
     * The headers array should contain headernames for keys, and their value
     * should be specified as either a string or an array.
     *
     * Any header that already existed will be overwritten.
     *
     * @param array $headers
     * @return void
     */
    function setHeaders(array $headers) {

        $this->inner->setHeaders($headers);

    }

    /**
     * Adds a HTTP header.
     *
     * This method will not overwrite any existing HTTP header, but instead add
     * another value. Individual values can be retrieved with
     * getHeadersAsArray.
     *
     * @param string $name
     * @param string $value
     * @return void
     */
    function addHeader($name, $value) {

        $this->inner->addHeader($name, $value);

    }

    /**
     * Adds a new set of HTTP headers.
     *
     * Any existing headers will not be overwritten.
     *
     * @param array $headers
     * @return void
     */
    function addHeaders(array $headers) {

        $this->inner->addHeaders($headers);

    }


    /**
     * Removes a HTTP header.
     *
     * The specified header name must be treated as case-insensitive.
     * This method should return true if the header was successfully deleted,
     * and false if the header did not exist.
     *
     * @param string $name
     * @return bool
     */
    function removeHeader($name) {

        return $this->inner->removeHeader($name);

    }

    /**
     * Sets the HTTP version.
     *
     * Should be 1.0 or 1.1.
     *
     * @param string $version
     * @return void
     */
    function setHttpVersion($version) {

        $this->inner->setHttpVersion($version);

    }

    /**
     * Returns the HTTP version.
     *
     * @return string
     */
    function getHttpVersion() {

        return $this->inner->getHttpVersion();

    }

}
<?php

namespace Sabre\HTTP;

/**
 * The MessageInterface is the base interface that's used by both
 * the RequestInterface and ResponseInterface.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface MessageInterface {

    /**
     * Returns the body as a readable stream resource.
     *
     * Note that the stream may not be rewindable, and therefore may only be
     * read once.
     *
     * @return resource
     */
    function getBodyAsStream();

    /**
     * Returns the body as a string.
     *
     * Note that because the underlying data may be based on a stream, this
     * method could only work correctly the first time.
     *
     * @return string
     */
    function getBodyAsString();

    /**
     * Returns the message body, as it's internal representation.
     *
     * This could be either a string or a stream.
     *
     * @return resource|string
     */
    function getBody();

    /**
     * Updates the body resource with a new stream.
     *
     * @param resource|string $body
     * @return void
     */
    function setBody($body);

    /**
     * Returns all the HTTP headers as an array.
     *
     * Every header is returned as an array, with one or more values.
     *
     * @return array
     */
    function getHeaders();

    /**
     * Will return true or false, depending on if a HTTP header exists.
     *
     * @param string $name
     * @return bool
     */
    function hasHeader($name);

    /**
     * Returns a specific HTTP header, based on it's name.
     *
     * The name must be treated as case-insensitive.
     * If the header does not exist, this method must return null.
     *
     * If a header appeared more than once in a HTTP request, this method will
     * concatenate all the values with a comma.
     *
     * Note that this not make sense for all headers. Some, such as
     * `Set-Cookie` cannot be logically combined with a comma. In those cases
     * you *should* use getHeaderAsArray().
     *
     * @param string $name
     * @return string|null
     */
    function getHeader($name);

    /**
     * Returns a HTTP header as an array.
     *
     * For every time the HTTP header appeared in the request or response, an
     * item will appear in the array.
     *
     * If the header did not exists, this method will return an empty array.
     *
     * @param string $name
     * @return string[]
     */
    function getHeaderAsArray($name);

    /**
     * Updates a HTTP header.
     *
     * The case-sensitity of the name value must be retained as-is.
     *
     * If the header already existed, it will be overwritten.
     *
     * @param string $name
     * @param string|string[] $value
     * @return void
     */
    function setHeader($name, $value);

    /**
     * Sets a new set of HTTP headers.
     *
     * The headers array should contain headernames for keys, and their value
     * should be specified as either a string or an array.
     *
     * Any header that already existed will be overwritten.
     *
     * @param array $headers
     * @return void
     */
    function setHeaders(array $headers);

    /**
     * Adds a HTTP header.
     *
     * This method will not overwrite any existing HTTP header, but instead add
     * another value. Individual values can be retrieved with
     * getHeadersAsArray.
     *
     * @param string $name
     * @param string $value
     * @return void
     */
    function addHeader($name, $value);

    /**
     * Adds a new set of HTTP headers.
     *
     * Any existing headers will not be overwritten.
     *
     * @param array $headers
     * @return void
     */
    function addHeaders(array $headers);

    /**
     * Removes a HTTP header.
     *
     * The specified header name must be treated as case-insenstive.
     * This method should return true if the header was successfully deleted,
     * and false if the header did not exist.
     *
     * @param string $name
     * @return bool
     */
    function removeHeader($name);

    /**
     * Sets the HTTP version.
     *
     * Should be 1.0 or 1.1.
     *
     * @param string $version
     * @return void
     */
    function setHttpVersion($version);

    /**
     * Returns the HTTP version.
     *
     * @return string
     */
    function getHttpVersion();

}
<?php

namespace Sabre\HTTP;

use InvalidArgumentException;
use Sabre\Uri;

/**
 * The Request class represents a single HTTP request.
 *
 * You can either simply construct the object from scratch, or if you need
 * access to the current HTTP request, use Sapi::getRequest.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Request extends Message implements RequestInterface {

    /**
     * HTTP Method
     *
     * @var string
     */
    protected $method;

    /**
     * Request Url
     *
     * @var string
     */
    protected $url;

    /**
     * Creates the request object
     *
     * @param string $method
     * @param string $url
     * @param array $headers
     * @param resource $body
     */
    function __construct($method = null, $url = null, array $headers = null, $body = null) {

        if (is_array($method)) {
            throw new InvalidArgumentException('The first argument for this constructor should be a string or null, not an array. Did you upgrade from sabre/http 1.0 to 2.0?');
        }
        if (!is_null($method))      $this->setMethod($method);
        if (!is_null($url))         $this->setUrl($url);
        if (!is_null($headers))     $this->setHeaders($headers);
        if (!is_null($body))        $this->setBody($body);

    }

    /**
     * Returns the current HTTP method
     *
     * @return string
     */
    function getMethod() {

        return $this->method;

    }

    /**
     * Sets the HTTP method
     *
     * @param string $method
     * @return void
     */
    function setMethod($method) {

        $this->method = $method;

    }

    /**
     * Returns the request url.
     *
     * @return string
     */
    function getUrl() {

        return $this->url;

    }

    /**
     * Sets the request url.
     *
     * @param string $url
     * @return void
     */
    function setUrl($url) {

        $this->url = $url;

    }

    /**
     * Returns the list of query parameters.
     *
     * This is equivalent to PHP's $_GET superglobal.
     *
     * @return array
     */
    function getQueryParameters() {

        $url = $this->getUrl();
        if (($index = strpos($url, '?')) === false) {
            return [];
        } else {
            parse_str(substr($url, $index + 1), $queryParams);
            return $queryParams;
        }

    }

    /**
     * Sets the absolute url.
     *
     * @param string $url
     * @return void
     */
    function setAbsoluteUrl($url) {

        $this->absoluteUrl = $url;

    }

    /**
     * Returns the absolute url.
     *
     * @return string
     */
    function getAbsoluteUrl() {

        return $this->absoluteUrl;

    }

    /**
     * Base url
     *
     * @var string
     */
    protected $baseUrl = '/';

    /**
     * Sets a base url.
     *
     * This url is used for relative path calculations.
     *
     * @param string $url
     * @return void
     */
    function setBaseUrl($url) {

        $this->baseUrl = $url;

    }

    /**
     * Returns the current base url.
     *
     * @return string
     */
    function getBaseUrl() {

        return $this->baseUrl;

    }

    /**
     * Returns the relative path.
     *
     * This is being calculated using the base url. This path will not start
     * with a slash, so it will always return something like
     * 'example/path.html'.
     *
     * If the full path is equal to the base url, this method will return an
     * empty string.
     *
     * This method will also urldecode the path, and if the url was incoded as
     * ISO-8859-1, it will convert it to UTF-8.
     *
     * If the path is outside of the base url, a LogicException will be thrown.
     *
     * @return string
     */
    function getPath() {

        // Removing duplicated slashes.
        $uri = str_replace('//', '/', $this->getUrl());

        $uri = Uri\normalize($uri);
        $baseUri = Uri\normalize($this->getBaseUrl());

        if (strpos($uri, $baseUri) === 0) {

            // We're not interested in the query part (everything after the ?).
            list($uri) = explode('?', $uri);
            return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/');

        }
        // A special case, if the baseUri was accessed without a trailing
        // slash, we'll accept it as well.
        elseif ($uri . '/' === $baseUri) {

            return '';

        }

        throw new \LogicException('Requested uri (' . $this->getUrl() . ') is out of base uri (' . $this->getBaseUrl() . ')');
    }

    /**
     * Equivalent of PHP's $_POST.
     *
     * @var array
     */
    protected $postData = [];

    /**
     * Sets the post data.
     *
     * This is equivalent to PHP's $_POST superglobal.
     *
     * This would not have been needed, if POST data was accessible as
     * php://input, but unfortunately we need to special case it.
     *
     * @param array $postData
     * @return void
     */
    function setPostData(array $postData) {

        $this->postData = $postData;

    }

    /**
     * Returns the POST data.
     *
     * This is equivalent to PHP's $_POST superglobal.
     *
     * @return array
     */
    function getPostData() {

        return $this->postData;

    }

    /**
     * An array containing the raw _SERVER array.
     *
     * @var array
     */
    protected $rawServerData;

    /**
     * Returns an item from the _SERVER array.
     *
     * If the value does not exist in the array, null is returned.
     *
     * @param string $valueName
     * @return string|null
     */
    function getRawServerValue($valueName) {

        if (isset($this->rawServerData[$valueName])) {
            return $this->rawServerData[$valueName];
        }

    }

    /**
     * Sets the _SERVER array.
     *
     * @param array $data
     * @return void
     */
    function setRawServerData(array $data) {

        $this->rawServerData = $data;

    }

    /**
     * Serializes the request object as a string.
     *
     * This is useful for debugging purposes.
     *
     * @return string
     */
    function __toString() {

        $out = $this->getMethod() . ' ' . $this->getUrl() . ' HTTP/' . $this->getHTTPVersion() . "\r\n";

        foreach ($this->getHeaders() as $key => $value) {
            foreach ($value as $v) {
                if ($key === 'Authorization') {
                    list($v) = explode(' ', $v, 2);
                    $v .= ' REDACTED';
                }
                $out .= $key . ": " . $v . "\r\n";
            }
        }
        $out .= "\r\n";
        $out .= $this->getBodyAsString();

        return $out;

    }

}
<?php

namespace Sabre\HTTP;

/**
 * Request Decorator
 *
 * This helper class allows you to easily create decorators for the Request
 * object.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class RequestDecorator implements RequestInterface {

    use MessageDecoratorTrait;

    /**
     * Constructor.
     *
     * @param RequestInterface $inner
     */
    function __construct(RequestInterface $inner) {

        $this->inner = $inner;

    }

    /**
     * Returns the current HTTP method
     *
     * @return string
     */
    function getMethod() {

        return $this->inner->getMethod();

    }

    /**
     * Sets the HTTP method
     *
     * @param string $method
     * @return void
     */
    function setMethod($method) {

        $this->inner->setMethod($method);

    }

    /**
     * Returns the request url.
     *
     * @return string
     */
    function getUrl() {

        return $this->inner->getUrl();

    }

    /**
     * Sets the request url.
     *
     * @param string $url
     * @return void
     */
    function setUrl($url) {

        $this->inner->setUrl($url);

    }

    /**
     * Returns the absolute url.
     *
     * @return string
     */
    function getAbsoluteUrl() {

        return $this->inner->getAbsoluteUrl();

    }

    /**
     * Sets the absolute url.
     *
     * @param string $url
     * @return void
     */
    function setAbsoluteUrl($url) {

        $this->inner->setAbsoluteUrl($url);

    }

    /**
     * Returns the current base url.
     *
     * @return string
     */
    function getBaseUrl() {

        return $this->inner->getBaseUrl();

    }

    /**
     * Sets a base url.
     *
     * This url is used for relative path calculations.
     *
     * The base url should default to /
     *
     * @param string $url
     * @return void
     */
    function setBaseUrl($url) {

        $this->inner->setBaseUrl($url);

    }

    /**
     * Returns the relative path.
     *
     * This is being calculated using the base url. This path will not start
     * with a slash, so it will always return something like
     * 'example/path.html'.
     *
     * If the full path is equal to the base url, this method will return an
     * empty string.
     *
     * This method will also urldecode the path, and if the url was incoded as
     * ISO-8859-1, it will convert it to UTF-8.
     *
     * If the path is outside of the base url, a LogicException will be thrown.
     *
     * @return string
     */
    function getPath() {

        return $this->inner->getPath();

    }

    /**
     * Returns the list of query parameters.
     *
     * This is equivalent to PHP's $_GET superglobal.
     *
     * @return array
     */
    function getQueryParameters() {

        return $this->inner->getQueryParameters();

    }

    /**
     * Returns the POST data.
     *
     * This is equivalent to PHP's $_POST superglobal.
     *
     * @return array
     */
    function getPostData() {

        return $this->inner->getPostData();

    }

    /**
     * Sets the post data.
     *
     * This is equivalent to PHP's $_POST superglobal.
     *
     * This would not have been needed, if POST data was accessible as
     * php://input, but unfortunately we need to special case it.
     *
     * @param array $postData
     * @return void
     */
    function setPostData(array $postData) {

        $this->inner->setPostData($postData);

    }


    /**
     * Returns an item from the _SERVER array.
     *
     * If the value does not exist in the array, null is returned.
     *
     * @param string $valueName
     * @return string|null
     */
    function getRawServerValue($valueName) {

        return $this->inner->getRawServerValue($valueName);

    }

    /**
     * Sets the _SERVER array.
     *
     * @param array $data
     * @return void
     */
    function setRawServerData(array $data) {

        $this->inner->setRawServerData($data);

    }

    /**
     * Serializes the request object as a string.
     *
     * This is useful for debugging purposes.
     *
     * @return string
     */
    function __toString() {

        return $this->inner->__toString();

    }
}
<?php

namespace Sabre\HTTP;

/**
 * The RequestInterface represents a HTTP request.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface RequestInterface extends MessageInterface {

    /**
     * Returns the current HTTP method
     *
     * @return string
     */
    function getMethod();

    /**
     * Sets the HTTP method
     *
     * @param string $method
     * @return void
     */
    function setMethod($method);

    /**
     * Returns the request url.
     *
     * @return string
     */
    function getUrl();

    /**
     * Sets the request url.
     *
     * @param string $url
     * @return void
     */
    function setUrl($url);

    /**
     * Returns the absolute url.
     *
     * @return string
     */
    function getAbsoluteUrl();

    /**
     * Sets the absolute url.
     *
     * @param string $url
     * @return void
     */
    function setAbsoluteUrl($url);

    /**
     * Returns the current base url.
     *
     * @return string
     */
    function getBaseUrl();

    /**
     * Sets a base url.
     *
     * This url is used for relative path calculations.
     *
     * The base url should default to /
     *
     * @param string $url
     * @return void
     */
    function setBaseUrl($url);

    /**
     * Returns the relative path.
     *
     * This is being calculated using the base url. This path will not start
     * with a slash, so it will always return something like
     * 'example/path.html'.
     *
     * If the full path is equal to the base url, this method will return an
     * empty string.
     *
     * This method will also urldecode the path, and if the url was incoded as
     * ISO-8859-1, it will convert it to UTF-8.
     *
     * If the path is outside of the base url, a LogicException will be thrown.
     *
     * @return string
     */
    function getPath();

    /**
     * Returns the list of query parameters.
     *
     * This is equivalent to PHP's $_GET superglobal.
     *
     * @return array
     */
    function getQueryParameters();

    /**
     * Returns the POST data.
     *
     * This is equivalent to PHP's $_POST superglobal.
     *
     * @return array
     */
    function getPostData();

    /**
     * Sets the post data.
     *
     * This is equivalent to PHP's $_POST superglobal.
     *
     * This would not have been needed, if POST data was accessible as
     * php://input, but unfortunately we need to special case it.
     *
     * @param array $postData
     * @return void
     */
    function setPostData(array $postData);

    /**
     * Returns an item from the _SERVER array.
     *
     * If the value does not exist in the array, null is returned.
     *
     * @param string $valueName
     * @return string|null
     */
    function getRawServerValue($valueName);

    /**
     * Sets the _SERVER array.
     *
     * @param array $data
     * @return void
     */
    function setRawServerData(array $data);


}
<?php

namespace Sabre\HTTP;

/**
 * This class represents a single HTTP response.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Response extends Message implements ResponseInterface {

    /**
     * This is the list of currently registered HTTP status codes.
     *
     * @var array
     */
    static $statusCodes = [
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authorative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-Status', // RFC 4918
        208 => 'Already Reported', // RFC 5842
        226 => 'IM Used', // RFC 3229
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        307 => 'Temporary Redirect',
        308 => 'Permanent Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        418 => 'I\'m a teapot', // RFC 2324
        421 => 'Misdirected Request', // RFC7540 (HTTP/2)
        422 => 'Unprocessable Entity', // RFC 4918
        423 => 'Locked', // RFC 4918
        424 => 'Failed Dependency', // RFC 4918
        426 => 'Upgrade Required',
        428 => 'Precondition Required', // RFC 6585
        429 => 'Too Many Requests', // RFC 6585
        431 => 'Request Header Fields Too Large', // RFC 6585
        451 => 'Unavailable For Legal Reasons', // draft-tbray-http-legally-restricted-status
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version not supported',
        506 => 'Variant Also Negotiates',
        507 => 'Insufficient Storage', // RFC 4918
        508 => 'Loop Detected', // RFC 5842
        509 => 'Bandwidth Limit Exceeded', // non-standard
        510 => 'Not extended',
        511 => 'Network Authentication Required', // RFC 6585
    ];

    /**
     * HTTP status code
     *
     * @var int
     */
    protected $status;

    /**
     * HTTP status text
     *
     * @var string
     */
    protected $statusText;

    /**
     * Creates the response object
     *
     * @param string|int $status
     * @param array $headers
     * @param resource $body
     */
    function __construct($status = null, array $headers = null, $body = null) {

        if (!is_null($status)) $this->setStatus($status);
        if (!is_null($headers)) $this->setHeaders($headers);
        if (!is_null($body)) $this->setBody($body);

    }


    /**
     * Returns the current HTTP status code.
     *
     * @return int
     */
    function getStatus() {

        return $this->status;

    }

    /**
     * Returns the human-readable status string.
     *
     * In the case of a 200, this may for example be 'OK'.
     *
     * @return string
     */
    function getStatusText() {

        return $this->statusText;

    }

    /**
     * Sets the HTTP status code.
     *
     * This can be either the full HTTP status code with human readable string,
     * for example: "403 I can't let you do that, Dave".
     *
     * Or just the code, in which case the appropriate default message will be
     * added.
     *
     * @param string|int $status
     * @throws \InvalidArgumentException
     * @return void
     */
    function setStatus($status) {

        if (ctype_digit($status) || is_int($status)) {

            $statusCode = $status;
            $statusText = isset(self::$statusCodes[$status]) ? self::$statusCodes[$status] : 'Unknown';

        } else {
            list(
                $statusCode,
                $statusText
            ) = explode(' ', $status, 2);
        }
        if ($statusCode < 100 || $statusCode > 999) {
            throw new \InvalidArgumentException('The HTTP status code must be exactly 3 digits');
        }

        $this->status = $statusCode;
        $this->statusText = $statusText;

    }

    /**
     * Serializes the response object as a string.
     *
     * This is useful for debugging purposes.
     *
     * @return string
     */
    function __toString() {

        $str = 'HTTP/' . $this->httpVersion . ' ' . $this->getStatus() . ' ' . $this->getStatusText() . "\r\n";
        foreach ($this->getHeaders() as $key => $value) {
            foreach ($value as $v) {
                $str .= $key . ": " . $v . "\r\n";
            }
        }
        $str .= "\r\n";
        $str .= $this->getBodyAsString();
        return $str;

    }

}
<?php

namespace Sabre\HTTP;

/**
 * Response Decorator
 *
 * This helper class allows you to easily create decorators for the Response
 * object.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ResponseDecorator implements ResponseInterface {

    use MessageDecoratorTrait;

    /**
     * Constructor.
     *
     * @param ResponseInterface $inner
     */
    function __construct(ResponseInterface $inner) {

        $this->inner = $inner;

    }

    /**
     * Returns the current HTTP status code.
     *
     * @return int
     */
    function getStatus() {

        return $this->inner->getStatus();

    }


    /**
     * Returns the human-readable status string.
     *
     * In the case of a 200, this may for example be 'OK'.
     *
     * @return string
     */
    function getStatusText() {

        return $this->inner->getStatusText();

    }
    /**
     * Sets the HTTP status code.
     *
     * This can be either the full HTTP status code with human readable string,
     * for example: "403 I can't let you do that, Dave".
     *
     * Or just the code, in which case the appropriate default message will be
     * added.
     *
     * @param string|int $status
     * @return void
     */
    function setStatus($status) {

        $this->inner->setStatus($status);

    }

    /**
     * Serializes the request object as a string.
     *
     * This is useful for debugging purposes.
     *
     * @return string
     */
    function __toString() {

        return $this->inner->__toString();

    }
}
<?php

namespace Sabre\HTTP;

/**
 * This interface represents a HTTP response.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface ResponseInterface extends MessageInterface {

    /**
     * Returns the current HTTP status code.
     *
     * @return int
     */
    function getStatus();

    /**
     * Returns the human-readable status string.
     *
     * In the case of a 200, this may for example be 'OK'.
     *
     * @return string
     */
    function getStatusText();

    /**
     * Sets the HTTP status code.
     *
     * This can be either the full HTTP status code with human readable string,
     * for example: "403 I can't let you do that, Dave".
     *
     * Or just the code, in which case the appropriate default message will be
     * added.
     *
     * @param string|int $status
     * @throws \InvalidArgumentException
     * @return void
     */
    function setStatus($status);

}
<?php

namespace Sabre\HTTP;

/**
 * PHP SAPI
 *
 * This object is responsible for:
 * 1. Constructing a Request object based on the current HTTP request sent to
 *    the PHP process.
 * 2. Sending the Response object back to the client.
 *
 * It could be said that this class provides a mapping between the Request and
 * Response objects, and php's:
 *
 * * $_SERVER
 * * $_POST
 * * $_FILES
 * * php://input
 * * echo()
 * * header()
 * * php://output
 *
 * You can choose to either call all these methods statically, but you can also
 * instantiate this as an object to allow for polymorhpism.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Sapi {

    /**
     * This static method will create a new Request object, based on the
     * current PHP request.
     *
     * @return Request
     */
    static function getRequest() {

        $r = self::createFromServerArray($_SERVER);
        $r->setBody(fopen('php://input', 'r'));
        $r->setPostData($_POST);
        return $r;

    }

    /**
     * Sends the HTTP response back to a HTTP client.
     *
     * This calls php's header() function and streams the body to php://output.
     *
     * @param ResponseInterface $response
     * @return void
     */
    static function sendResponse(ResponseInterface $response) {

        header('HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText());
        foreach ($response->getHeaders() as $key => $value) {

            foreach ($value as $k => $v) {
                if ($k === 0) {
                    header($key . ': ' . $v);
                } else {
                    header($key . ': ' . $v, false);
                }
            }

        }

        $body = $response->getBody();
        if (is_null($body)) return;

        $contentLength = $response->getHeader('Content-Length');
        if ($contentLength !== null) {
            $output = fopen('php://output', 'wb');
            if (is_resource($body) && get_resource_type($body) == 'stream') {
                stream_copy_to_stream($body, $output, $contentLength);
            } else {
                fwrite($output, $body, $contentLength);
            }
        } else {
            file_put_contents('php://output', $body);
        }

        if (is_resource($body)) {
            fclose($body);
        }

    }

    /**
     * This static method will create a new Request object, based on a PHP
     * $_SERVER array.
     *
     * @param array $serverArray
     * @return Request
     */
    static function createFromServerArray(array $serverArray) {

        $headers = [];
        $method = null;
        $url = null;
        $httpVersion = '1.1';

        $protocol = 'http';
        $hostName = 'localhost';

        foreach ($serverArray as $key => $value) {

            switch ($key) {

                case 'SERVER_PROTOCOL' :
                    if ($value === 'HTTP/1.0') {
                        $httpVersion = '1.0';
                    }
                    break;
                case 'REQUEST_METHOD' :
                    $method = $value;
                    break;
                case 'REQUEST_URI' :
                    $url = $value;
                    break;

                // These sometimes show up without a HTTP_ prefix
                case 'CONTENT_TYPE' :
                    $headers['Content-Type'] = $value;
                    break;
                case 'CONTENT_LENGTH' :
                    $headers['Content-Length'] = $value;
                    break;

                // mod_php on apache will put credentials in these variables.
                // (fast)cgi does not usually do this, however.
                case 'PHP_AUTH_USER' :
                    if (isset($serverArray['PHP_AUTH_PW'])) {
                        $headers['Authorization'] = 'Basic ' . base64_encode($value . ':' . $serverArray['PHP_AUTH_PW']);
                    }
                    break;

                // Similarly, mod_php may also screw around with digest auth.
                case 'PHP_AUTH_DIGEST' :
                    $headers['Authorization'] = 'Digest ' . $value;
                    break;

                // Apache may prefix the HTTP_AUTHORIZATION header with
                // REDIRECT_, if mod_rewrite was used.
                case 'REDIRECT_HTTP_AUTHORIZATION' :
                    $headers['Authorization'] = $value;
                    break;

                case 'HTTP_HOST' :
                    $hostName = $value;
                    $headers['Host'] = $value;
                    break;

                case 'HTTPS' :
                    if (!empty($value) && $value !== 'off') {
                        $protocol = 'https';
                    }
                    break;

                default :
                    if (substr($key, 0, 5) === 'HTTP_') {
                        // It's a HTTP header

                        // Normalizing it to be prettier
                        $header = strtolower(substr($key, 5));

                        // Transforming dashes into spaces, and uppercasing
                        // every first letter.
                        $header = ucwords(str_replace('_', ' ', $header));

                        // Turning spaces into dashes.
                        $header = str_replace(' ', '-', $header);
                        $headers[$header] = $value;

                    }
                    break;


            }

        }

        $r = new Request($method, $url, $headers);
        $r->setHttpVersion($httpVersion);
        $r->setRawServerData($serverArray);
        $r->setAbsoluteUrl($protocol . '://' . $hostName . $url);
        return $r;

    }

}
<?php

namespace Sabre\HTTP;

use Sabre\URI;

/**
 * URL utility class
 *
 * Note: this class is deprecated. All its functionality moved to functions.php
 * or sabre\uri.
 *
 * @deprecated
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class URLUtil {

    /**
     * Encodes the path of a url.
     *
     * slashes (/) are treated as path-separators.
     *
     * @deprecated use \Sabre\HTTP\encodePath()
     * @param string $path
     * @return string
     */
    static function encodePath($path) {

        return encodePath($path);

    }

    /**
     * Encodes a 1 segment of a path
     *
     * Slashes are considered part of the name, and are encoded as %2f
     *
     * @deprecated use \Sabre\HTTP\encodePathSegment()
     * @param string $pathSegment
     * @return string
     */
    static function encodePathSegment($pathSegment) {

        return encodePathSegment($pathSegment);

    }

    /**
     * Decodes a url-encoded path
     *
     * @deprecated use \Sabre\HTTP\decodePath
     * @param string $path
     * @return string
     */
    static function decodePath($path) {

        return decodePath($path);

    }

    /**
     * Decodes a url-encoded path segment
     *
     * @deprecated use \Sabre\HTTP\decodePathSegment()
     * @param string $path
     * @return string
     */
    static function decodePathSegment($path) {

        return decodePathSegment($path);

    }

    /**
     * Returns the 'dirname' and 'basename' for a path.
     *
     * @deprecated Use Sabre\Uri\split().
     * @param string $path
     * @return array
     */
    static function splitPath($path) {

        return Uri\split($path);

    }

    /**
     * Resolves relative urls, like a browser would.
     *
     * @deprecated Use Sabre\Uri\resolve().
     * @param string $basePath
     * @param string $newPath
     * @return string
     */
    static function resolve($basePath, $newPath) {

        return Uri\resolve($basePath, $newPath);

    }

}
<?php

namespace Sabre\HTTP;

/**
 * HTTP utility methods
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @author Paul Voegler
 * @deprecated All these functions moved to functions.php
 * @license http://sabre.io/license/ Modified BSD License
 */
class Util {

    /**
     * Content negotiation
     *
     * @deprecated Use \Sabre\HTTP\negotiateContentType
     * @param string|null $acceptHeaderValue
     * @param array $availableOptions
     * @return string|null
     */
    static function negotiateContentType($acceptHeaderValue, array $availableOptions) {

        return negotiateContentType($acceptHeaderValue, $availableOptions);

    }

    /**
     * Deprecated! Use negotiateContentType.
     *
     * @deprecated Use \Sabre\HTTP\NegotiateContentType
     * @param string|null $acceptHeaderValue
     * @param array $availableOptions
     * @return string|null
     */
    static function negotiate($acceptHeaderValue, array $availableOptions) {

        return negotiateContentType($acceptHeaderValue, $availableOptions);

    }

    /**
     * Parses a RFC2616-compatible date string
     *
     * This method returns false if the date is invalid
     *
     * @deprecated Use parseDate
     * @param string $dateHeader
     * @return bool|DateTime
     */
    static function parseHTTPDate($dateHeader) {

        return parseDate($dateHeader);

    }

    /**
     * Transforms a DateTime object to HTTP's most common date format.
     *
     * We're serializing it as the RFC 1123 date, which, for HTTP must be
     * specified as GMT.
     *
     * @deprecated Use toDate
     * @param \DateTime $dateTime
     * @return string
     */
    static function toHTTPDate(\DateTime $dateTime) {

        return toDate($dateTime);

    }
}
<?php

namespace Sabre\HTTP;

/**
 * This class contains the version number for the HTTP package
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Version {

    /**
     * Full version number
     */
    const VERSION = '4.2.2';

}
Copyright (C) 2009-2017 fruux GmbH (https://fruux.com/)

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * 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.
    * Neither the name Sabre 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
sabre/http
==========

This library provides a toolkit to make working with the HTTP protocol easier.

Most PHP scripts run within a HTTP request but accessing information about the
HTTP request is cumbersome at least.

There's bad practices, inconsistencies and confusion. This library is
effectively a wrapper around the following PHP constructs:

For Input:

* `$_GET`,
* `$_POST`,
* `$_SERVER`,
* `php://input` or `$HTTP_RAW_POST_DATA`.

For output:

* `php://output` or `echo`,
* `header()`.

What this library provides, is a `Request` object, and a `Response` object.

The objects are extendable and easily mockable.

Build status
------------

| branch | status |
| ------ | ------ |
| master | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=master)](https://travis-ci.org/fruux/sabre-http) |
| 3.0    | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-http) |

Installation
------------

Make sure you have [composer][1] installed. In your project directory, create,
or edit a `composer.json` file, and make sure it contains something like this:

```json
{
    "require" : {
        "sabre/http" : "~3.0.0"
    }
}
```

After that, just hit `composer install` and you should be rolling.

Quick history
-------------

This library came to existence in 2009, as a part of the [`sabre/dav`][2]
project, which uses it heavily.

It got split off into a separate library to make it easier to manage
releases and hopefully giving it use outside of the scope of just `sabre/dav`.

Although completely independently developed, this library has a LOT of
overlap with [Symfony's `HttpFoundation`][3].

Said library does a lot more stuff and is significantly more popular,
so if you are looking for something to fulfill this particular requirement,
I'd recommend also considering [`HttpFoundation`][3].


Getting started
---------------

First and foremost, this library wraps the superglobals. The easiest way to
instantiate a request object is as follows:

```php
use Sabre\HTTP;

include 'vendor/autoload.php';

$request = HTTP\Sapi::getRequest();
```

This line should only happen once in your entire application. Everywhere else
you should pass this request object around using dependency injection.

You should always typehint on it's interface:

```php
function handleRequest(HTTP\RequestInterface $request) {

    // Do something with this request :)

}
```

A response object you can just create as such:

```php
use Sabre\HTTP;

include 'vendor/autoload.php';

$response = new HTTP\Response();
$response->setStatus(201); // created !
$response->setHeader('X-Foo', 'bar');
$response->setBody(
    'success!'
);

```

After you fully constructed your response, you must call:

```php
HTTP\Sapi::sendResponse($response);
```

This line should generally also appear once in your application (at the very
end).

Decorators
----------

It may be useful to extend the `Request` and `Response` objects in your
application, if you for example would like them to carry a bit more
information about the current request.

For instance, you may want to add an `isLoggedIn` method to the Request
object.

Simply extending Request and Response may pose some problems:

1. You may want to extend the objects with new behaviors differently, in
   different subsystems of your application,
2. The `Sapi::getRequest` factory always returns a instance of
   `Request` so you would have to override the factory method as well,
3. By controlling the instantation and depend on specific `Request` and
   `Response` instances in your library or application, you make it harder to
   work with other applications which also use `sabre/http`.

In short: it would be bad design. Instead, it's recommended to use the
[decorator pattern][6] to add new behavior where you need it. `sabre/http`
provides helper classes to quickly do this.

Example:

```php
use Sabre\HTTP;

class MyRequest extends HTTP\RequestDecorator {

    function isLoggedIn() {

        return true;

    }

}
```

Our application assumes that the true `Request` object was instantiated
somewhere else, by some other subsystem. This could simply be a call like
`$request = Sapi::getRequest()` at the top of your application,
but could also be somewhere in a unittest.

All we know in the current subsystem, is that we received a `$request` and
that it implements `Sabre\HTTP\RequestInterface`. To decorate this object,
all we need to do is:

```php
$request = new MyRequest($request);
```

And that's it, we now have an `isLoggedIn` method, without having to mess
with the core instances.


Client
------

This package also contains a simple wrapper around [cURL][4], which will allow
you to write simple clients, using the `Request` and `Response` objects you're
already familiar with.

It's by no means a replacement for something like [Guzzle][7], but it provides
a simple and lightweight API for making the occasional API call.

### Usage

```php
use Sabre\HTTP;

$request = new HTTP\Request('GET', 'http://example.org/');
$request->setHeader('X-Foo', 'Bar');

$client = new HTTP\Client();
$response = $client->send($request);

echo $response->getBodyAsString();
```

The client emits 3 event using [`sabre/event`][5]. `beforeRequest`,
`afterRequest` and `error`.

```php
$client = new HTTP\Client();
$client->on('beforeRequest', function($request) {

    // You could use beforeRequest to for example inject a few extra headers.
    // into the Request object.

});

$client->on('afterRequest', function($request, $response) {

    // The afterRequest event could be a good time to do some logging, or
    // do some rewriting in the response.

});

$client->on('error', function($request, $response, &$retry, $retryCount) {

    // The error event is triggered for every response with a HTTP code higher
    // than 399.

});

$client->on('error:401', function($request, $response, &$retry, $retryCount) {

    // You can also listen for specific error codes. This example shows how
    // to inject HTTP authentication headers if a 401 was returned.

    if ($retryCount > 1) {
        // We're only going to retry exactly once.
    }

    $request->setHeader('Authorization', 'Basic xxxxxxxxxx');
    $retry = true;

});
```

### Asynchronous requests

The `Client` also supports doing asynchronous requests. This is especially handy
if you need to perform a number of requests, that are allowed to be executed
in parallel.

The underlying system for this is simply [cURL's multi request handler][8],
but this provides a much nicer API to handle this.

Sample usage:

```php

use Sabre\HTTP;

$request = new Request('GET', 'http://localhost/');
$client = new Client();

// Executing 1000 requests
for ($i = 0; $i < 1000; $i++) {
    $client->sendAsync(
        $request,
        function(ResponseInterface $response) {
            // Success handler
        },
        function($error) {
            // Error handler
        }
    ); 
}

// Wait for all requests to get a result.
$client->wait();

```

Check out `examples/asyncclient.php` for more information.

Writing a reverse proxy
-----------------------

With all these tools combined, it becomes very easy to write a simple reverse
http proxy.

```php
use
    Sabre\HTTP\Sapi,
    Sabre\HTTP\Client;

// The url we're proxying to.
$remoteUrl = 'http://example.org/';

// The url we're proxying from. Please note that this must be a relative url,
// and basically acts as the base url.
//
// If youre $remoteUrl doesn't end with a slash, this one probably shouldn't
// either.
$myBaseUrl = '/reverseproxy.php';
// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/';

$request = Sapi::getRequest();
$request->setBaseUrl($myBaseUrl);

$subRequest = clone $request;

// Removing the Host header.
$subRequest->removeHeader('Host');

// Rewriting the url.
$subRequest->setUrl($remoteUrl . $request->getPath());

$client = new Client();

// Sends the HTTP request to the server
$response = $client->send($subRequest);

// Sends the response back to the client that connected to the proxy.
Sapi::sendResponse($response);
```

The Request and Response API's
------------------------------

### Request

```php

/**
 * Creates the request object
 *
 * @param string $method
 * @param string $url
 * @param array $headers
 * @param resource $body
 */
public function __construct($method = null, $url = null, array $headers = null, $body = null);

/**
 * Returns the current HTTP method
 *
 * @return string
 */
function getMethod();

/**
 * Sets the HTTP method
 *
 * @param string $method
 * @return void
 */
function setMethod($method);

/**
 * Returns the request url.
 *
 * @return string
 */
function getUrl();

/**
 * Sets the request url.
 *
 * @param string $url
 * @return void
 */
function setUrl($url);

/**
 * Returns the absolute url.
 *
 * @return string
 */
function getAbsoluteUrl();

/**
 * Sets the absolute url.
 *
 * @param string $url
 * @return void
 */
function setAbsoluteUrl($url);

/**
 * Returns the current base url.
 *
 * @return string
 */
function getBaseUrl();

/**
 * Sets a base url.
 *
 * This url is used for relative path calculations.
 *
 * The base url should default to /
 *
 * @param string $url
 * @return void
 */
function setBaseUrl($url);

/**
 * Returns the relative path.
 *
 * This is being calculated using the base url. This path will not start
 * with a slash, so it will always return something like
 * 'example/path.html'.
 *
 * If the full path is equal to the base url, this method will return an
 * empty string.
 *
 * This method will also urldecode the path, and if the url was incoded as
 * ISO-8859-1, it will convert it to UTF-8.
 *
 * If the path is outside of the base url, a LogicException will be thrown.
 *
 * @return string
 */
function getPath();

/**
 * Returns the list of query parameters.
 *
 * This is equivalent to PHP's $_GET superglobal.
 *
 * @return array
 */
function getQueryParameters();

/**
 * Returns the POST data.
 *
 * This is equivalent to PHP's $_POST superglobal.
 *
 * @return array
 */
function getPostData();

/**
 * Sets the post data.
 *
 * This is equivalent to PHP's $_POST superglobal.
 *
 * This would not have been needed, if POST data was accessible as
 * php://input, but unfortunately we need to special case it.
 *
 * @param array $postData
 * @return void
 */
function setPostData(array $postData);

/**
 * Returns an item from the _SERVER array.
 *
 * If the value does not exist in the array, null is returned.
 *
 * @param string $valueName
 * @return string|null
 */
function getRawServerValue($valueName);

/**
 * Sets the _SERVER array.
 *
 * @param array $data
 * @return void
 */
function setRawServerData(array $data);

/**
 * Returns the body as a readable stream resource.
 *
 * Note that the stream may not be rewindable, and therefore may only be
 * read once.
 *
 * @return resource
 */
function getBodyAsStream();

/**
 * Returns the body as a string.
 *
 * Note that because the underlying data may be based on a stream, this
 * method could only work correctly the first time.
 *
 * @return string
 */
function getBodyAsString();

/**
 * Returns the message body, as it's internal representation.
 *
 * This could be either a string or a stream.
 *
 * @return resource|string
 */
function getBody();

/**
 * Updates the body resource with a new stream.
 *
 * @param resource $body
 * @return void
 */
function setBody($body);

/**
 * Returns all the HTTP headers as an array.
 *
 * @return array
 */
function getHeaders();

/**
 * Returns a specific HTTP header, based on it's name.
 *
 * The name must be treated as case-insensitive.
 *
 * If the header does not exist, this method must return null.
 *
 * @param string $name
 * @return string|null
 */
function getHeader($name);

/**
 * Updates a HTTP header.
 *
 * The case-sensitity of the name value must be retained as-is.
 *
 * @param string $name
 * @param string $value
 * @return void
 */
function setHeader($name, $value);

/**
 * Resets HTTP headers
 *
 * This method overwrites all existing HTTP headers
 *
 * @param array $headers
 * @return void
 */
function setHeaders(array $headers);

/**
 * Adds a new set of HTTP headers.
 *
 * Any header specified in the array that already exists will be
 * overwritten, but any other existing headers will be retained.
 *
 * @param array $headers
 * @return void
 */
function addHeaders(array $headers);

/**
 * Removes a HTTP header.
 *
 * The specified header name must be treated as case-insenstive.
 * This method should return true if the header was successfully deleted,
 * and false if the header did not exist.
 *
 * @return bool
 */
function removeHeader($name);

/**
 * Sets the HTTP version.
 *
 * Should be 1.0 or 1.1.
 *
 * @param string $version
 * @return void
 */
function setHttpVersion($version);

/**
 * Returns the HTTP version.
 *
 * @return string
 */
function getHttpVersion();
```

### Response

```php
/**
 * Returns the current HTTP status.
 *
 * This is the status-code as well as the human readable string.
 *
 * @return string
 */
function getStatus();

/**
 * Sets the HTTP status code.
 *
 * This can be either the full HTTP status code with human readable string,
 * for example: "403 I can't let you do that, Dave".
 *
 * Or just the code, in which case the appropriate default message will be
 * added.
 *
 * @param string|int $status
 * @throws \InvalidArgumentExeption
 * @return void
 */
function setStatus($status);

/**
 * Returns the body as a readable stream resource.
 *
 * Note that the stream may not be rewindable, and therefore may only be
 * read once.
 *
 * @return resource
 */
function getBodyAsStream();

/**
 * Returns the body as a string.
 *
 * Note that because the underlying data may be based on a stream, this
 * method could only work correctly the first time.
 *
 * @return string
 */
function getBodyAsString();

/**
 * Returns the message body, as it's internal representation.
 *
 * This could be either a string or a stream.
 *
 * @return resource|string
 */
function getBody();


/**
 * Updates the body resource with a new stream.
 *
 * @param resource $body
 * @return void
 */
function setBody($body);

/**
 * Returns all the HTTP headers as an array.
 *
 * @return array
 */
function getHeaders();

/**
 * Returns a specific HTTP header, based on it's name.
 *
 * The name must be treated as case-insensitive.
 *
 * If the header does not exist, this method must return null.
 *
 * @param string $name
 * @return string|null
 */
function getHeader($name);

/**
 * Updates a HTTP header.
 *
 * The case-sensitity of the name value must be retained as-is.
 *
 * @param string $name
 * @param string $value
 * @return void
 */
function setHeader($name, $value);

/**
 * Resets HTTP headers
 *
 * This method overwrites all existing HTTP headers
 *
 * @param array $headers
 * @return void
 */
function setHeaders(array $headers);

/**
 * Adds a new set of HTTP headers.
 *
 * Any header specified in the array that already exists will be
 * overwritten, but any other existing headers will be retained.
 *
 * @param array $headers
 * @return void
 */
function addHeaders(array $headers);

/**
 * Removes a HTTP header.
 *
 * The specified header name must be treated as case-insenstive.
 * This method should return true if the header was successfully deleted,
 * and false if the header did not exist.
 *
 * @return bool
 */
function removeHeader($name);

/**
 * Sets the HTTP version.
 *
 * Should be 1.0 or 1.1.
 *
 * @param string $version
 * @return void
 */
function setHttpVersion($version);

/**
 * Returns the HTTP version.
 *
 * @return string
 */
function getHttpVersion();
```

Made at fruux
-------------

This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.

[1]: http://getcomposer.org/
[2]: http://sabre.io/
[3]: https://github.com/symfony/HttpFoundation
[4]: http://php.net/curl
[5]: https://github.com/fruux/sabre-event
[6]: http://en.wikipedia.org/wiki/Decorator_pattern
[7]: http://guzzlephp.org/
[8]: http://php.net/curl_multi_init
<?php

date_default_timezone_set('UTC');

ini_set('error_reporting', E_ALL | E_STRICT | E_DEPRECATED);

// Composer autoloader
include __DIR__ . '/../vendor/autoload.php';
<?php

namespace Sabre\HTTP\Auth;

use Sabre\HTTP\Request;
use Sabre\HTTP\Response;

class AWSTest extends \PHPUnit_Framework_TestCase {

    /**
     * @var Sabre\HTTP\Response
     */
    private $response;

    /**
     * @var Sabre\HTTP\Request
     */
    private $request;

    /**
     * @var Sabre\HTTP\Auth\AWS
     */
    private $auth;

    const REALM = 'SabreDAV unittest';

    function setUp() {

        $this->response = new Response();
        $this->request = new Request();
        $this->auth = new AWS(self::REALM, $this->request, $this->response);

    }

    function testNoHeader() {

        $this->request->setMethod('GET');
        $result = $this->auth->init();

        $this->assertFalse($result, 'No AWS Authorization header was supplied, so we should have gotten false');
        $this->assertEquals(AWS::ERR_NOAWSHEADER, $this->auth->errorCode);

    }

    function testIncorrectContentMD5() {

        $accessKey = 'accessKey';
        $secretKey = 'secretKey';

        $this->request->setMethod('GET');
        $this->request->setHeaders([
            'Authorization' => "AWS $accessKey:sig",
            'Content-MD5'   => 'garbage',
        ]);
        $this->request->setUrl('/');

        $this->auth->init();
        $result = $this->auth->validate($secretKey);

        $this->assertFalse($result);
        $this->assertEquals(AWS::ERR_MD5CHECKSUMWRONG, $this->auth->errorCode);

    }

    function testNoDate() {

        $accessKey = 'accessKey';
        $secretKey = 'secretKey';
        $content = 'thisisthebody';
        $contentMD5 = base64_encode(md5($content, true));

        $this->request->setMethod('POST');
        $this->request->setHeaders([
            'Authorization' => "AWS $accessKey:sig",
            'Content-MD5'   => $contentMD5,
        ]);
        $this->request->setUrl('/');
        $this->request->setBody($content);

        $this->auth->init();
        $result = $this->auth->validate($secretKey);

        $this->assertFalse($result);
        $this->assertEquals(AWS::ERR_INVALIDDATEFORMAT, $this->auth->errorCode);

    }

    function testFutureDate() {

        $accessKey = 'accessKey';
        $secretKey = 'secretKey';
        $content = 'thisisthebody';
        $contentMD5 = base64_encode(md5($content, true));

        $date = new \DateTime('@' . (time() + (60 * 20)));
        $date->setTimeZone(new \DateTimeZone('GMT'));
        $date = $date->format('D, d M Y H:i:s \\G\\M\\T');

        $this->request->setMethod('POST');
        $this->request->setHeaders([
            'Authorization' => "AWS $accessKey:sig",
            'Content-MD5'   => $contentMD5,
            'Date'          => $date,
        ]);

        $this->request->setBody($content);

        $this->auth->init();
        $result = $this->auth->validate($secretKey);

        $this->assertFalse($result);
        $this->assertEquals(AWS::ERR_REQUESTTIMESKEWED, $this->auth->errorCode);

    }

    function testPastDate() {

        $accessKey = 'accessKey';
        $secretKey = 'secretKey';
        $content = 'thisisthebody';
        $contentMD5 = base64_encode(md5($content, true));

        $date = new \DateTime('@' . (time() - (60 * 20)));
        $date->setTimeZone(new \DateTimeZone('GMT'));
        $date = $date->format('D, d M Y H:i:s \\G\\M\\T');

        $this->request->setMethod('POST');
        $this->request->setHeaders([
            'Authorization' => "AWS $accessKey:sig",
            'Content-MD5'   => $contentMD5,
            'Date'          => $date,
        ]);

        $this->request->setBody($content);

        $this->auth->init();
        $result = $this->auth->validate($secretKey);

        $this->assertFalse($result);
        $this->assertEquals(AWS::ERR_REQUESTTIMESKEWED, $this->auth->errorCode);

    }

    function testIncorrectSignature() {

        $accessKey = 'accessKey';
        $secretKey = 'secretKey';
        $content = 'thisisthebody';

        $contentMD5 = base64_encode(md5($content, true));

        $date = new \DateTime('now');
        $date->setTimeZone(new \DateTimeZone('GMT'));
        $date = $date->format('D, d M Y H:i:s \\G\\M\\T');

        $this->request->setUrl('/');
        $this->request->setMethod('POST');
        $this->request->setHeaders([
            'Authorization' => "AWS $accessKey:sig",
            'Content-MD5'   => $contentMD5,
            'X-amz-date'    => $date,
        ]);
        $this->request->setBody($content);

        $this->auth->init();
        $result = $this->auth->validate($secretKey);

        $this->assertFalse($result);
        $this->assertEquals(AWS::ERR_INVALIDSIGNATURE, $this->auth->errorCode);

    }

    function testValidRequest() {

        $accessKey = 'accessKey';
        $secretKey = 'secretKey';
        $content = 'thisisthebody';
        $contentMD5 = base64_encode(md5($content, true));

        $date = new \DateTime('now');
        $date->setTimeZone(new \DateTimeZone('GMT'));
        $date = $date->format('D, d M Y H:i:s \\G\\M\\T');


        $sig = base64_encode($this->hmacsha1($secretKey,
            "POST\n$contentMD5\n\n$date\nx-amz-date:$date\n/evert"
        ));

        $this->request->setUrl('/evert');
        $this->request->setMethod('POST');
        $this->request->setHeaders([
            'Authorization' => "AWS $accessKey:$sig",
            'Content-MD5'   => $contentMD5,
            'X-amz-date'    => $date,
        ]);

        $this->request->setBody($content);

        $this->auth->init();
        $result = $this->auth->validate($secretKey);

        $this->assertTrue($result, 'Signature did not validate, got errorcode ' . $this->auth->errorCode);
        $this->assertEquals($accessKey, $this->auth->getAccessKey());

    }

    function test401() {

        $this->auth->requireLogin();
        $test = preg_match('/^AWS$/', $this->response->getHeader('WWW-Authenticate'), $matches);
        $this->assertTrue($test == true, 'The WWW-Authenticate response didn\'t match our pattern');

    }

    /**
     * Generates an HMAC-SHA1 signature
     *
     * @param string $key
     * @param string $message
     * @return string
     */
    private function hmacsha1($key, $message) {

        $blocksize = 64;
        if (strlen($key) > $blocksize)
            $key = pack('H*', sha1($key));
        $key = str_pad($key, $blocksize, chr(0x00));
        $ipad = str_repeat(chr(0x36), $blocksize);
        $opad = str_repeat(chr(0x5c), $blocksize);
        $hmac = pack('H*', sha1(($key ^ $opad) . pack('H*', sha1(($key ^ $ipad) . $message))));
        return $hmac;

    }

}
<?php

namespace Sabre\HTTP\Auth;

use Sabre\HTTP\Request;
use Sabre\HTTP\Response;

class BasicTest extends \PHPUnit_Framework_TestCase {

    function testGetCredentials() {

        $request = new Request('GET', '/', [
            'Authorization' => 'Basic ' . base64_encode('user:pass:bla')
        ]);

        $basic = new Basic('Dagger', $request, new Response());

        $this->assertEquals([
            'user',
            'pass:bla',
        ], $basic->getCredentials());

    }

    function testGetInvalidCredentialsColonMissing() {

        $request = new Request('GET', '/', [
            'Authorization' => 'Basic ' . base64_encode('userpass')
        ]);

        $basic = new Basic('Dagger', $request, new Response());

        $this->assertNull($basic->getCredentials());

    }

    function testGetCredentialsNoheader() {

        $request = new Request('GET', '/', []);
        $basic = new Basic('Dagger', $request, new Response());

        $this->assertNull($basic->getCredentials());

    }

    function testGetCredentialsNotBasic() {

        $request = new Request('GET', '/', [
            'Authorization' => 'QBasic ' . base64_encode('user:pass:bla')
        ]);
        $basic = new Basic('Dagger', $request, new Response());

        $this->assertNull($basic->getCredentials());

    }

    function testRequireLogin() {

        $response = new Response();
        $basic = new Basic('Dagger', new Request(), $response);

        $basic->requireLogin();

        $this->assertEquals('Basic realm="Dagger"', $response->getHeader('WWW-Authenticate'));
        $this->assertEquals(401, $response->getStatus());

    }

}
<?php

namespace Sabre\HTTP\Auth;

use Sabre\HTTP\Request;
use Sabre\HTTP\Response;

class BearerTest extends \PHPUnit_Framework_TestCase {

    function testGetToken() {

        $request = new Request('GET', '/', [
            'Authorization' => 'Bearer 12345'
        ]);

        $bearer = new Bearer('Dagger', $request, new Response());

        $this->assertEquals(
            '12345',
            $bearer->getToken()
        );

    }

    function testGetCredentialsNoheader() {

        $request = new Request('GET', '/', []);
        $bearer = new Bearer('Dagger', $request, new Response());

        $this->assertNull($bearer->getToken());

    }

    function testGetCredentialsNotBearer() {

        $request = new Request('GET', '/', [
            'Authorization' => 'QBearer 12345'
        ]);
        $bearer = new Bearer('Dagger', $request, new Response());

        $this->assertNull($bearer->getToken());

    }

    function testRequireLogin() {

        $response = new Response();
        $bearer = new Bearer('Dagger', new Request(), $response);

        $bearer->requireLogin();

        $this->assertEquals('Bearer realm="Dagger"', $response->getHeader('WWW-Authenticate'));
        $this->assertEquals(401, $response->getStatus());

    }

}
<?php

namespace Sabre\HTTP\Auth;

use Sabre\HTTP\Request;
use Sabre\HTTP\Response;

class DigestTest extends \PHPUnit_Framework_TestCase {

    /**
     * @var Sabre\HTTP\Response
     */
    private $response;

    /**
     * request
     *
     * @var Sabre\HTTP\Request
     */
    private $request;

    /**
     * @var Sabre\HTTP\Auth\Digest
     */
    private $auth;

    const REALM = 'SabreDAV unittest';

    function setUp() {

        $this->response = new Response();
        $this->request = new Request();
        $this->auth = new Digest(self::REALM, $this->request, $this->response);


    }

    function testDigest() {

        list($nonce, $opaque) = $this->getServerTokens();

        $username = 'admin';
        $password = 12345;
        $nc = '00002';
        $cnonce = uniqid();

        $digestHash = md5(
            md5($username . ':' . self::REALM . ':' . $password) . ':' .
            $nonce . ':' .
            $nc . ':' .
            $cnonce . ':' .
            'auth:' .
            md5('GET' . ':' . '/')
        );

        $this->request->setMethod('GET');
        $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth,nc=' . $nc . ',cnonce="' . $cnonce . '"');

        $this->auth->init();

        $this->assertEquals($username, $this->auth->getUsername());
        $this->assertEquals(self::REALM, $this->auth->getRealm());
        $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1');
        $this->assertTrue($this->auth->validatePassword($password), 'Authentication is deemed invalid through validatePassword');

    }

    function testInvalidDigest() {

        list($nonce, $opaque) = $this->getServerTokens();

        $username = 'admin';
        $password = 12345;
        $nc = '00002';
        $cnonce = uniqid();

        $digestHash = md5(
            md5($username . ':' . self::REALM . ':' . $password) . ':' .
            $nonce . ':' .
            $nc . ':' .
            $cnonce . ':' .
            'auth:' .
            md5('GET' . ':' . '/')
        );

        $this->request->setMethod('GET');
        $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth,nc=' . $nc . ',cnonce="' . $cnonce . '"');

        $this->auth->init();

        $this->assertFalse($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . ($password . 'randomness'))), 'Authentication is deemed invalid through validateA1');

    }

    function testInvalidDigest2() {

        $this->request->setMethod('GET');
        $this->request->setHeader('Authorization', 'basic blablabla');

        $this->auth->init();
        $this->assertFalse($this->auth->validateA1(md5('user:realm:password')));

    }


    function testDigestAuthInt() {

        $this->auth->setQOP(Digest::QOP_AUTHINT);
        list($nonce, $opaque) = $this->getServerTokens(Digest::QOP_AUTHINT);

        $username = 'admin';
        $password = 12345;
        $nc = '00003';
        $cnonce = uniqid();

        $digestHash = md5(
            md5($username . ':' . self::REALM . ':' . $password) . ':' .
            $nonce . ':' .
            $nc . ':' .
            $cnonce . ':' .
            'auth-int:' .
            md5('POST' . ':' . '/' . ':' . md5('body'))
        );

        $this->request->setMethod('POST');
        $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc=' . $nc . ',cnonce="' . $cnonce . '"');
        $this->request->setBody('body');

        $this->auth->init();

        $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1');

    }

    function testDigestAuthBoth() {

        $this->auth->setQOP(Digest::QOP_AUTHINT | Digest::QOP_AUTH);
        list($nonce, $opaque) = $this->getServerTokens(Digest::QOP_AUTHINT | Digest::QOP_AUTH);

        $username = 'admin';
        $password = 12345;
        $nc = '00003';
        $cnonce = uniqid();

        $digestHash = md5(
            md5($username . ':' . self::REALM . ':' . $password) . ':' .
            $nonce . ':' .
            $nc . ':' .
            $cnonce . ':' .
            'auth-int:' .
            md5('POST' . ':' . '/' . ':' . md5('body'))
        );

        $this->request->setMethod('POST');
        $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc=' . $nc . ',cnonce="' . $cnonce . '"');
        $this->request->setBody('body');

        $this->auth->init();

        $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1');

    }


    private function getServerTokens($qop = Digest::QOP_AUTH) {

        $this->auth->requireLogin();

        switch ($qop) {
            case Digest::QOP_AUTH    : $qopstr = 'auth'; break;
            case Digest::QOP_AUTHINT : $qopstr = 'auth-int'; break;
            default                  : $qopstr = 'auth,auth-int'; break;
        }

        $test = preg_match('/Digest realm="' . self::REALM . '",qop="' . $qopstr . '",nonce="([0-9a-f]*)",opaque="([0-9a-f]*)"/',
            $this->response->getHeader('WWW-Authenticate'), $matches);

        $this->assertTrue($test == true, 'The WWW-Authenticate response didn\'t match our pattern. We received: ' . $this->response->getHeader('WWW-Authenticate'));

        $nonce = $matches[1];
        $opaque = $matches[2];

        // Reset our environment
        $this->setUp();
        $this->auth->setQOP($qop);

        return [$nonce,$opaque];

    }

}
<?php

namespace Sabre\HTTP;

class ClientTest extends \PHPUnit_Framework_TestCase {

    function testCreateCurlSettingsArrayGET() {

        $client = new ClientMock();
        $client->addCurlSetting(CURLOPT_POSTREDIR, 0);

        $request = new Request('GET', 'http://example.org/', ['X-Foo' => 'bar']);

        $settings = [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HEADER         => true,
                CURLOPT_POSTREDIR      => 0,
                CURLOPT_HTTPHEADER     => ['X-Foo: bar'],
                CURLOPT_NOBODY         => false,
                CURLOPT_URL            => 'http://example.org/',
                CURLOPT_CUSTOMREQUEST  => 'GET',
                CURLOPT_POSTFIELDS     => '',
                CURLOPT_PUT            => false,
                CURLOPT_USERAGENT      => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
            ];

        // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM
        // at least if this unit test fails in the future we know it is :)
        if (defined('HHVM_VERSION') === false) {
            $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
            $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }


        $this->assertEquals($settings, $client->createCurlSettingsArray($request));

    }

    function testCreateCurlSettingsArrayHEAD() {

        $client = new ClientMock();
        $request = new Request('HEAD', 'http://example.org/', ['X-Foo' => 'bar']);


        $settings = [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HEADER         => true,
                CURLOPT_NOBODY         => true,
                CURLOPT_CUSTOMREQUEST  => 'HEAD',
                CURLOPT_HTTPHEADER     => ['X-Foo: bar'],
                CURLOPT_URL            => 'http://example.org/',
                CURLOPT_POSTFIELDS     => '',
                CURLOPT_PUT            => false,
                CURLOPT_USERAGENT      => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
            ];

        // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM
        // at least if this unit test fails in the future we know it is :)
        if (defined('HHVM_VERSION') === false) {
            $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
            $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }

        $this->assertEquals($settings, $client->createCurlSettingsArray($request));

    }

    function testCreateCurlSettingsArrayGETAfterHEAD() {

        $client = new ClientMock();
        $request = new Request('HEAD', 'http://example.org/', ['X-Foo' => 'bar']);

        // Parsing the settings for this method, and discarding the result.
        // This will cause the client to automatically persist previous
        // settings and will help us detect problems.
        $client->createCurlSettingsArray($request);

        // This is the real request.
        $request = new Request('GET', 'http://example.org/', ['X-Foo' => 'bar']);

        $settings = [
                CURLOPT_CUSTOMREQUEST  => 'GET',
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HEADER         => true,
                CURLOPT_HTTPHEADER     => ['X-Foo: bar'],
                CURLOPT_NOBODY         => false,
                CURLOPT_URL            => 'http://example.org/',
                CURLOPT_POSTFIELDS     => '',
                CURLOPT_PUT            => false,
                CURLOPT_USERAGENT      => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
            ];

        // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM
        // at least if this unit test fails in the future we know it is :)
        if (defined('HHVM_VERSION') === false) {
            $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
            $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }

        $this->assertEquals($settings, $client->createCurlSettingsArray($request));

    }

    function testCreateCurlSettingsArrayPUTStream() {

        $client = new ClientMock();

        $h = fopen('php://memory', 'r+');
        fwrite($h, 'booh');
        $request = new Request('PUT', 'http://example.org/', ['X-Foo' => 'bar'], $h);

        $settings = [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HEADER         => true,
                CURLOPT_PUT            => true,
                CURLOPT_INFILE         => $h,
                CURLOPT_NOBODY         => false,
                CURLOPT_CUSTOMREQUEST  => 'PUT',
                CURLOPT_HTTPHEADER     => ['X-Foo: bar'],
                CURLOPT_URL            => 'http://example.org/',
                CURLOPT_USERAGENT      => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
            ];

        // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM
        // at least if this unit test fails in the future we know it is :)
        if (defined('HHVM_VERSION') === false) {
            $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
            $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }

        $this->assertEquals($settings, $client->createCurlSettingsArray($request));

    }

    function testCreateCurlSettingsArrayPUTString() {

        $client = new ClientMock();
        $request = new Request('PUT', 'http://example.org/', ['X-Foo' => 'bar'], 'boo');

        $settings = [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HEADER         => true,
                CURLOPT_NOBODY         => false,
                CURLOPT_POSTFIELDS     => 'boo',
                CURLOPT_CUSTOMREQUEST  => 'PUT',
                CURLOPT_HTTPHEADER     => ['X-Foo: bar'],
                CURLOPT_URL            => 'http://example.org/',
                CURLOPT_USERAGENT      => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)',
            ];

        // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM
        // at least if this unit test fails in the future we know it is :)
        if (defined('HHVM_VERSION') === false) {
            $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
            $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
        }

        $this->assertEquals($settings, $client->createCurlSettingsArray($request));

    }

    function testSend() {

        $client = new ClientMock();
        $request = new Request('GET', 'http://example.org/');

        $client->on('doRequest', function($request, &$response) {
            $response = new Response(200);
        });

        $response = $client->send($request);

        $this->assertEquals(200, $response->getStatus());

    }

    function testSendClientError() {

        $client = new ClientMock();
        $request = new Request('GET', 'http://example.org/');

        $client->on('doRequest', function($request, &$response) {
            throw new ClientException('aaah', 1);
        });
        $called = false;
        $client->on('exception', function() use (&$called) {
            $called = true;
        });

        try {
            $client->send($request);
            $this->fail('send() should have thrown an exception');
        } catch (ClientException $e) {

        }
        $this->assertTrue($called);

    }

    function testSendHttpError() {

        $client = new ClientMock();
        $request = new Request('GET', 'http://example.org/');

        $client->on('doRequest', function($request, &$response) {
            $response = new Response(404);
        });
        $called = 0;
        $client->on('error', function() use (&$called) {
            $called++;
        });
        $client->on('error:404', function() use (&$called) {
            $called++;
        });

        $client->send($request);
        $this->assertEquals(2, $called);

    }

    function testSendRetry() {

        $client = new ClientMock();
        $request = new Request('GET', 'http://example.org/');

        $called = 0;
        $client->on('doRequest', function($request, &$response) use (&$called) {
            $called++;
            if ($called < 3) {
                $response = new Response(404);
            } else {
                $response = new Response(200);
            }
        });

        $errorCalled = 0;
        $client->on('error', function($request, $response, &$retry, $retryCount) use (&$errorCalled) {

            $errorCalled++;
            $retry = true;

        });

        $response = $client->send($request);
        $this->assertEquals(3, $called);
        $this->assertEquals(2, $errorCalled);
        $this->assertEquals(200, $response->getStatus());

    }

    function testHttpErrorException() {

        $client = new ClientMock();
        $client->setThrowExceptions(true);
        $request = new Request('GET', 'http://example.org/');

        $client->on('doRequest', function($request, &$response) {
            $response = new Response(404);
        });

        try {
            $client->send($request);
            $this->fail('An exception should have been thrown');
        } catch (ClientHttpException $e) {
            $this->assertEquals(404, $e->getHttpStatus());
            $this->assertInstanceOf('Sabre\HTTP\Response', $e->getResponse());
        }

    }

    function testParseCurlResult() {

        $client = new ClientMock();
        $client->on('curlStuff', function(&$return) {

            $return = [
                [
                    'header_size' => 33,
                    'http_code'   => 200,
                ],
                0,
                '',
            ];

        });

        $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo";
        $result = $client->parseCurlResult($body, 'foobar');

        $this->assertEquals(Client::STATUS_SUCCESS, $result['status']);
        $this->assertEquals(200, $result['http_code']);
        $this->assertEquals(200, $result['response']->getStatus());
        $this->assertEquals(['Header1' => ['Val1']], $result['response']->getHeaders());
        $this->assertEquals('Foo', $result['response']->getBodyAsString());

    }

    function testParseCurlError() {

        $client = new ClientMock();
        $client->on('curlStuff', function(&$return) {

            $return = [
                [],
                1,
                'Curl error',
            ];

        });

        $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo";
        $result = $client->parseCurlResult($body, 'foobar');

        $this->assertEquals(Client::STATUS_CURLERROR, $result['status']);
        $this->assertEquals(1, $result['curl_errno']);
        $this->assertEquals('Curl error', $result['curl_errmsg']);

    }

    function testDoRequest() {

        $client = new ClientMock();
        $request = new Request('GET', 'http://example.org/');
        $client->on('curlExec', function(&$return) {

            $return = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo";

        });
        $client->on('curlStuff', function(&$return) {

            $return = [
                [
                    'header_size' => 33,
                    'http_code'   => 200,
                ],
                0,
                '',
            ];

        });
        $response = $client->doRequest($request);
        $this->assertEquals(200, $response->getStatus());
        $this->assertEquals(['Header1' => ['Val1']], $response->getHeaders());
        $this->assertEquals('Foo', $response->getBodyAsString());

    }

    function testDoRequestCurlError() {

        $client = new ClientMock();
        $request = new Request('GET', 'http://example.org/');
        $client->on('curlExec', function(&$return) {

            $return = "";

        });
        $client->on('curlStuff', function(&$return) {

            $return = [
                [],
                1,
                'Curl error',
            ];

        });

        try {
            $response = $client->doRequest($request);
            $this->fail('This should have thrown an exception');
        } catch (ClientException $e) {
            $this->assertEquals(1, $e->getCode());
            $this->assertEquals('Curl error', $e->getMessage());
        }

    }

}

class ClientMock extends Client {

    protected $persistedSettings = [];

    /**
     * Making this method public.
     *
     * We are also going to persist all settings this method generates. While
     * the underlying object doesn't behave exactly the same, it helps us
     * simulate what curl does internally, and helps us identify problems with
     * settings that are set by _some_ methods and not correctly reset by other
     * methods after subsequent use.
     * forces
     */
    function createCurlSettingsArray(RequestInterface $request) {

        $settings = parent::createCurlSettingsArray($request);
        $settings = $settings + $this->persistedSettings;
        $this->persistedSettings = $settings;
        return $settings;

    }
    /**
     * Making this method public.
     */
    function parseCurlResult($response, $curlHandle) {

        return parent::parseCurlResult($response, $curlHandle);

    }

    /**
     * This method is responsible for performing a single request.
     *
     * @param RequestInterface $request
     * @return ResponseInterface
     */
    function doRequest(RequestInterface $request) {

        $response = null;
        $this->emit('doRequest', [$request, &$response]);

        // If nothing modified $response, we're using the default behavior.
        if (is_null($response)) {
            return parent::doRequest($request);
        } else {
            return $response;
        }

    }

    /**
     * Returns a bunch of information about a curl request.
     *
     * This method exists so it can easily be overridden and mocked.
     *
     * @param resource $curlHandle
     * @return array
     */
    protected function curlStuff($curlHandle) {

        $return = null;
        $this->emit('curlStuff', [&$return]);

        // If nothing modified $return, we're using the default behavior.
        if (is_null($return)) {
            return parent::curlStuff($curlHandle);
        } else {
            return $return;
        }

    }

    /**
     * Calls curl_exec
     *
     * This method exists so it can easily be overridden and mocked.
     *
     * @param resource $curlHandle
     * @return string
     */
    protected function curlExec($curlHandle) {

        $return = null;
        $this->emit('curlExec', [&$return]);

        // If nothing modified $return, we're using the default behavior.
        if (is_null($return)) {
            return parent::curlExec($curlHandle);
        } else {
            return $return;
        }

    }

}
<?php

namespace Sabre\HTTP;

class FunctionsTest extends \PHPUnit_Framework_TestCase {

    /**
     * @dataProvider getHeaderValuesData
     */
    function testGetHeaderValues($input, $output) {

        $this->assertEquals(
            $output,
            getHeaderValues($input)
        );

    }

    function getHeaderValuesData() {

        return [
            [
                "a",
                ["a"]
            ],
            [
                "a,b",
                ["a", "b"]
            ],
            [
                "a, b",
                ["a", "b"]
            ],
            [
                ["a, b"],
                ["a", "b"]
            ],
            [
                ["a, b", "c", "d,e"],
                ["a", "b", "c", "d", "e"]
            ],
        ];

    }

    /**
     * @dataProvider preferData
     */
    function testPrefer($input, $output) {

        $this->assertEquals(
            $output,
            parsePrefer($input)
        );

    }

    function preferData() {

        return [
            [
                'foo; bar',
                ['foo' => true]
            ],
            [
                'foo; bar=""',
                ['foo' => true]
            ],
            [
                'foo=""; bar',
                ['foo' => true]
            ],
            [
                'FOO',
                ['foo' => true]
            ],
            [
                'respond-async',
                ['respond-async' => true]
            ],
            [

                ['respond-async, wait=100', 'handling=lenient'],
                ['respond-async' => true, 'wait' => 100, 'handling' => 'lenient']
            ],
            [

                ['respond-async, wait=100, handling=lenient'],
                ['respond-async' => true, 'wait' => 100, 'handling' => 'lenient']
            ],
            // Old values
            [

                'return-asynch, return-representation',
                ['respond-async' => true, 'return' => 'representation'],
            ],
            [

                'return-minimal',
                ['return' => 'minimal'],
            ],
            [

                'strict',
                ['handling' => 'strict'],
            ],
            [

                'lenient',
                ['handling' => 'lenient'],
            ],
            // Invalid token
            [
                ['foo=%bar%'],
                [],
            ]
        ];

    }

}
<?php

namespace Sabre\HTTP;

class MessageDecoratorTest extends \PHPUnit_Framework_TestCase {

    protected $inner;
    protected $outer;

    function setUp() {

        $this->inner = new Request();
        $this->outer = new RequestDecorator($this->inner);

    }

    function testBody() {

        $this->outer->setBody('foo');
        $this->assertEquals('foo', stream_get_contents($this->inner->getBodyAsStream()));
        $this->assertEquals('foo', stream_get_contents($this->outer->getBodyAsStream()));
        $this->assertEquals('foo', $this->inner->getBodyAsString());
        $this->assertEquals('foo', $this->outer->getBodyAsString());
        $this->assertEquals('foo', $this->inner->getBody());
        $this->assertEquals('foo', $this->outer->getBody());

    }

    function testHeaders() {

        $this->outer->setHeaders([
            'a' => 'b',
            ]);

        $this->assertEquals(['a' => ['b']], $this->inner->getHeaders());
        $this->assertEquals(['a' => ['b']], $this->outer->getHeaders());

        $this->outer->setHeaders([
            'c' => 'd',
        ]);

        $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->inner->getHeaders());
        $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->outer->getHeaders());

        $this->outer->addHeaders([
            'e' => 'f',
            ]);

        $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->inner->getHeaders());
        $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->outer->getHeaders());
    }

    function testHeader() {

        $this->assertFalse($this->outer->hasHeader('a'));
        $this->assertFalse($this->inner->hasHeader('a'));
        $this->outer->setHeader('a', 'c');
        $this->assertTrue($this->outer->hasHeader('a'));
        $this->assertTrue($this->inner->hasHeader('a'));

        $this->assertEquals('c', $this->inner->getHeader('A'));
        $this->assertEquals('c', $this->outer->getHeader('A'));

        $this->outer->addHeader('A', 'd');

        $this->assertEquals(
            ['c', 'd'],
            $this->inner->getHeaderAsArray('A')
        );
        $this->assertEquals(
            ['c', 'd'],
            $this->outer->getHeaderAsArray('A')
        );

        $success = $this->outer->removeHeader('a');

        $this->assertTrue($success);
        $this->assertNull($this->inner->getHeader('A'));
        $this->assertNull($this->outer->getHeader('A'));

        $this->assertFalse($this->outer->removeHeader('i-dont-exist'));
    }

    function testHttpVersion() {

        $this->outer->setHttpVersion('1.0');

        $this->assertEquals('1.0', $this->inner->getHttpVersion());
        $this->assertEquals('1.0', $this->outer->getHttpVersion());

    }

}
<?php

namespace Sabre\HTTP;

class MessageTest extends \PHPUnit_Framework_TestCase {

    function testConstruct() {

        $message = new MessageMock();
        $this->assertInstanceOf('Sabre\HTTP\Message', $message);

    }

    function testStreamBody() {

        $body = 'foo';
        $h = fopen('php://memory', 'r+');
        fwrite($h, $body);
        rewind($h);

        $message = new MessageMock();
        $message->setBody($h);

        $this->assertEquals($body, $message->getBodyAsString());
        rewind($h);
        $this->assertEquals($body, stream_get_contents($message->getBodyAsStream()));
        rewind($h);
        $this->assertEquals($body, stream_get_contents($message->getBody()));

    }

    function testStringBody() {

        $body = 'foo';

        $message = new MessageMock();
        $message->setBody($body);

        $this->assertEquals($body, $message->getBodyAsString());
        $this->assertEquals($body, stream_get_contents($message->getBodyAsStream()));
        $this->assertEquals($body, $message->getBody());

    }

    /**
     * It's possible that streams contains more data than the Content-Length.
     *
     * The request object should make sure to never emit more than
     * Content-Length, if Content-Length is set.
     *
     * This is in particular useful when respoding to range requests with
     * streams that represent files on the filesystem, as it's possible to just
     * seek the stream to a certain point, set the content-length and let the
     * request object do the rest.
     */
    function testLongStreamToStringBody() {

        $body = fopen('php://memory', 'r+');
        fwrite($body, 'abcdefg');
        fseek($body, 2);

        $message = new MessageMock();
        $message->setBody($body);
        $message->setHeader('Content-Length', '4');

        $this->assertEquals(
            'cdef',
            $message->getBodyAsString()
        );

    }

    /**
     * Some clients include a content-length header, but the header is empty.
     * This is definitely broken behavior, but we should support it.
     */
    function testEmptyContentLengthHeader() {

        $body = fopen('php://memory', 'r+');
        fwrite($body, 'abcdefg');
        fseek($body, 2);

        $message = new MessageMock();
        $message->setBody($body);
        $message->setHeader('Content-Length', '');

        $this->assertEquals(
            'cdefg',
            $message->getBodyAsString()
        );

    }


    function testGetEmptyBodyStream() {

        $message = new MessageMock();
        $body = $message->getBodyAsStream();

        $this->assertEquals('', stream_get_contents($body));

    }

    function testGetEmptyBodyString() {

        $message = new MessageMock();
        $body = $message->getBodyAsString();

        $this->assertEquals('', $body);

    }

    function testHeaders() {

        $message = new MessageMock();
        $message->setHeader('X-Foo', 'bar');

        // Testing caselessness
        $this->assertEquals('bar', $message->getHeader('X-Foo'));
        $this->assertEquals('bar', $message->getHeader('x-fOO'));

        $this->assertTrue(
            $message->removeHeader('X-FOO')
        );
        $this->assertNull($message->getHeader('X-Foo'));
        $this->assertFalse(
            $message->removeHeader('X-FOO')
        );

    }

    function testSetHeaders() {

        $message = new MessageMock();

        $headers = [
            'X-Foo' => ['1'],
            'X-Bar' => ['2'],
        ];

        $message->setHeaders($headers);
        $this->assertEquals($headers, $message->getHeaders());

        $message->setHeaders([
            'X-Foo' => ['3', '4'],
            'X-Bar' => '5',
        ]);

        $expected = [
            'X-Foo' => ['3','4'],
            'X-Bar' => ['5'],
        ];

        $this->assertEquals($expected, $message->getHeaders());

    }

    function testAddHeaders() {

        $message = new MessageMock();

        $headers = [
            'X-Foo' => ['1'],
            'X-Bar' => ['2'],
        ];

        $message->addHeaders($headers);
        $this->assertEquals($headers, $message->getHeaders());

        $message->addHeaders([
            'X-Foo' => ['3', '4'],
            'X-Bar' => '5',
        ]);

        $expected = [
            'X-Foo' => ['1','3','4'],
            'X-Bar' => ['2','5'],
        ];

        $this->assertEquals($expected, $message->getHeaders());

    }

    function testSendBody() {

        $message = new MessageMock();

        // String
        $message->setBody('foo');

        // Stream
        $h = fopen('php://memory', 'r+');
        fwrite($h, 'bar');
        rewind($h);
        $message->setBody($h);

        $body = $message->getBody();
        rewind($body);

        $this->assertEquals('bar', stream_get_contents($body));

    }

    function testMultipleHeaders() {

        $message = new MessageMock();
        $message->setHeader('a', '1');
        $message->addHeader('A', '2');

        $this->assertEquals(
            "1,2",
            $message->getHeader('A')
        );
        $this->assertEquals(
            "1,2",
            $message->getHeader('a')
        );

        $this->assertEquals(
            ['1', '2'],
            $message->getHeaderAsArray('a')
        );
        $this->assertEquals(
            ['1', '2'],
            $message->getHeaderAsArray('A')
        );
        $this->assertEquals(
            [],
            $message->getHeaderAsArray('B')
        );

    }

    function testHasHeaders() {

        $message = new MessageMock();

        $this->assertFalse($message->hasHeader('X-Foo'));
        $message->setHeader('X-Foo', 'Bar');
        $this->assertTrue($message->hasHeader('X-Foo'));

    }

}

class MessageMock extends Message { }
<?php

namespace Sabre\HTTP;

class RequestDecoratorTest extends \PHPUnit_Framework_TestCase {

    protected $inner;
    protected $outer;

    function setUp() {

        $this->inner = new Request();
        $this->outer = new RequestDecorator($this->inner);

    }

    function testMethod() {

        $this->outer->setMethod('FOO');
        $this->assertEquals('FOO', $this->inner->getMethod());
        $this->assertEquals('FOO', $this->outer->getMethod());

    }

    function testUrl() {

        $this->outer->setUrl('/foo');
        $this->assertEquals('/foo', $this->inner->getUrl());
        $this->assertEquals('/foo', $this->outer->getUrl());

    }

    function testAbsoluteUrl() {

        $this->outer->setAbsoluteUrl('http://example.org/foo');
        $this->assertEquals('http://example.org/foo', $this->inner->getAbsoluteUrl());
        $this->assertEquals('http://example.org/foo', $this->outer->getAbsoluteUrl());

    }

    function testBaseUrl() {

        $this->outer->setBaseUrl('/foo');
        $this->assertEquals('/foo', $this->inner->getBaseUrl());
        $this->assertEquals('/foo', $this->outer->getBaseUrl());

    }

    function testPath() {

        $this->outer->setBaseUrl('/foo');
        $this->outer->setUrl('/foo/bar');
        $this->assertEquals('bar', $this->inner->getPath());
        $this->assertEquals('bar', $this->outer->getPath());

    }

    function testQueryParams() {

        $this->outer->setUrl('/foo?a=b&c=d&e');
        $expected = [
            'a' => 'b',
            'c' => 'd',
            'e' => null,
        ];

        $this->assertEquals($expected, $this->inner->getQueryParameters());
        $this->assertEquals($expected, $this->outer->getQueryParameters());

    }

    function testPostData() {

        $postData = [
            'a' => 'b',
            'c' => 'd',
            'e' => null,
        ];

        $this->outer->setPostData($postData);
        $this->assertEquals($postData, $this->inner->getPostData());
        $this->assertEquals($postData, $this->outer->getPostData());

    }


    function testServerData() {

        $serverData = [
            'HTTPS' => 'On',
        ];

        $this->outer->setRawServerData($serverData);
        $this->assertEquals('On', $this->inner->getRawServerValue('HTTPS'));
        $this->assertEquals('On', $this->outer->getRawServerValue('HTTPS'));

        $this->assertNull($this->inner->getRawServerValue('FOO'));
        $this->assertNull($this->outer->getRawServerValue('FOO'));
    }

    function testToString() {

        $this->inner->setMethod('POST');
        $this->inner->setUrl('/foo/bar/');
        $this->inner->setBody('foo');
        $this->inner->setHeader('foo', 'bar');

        $this->assertEquals((string)$this->inner, (string)$this->outer);

    }

}
<?php

namespace Sabre\HTTP;

class RequestTest extends \PHPUnit_Framework_TestCase {

    function testConstruct() {

        $request = new Request('GET', '/foo', [
            'User-Agent' => 'Evert',
        ]);
        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('/foo', $request->getUrl());
        $this->assertEquals([
            'User-Agent' => ['Evert'],
        ], $request->getHeaders());

    }

    function testGetQueryParameters() {

        $request = new Request('GET', '/foo?a=b&c&d=e');
        $this->assertEquals([
            'a' => 'b',
            'c' => null,
            'd' => 'e',
        ], $request->getQueryParameters());

    }

    function testGetQueryParametersNoData() {

        $request = new Request('GET', '/foo');
        $this->assertEquals([], $request->getQueryParameters());

    }

    /**
     * @backupGlobals
     */
    function testCreateFromPHPRequest() {

        $_SERVER['REQUEST_METHOD'] = 'PUT';

        $request = Sapi::getRequest();
        $this->assertEquals('PUT', $request->getMethod());

    }

    function testGetAbsoluteUrl() {

        $s = [
            'HTTP_HOST'   => 'sabredav.org',
            'REQUEST_URI' => '/foo'
        ];

        $r = Sapi::createFromServerArray($s);

        $this->assertEquals('http://sabredav.org/foo', $r->getAbsoluteUrl());

        $s = [
            'HTTP_HOST'   => 'sabredav.org',
            'REQUEST_URI' => '/foo',
            'HTTPS'       => 'on',
        ];

        $r = Sapi::createFromServerArray($s);

        $this->assertEquals('https://sabredav.org/foo', $r->getAbsoluteUrl());

    }

    function testGetPostData() {

        $post = [
            'bla' => 'foo',
        ];
        $r = new Request();
        $r->setPostData($post);
        $this->assertEquals($post, $r->getPostData());

    }

    function testGetPath() {

        $request = new Request();
        $request->setBaseUrl('/foo');
        $request->setUrl('/foo/bar/');

        $this->assertEquals('bar', $request->getPath());

    }

    function testGetPathStrippedQuery() {

        $request = new Request();
        $request->setBaseUrl('/foo');
        $request->setUrl('/foo/bar/?a=b');

        $this->assertEquals('bar', $request->getPath());

    }

    function testGetPathMissingSlash() {

        $request = new Request();
        $request->setBaseUrl('/foo/');
        $request->setUrl('/foo');

        $this->assertEquals('', $request->getPath());

    }

    /**
     * @expectedException \LogicException
     */
    function testGetPathOutsideBaseUrl() {

        $request = new Request();
        $request->setBaseUrl('/foo/');
        $request->setUrl('/bar/');

        $request->getPath();

    }

    function testToString() {

        $request = new Request('PUT', '/foo/bar', ['Content-Type' => 'text/xml']);
        $request->setBody('foo');

        $expected = <<<HI
PUT /foo/bar HTTP/1.1\r
Content-Type: text/xml\r
\r
foo
HI;
        $this->assertEquals($expected, (string)$request);

    }

    function testToStringAuthorization() {

        $request = new Request('PUT', '/foo/bar', ['Content-Type' => 'text/xml', 'Authorization' => 'Basic foobar']);
        $request->setBody('foo');

        $expected = <<<HI
PUT /foo/bar HTTP/1.1\r
Content-Type: text/xml\r
Authorization: Basic REDACTED\r
\r
foo
HI;
        $this->assertEquals($expected, (string)$request);

    }

    /**
     * @expectedException \InvalidArgumentException
     */
    function testConstructorWithArray() {

        $request = new Request([]);

    }

}
<?php

namespace Sabre\HTTP;

class ResponseDecoratorTest extends \PHPUnit_Framework_TestCase {

    protected $inner;
    protected $outer;

    function setUp() {

        $this->inner = new Response();
        $this->outer = new ResponseDecorator($this->inner);

    }

    function testStatus() {

        $this->outer->setStatus(201);
        $this->assertEquals(201, $this->inner->getStatus());
        $this->assertEquals(201, $this->outer->getStatus());
        $this->assertEquals('Created', $this->inner->getStatusText());
        $this->assertEquals('Created', $this->outer->getStatusText());

    }

    function testToString() {

        $this->inner->setStatus(201);
        $this->inner->setBody('foo');
        $this->inner->setHeader('foo', 'bar');

        $this->assertEquals((string)$this->inner, (string)$this->outer);

    }

}
<?php

namespace Sabre\HTTP;

class ResponseTest extends \PHPUnit_Framework_TestCase {

    function testConstruct() {

        $response = new Response(200, ['Content-Type' => 'text/xml']);
        $this->assertEquals(200, $response->getStatus());
        $this->assertEquals('OK', $response->getStatusText());

    }

    function testSetStatus() {

        $response = new Response();
        $response->setStatus('402 Where\'s my money?');
        $this->assertEquals(402, $response->getStatus());
        $this->assertEquals('Where\'s my money?', $response->getStatusText());

    }

    /**
     * @expectedException InvalidArgumentException
     */
    function testInvalidStatus() {

        $response = new Response(1000);

    }

    function testToString() {

        $response = new Response(200, ['Content-Type' => 'text/xml']);
        $response->setBody('foo');

        $expected = <<<HI
HTTP/1.1 200 OK\r
Content-Type: text/xml\r
\r
foo
HI;
        $this->assertEquals($expected, (string)$response);

    }

}
<?php

namespace Sabre\HTTP;

class SapiTest extends \PHPUnit_Framework_TestCase {

    function testConstructFromServerArray() {

        $request = Sapi::createFromServerArray([
            'REQUEST_URI'     => '/foo',
            'REQUEST_METHOD'  => 'GET',
            'HTTP_USER_AGENT' => 'Evert',
            'CONTENT_TYPE'    => 'text/xml',
            'CONTENT_LENGTH'  => '400',
            'SERVER_PROTOCOL' => 'HTTP/1.0',
        ]);

        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('/foo', $request->getUrl());
        $this->assertEquals([
            'User-Agent'     => ['Evert'],
            'Content-Type'   => ['text/xml'],
            'Content-Length' => ['400'],
        ], $request->getHeaders());

        $this->assertEquals('1.0', $request->getHttpVersion());

        $this->assertEquals('400', $request->getRawServerValue('CONTENT_LENGTH'));
        $this->assertNull($request->getRawServerValue('FOO'));

    }

    function testConstructPHPAuth() {

        $request = Sapi::createFromServerArray([
            'REQUEST_URI'    => '/foo',
            'REQUEST_METHOD' => 'GET',
            'PHP_AUTH_USER'  => 'user',
            'PHP_AUTH_PW'    => 'pass',
        ]);

        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('/foo', $request->getUrl());
        $this->assertEquals([
            'Authorization' => ['Basic ' . base64_encode('user:pass')],
        ], $request->getHeaders());

    }

    function testConstructPHPAuthDigest() {

        $request = Sapi::createFromServerArray([
            'REQUEST_URI'     => '/foo',
            'REQUEST_METHOD'  => 'GET',
            'PHP_AUTH_DIGEST' => 'blabla',
        ]);

        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('/foo', $request->getUrl());
        $this->assertEquals([
            'Authorization' => ['Digest blabla'],
        ], $request->getHeaders());

    }

    function testConstructRedirectAuth() {

        $request = Sapi::createFromServerArray([
            'REQUEST_URI'                 => '/foo',
            'REQUEST_METHOD'              => 'GET',
            'REDIRECT_HTTP_AUTHORIZATION' => 'Basic bla',
        ]);

        $this->assertEquals('GET', $request->getMethod());
        $this->assertEquals('/foo', $request->getUrl());
        $this->assertEquals([
            'Authorization' => ['Basic bla'],
        ], $request->getHeaders());

    }

    /**
     * @runInSeparateProcess
     *
     * Unfortunately we have no way of testing if the HTTP response code got
     * changed.
     */
    function testSend() {

        if (!function_exists('xdebug_get_headers')) {
            $this->markTestSkipped('XDebug needs to be installed for this test to run');
        }

        $response = new Response(204, ['Content-Type' => 'text/xml;charset=UTF-8']);

        // Second Content-Type header. Normally this doesn't make sense.
        $response->addHeader('Content-Type', 'application/xml');
        $response->setBody('foo');

        ob_start();

        Sapi::sendResponse($response);
        $headers = xdebug_get_headers();

        $result = ob_get_clean();
        header_remove();

        $this->assertEquals(
            [
                "Content-Type: text/xml;charset=UTF-8",
                "Content-Type: application/xml",
            ],
            $headers
        );

        $this->assertEquals('foo', $result);

    }

    /**
     * @runInSeparateProcess
     * @depends testSend
     */
    function testSendLimitedByContentLengthString() {

        $response = new Response(200);

        $response->addHeader('Content-Length', 19);
        $response->setBody('Send this sentence. Ignore this one.');

        ob_start();

        Sapi::sendResponse($response);

        $result = ob_get_clean();
        header_remove();

        $this->assertEquals('Send this sentence.', $result);

    }

    /**
     * @runInSeparateProcess
     * @depends testSend
     */
    function testSendLimitedByContentLengthStream() {

        $response = new Response(200, ['Content-Length' => 19]);

        $body = fopen('php://memory', 'w');
        fwrite($body, 'Ignore this. Send this sentence. Ignore this too.');
        rewind($body);
        fread($body, 13);
        $response->setBody($body);

        ob_start();

        Sapi::sendResponse($response);

        $result = ob_get_clean();
        header_remove();

        $this->assertEquals('Send this sentence.', $result);

    }

}
<?php

namespace Sabre\HTTP;

class URLUtilTest extends \PHPUnit_Framework_TestCase{

    function testEncodePath() {

        $str = '';
        for ($i = 0;$i < 128;$i++) $str .= chr($i);

        $newStr = URLUtil::encodePath($str);

        $this->assertEquals(
            '%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f' .
            '%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f' .
            '%20%21%22%23%24%25%26%27()%2a%2b%2c-./' .
            '0123456789:%3b%3c%3d%3e%3f' .
            '@ABCDEFGHIJKLMNO' .
            'PQRSTUVWXYZ%5b%5c%5d%5e_' .
            '%60abcdefghijklmno' .
            'pqrstuvwxyz%7b%7c%7d~%7f',
            $newStr);

        $this->assertEquals($str, URLUtil::decodePath($newStr));

    }

    function testEncodePathSegment() {

        $str = '';
        for ($i = 0;$i < 128;$i++) $str .= chr($i);

        $newStr = URLUtil::encodePathSegment($str);

        // Note: almost exactly the same as the last test, with the
        // exception of the encoding of / (ascii code 2f)
        $this->assertEquals(
            '%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f' .
            '%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f' .
            '%20%21%22%23%24%25%26%27()%2a%2b%2c-.%2f' .
            '0123456789:%3b%3c%3d%3e%3f' .
            '@ABCDEFGHIJKLMNO' .
            'PQRSTUVWXYZ%5b%5c%5d%5e_' .
            '%60abcdefghijklmno' .
            'pqrstuvwxyz%7b%7c%7d~%7f',
            $newStr);

        $this->assertEquals($str, URLUtil::decodePathSegment($newStr));

    }

    function testDecode() {

        $str = 'Hello%20Test+Test2.txt';
        $newStr = URLUtil::decodePath($str);
        $this->assertEquals('Hello Test+Test2.txt', $newStr);

    }

    /**
     * @depends testDecode
     */
    function testDecodeUmlaut() {

        $str = 'Hello%C3%BC.txt';
        $newStr = URLUtil::decodePath($str);
        $this->assertEquals("Hello\xC3\xBC.txt", $newStr);

    }

    /**
     * @depends testDecodeUmlaut
     */
    function testDecodeUmlautLatin1() {

        $str = 'Hello%FC.txt';
        $newStr = URLUtil::decodePath($str);
        $this->assertEquals("Hello\xC3\xBC.txt", $newStr);

    }

    /**
     * This testcase was sent by a bug reporter
     *
     * @depends testDecode
     */
    function testDecodeAccentsWindows7() {

        $str = '/webdav/%C3%A0fo%C3%B3';
        $newStr = URLUtil::decodePath($str);
        $this->assertEquals(strtolower($str), URLUtil::encodePath($newStr));

    }

    function testSplitPath() {

        $strings = [

            // input       // expected result
            '/foo/bar'     => ['/foo','bar'],
            '/foo/bar/'    => ['/foo','bar'],
            'foo/bar/'     => ['foo','bar'],
            'foo/bar'      => ['foo','bar'],
            'foo/bar/baz'  => ['foo/bar','baz'],
            'foo/bar/baz/' => ['foo/bar','baz'],
            'foo'          => ['','foo'],
            'foo/'         => ['','foo'],
            '/foo/'        => ['','foo'],
            '/foo'         => ['','foo'],
            ''             => [null,null],

            // UTF-8
            "/\xC3\xA0fo\xC3\xB3/bar"  => ["/\xC3\xA0fo\xC3\xB3",'bar'],
            "/\xC3\xA0foo/b\xC3\xBCr/" => ["/\xC3\xA0foo","b\xC3\xBCr"],
            "foo/\xC3\xA0\xC3\xBCr"    => ["foo","\xC3\xA0\xC3\xBCr"],

        ];

        foreach ($strings as $input => $expected) {

            $output = URLUtil::splitPath($input);
            $this->assertEquals($expected, $output, 'The expected output for \'' . $input . '\' was incorrect');


        }

    }

    /**
     * @dataProvider resolveData
     */
    function testResolve($base, $update, $expected) {

        $this->assertEquals(
            $expected,
            URLUtil::resolve($base, $update)
        );

    }

    function resolveData() {

        return [
            [
                'http://example.org/foo/baz',
                '/bar',
                'http://example.org/bar',
            ],
            [
                'https://example.org/foo',
                '//example.net/',
                'https://example.net/',
            ],
            [
                'https://example.org/foo',
                '?a=b',
                'https://example.org/foo?a=b',
            ],
            [
                '//example.org/foo',
                '?a=b',
                '//example.org/foo?a=b',
            ],
            // Ports and fragments
            [
                'https://example.org:81/foo#hey',
                '?a=b#c=d',
                'https://example.org:81/foo?a=b#c=d',
            ],
            // Relative.. in-directory paths
            [
                'http://example.org/foo/bar',
                'bar2',
                'http://example.org/foo/bar2',
            ],
            // Now the base path ended with a slash
            [
                'http://example.org/foo/bar/',
                'bar2/bar3',
                'http://example.org/foo/bar/bar2/bar3',
            ],
        ];

    }

}
<?php

namespace Sabre\HTTP;

class UtilTest extends \PHPUnit_Framework_TestCase {

    function testParseHTTPDate() {

        $times = [
            'Wed, 13 Oct 2010 10:26:00 GMT',
            'Wednesday, 13-Oct-10 10:26:00 GMT',
            'Wed Oct 13 10:26:00 2010',
        ];

        $expected = 1286965560;

        foreach ($times as $time) {
            $result = Util::parseHTTPDate($time);
            $this->assertEquals($expected, $result->format('U'));
        }

        $result = Util::parseHTTPDate('Wed Oct  6 10:26:00 2010');
        $this->assertEquals(1286360760, $result->format('U'));

    }

    function testParseHTTPDateFail() {

        $times = [
            //random string
            'NOW',
            // not-GMT timezone
            'Wednesday, 13-Oct-10 10:26:00 UTC',
            // No space before the 6
            'Wed Oct 6 10:26:00 2010',
            // Invalid day
            'Wed Oct  0 10:26:00 2010',
            'Wed Oct 32 10:26:00 2010',
            'Wed, 0 Oct 2010 10:26:00 GMT',
            'Wed, 32 Oct 2010 10:26:00 GMT',
            'Wednesday, 32-Oct-10 10:26:00 GMT',
            // Invalid hour
            'Wed, 13 Oct 2010 24:26:00 GMT',
            'Wednesday, 13-Oct-10 24:26:00 GMT',
            'Wed Oct 13 24:26:00 2010',
        ];

        foreach ($times as $time) {
            $this->assertFalse(Util::parseHTTPDate($time), 'We used the string: ' . $time);
        }

    }

    function testTimezones() {

        $default = date_default_timezone_get();
        date_default_timezone_set('Europe/Amsterdam');

        $this->testParseHTTPDate();

        date_default_timezone_set($default);

    }

    function testToHTTPDate() {

        $dt = new \DateTime('2011-12-10 12:00:00 +0200');

        $this->assertEquals(
            'Sat, 10 Dec 2011 10:00:00 GMT',
            Util::toHTTPDate($dt)
        );

    }

    /**
     * @dataProvider negotiateData
     */
    function testNegotiate($acceptHeader, $available, $expected) {

        $this->assertEquals(
            $expected,
            Util::negotiate($acceptHeader, $available)
        );

    }

    function negotiateData() {

        return [
            [ // simple
                'application/xml',
                ['application/xml'],
                'application/xml',
            ],
            [ // no header
                null,
                ['application/xml'],
                'application/xml',
            ],
            [ // 2 options
                'application/json',
                ['application/xml', 'application/json'],
                'application/json',
            ],
            [ // 2 choices
                'application/json, application/xml',
                ['application/xml'],
                'application/xml',
            ],
            [ // quality
                'application/xml;q=0.2, application/json',
                ['application/xml', 'application/json'],
                'application/json',
            ],
            [ // wildcard
                'image/jpeg, image/png, */*',
                ['application/xml', 'application/json'],
                'application/xml',
            ],
            [ // wildcard + quality
                'image/jpeg, image/png; q=0.5, */*',
                ['application/xml', 'application/json', 'image/png'],
                'application/xml',
            ],
            [ // no match
                'image/jpeg',
                ['application/xml'],
                null,
            ],
            [ // This is used in sabre/dav
                'text/vcard; version=4.0',
                [
                    // Most often used mime-type. Version 3
                    'text/x-vcard',
                    // The correct standard mime-type. Defaults to version 3 as
                    // well.
                    'text/vcard',
                    // vCard 4
                    'text/vcard; version=4.0',
                    // vCard 3
                    'text/vcard; version=3.0',
                    // jCard
                    'application/vcard+json',
                ],
                'text/vcard; version=4.0',

            ],
            [ // rfc7231 example 1
                'audio/*; q=0.2, audio/basic',
                [
                    'audio/pcm',
                    'audio/basic',
                ],
                'audio/basic',
            ],
            [ // Lower quality after
                'audio/pcm; q=0.2, audio/basic; q=0.1',
                [
                    'audio/pcm',
                    'audio/basic',
                ],
                'audio/pcm',
            ],
            [ // Random parameter, should be ignored
                'audio/pcm; hello; q=0.2, audio/basic; q=0.1',
                [
                    'audio/pcm',
                    'audio/basic',
                ],
                'audio/pcm',
            ],
            [ // No whitepace after type, should pick the one that is the most specific.
                'text/vcard;version=3.0, text/vcard',
                [
                    'text/vcard',
                    'text/vcard; version=3.0'
                ],
                'text/vcard; version=3.0',
            ],
            [ // Same as last one, but order is different
                'text/vcard, text/vcard;version=3.0',
                [
                    'text/vcard; version=3.0',
                    'text/vcard',
                ],
                'text/vcard; version=3.0',
            ],
            [ // Charset should be ignored here.
                'text/vcard; charset=utf-8; version=3.0, text/vcard',
                [
                    'text/vcard',
                    'text/vcard; version=3.0'
                ],
                'text/vcard; version=3.0',
            ],
            [ // Undefined offset issue.
                'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2',
                ['application/xml', 'application/json', 'image/png'],
                'application/xml',
            ],

        ];

    }
}
<?xml version="1.0"?>
<ruleset name="sabre.php">
    <description>sabre.io codesniffer ruleset</description>

     <!-- Include the whole PSR-1 standard -->
     <rule ref="PSR1" />

     <!-- All PHP files MUST use the Unix LF (linefeed) line ending. -->
     <rule ref="Generic.Files.LineEndings">
      <properties>
       <property name="eolChar" value="\n"/>
      </properties>
     </rule>

     <!-- The closing ?> tag MUST be omitted from files containing only PHP. -->
     <rule ref="Zend.Files.ClosingTag"/>

     <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. -->
     <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
      <properties>
       <property name="ignoreBlankLines" value="true"/>
       </properties>
   </rule>

   <!-- There MUST NOT be more than one statement per line. -->
   <rule ref="Generic.Formatting.DisallowMultipleStatements"/>

   <rule ref="Generic.WhiteSpace.ScopeIndent">
      <properties>
       <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/>
      </properties>
   </rule>
   <rule ref="Generic.WhiteSpace.DisallowTabIndent"/>

   <!-- PHP keywords MUST be in lower case. -->
   <rule ref="Generic.PHP.LowerCaseKeyword"/>

   <!-- The PHP constants true, false, and null MUST be in lower case. -->
   <rule ref="Generic.PHP.LowerCaseConstant"/>

   <!-- <rule ref="Squiz.Scope.MethodScope"/> -->
   <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/>

   <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. -->
   <!--
   <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing">
    <properties>
     <property name="equalsSpacing" value="1"/>
    </properties>
   </rule>
   <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint">
    <severity>0</severity>
   </rule>
    -->
   <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/>

</ruleset>
<phpunit
  colors="true"
  bootstrap="bootstrap.php"
  convertErrorsToExceptions="true"
  convertNoticesToExceptions="true"
  convertWarningsToExceptions="true"
  strict="true"
  >
  <testsuite name="Sabre_HTTP">
      <directory>HTTP/</directory>
  </testsuite>

  <filter>
    <whitelist addUncoveredFilesFromWhitelist="true">
       <directory suffix=".php">../lib/</directory>
    </whitelist>
  </filter>
</phpunit>
# Composer
vendor/
composer.lock

# Tests
tests/cov/

# Composer binaries
bin/phpunit
bin/phpcs

# Vim
.*.swp
language: php
php:
  - 5.4
  - 5.5
  - 5.6
  - 7
  - 7.1

script:
  - ./bin/phpunit --configuration tests/phpunit.xml.dist
  - ./bin/sabre-cs-fixer fix lib/ --dry-run --diff

before_script: composer install --dev

ChangeLog
=========

1.2.1 (2017-02-20)
------------------

* #16: Correctly parse urls that are only a fragment `#`.


1.2.0 (2016-12-06)
------------------

* Now throwing `InvalidUriException` if a uri passed to the `parse` function
  is invalid or could not be parsed.
* #11: Fix support for URIs that start with a triple slash. PHP's `parse_uri()`
  doesn't support them, so we now have a pure-php fallback in case it fails.
* #9: Fix support for relative URI's that have a non-uri encoded colon `:` in
  them.


1.1.1 (2016-10-27)
------------------

* #10: Correctly support file:// URIs in the build() method. (@yuloh)


1.1.0 (2016-03-07)
------------------

* #6: PHP's `parse_url()` corrupts strings if they contain certain
  non ascii-characters such as Chinese or Hebrew. sabre/uri's `parse()`
  function now percent-encodes these characters beforehand.


1.0.1 (2015-04-28)
------------------

* #4: Using php-cs-fixer to automatically enforce conding standards.
* #5: Resolving to and building `mailto:` urls were not correctly handled.


1.0.0 (2015-01-27)
------------------

* Added a `normalize` function.
* Added a `buildUri` function.
* Fixed a bug in the `resolve` when only a new fragment is specified.

San José, CalConnect XXXII release!

0.0.1 (2014-11-17)
------------------

* First version!
* Source was lifted from sabre/http package.
* Provides a `resolve` and a `split` function.
* Requires PHP 5.4.8 and up.
{
    "name": "sabre/uri",
    "description": "Functions for making sense out of URIs.",
    "keywords": [
        "URI",
        "URL",
        "rfc3986"
    ],
    "homepage": "http://sabre.io/uri/",
    "license": "BSD-3-Clause",
    "require": {
        "php": ">=5.4.7"
    },
    "authors": [
        {
            "name": "Evert Pot",
            "email": "me@evertpot.com",
            "homepage": "http://evertpot.com/",
            "role": "Developer"
        }
    ],
    "support": {
        "forum": "https://groups.google.com/group/sabredav-discuss",
        "source": "https://github.com/fruux/sabre-uri"
    },
    "autoload": {
        "files" : [
            "lib/functions.php"
        ],
        "psr-4" : {
            "Sabre\\Uri\\" : "lib/"
        }
    },
    "require-dev": {
        "sabre/cs": "~1.0.0",
        "phpunit/phpunit" : ">=4.0,<6.0"
    },
    "config" : {
        "bin-dir" : "bin/"
    }
}
<?php

namespace Sabre\Uri;

/**
 * This file contains all the uri handling functions.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/
 */

/**
 * Resolves relative urls, like a browser would.
 *
 * This function takes a basePath, which itself _may_ also be relative, and
 * then applies the relative path on top of it.
 *
 * @param string $basePath
 * @param string $newPath
 * @return string
 */
function resolve($basePath, $newPath) {

    $base = parse($basePath);
    $delta = parse($newPath);

    $pick = function($part) use ($base, $delta) {

        if ($delta[$part]) {
            return $delta[$part];
        } elseif ($base[$part]) {
            return $base[$part];
        }
        return null;

    };

    // If the new path defines a scheme, it's absolute and we can just return
    // that.
    if ($delta['scheme']) {
        return build($delta);
    }

    $newParts = [];

    $newParts['scheme'] = $pick('scheme');
    $newParts['host'] = $pick('host');
    $newParts['port'] = $pick('port');

    $path = '';
    if ($delta['path']) {
        // If the path starts with a slash
        if ($delta['path'][0] === '/') {
            $path = $delta['path'];
        } else {
            // Removing last component from base path.
            $path = $base['path'];
            if (strpos($path, '/') !== false) {
                $path = substr($path, 0, strrpos($path, '/'));
            }
            $path .= '/' . $delta['path'];
        }
    } else {
        $path = $base['path'] ?: '/';
    }
    // Removing .. and .
    $pathParts = explode('/', $path);
    $newPathParts = [];
    foreach ($pathParts as $pathPart) {

        switch ($pathPart) {
            //case '' :
            case '.' :
                break;
            case '..' :
                array_pop($newPathParts);
                break;
            default :
                $newPathParts[] = $pathPart;
                break;
        }
    }

    $path = implode('/', $newPathParts);

    // If the source url ended with a /, we want to preserve that.
    $newParts['path'] = $path;
    if ($delta['query']) {
        $newParts['query'] = $delta['query'];
    } elseif (!empty($base['query']) && empty($delta['host']) && empty($delta['path'])) {
        // Keep the old query if host and path didn't change
        $newParts['query'] = $base['query'];
    }
    if ($delta['fragment']) {
        $newParts['fragment'] = $delta['fragment'];
    }
    return build($newParts);

}

/**
 * Takes a URI or partial URI as its argument, and normalizes it.
 *
 * After normalizing a URI, you can safely compare it to other URIs.
 * This function will for instance convert a %7E into a tilde, according to
 * rfc3986.
 *
 * It will also change a %3a into a %3A.
 *
 * @param string $uri
 * @return string
 */
function normalize($uri) {

    $parts = parse($uri);

    if (!empty($parts['path'])) {
        $pathParts = explode('/', ltrim($parts['path'], '/'));
        $newPathParts = [];
        foreach ($pathParts as $pathPart) {
            switch ($pathPart) {
                case '.':
                    // skip
                    break;
                case '..' :
                    // One level up in the hierarchy
                    array_pop($newPathParts);
                    break;
                default :
                    // Ensuring that everything is correctly percent-encoded.
                    $newPathParts[] = rawurlencode(rawurldecode($pathPart));
                    break;
            }
        }
        $parts['path'] = '/' . implode('/', $newPathParts);
    }

    if ($parts['scheme']) {
        $parts['scheme'] = strtolower($parts['scheme']);
        $defaultPorts = [
            'http'  => '80',
            'https' => '443',
        ];

        if (!empty($parts['port']) && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) {
            // Removing default ports.
            unset($parts['port']);
        }
        // A few HTTP specific rules.
        switch ($parts['scheme']) {
            case 'http' :
            case 'https' :
                if (empty($parts['path'])) {
                    // An empty path is equivalent to / in http.
                    $parts['path'] = '/';
                }
                break;
        }
    }

    if ($parts['host']) $parts['host'] = strtolower($parts['host']);

    return build($parts);

}

/**
 * Parses a URI and returns its individual components.
 *
 * This method largely behaves the same as PHP's parse_url, except that it will
 * return an array with all the array keys, including the ones that are not
 * set by parse_url, which makes it a bit easier to work with.
 *
 * Unlike PHP's parse_url, it will also convert any non-ascii characters to
 * percent-encoded strings. PHP's parse_url corrupts these characters on OS X.
 *
 * @param string $uri
 * @return array
 */
function parse($uri) {

    // Normally a URI must be ASCII, however. However, often it's not and
    // parse_url might corrupt these strings.
    //
    // For that reason we take any non-ascii characters from the uri and
    // uriencode them first.
    $uri = preg_replace_callback(
        '/[^[:ascii:]]/u',
        function($matches) {
            return rawurlencode($matches[0]);
        },
        $uri
    );

    $result = parse_url($uri);
    if (!$result) {
        $result = _parse_fallback($uri);
    }

    return
         $result + [
            'scheme'   => null,
            'host'     => null,
            'path'     => null,
            'port'     => null,
            'user'     => null,
            'query'    => null,
            'fragment' => null,
        ];

}

/**
 * This function takes the components returned from PHP's parse_url, and uses
 * it to generate a new uri.
 *
 * @param array $parts
 * @return string
 */
function build(array $parts) {

    $uri = '';

    $authority = '';
    if (!empty($parts['host'])) {
        $authority = $parts['host'];
        if (!empty($parts['user'])) {
            $authority = $parts['user'] . '@' . $authority;
        }
        if (!empty($parts['port'])) {
            $authority = $authority . ':' . $parts['port'];
        }
    }

    if (!empty($parts['scheme'])) {
        // If there's a scheme, there's also a host.
        $uri = $parts['scheme'] . ':';

    }
    if ($authority || (!empty($parts['scheme']) && $parts['scheme'] === 'file')) {
        // No scheme, but there is a host.
        $uri .= '//' . $authority;

    }

    if (!empty($parts['path'])) {
        $uri .= $parts['path'];
    }
    if (!empty($parts['query'])) {
        $uri .= '?' . $parts['query'];
    }
    if (!empty($parts['fragment'])) {
        $uri .= '#' . $parts['fragment'];
    }

    return $uri;

}

/**
 * Returns the 'dirname' and 'basename' for a path.
 *
 * The reason there is a custom function for this purpose, is because
 * basename() is locale aware (behaviour changes if C locale or a UTF-8 locale
 * is used) and we need a method that just operates on UTF-8 characters.
 *
 * In addition basename and dirname are platform aware, and will treat
 * backslash (\) as a directory separator on windows.
 *
 * This method returns the 2 components as an array.
 *
 * If there is no dirname, it will return an empty string. Any / appearing at
 * the end of the string is stripped off.
 *
 * @param string $path
 * @return array
 */
function split($path) {

    $matches = [];
    if (preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) {
        return [$matches[1], $matches[2]];
    }
    return [null,null];

}

/**
 * This function is another implementation of parse_url, except this one is
 * fully written in PHP.
 *
 * The reason is that the PHP bug team is not willing to admit that there are
 * bugs in the parse_url implementation.
 *
 * This function is only called if the main parse method fails. It's pretty
 * crude and probably slow, so the original parse_url is usually preferred.
 *
 * @param string $uri
 * @return array
 */
function _parse_fallback($uri) {

    // Normally a URI must be ASCII, however. However, often it's not and
    // parse_url might corrupt these strings.
    //
    // For that reason we take any non-ascii characters from the uri and
    // uriencode them first.
    $uri = preg_replace_callback(
        '/[^[:ascii:]]/u',
        function($matches) {
            return rawurlencode($matches[0]);
        },
        $uri
    );

    $result = [
        'scheme'   => null,
        'host'     => null,
        'port'     => null,
        'user'     => null,
        'path'     => null,
        'fragment' => null,
        'query'    => null,
    ];

    if (preg_match('% ^([A-Za-z][A-Za-z0-9+-\.]+): %x', $uri, $matches)) {

        $result['scheme'] = $matches[1];
        // Take what's left.
        $uri = substr($uri, strlen($result['scheme']) + 1);

    }

    // Taking off a fragment part
    if (strpos($uri, '#') !== false) {
        list($uri, $result['fragment']) = explode('#', $uri, 2);
    }
    // Taking off the query part
    if (strpos($uri, '?') !== false) {
        list($uri, $result['query']) = explode('?', $uri, 2);
    }

    if (substr($uri, 0, 3) === '///') {
      // The triple slash uris are a bit unusual, but we have special handling
      // for them.
      $result['path'] = substr($uri, 2);
      $result['host'] = '';
    } elseif (substr($uri, 0, 2) === '//') {
        // Uris that have an authority part.
        $regex = '
          %^
            //
            (?: (?<user> [^:@]+) (: (?<pass> [^@]+)) @)?
            (?<host> ( [^:/]* | \[ [^\]]+ \] ))
            (?: : (?<port> [0-9]+))?
            (?<path> / .*)?
          $%x
        ';
        if (!preg_match($regex, $uri, $matches)) {
            throw new InvalidUriException('Invalid, or could not parse URI');
        }
        if ($matches['host']) $result['host'] = $matches['host'];
        if ($matches['port']) $result['port'] = (int)$matches['port'];
        if (isset($matches['path'])) $result['path'] = $matches['path'];
        if ($matches['user']) $result['user'] = $matches['user'];
        if ($matches['pass']) $result['pass'] = $matches['pass'];
    } else {
        $result['path'] = $uri;
    }

    return $result;
}
<?php

namespace Sabre\Uri;

/**
 * Invalid Uri
 *
 * This is thrown when an attempt was made to use Sabre\Uri parse a uri that
 * it could not.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (https://evertpot.com/)
 * @license http://sabre.io/license/
 */
class InvalidUriException extends \Exception {

}
<?php

namespace Sabre\Uri;

/**
 * This class contains the version number for this package.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/
 */
class Version {

    /**
     * Full version number
     */
    const VERSION = '1.2.1';

}
Copyright (C) 2014-2017 fruux GmbH (https://fruux.com/)

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * 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.
    * Neither the name Sabre 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
sabre/uri
=========

sabre/uri is a lightweight library that provides several functions for working
with URIs, staying true to the rules of [RFC3986][2].

Partially inspired by [Node.js URL library][3], and created to solve real
problems in PHP applications. 100% unitested and many tests are based on
examples from RFC3986.

The library provides the following functions:

1. `resolve` to resolve relative urls.
2. `normalize` to aid in comparing urls.
3. `parse`, which works like PHP's [parse_url][6].
4. `build` to do the exact opposite of `parse`.
5. `split` to easily get the 'dirname' and 'basename' of a URL without all the
   problems those two functions have.


Further reading
---------------

* [Installation][7]
* [Usage][8]


Questions?
----------

Head over to the [sabre/dav mailinglist][4], or you can also just open a ticket
on [GitHub][5].


Made at fruux
-------------

This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.

[1]: http://sabre.io/uri/
[2]: https://tools.ietf.org/html/rfc3986/
[3]: http://nodejs.org/api/url.html
[4]: http://groups.google.com/group/sabredav-discuss
[5]: https://github.com/fruux/sabre-uri/issues/
[6]: http://php.net/manual/en/function.parse-url.php
[7]: http://sabre.io/uri/install/
[8]: http://sabre.io/uri/usage/
<?php

namespace Sabre\Uri;

class BuildTest extends \PHPUnit_Framework_TestCase{

    /**
     * @dataProvider buildUriData
     */
    function testBuild($value) {

        $this->assertEquals(
            $value,
            build(parse_url($value))
        );

    }

    function buildUriData() {

        return [
            ['http://example.org/'],
            ['http://example.org/foo/bar'],
            ['//example.org/foo/bar'],
            ['/foo/bar'],
            ['http://example.org:81/'],
            ['http://user@example.org:81/'],
            ['http://example.org:81/hi?a=b'],
            ['http://example.org:81/hi?a=b#c=d'],
            // [ '//example.org:81/hi?a=b#c=d'], // Currently fails due to a
            // PHP bug.
            ['/hi?a=b#c=d'],
            ['?a=b#c=d'],
            ['#c=d'],
            ['file:///etc/hosts'],
            ['file://localhost/etc/hosts'],
        ];

    }

}
<?php

namespace Sabre\Uri;

class NormalizeTest extends \PHPUnit_Framework_TestCase{

    /**
     * @dataProvider normalizeData
     */
    function testNormalize($in, $out) {

        $this->assertEquals(
            $out,
            normalize($in)
        );

    }

    function normalizeData() {

        return [
            ['http://example.org/',             'http://example.org/'],
            ['HTTP://www.EXAMPLE.com/',         'http://www.example.com/'],
            ['http://example.org/%7Eevert',     'http://example.org/~evert'],
            ['http://example.org/./evert',      'http://example.org/evert'],
            ['http://example.org/../evert',     'http://example.org/evert'],
            ['http://example.org/foo/../evert', 'http://example.org/evert'],
            ['/%41',                            '/A'],
            ['/%3F',                            '/%3F'],
            ['/%3f',                            '/%3F'],
            ['http://example.org',              'http://example.org/'],
            ['http://example.org:/',            'http://example.org/'],
            ['http://example.org:80/',          'http://example.org/'],
            // See issue #6. parse_url corrupts strings like this, but only on
            // macs.
            //[ 'http://example.org/有词法别名.zh','http://example.org/%E6%9C%89%E8%AF%8D%E6%B3%95%E5%88%AB%E5%90%8D.zh'],

        ];

    }

}
<?php

namespace Sabre\Uri;

class ParseTest extends \PHPUnit_Framework_TestCase{

    /**
     * @dataProvider parseData
     */
    function testParse($in, $out) {

        $this->assertEquals(
            $out,
            parse($in)
        );

    }

    /**
     * @dataProvider parseData
     */
    function testParseFallback($in, $out) {

        $result = _parse_fallback($in);
        $result = $result + [
            'scheme'   => null,
            'host'     => null,
            'path'     => null,
            'port'     => null,
            'user'     => null,
            'query'    => null,
            'fragment' => null,
        ];

        $this->assertEquals(
            $out,
            $result
        );

    }

    function parseData() {

        return [
            [
                'http://example.org/hello?foo=bar#test',
                [
                    'scheme'   => 'http',
                    'host'     => 'example.org',
                    'path'     => '/hello',
                    'port'     => null,
                    'user'     => null,
                    'query'    => 'foo=bar',
                    'fragment' => 'test'
                ]
            ],
            // See issue #6. parse_url corrupts strings like this, but only on
            // macs.
            [
                'http://example.org/有词法别名.zh',
                [
                    'scheme'   => 'http',
                    'host'     => 'example.org',
                    'path'     => '/%E6%9C%89%E8%AF%8D%E6%B3%95%E5%88%AB%E5%90%8D.zh',
                    'port'     => null,
                    'user'     => null,
                    'query'    => null,
                    'fragment' => null
                ]
            ],
            [
                'ftp://user:password@ftp.example.org/',
                [
                    'scheme'   => 'ftp',
                    'host'     => 'ftp.example.org',
                    'path'     => '/',
                    'port'     => null,
                    'user'     => 'user',
                    'pass'     => 'password',
                    'query'    => null,
                    'fragment' => null,
                ]
            ],
            // See issue #9, parse_url doesn't like colons followed by numbers even
            // though they are allowed since RFC 3986
            [
                'http://example.org/hello:12?foo=bar#test',
                [
                    'scheme'   => 'http',
                    'host'     => 'example.org',
                    'path'     => '/hello:12',
                    'port'     => null,
                    'user'     => null,
                    'query'    => 'foo=bar',
                    'fragment' => 'test'
                ]
            ],
            [
                '/path/to/colon:34',
                [
                    'scheme'   => null,
                    'host'     => null,
                    'path'     => '/path/to/colon:34',
                    'port'     => null,
                    'user'     => null,
                    'query'    => null,
                    'fragment' => null,
                ]
            ],
            // File scheme
            [
                'file:///foo/bar',
                [
                    'scheme'   => 'file',
                    'host'     => '',
                    'path'     => '/foo/bar',
                    'port'     => null,
                    'user'     => null,
                    'query'    => null,
                    'fragment' => null,
                ]
            ],
            // Weird scheme with triple-slash. See Issue #11.
            [
                'vfs:///somefile',
                [
                    'scheme'   => 'vfs',
                    'host'     => '',
                    'path'     => '/somefile',
                    'port'     => null,
                    'user'     => null,
                    'query'    => null,
                    'fragment' => null,
                ]
            ],
            // Examples from RFC3986
            [
                'ldap://[2001:db8::7]/c=GB?objectClass?one',
                [
                    'scheme'   => 'ldap',
                    'host'     => '[2001:db8::7]',
                    'path'     => '/c=GB',
                    'port'     => null,
                    'user'     => null,
                    'query'    => 'objectClass?one',
                    'fragment' => null,
                ]
            ],
            [
                'news:comp.infosystems.www.servers.unix',
                [
                    'scheme'   => 'news',
                    'host'     => null,
                    'path'     => 'comp.infosystems.www.servers.unix',
                    'port'     => null,
                    'user'     => null,
                    'query'    => null,
                    'fragment' => null,
                ]
            ],
            // Port
            [
                'http://example.org:8080/',
                [
                    'scheme'   => 'http',
                    'host'     => 'example.org',
                    'path'     => '/',
                    'port'     => 8080,
                    'user'     => null,
                    'query'    => null,
                    'fragment' => null,
                ]
            ],
            // Parial url
            [
                '#foo',
                [
                    'scheme'   => null,
                    'host'     => null,
                    'path'     => null,
                    'port'     => null,
                    'user'     => null,
                    'query'    => null,
                    'fragment' => 'foo',
                ]

            ]

        ];

    }

}
<?xml version="1.0"?>
<ruleset name="sabre.php">
    <description>sabre.io codesniffer ruleset</description>

     <!-- Include the whole PSR-1 standard -->
     <rule ref="PSR1" />

     <!-- All PHP files MUST use the Unix LF (linefeed) line ending. -->
     <rule ref="Generic.Files.LineEndings">
      <properties>
       <property name="eolChar" value="\n"/>
      </properties>
     </rule>

     <!-- The closing ?> tag MUST be omitted from files containing only PHP. -->
     <rule ref="Zend.Files.ClosingTag"/>

     <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. -->
     <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
      <properties>
       <property name="ignoreBlankLines" value="true"/>
       </properties>
   </rule>

   <!-- There MUST NOT be more than one statement per line. -->
   <rule ref="Generic.Formatting.DisallowMultipleStatements"/>

   <rule ref="Generic.WhiteSpace.ScopeIndent">
      <properties>
       <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/>
      </properties>
   </rule>
   <rule ref="Generic.WhiteSpace.DisallowTabIndent"/>

   <!-- PHP keywords MUST be in lower case. -->
   <rule ref="Generic.PHP.LowerCaseKeyword"/>

   <!-- The PHP constants true, false, and null MUST be in lower case. -->
   <rule ref="Generic.PHP.LowerCaseConstant"/>

   <!-- <rule ref="Squiz.Scope.MethodScope"/> -->
   <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/>

   <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. -->
   <!--
   <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing">
    <properties>
     <property name="equalsSpacing" value="1"/>
    </properties>
   </rule>
   <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint">
    <severity>0</severity>
   </rule>
    -->
   <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/>

</ruleset>
<phpunit
  colors="true"
  bootstrap="../vendor/autoload.php"
  convertErrorsToExceptions="true"
  convertNoticesToExceptions="true"
  convertWarningsToExceptions="true"
  strict="true"
  >
  <testsuite name="sabre-uri">
    <directory>.</directory>
  </testsuite>

  <filter>
    <whitelist addUncoveredFilesFromWhitelist="true">
        <directory suffix=".php">../lib/</directory>
   </whitelist>
  </filter>
</phpunit>
<?php

namespace Sabre\Uri;

class ResolveTest extends \PHPUnit_Framework_TestCase{

    /**
     * @dataProvider resolveData
     */
    function testResolve($base, $update, $expected) {

        $this->assertEquals(
            $expected,
            resolve($base, $update)
        );

    }

    function resolveData() {

        return [
            [
                'http://example.org/foo/baz',
                '/bar',
                'http://example.org/bar',
            ],
            [
                'https://example.org/foo',
                '//example.net/',
                'https://example.net/',
            ],
            [
                'https://example.org/foo',
                '?a=b',
                'https://example.org/foo?a=b',
            ],
            [
                '//example.org/foo',
                '?a=b',
                '//example.org/foo?a=b',
            ],
            // Ports and fragments
            [
                'https://example.org:81/foo#hey',
                '?a=b#c=d',
                'https://example.org:81/foo?a=b#c=d',
            ],
            // Relative.. in-directory paths
            [
                'http://example.org/foo/bar',
                'bar2',
                'http://example.org/foo/bar2',
            ],
            // Now the base path ended with a slash
            [
                'http://example.org/foo/bar/',
                'bar2/bar3',
                'http://example.org/foo/bar/bar2/bar3',
            ],
            // .. and .
            [
                'http://example.org/foo/bar/',
                '../bar2/.././/bar3/',
                'http://example.org/foo//bar3/',
            ],
            // Only updating the fragment
            [
                'https://example.org/foo?a=b',
                '#comments',
                'https://example.org/foo?a=b#comments',
            ],
            // Switching to mailto!
            [
                'https://example.org/foo?a=b',
                'mailto:foo@example.org',
                'mailto:foo@example.org',
            ],
            // Resolving empty path
            [
                'http://www.example.org',
                '#foo',
                'http://www.example.org/#foo',
            ],
            // Another fragment test
            [
                'http://example.org/path.json',
                '#',
                'http://example.org/path.json',
            ],
            [
                'http://www.example.com',
                '#',
                'http://www.example.com/',
            ]
        ];

    }

}
<?php

namespace Sabre\Uri;

class SplitTest extends \PHPUnit_Framework_TestCase{

    function testSplit() {

        $strings = [

            // input                    // expected result
            '/foo/bar'     => ['/foo','bar'],
            '/foo/bar/'    => ['/foo','bar'],
            'foo/bar/'     => ['foo','bar'],
            'foo/bar'      => ['foo','bar'],
            'foo/bar/baz'  => ['foo/bar','baz'],
            'foo/bar/baz/' => ['foo/bar','baz'],
            'foo'          => ['','foo'],
            'foo/'         => ['','foo'],
            '/foo/'        => ['','foo'],
            '/foo'         => ['','foo'],
            ''             => [null,null],

            // UTF-8
            "/\xC3\xA0fo\xC3\xB3/bar"  => ["/\xC3\xA0fo\xC3\xB3",'bar'],
            "/\xC3\xA0foo/b\xC3\xBCr/" => ["/\xC3\xA0foo","b\xC3\xBCr"],
            "foo/\xC3\xA0\xC3\xBCr"    => ["foo","\xC3\xA0\xC3\xBCr"],

        ];

        foreach ($strings as $input => $expected) {

            $output = split($input);
            $this->assertEquals($expected, $output, 'The expected output for \'' . $input . '\' was incorrect');


        }

    }

}
# Composer stuff
vendor/
composer.lock
tests/cov/
tests/temp

#vim
.*.swp

#binaries
bin/phpunit
bin/phpcs
bin/php-cs-fixer
bin/sabre-cs-fixer
bin/hoa

# Development stuff
testdata/

# OS X
.DS_Store
language: php
php:
  - 5.5
  - 5.6
  - 7.0
  - 7.1

sudo: false

script:
  - phpunit --configuration tests/phpunit.xml
  - ./bin/sabre-cs-fixer fix . --dry-run --diff

before_script:
  - phpenv config-rm xdebug.ini; true
  - composer install

cache:
  directories:
    - $HOME/.composer/cache
#!/usr/bin/env php
<?php

include __DIR__ . '/../vendor/autoload.php';

$data = stream_get_contents(STDIN);

$start = microtime(true);

$lol = Sabre\VObject\Reader::read($data);

echo "time: " . (microtime(true) - $start) . "\n";
<?php

include __DIR__ . '/../vendor/autoload.php';

if ($argc < 2) {
    echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " freebusy benchmark\n";
    echo "\n";
    echo "This script can be used to measure the speed of generating a\n";
    echo "free-busy report based on a calendar.\n";
    echo "\n";
    echo "The process will be repeated 100 times to get accurate stats\n";
    echo "\n";
    echo "Usage: " . $argv[0] . " inputfile.ics\n";
    die();
}

list(, $inputFile) = $argv;

$bench = new Hoa\Bench\Bench();
$bench->parse->start();

$vcal = Sabre\VObject\Reader::read(fopen($inputFile, 'r'));

$bench->parse->stop();

$repeat = 100;
$start = new \DateTime('2000-01-01');
$end = new \DateTime('2020-01-01');
$timeZone = new \DateTimeZone('America/Toronto');

$bench->fb->start();

for ($i = 0; $i < $repeat; $i++) {

    $fb = new Sabre\VObject\FreeBusyGenerator($start, $end, $vcal, $timeZone);
    $results = $fb->getResult();

}
$bench->fb->stop();



echo $bench,"\n";

function formatMemory($input) {

    if (strlen($input) > 6) {

        return round($input / (1024 * 1024)) . 'M';

    } elseif (strlen($input) > 3) {

        return round($input / 1024) . 'K';

    }

}

unset($input, $splitter);

echo "peak memory usage: " . formatMemory(memory_get_peak_usage()), "\n";
echo "current memory usage: " . formatMemory(memory_get_usage()), "\n";
<?php

include __DIR__ . '/../vendor/autoload.php';

if ($argc < 2) {
    echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " manipulation benchmark\n";
    echo "\n";
    echo "This script can be used to measure the speed of opening a large amount of\n";
    echo "vcards, making a few alterations and serializing them again.\n";
    echo "system.";
    echo "\n";
    echo "Usage: " . $argv[0] . " inputfile.vcf\n";
    die();
}

list(, $inputFile) = $argv;

$input = file_get_contents($inputFile);

$splitter = new Sabre\VObject\Splitter\VCard($input);

$bench = new Hoa\Bench\Bench();

while (true) {

    $bench->parse->start();
    $vcard = $splitter->getNext();
    $bench->parse->pause();

    if (!$vcard) break;

    $bench->manipulate->start();
    $vcard->{'X-FOO'} = 'Random new value!';
    $emails = [];
    if (isset($vcard->EMAIL)) foreach ($vcard->EMAIL as $email) {
        $emails[] = (string)$email;
    }
    $bench->manipulate->pause();

    $bench->serialize->start();
    $vcard2 = $vcard->serialize();
    $bench->serialize->pause();

    $vcard->destroy();

}



echo $bench,"\n";

function formatMemory($input) {

    if (strlen($input) > 6) {

        return round($input / (1024 * 1024)) . 'M';

    } elseif (strlen($input) > 3) {

        return round($input / 1024) . 'K';

    }

}

unset($input, $splitter);

echo "peak memory usage: " . formatMemory(memory_get_peak_usage()), "\n";
echo "current memory usage: " . formatMemory(memory_get_usage()), "\n";
#!/usr/bin/env php
<?php

$windowsZonesUrl = 'http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml';
$outputFile = __DIR__ . '/../lib/timezonedata/windowszones.php';

echo "Fetching timezone map from: " . $windowsZonesUrl, "\n";

$data = file_get_contents($windowsZonesUrl);

$xml = simplexml_load_string($data);

$map = [];

foreach ($xml->xpath('//mapZone') as $mapZone) {

    $from = (string)$mapZone['other'];
    $to = (string)$mapZone['type'];

    list($to) = explode(' ', $to, 2);

    if (!isset($map[$from])) {
        $map[$from] = $to;
    }

}

ksort($map);
echo "Writing to: $outputFile\n";

$f = fopen($outputFile, 'w');
fwrite($f, "<?php\n\n");
fwrite($f, "/**\n");
fwrite($f, " * Automatically generated timezone file\n");
fwrite($f, " *\n");
fwrite($f, " * Last update: " . date(DATE_W3C) . "\n");
fwrite($f, " * Source: " . $windowsZonesUrl . "\n");
fwrite($f, " *\n");
fwrite($f, " * @copyright Copyright (C) fruux GmbH (https://fruux.com/).\n");
fwrite($f, " * @license http://sabre.io/license/ Modified BSD License\n");
fwrite($f, " */\n");
fwrite($f, "\n");
fwrite($f, "return ");
fwrite($f, var_export($map, true) . ';');
fclose($f);

echo "Formatting\n";

exec(__DIR__ . '/sabre-cs-fixer fix ' . escapeshellarg($outputFile));

echo "Done\n";
#!/usr/bin/env php
<?php

use Sabre\VObject;

if ($argc < 2) {
    $cmd = $argv[0];
    fwrite(STDERR, <<<HI
Fruux test data generator

This script generates a lot of test data. This is used for profiling and stuff.
Currently it just generates events in a single calendar.

The iCalendar output goes to stdout. Other messages to stderr.

{$cmd} [events]


HI
    );
    die();
}

$events = 100;

if (isset($argv[1])) $events = (int)$argv[1];

include __DIR__ . '/../vendor/autoload.php';

fwrite(STDERR, "Generating " . $events . " events\n");

$currentDate = new DateTime('-' . round($events / 2) . ' days');

$calendar = new VObject\Component\VCalendar();

$ii = 0;

while ($ii < $events) {

    $ii++;

    $event = $calendar->add('VEVENT');
    $event->DTSTART = 'bla';
    $event->SUMMARY = 'Event #' . $ii;
    $event->UID = md5(microtime(true));

    $doctorRandom = mt_rand(1, 1000);

    switch ($doctorRandom) {
        // All-day event
        case 1 :
            $event->DTEND = 'bla';
            $dtStart = clone $currentDate;
            $dtEnd = clone $currentDate;
            $dtEnd->modify('+' . mt_rand(1, 3) . ' days');
            $event->DTSTART->setDateTime($dtStart);
            $event->DTSTART['VALUE'] = 'DATE';
            $event->DTEND->setDateTime($dtEnd);
            break;
        case 2 :
            $event->RRULE = 'FREQ=DAILY;COUNT=' . mt_rand(1, 10);
            // No break intentional
        default :
            $dtStart = clone $currentDate;
            $dtStart->setTime(mt_rand(1, 23), mt_rand(0, 59), mt_rand(0, 59));
            $event->DTSTART->setDateTime($dtStart);
            $event->DURATION = 'PT' . mt_rand(1, 3) . 'H';
            break;

    }
    
    $currentDate->modify('+ ' . mt_rand(0, 3) . ' days');

}
fwrite(STDERR, "Validating\n");

$result = $calendar->validate();
if ($result) {
    fwrite(STDERR, "Errors!\n");
    fwrite(STDERR, print_r($result, true));
    die(-1);
}

fwrite(STDERR, "Serializing this beast\n");

echo $calendar->serialize();

fwrite(STDERR, "done.\n");
#!/usr/bin/env php
<?php

namespace Sabre\VObject;

// This sucks.. we have to try to find the composer autoloader. But chances
// are, we can't find it this way. So we'll do our bestest
$paths = [
    __DIR__ . '/../vendor/autoload.php',  // In case vobject is cloned directly
    __DIR__ . '/../../../autoload.php',   // In case vobject is a composer dependency.
];

foreach ($paths as $path) {
    if (file_exists($path)) {
        include $path;
        break;
    }
}

if (!class_exists('Sabre\\VObject\\Version')) {
    fwrite(STDERR, "Composer autoloader could not be loaded.\n");
    die(1);
}

echo "sabre/vobject ", Version::VERSION, " duplicate contact merge tool\n";

if ($argc < 3) {

    echo "\n";
    echo "Usage: ", $argv[0], " input.vcf output.vcf [debug.log]\n";
    die(1);

}

$input = fopen($argv[1], 'r');
$output = fopen($argv[2], 'w');
$debug = isset($argv[3]) ? fopen($argv[3], 'w') : null;

$splitter = new Splitter\VCard($input);

// The following properties are ignored. If they appear in some vcards
// but not in others, we don't consider them for the sake of finding
// differences.
$ignoredProperties = [
    "PRODID",
    "VERSION",
    "REV",
    "UID",
    "X-ABLABEL",
];


$collectedNames = [];

$stats = [
    "Total vcards"       => 0,
    "No FN property"     => 0,
    "Ignored duplicates" => 0,
    "Merged values"      => 0,
    "Error"              => 0,
    "Unique cards"       => 0,
    "Total written"      => 0,
];

function writeStats() {

    global $stats;
    foreach ($stats as $name => $value) {
        echo str_pad($name, 23, " ", STR_PAD_RIGHT), str_pad($value, 6, " ", STR_PAD_LEFT), "\n";
    }
    // Moving cursor back a few lines.
    echo "\033[" . count($stats) . "A";

}

function write($vcard) {

    global $stats, $output;

    $stats["Total written"]++;
    fwrite($output, $vcard->serialize() . "\n");

}

while ($vcard = $splitter->getNext()) {

    $stats["Total vcards"]++;
    writeStats();

    $fn = isset($vcard->FN) ? (string)$vcard->FN : null;

    if (empty($fn)) {

        // Immediately write this vcard, we don't compare it.
        $stats["No FN property"]++;
        $stats['Unique cards']++;
        write($vcard);
        $vcard->destroy();
        continue;

    }

    if (!isset($collectedNames[$fn])) {

        $collectedNames[$fn] = $vcard;
        $stats['Unique cards']++;
        continue;

    } else {

        // Starting comparison for all properties. We only check if properties
        // in the current vcard exactly appear in the earlier vcard as well.
        foreach ($vcard->children() as $newProp) {

            if (in_array($newProp->name, $ignoredProperties)) {
                // We don't care about properties such as UID and REV.
                continue;
            }
            $ok = false;
            foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) {

                if ($compareProp->serialize() === $newProp->serialize()) {
                    $ok = true;
                    break;
                }
            }

            if (!$ok) {

                if ($newProp->name === 'EMAIL' || $newProp->name === 'TEL') {

                    // We're going to make another attempt to find this
                    // property, this time just by value. If we find it, we
                    // consider it a success.
                    foreach ($collectedNames[$fn]->select($newProp->name) as $compareProp) {

                        if ($compareProp->getValue() === $newProp->getValue()) {
                            $ok = true;
                            break;
                        }
                    }

                    if (!$ok) {

                        // Merging the new value in the old vcard.
                        $collectedNames[$fn]->add(clone $newProp);
                        $ok = true;
                        $stats['Merged values']++;

                    }

                }

            }

            if (!$ok) {

                // echo $newProp->serialize() . " does not appear in earlier vcard!\n";
                $stats['Error']++;
                if ($debug) fwrite($debug, "Missing '" . $newProp->name . "' property in duplicate. Earlier vcard:\n" . $collectedNames[$fn]->serialize() . "\n\nLater:\n" . $vcard->serialize() . "\n\n");
                
                $vcard->destroy();
                continue 2;
            }

        }

    }

    $vcard->destroy();
    $stats['Ignored duplicates']++;

}

foreach ($collectedNames as $vcard) {

    // Overwriting any old PRODID
    $vcard->PRODID = '-//Sabre//Sabre VObject ' . Version::VERSION . '//EN';
    write($vcard);
    writeStats();

}

echo str_repeat("\n", count($stats)), "\nDone.\n";
<?php

include __DIR__ . '/../vendor/autoload.php';

if ($argc < 4) {
    echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " RRULE benchmark\n";
    echo "\n";
    echo "This script can be used to measure the speed of the 'recurrence expansion'\n";
    echo "system.";
    echo "\n";
    echo "Usage: " . $argv[0] . " inputfile.ics startdate enddate\n";
    die();
}

list(, $inputFile, $startDate, $endDate) = $argv;

$bench = new Hoa\Bench\Bench();
$bench->parse->start();

echo "Parsing.\n";
$vobj = Sabre\VObject\Reader::read(fopen($inputFile, 'r'));

$bench->parse->stop();

echo "Expanding.\n";
$bench->expand->start();

$vobj->expand(new DateTime($startDate), new DateTime($endDate));

$bench->expand->stop();

echo $bench,"\n";
ChangeLog
=========

4.1.2 (2016-12-15)
------------------

* #340: Support for `BYYEARDAY` recurrence when `FREQ=YEARLY`. (@PHPGangsta)
* #341: Support for `BYWEEKNO` recurrence when `FREQ=YEARLY`. (@PHPGangsta)
* Updated to the latest windows timezone data mappings.
* #344: Auto-detecting more Outlook 365-generated timezone identifiers.
  (@jpirkey)
* #348: `FreeBusyGenerator` can now accept streams.
* Support sabre/xml 1.5 and 2.0.
* #355: Support `DateTimeInterface` in more places where only `DateTime` was
  supported. (@gharlan).
* #351: Fixing an inclusive/exclusive problem with `isInTimeRange` and
  `fastForward` with all-day events. (@strokyl, thanks you are brilliant).


4.1.1 (2016-07-15)
------------------

* #327: Throwing `InvalidDataException` in more cases where invalid iCalendar
  dates and times were provided. (@rsto)
* #331: Fix dealing with multiple overridden instances falling on the same
  date/time (@afedyk-sugarcrm).
* #333: Fix endless loop on invalid `BYMONTH` values in recurrence.
  (@PHPGangsta)
* #339: Fixed a few `validate()` results when repair is off. (@PHPGangsta)
* #338: Stripping invalid `BYMONTH=` rules during `validate()` (@PHPGangsta)
* #336: Fix incorrect `BYSECOND=` validation. (@PHPGangsta)


4.1.0 (2016-04-06)
------------------

* #309: When expanding recurring events, the first event should also have a
  `RECURRENCE-ID` property.
* #306: iTip REPLYs to the first instance of a recurring event was not handled
  correctly.
* Slightly better error message during validation of `N` and `ADR` properties.
* #312: Correctly extracing timezone in the iTip broker, even when we don't
  have a master event. (@vkomrakov-sugar).
* When validating a component's property that must appear once and which could
  automatically be repaired, make sure we report the change as 'repaired'.
* Added a PHPUnitAssertions trait. This trait makes it easy to compare two
  vcards or iCalendar objects semantically.
* Better error message when parsing objects with an invalid `VALUE` parameter.


4.0.3 (2016-03-12)
------------------

* #300: Added `VCard::getByType()` to quickly get a property with a specific
  `TYPE` parameter. (@kbond)
* #302: `UNTIL` was not encoded correctly when converting to jCal.
  (@GrahamLinagora)
* #303: `COUNT` is now encoded as an int in jCal instead of a string. (@strokyl)
* #295: `RRULE` now has more validation and repair rules.


4.0.2 (2016-01-11)
------------------

* #288: Only decode `CHARSET` if we're reading vCard 2.1. If it appears
  in any other document, we must ignore it.


4.0.1 (2016-01-04)
------------------

* #284: When generating `CANCEL` iTip messages, we now include `DTEND`.
  (@kewisch)


4.0.0 (2015-12-11)
------------------

* #274: When creating new vCards, the default vCard version is now 4.0.
* #275: `VEVENT`, `VTODO` and `VCARD` now automatically get a `UID` and
  `DTSTAMP` property if this was not already specified.
* `ParseException` now extends `\Exception`.
* `Sabre\VObject\Reader::read` now has a `$charset` argument.
* #272: `Sabre\VObject\Recur\EventIterator::$maxInstances` is now
  `Sabre\VObject\Settings::$maxRecurrences` and is also honored by the
  FreeBusyGenerator.
* #278: `expand()` did not work correctly on events with sub-components.


4.0.0-beta1 (2015-12-02)
------------------------

* #258: Support for expanding events that use `RDATE`. (@jabdoa2)
* #258: Correctly support TZID for events that use `RDATE`. (@jabdoa2)
* #240: `Component\VCalendar::expand()` now returns a new expanded `VCalendar`
  object, instead of editing the existing `VCalendar` in-place. This is a BC
  break.
* #265: Using the new `InvalidDataException` in place of
  `InvalidArgumentException` and `LogicException` in all places where we fail
  because there was something wrong with input data.
* #227: Always add `VALUE=URI` to `PHOTO` properties.
* #235: Always add `VALUE=URI` to `URL` properties.
* It's now possible to override which class is used instead of
  `Component\VCalendar` or `Component\VCard` during parsing.
* #263: Lots of small cleanups. (@jakobsack)
* #220: Automatically stop recurring after 3500 recurrences.
* #41: Allow user to set different encoding than UTF-8 when decoding vCards.
* #41: Support the `ENCODING` parameter from vCard 2.1.
  Both ISO-8859-1 and Windows-1252 are currently supported.
* #185: Fix encoding/decoding of `TIME` values in jCal/jCard.


4.0.0-alpha2 (2015-09-04)
-------------------------

* Updated windows timezone file to support new mexican timezone.
* #239: Added a `BirthdayCalendarGenerator`. (@DominikTo)
* #250: `isInTimeRange()` now considers the timezone for floating dates and
  times. (@armin-hackmann)
* Added a duplicate vcard merging tool for the command line.
* #253: `isInTimeRange()` now correctly handles events that throw the
  `NoInstancesException` exception. (@migrax, @DominikTo)
* #254: The parser threw an `E_NOTICE` for certain invalid objects. It now
  correctly throws a `ParseException`.


4.0.0-alpha1 (2015-07-17)
-------------------------

* sabre/vobject now requires PHP 5.5.
* #244: PHP7 support.
* Lots of speedups and reduced memory usage!
* #160: Support for xCal a.k.a. RFC6321! (@Hywan)
* #192: Support for xCard a.k.a. RFC6351! (@Hywan)
* #139: We now accept `DateTimeInterface` wherever it accepted `DateTime`
   before in arguments. This means that either `DateTime` or
  `DateTimeImmutable` may be used everywhere.
* #242: Full support for the `VAVAILABILITY` component, and calculating
  `VFREEBUSY` based on `VAVAILABILITY` data.
* #186: Fixing conversion of `UTC-OFFSET` properties when going back and
  forward between jCal and iCalendar.
* Properties, Components and Parameters now implement PHP's `JsonSerializable`
  interface.
* #139: We now _always_ return `DateTimeImmutable` from any method. This could
  potentially have big implications if you manipulate Date objects anywhere.
* #161: Simplified `ElementList` by extending `ArrayIterator`.
* Removed `RecurrenceIterator` (use Recur\EventIterator instead).
* Now using php-cs-fixer to automatically enforce and correct CS.
* #233: The `+00:00` timezone is now recognized as UTC. (@c960657)
* #237: Added a `destroy()` method to all documents. This method breaks any
  circular references, allowing PHP to free up memory.
* #197: Made accessing properties and objects by their name a lot faster. This
  especially helps objects that have a lot of sub-components or properties,
  such as large iCalendar objects.
* #197: The `$children` property on components has been changed from `public`
  to `protected`. Use the `children()` method instead to get a flat list of
  objects.
* #244: The `Float` and `Integer` classes have been renamed to `FloatValue`
  and `IntegerValue` to allow PHP 7 compatibility.


3.5.3 (2016-10-06)
------------------

* #331: Fix dealing with multiple overridden instances falling on the same
  date/time (@afedyk-sugarcrm).


3.5.2 (2016-04-24)
-----------------

* #312: Backported a fix related to iTip processing of events with timezones,
  without a master event.


3.5.1 (2016-04-06)
------------------

* #309: When expanding recurring events, the first event should also have a
  `RECURRENCE-ID` property.
* #306: iTip REPLYs to the first instance of a recurring event was not handled
  correctly.


3.5.0 (2016-01-11)
------------------

* This release supports PHP 7, contrary to 3.4.x versions.
* BC Break: `Sabre\VObject\Property\Float` has been renamed to
  `Sabre\VObject\Property\FloatValue`.
* BC Break: `Sabre\VObject\Property\Integer` has been renamed to
  `Sabre\VObject\Property\IntegerValue`.


3.4.9 (2016-01-11)
------------------

* This package now specifies in composer.json that it does not support PHP 7.
  For PHP 7, use version 3.5.x or 4.x.


3.4.8 (2016-01-04)
------------------

* #284: When generating `CANCEL` iTip messages, we now include `DTEND`.
  (@kewisch).


3.4.7 (2015-09-05)
------------------

* #253: Handle `isInTimeRange` for recurring events that have 0 valid
  instances. (@DominikTo, @migrax).


3.4.6 (2015-08-06)
------------------

* #250: Recurring all-day events are incorrectly included in time range
  requests when not using UTC in the time range. (@armin-hackmann)


3.4.5 (2015-06-02)
------------------

* #229: Converting vcards from 3.0 to 4.0 that contained a `LANG` property
  would throw an error.


3.4.4 (2015-05-27)
------------------

* #228: Fixed a 'party crasher' bug in the iTip broker. This would break
  scheduling in some cases.


3.4.3 (2015-05-19)
------------------

* #219: Corrected validation of `EXDATE` properties with more than one value.
* #212: `BYSETPOS` with values below `-1` was broken and could cause infinite
  loops.
* #211: Fix `BYDAY=-5TH` in recurrence iterator. (@lindquist)
* #216: `ENCODING` parameter is now validated for all document types.
* #217: Initializing vCard `DATE` objects with a PHP DateTime object will now
  work correctly. (@thomascube)


3.4.2 (2015-02-25)
------------------

* #210: iTip: Replying to an event without a master event was broken.


3.4.1 (2015-02-24)
------------------

* A minor change to ensure that unittests work correctly in the sabre/dav
  test-suite.


3.4.0 (2015-02-23)
------------------

* #196: Made parsing recurrence rules a lot faster on big calendars.
* Updated windows timezone mappings to latest unicode version.
* #202: Support for parsing and validating `VAVAILABILITY` components. (@Hywan)
* #195: PHP 5.3 compatibility in 'generatevcards' script. (@rickdenhaan)
* #205: Improving handling of multiple `EXDATE` when processing iTip changes.
  (@armin-hackmann)
* #187: Fixed validator rules for `LAST-MODIFIED` properties.
* #188: Retain floating times when generating instances using
  `Recur\EventIterator`.
* #203: Skip tests for timezones that are not supported on older PHP versions,
  instead of a hard fail.
* #204: Dealing a bit better with vCard date-time values that contained
  milliseconds. (which is normally invalid). (@armin-hackmann)


3.3.5 (2015-01-09)
------------------

* #168: Expanding calendars now removes objects with recurrence rules that
  don't have a valid recurrence instance.
* #177: SCHEDULE-STATUS should not contain a reason phrase, only a status
  code.
* #175: Parser can now read and skip the UTF-8 BOM.
* #179: Added `isFloating` to `DATE-TIME` properties.
* #179: Fixed jCal serialization of floating `DATE-TIME` properties.
* #173: vCard converter failed for `X-ABDATE` properties that had no
  `X-ABLABEL`.
* #180: Added `PROFILE_CALDAV` and `PROFILE_CARDDAV` to enable validation rules
  specific for CalDAV/CardDAV servers.
* #176: A missing `UID` is no longer an error, but a warning for the vCard
  validator, unless `PROFILE_CARDDAV` is specified.


3.3.4 (2014-11-19)
------------------

* #154: Converting `ANNIVERSARY` to `X-ANNIVERSARY` and `X-ABDATE` and
  vice-versa when converting to/from vCard 4.
* #154: It's now possible to easily select all vCard properties belonging to
  a single group with `$vcard->{'ITEM1.'}` syntax. (@armin-hackmann)
* #156: Simpler way to check if a string is UTF-8. (@Hywan)
* Unittest improvements.
* #159: The recurrence iterator, freebusy generator and iCalendar DATE and
  DATE-TIME properties can now all accept a reference timezone when working
  floating times or all-day events.
* #159: Master events will no longer get a `RECURRENCE-ID` when expanding.
* #159: `RECURRENCE-ID` for all-day events will now be correct when expanding.
* #163: Added a `getTimeZone()` method to `VTIMEZONE` components.


3.3.3 (2014-10-09)
------------------

* #142: `CANCEL` and `REPLY` messages now include the `DTSTART` from the
  original event.
* #143: `SCHEDULE-AGENT` on the `ORGANIZER` property is respected.
* #144: `PARTSTAT=NEEDS-ACTION` is now set for new invites, if no `PARTSTAT` is
  set to support the inbox feature of iOS.
* #147: Bugs related to scheduling all-day events.
* #148: Ignore events that have attendees but no organizer.
* #149: Avoiding logging errors during timezone detection. This is a workaround
  for a PHP bug.
* Support for "Line Islands Standard Time" windows timezone.
* #154: Correctly work around vCard parameters that have a value but no name.


3.3.2 (2014-09-19)
------------------

* Changed: iTip broker now sets RSVP status to false when replies are received.
* #118: iTip Message now has a `getScheduleStatus()` method.
* #119: Support for detecting 'significant changes'.
* #120: Support for `SCHEDULE-FORCE-SEND`.
* #121: iCal demands parameters containing the + sign to be quoted.
* #122: Don't generate REPLY messages for events that have been cancelled.
* #123: Added `SUMMARY` to iTip messages.
* #130: Incorrect validation rules for `RELATED` (should be `RELATED-TO`).
* #128: `ATTACH` in iCalendar is `URI` by default, not `BINARY`.
* #131: RRULE that doesn't provide a single valid instance now throws an
  exception.
* #136: Validator rejects *all* control characters. We were missing a few.
* #133: Splitter objects will throw exceptions when receiving incompatible
  objects.
* #127: Attendees who delete recurring event instances events they had already
  declined earlier will no longer generate another reply.
* #125: Send CANCEL messages when ORGANIZER property gets deleted.


3.3.1 (2014-08-18)
------------------

* Changed: It's now possible to pass DateTime objects when using the magic
  setters on properties. (`$event->DTSTART = new DateTime('now')`).
* #111: iTip Broker does not process attendee adding events to EXDATE.
* #112: EventIterator now sets TZID on RECURRENCE-ID.
* #113: Timezone support during creation of iTip REPLY messages.
* #114: VTIMEZONE is retained when generating new REQUEST objects.
* #114: Support for 'MAILTO:' style email addresses (in uppercase) in the iTip
  broker. This improves evolution support.
* #115: Using REQUEST-STATUS from REPLY messages and now propegating that into
  SCHEDULE-STATUS.


3.3.0 (2014-08-07)
------------------

* We now use PSR-4 for the directory structure. This means that everything
  that was used to be in the `lib/Sabre/VObject` directory is now moved to
  `lib/`. If you use composer to load this library, you shouldn't have to do
  anything about that though.
* VEVENT now get populated with a DTSTAMP and UID property by default.
* BC Break: Removed the 'includes.php' file. Use composer instead.
* #103: Added support for processing [iTip][iTip] messages. This allows a user
  to parse incoming iTip messages and apply the result on existing calendars,
  or automatically generate invites/replies/cancellations based on changes that
  a user made on objects.
* #75, #58, #18: Fixes related to overriding the first event in recurrences.
* Added: VCalendar::getBaseComponent to find the 'master' component in a
  calendar.
* #51: Support for iterating RDATE properties.
* Fixed: Issue #101: RecurrenceIterator::nextMonthly() shows events that are
  excluded events with wrong time


3.2.4 (2014-07-14)
------------------

* Added: Issue #98. The VCardConverter now takes `X-APPLE-OMIT-YEAR` into
  consideration when converting between vCard 3 and 4.
* Fixed: Issue #96. Some support for Yahoo's broken vcards.
* Fixed: PHP 5.3 support was broken in the cli tool.


3.2.3 (2014-06-12)
------------------

* Validator now checks if DUE and DTSTART are of the same type in VTODO, and
  ensures that DUE is always after DTSTART.
* Removed documentation from source repository, to http://sabre.io/vobject/
* Expanded the vobject cli tool validation output to make it easier to find
  issues.
* Fixed: vobject repair. It was not working for iCalendar objects.


3.2.2 (2014-05-07)
------------------

* Minor tweak in unittests to make it run on PHP 5.5.12. Json-prettifying
  slightly changed which caused the test to fail.


3.2.1 (2014-05-03)
------------------

* Minor tweak to make the unittests run with the latest hhvm on travis.
* Updated timezone definitions.
* Updated copyright links to point to http://sabre.io/


3.2.0 (2014-04-02)
------------------

* Now hhvm compatible!
* The validator can now detect a _lot_ more problems. Many rules for both
  iCalendar and vCard were added.
* Added: bin/generate_vcards, a utility to generate random vcards for testing
  purposes. Patches are welcome to add more data.
* Updated: Windows timezone mapping to latest version from unicode.org
* Changed: The timezone maps are now loaded in from external files, in
  lib/Sabre/VObject/timezonedata.
* Added: Fixing badly encoded URL's from google contacts vcards.
* Fixed: Issue #68. Couldn't decode properties ending in a colon.
* Fixed: Issue #72. RecurrenceIterator should respect timezone in the UNTIL
  clause.
* Fixed: Issue #67. BYMONTH limit on DAILY recurrences.
* Fixed: Issue #26. Return a more descriptive error when coming across broken
  BYDAY rules.
* Fixed: Issue #28. Incorrect timezone detection for some timezones.
* Fixed: Issue #70. Casting a parameter with a null value to string would fail.
* Added: Support for rfc6715 and rfc6474.
* Added: Support for DateTime objects in the VCard DATE-AND-OR-TIME property.
* Added: UUIDUtil, for easily creating unique identifiers.
* Fixed: Issue #83. Creating new VALUE=DATE objects using php's DateTime.
* Fixed: Issue #86. Don't go into an infinite loop when php errors are
  disabled and an invalid file is read.


3.1.4 (2014-03-30)
------------------

* Fixed: Issue #87: Several compatibility fixes related to timezone handling
  changes in PHP 5.5.10.


3.1.3 (2013-10-02)
------------------

* Fixed: Support from properties from draft-daboo-valarm-extensions-04. Issue
  #56.
* Fixed: Issue #54. Parsing a stream of multiple vcards separated by more than
  one newline. Thanks @Vedmak for the patch.
* Fixed: Serializing vcard 2.1 parameters with no name caused a literal '1' to
  be inserted.
* Added: VCardConverter removed properties that are no longer supported in vCard
  4.0.
* Added: vCards with a minimum number of values (such as N), but don't have that
  many, are now automatically padded with empty components.
* Added: The vCard validator now also checks for a minimum number of components,
  and has the ability to repair these.
* Added: Some support for vCard 2.1 in the VCard converter, to upgrade to vCard
  3.0 or 4.0.
* Fixed: Issue 60 Use Document::$componentMap when instantiating the top-level
  VCalendar and VCard components.
* Fixed: Issue 62: Parsing iCalendar parameters with no value.
* Added: --forgiving option to vobject utility.
* Fixed: Compound properties such as ADR were not correctly split up in vCard
  2.1 quoted printable-encoded properties.
* Fixed: Issue 64: Encoding of binary properties of converted vCards. Thanks
  @DominikTo for the patch.


3.1.2 (2013-08-13)
------------------

* Fixed: Setting correct property group on VCard conversion


3.1.1 (2013-08-02)
------------------

* Fixed: Issue #53. A regression in RecurrenceIterator.


3.1.0 (2013-07-27)
------------------

* Added: bad-ass new cli debugging utility (in bin/vobject).
* Added: jCal and jCard parser.
* Fixed: URI properties should not escape ; and ,.
* Fixed: VCard 4 documents now correctly use URI as a default value-type for
  PHOTO and others. BINARY no longer exists in vCard 4.
* Added: Utility to convert between 2.1, 3.0 and 4.0 vCards.
* Added: You can now add() multiple parameters to a property in one call.
* Added: Parameter::has() for easily checking if a parameter value exists.
* Added: VCard::preferred() to find a preferred email, phone number, etc for a
  contact.
* Changed: All $duration properties are now public.
* Added: A few validators for iCalendar documents.
* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception
  events are out of order in the iCalendar file.
* Fixed: Issue #48. Overridden events in the recurrence iterator that were past
  the UNTIL date were ignored.
* Added: getDuration for DURATION values such as TRIGGER. Thanks to
  @SimonSimCity.
* Fixed: Issue #52. vCard 2.1 parameters with no name may lose values if there's
  more than 1. Thanks to @Vedmak.


3.0.0 (2013-06-21)
------------------

* Fixed: includes.php file was still broken. Our tool to generate it had some
  bugs.


3.0.0-beta4 (2013-06-21)
------------------------

* Fixed: includes.php was no longer up to date.


3.0.0-beta3 (2013-06-17)
------------------------

* Added: OPTION_FORGIVING now also allows slashes in property names.
* Fixed: DateTimeParser no longer fails on dates with years < 1000 & > 4999
* Fixed: Issue 36: Workaround for the recurrenceiterator and caldav events with
  a missing base event.
* Fixed: jCard encoding of TIME properties.
* Fixed: jCal encoding of REQUEST-STATUS, GEO and PERIOD values.


3.0.0-beta2 (2013-06-10)
------------------------

* Fixed: Corrected includes.php file.
* Fixed: vCard date-time parser supported extended-format dates as well.
* Changed: Properties have been moved to an ICalendar or VCard directory.
* Fixed: Couldn't parse vCard 3 extended format dates and times.
* Fixed: Couldn't export jCard DATE values correctly.
* Fixed: Recursive loop in ICalendar\DateTime property.


3.0.0-beta1 (2013-06-07)
------------------------

* Added: jsonSerialize() for creating jCal and jCard documents.
* Added: helper method to parse vCard dates and times.
* Added: Specialized classes for FLOAT, LANGUAGE-TAG, TIME, TIMESTAMP,
  DATE-AND-OR-TIME, CAL-ADDRESS, UNKNOWN and UTC-OFFSET properties.
* Removed: CommaSeparatedText property. Now included into Text.
* Fixed: Multiple parameters with the same name are now correctly encoded.
* Fixed: Parameter values containing a comma are now enclosed in double-quotes.
* Fixed: Iterating parameter values should now fully work as expected.
* Fixed: Support for vCard 2.1 nameless parameters.
* Changed: $valueMap, $componentMap and $propertyMap now all use fully-qualified
  class names, so they are actually overridable.
* Fixed: Updating DATE-TIME to DATE values now behaves like expected.


3.0.0-alpha4 (2013-05-31)
-------------------------

* Added: It's now possible to send parser options to the splitter classes.
* Added: A few tweaks to improve component and property creation.


3.0.0-alpha3 (2013-05-13)
-------------------------

* Changed: propertyMap, valueMap and componentMap are now static properties.
* Changed: Component::remove() will throw an exception when trying to a node
  that's not a child of said component.
* Added: Splitter objects are now faster, line numbers are accurately reported
  and use less memory.
* Added: MimeDir parser can now continue parsing with the same stream buffer.
* Fixed: vobjectvalidate.php is operational again.
* Fixed: \r is properly stripped in text values.
* Fixed: QUOTED-PRINTABLE is now correctly encoded as well as encoded, for
  vCards 2.1.
* Fixed: Parser assumes vCard 2.1, if no version was supplied.


3.0.0-alpha2 (2013-05-22)
-------------------------

* Fixed: vCard URL properties were referencing a non-existant class.


3.0.0-alpha1 (2013-05-21)
-------------------------

* Fixed: Now correctly dealing with escaping of properties. This solves the
  problem with double-backslashes where they don't belong.
* Added: Easy support for properties with more than one value, using setParts
  and getParts.
* Added: Support for broken 2.1 vCards produced by microsoft.
* Added: Automatically decoding quoted-printable values.
* Added: Automatically decoding base64 values.
* Added: Decoding RFC6868 parameter values (uses ^ as an escape character).
* Added: Fancy new MimeDir parser that can also parse streams.
* Added: Automatically mapping many, many properties to a property-class with
  specialized API's.
* Added: remove() method for easily removing properties and sub-components
  components.
* Changed: Components, Properties and Parameters can no longer be created with
  Component::create, Property::create and Parameter::create. They must instead
  be created through the root component. (A VCalendar or VCard object).
* Changed: API for DateTime properties has slightly changed.
* Changed: the ->value property is now protected everywhere. Use getParts() and
  getValue() instead.
* BC Break: No support for mac newlines (\r). Never came across these anyway.
* Added: add() method to the Property class.
* Added: It's now possible to easy set multi-value properties as arrays.
* Added: When setting date-time properties you can just pass PHP's DateTime
  object.
* Added: New components automatically get a bunch of default properties, such as
  VERSION and CALSCALE.
* Added: You can add new sub-components much quicker with the magic setters, and
  add() method.


2.1.7 (2015-01-21)
------------------

* Fixed: Issue #94, a workaround for bad escaping of ; and , in compound
  properties. It's not a full solution, but it's an improvement for those
  stuck in the 2.1 versions.


2.1.6 (2014-12-10)
------------------

* Fixed: Minor change to make sure that unittests succeed on every PHP version.


2.1.5 (2014-06-03)
------------------

* Fixed: #94: Better parameter escaping.
* Changed: Documentation cleanups.


2.1.4 (2014-03-30)
------------------

* Fixed: Issue #87: Several compatibility fixes related to timezone handling
  changes in PHP 5.5.10.


2.1.3 (2013-10-02)
------------------

* Fixed: Issue #55. \r must be stripped from property values.
* Fixed: Issue #65. Putting quotes around parameter values that contain a colon.


2.1.2 (2013-08-02)
------------------

* Fixed: Issue #53. A regression in RecurrenceIterator.


2.1.1 (2013-07-27)
------------------

* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception
  events are out of order in the iCalendar file.
* Fixed: Issue #48. Overridden events in the recurrence iterator that were past
  the UNTIL date were ignored.


2.1.0 (2013-06-17)
------------------

* This version is fully backwards compatible with 2.0.\*. However, it contains a
  few new API's that mimic the VObject 3 API. This allows it to be used a
  'bridge' version. Specifically, this new version exists so SabreDAV 1.7 and
  1.8 can run with both the 2 and 3 versions of this library.
* Added: Property\DateTime::hasTime().
* Added: Property\MultiDateTime::hasTime().
* Added: Property::getValue().
* Added: Document class.
* Added: Document::createComponent and Document::createProperty.
* Added: Parameter::getValue().


2.0.7 (2013-03-05)
------------------

* Fixed: Microsoft re-uses their magic numbers for different timezones,
  specifically id 2 for both Sarajevo and Lisbon). A workaround was added to
  deal with this.


2.0.6 (2013-02-17)
------------------

* Fixed: The reader now properly parses parameters without a value.


2.0.5 (2012-11-05)
------------------

* Fixed: The FreeBusyGenerator is now properly using the factory methods for
  creation of components and properties.


2.0.4 (2012-11-02)
------------------

* Added: Known Lotus Notes / Domino timezone id's.


2.0.3 (2012-10-29)
------------------

* Added: Support for 'GMT+????' format in TZID's.
* Added: Support for formats like SystemV/EST5EDT in TZID's.
* Fixed: RecurrenceIterator now repairs recurrence rules where UNTIL < DTSTART.
* Added: Support for BYHOUR in FREQ=DAILY (@hollodk).
* Added: Support for BYHOUR and BYDAY in FREQ=WEEKLY.


2.0.2 (2012-10-06)
------------------

* Added: includes.php file, to load the entire library in one go.
* Fixed: A problem with determining alarm triggers for TODO's.


2.0.1 (2012-09-22)
------------------

* Removed: Element class. It wasn't used.
* Added: Basic validation and repair methods for broken input data.
* Fixed: RecurrenceIterator could infinitely loop when an INTERVAL of 0 was
  specified.
* Added: A cli script that can validate and automatically repair vcards and
  iCalendar objects.
* Added: A new 'Compound' property, that can automatically split up parts for
  properties such as N, ADR, ORG and CATEGORIES.
* Added: Splitter classes, that can split up large objects (such as exports)
  into individual objects (thanks @DominikTo and @armin-hackmann).
* Added: VFREEBUSY component, which allows easily checking wether timeslots are
  available.
* Added: The Reader class now has a 'FORGIVING' option, which allows it to parse
  properties with incorrect characters in the name (at this time, it just allows
  underscores).
* Added: Also added the 'IGNORE_INVALID_LINES' option, to completely disregard
  any invalid lines.
* Fixed: A bug in Windows timezone-id mappings for times created in Greenlands
  timezone (sorry Greenlanders! I do care!).
* Fixed: DTEND was not generated correctly for VFREEBUSY reports.
* Fixed: Parser is at least 25% faster with real-world data.


2.0.0 (2012-08-08)
------------------

* VObject is now a separate project from SabreDAV. See the SabreDAV changelog
  for version information before 2.0.
* New: VObject library now uses PHP 5.3 namespaces.
* New: It's possible to specify lists of parameters when constructing
  properties.
* New: made it easier to construct the FreeBusyGenerator.

[iTip]: http://tools.ietf.org/html/rfc5546
{
    "name": "sabre/vobject",
    "description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
    "keywords" : [
        "iCalendar",
        "iCal",
        "vCalendar",
        "vCard",
        "jCard",
        "jCal",
        "ics",
        "vcf",
        "xCard",
        "xCal",
        "freebusy",
        "recurrence",
        "availability",
        "rfc2425",
        "rfc2426",
        "rfc2739",
        "rfc4770",
        "rfc5545",
        "rfc5546",
        "rfc6321",
        "rfc6350",
        "rfc6351",
        "rfc6474",
        "rfc6638",
        "rfc6715",
        "rfc6868"
    ],
    "homepage" : "http://sabre.io/vobject/",
    "license" : "BSD-3-Clause",
    "require" : {
        "php"          : ">=5.5",
        "ext-mbstring" : "*",
        "sabre/xml"    : ">=1.5 <3.0"
    },
    "require-dev" : {
        "phpunit/phpunit" : "*",
        "sabre/cs"        : "^1.0.0"

    },
    "suggest" : {
        "hoa/bench"       : "If you would like to run the benchmark scripts"
    },
    "authors" : [
        {
            "name" : "Evert Pot",
            "email" : "me@evertpot.com",
            "homepage" : "http://evertpot.com/",
            "role" : "Developer"
        },
        {
            "name" : "Dominik Tobschall",
            "email" : "dominik@fruux.com",
            "homepage" : "http://tobschall.de/",
            "role" : "Developer"
        },
        {
            "name" : "Ivan Enderlin",
            "email" : "ivan.enderlin@hoa-project.net",
            "homepage" : "http://mnt.io/",
            "role" : "Developer"
        }
    ],
    "support" : {
        "forum" : "https://groups.google.com/group/sabredav-discuss",
        "source" : "https://github.com/fruux/sabre-vobject"
    },
    "autoload" : {
        "psr-4" : {
            "Sabre\\VObject\\" : "lib/"
        }
    },
    "bin" : [
        "bin/vobject",
        "bin/generate_vcards"
    ],
    "extra" : {
        "branch-alias" : {
            "dev-master" : "4.0.x-dev"
        }
    },
    "config" : {
        "bin-dir" : "bin"
    }
}
<?php

namespace Sabre\VObject;

use Sabre\VObject\Component\VCalendar;

/**
 * This class generates birthday calendars.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Dominik Tobschall (http://tobschall.de/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class BirthdayCalendarGenerator {

    /**
     * Input objects.
     *
     * @var array
     */
    protected $objects = [];

    /**
     * Default year.
     * Used for dates without a year.
     */
    const DEFAULT_YEAR = 2000;

    /**
     * Output format for the SUMMARY.
     *
     * @var string
     */
    protected $format = '%1$s\'s Birthday';

    /**
     * Creates the generator.
     *
     * Check the setTimeRange and setObjects methods for details about the
     * arguments.
     *
     * @param mixed $objects
     */
    function __construct($objects = null) {

        if ($objects) {
            $this->setObjects($objects);
        }

    }

    /**
     * Sets the input objects.
     *
     * You must either supply a vCard as a string or as a Component/VCard object.
     * It's also possible to supply an array of strings or objects.
     *
     * @param mixed $objects
     *
     * @return void
     */
    function setObjects($objects) {

        if (!is_array($objects)) {
            $objects = [$objects];
        }

        $this->objects = [];
        foreach ($objects as $object) {

            if (is_string($object)) {

                $vObj = Reader::read($object);
                if (!$vObj instanceof Component\VCard) {
                    throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
                }

                $this->objects[] = $vObj;

            } elseif ($object instanceof Component\VCard) {

                $this->objects[] = $object;

            } else {

                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');

            }

        }

    }

    /**
     * Sets the output format for the SUMMARY
     *
     * @param string $format
     *
     * @return void
     */
    function setFormat($format) {

        $this->format = $format;

    }

    /**
     * Parses the input data and returns a VCALENDAR.
     *
     * @return Component/VCalendar
     */
    function getResult() {

        $calendar = new VCalendar();

        foreach ($this->objects as $object) {

            // Skip if there is no BDAY property.
            if (!$object->select('BDAY')) {
                continue;
            }

            // We've seen clients (ez-vcard) putting "BDAY:" properties
            // without a value into vCards. If we come across those, we'll
            // skip them.
            if (empty($object->BDAY->getValue())) {
                continue;
            }

            // We're always converting to vCard 4.0 so we can rely on the
            // VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
            $object = $object->convert(Document::VCARD40);

            // Skip if the card has no FN property.
            if (!isset($object->FN)) {
                continue;
            }

            // Skip if the BDAY property is not of the right type.
            if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) {
                continue;
            }

            // Skip if we can't parse the BDAY value.
            try {
                $dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
            } catch (InvalidDataException $e) {
                continue;
            }

            // Set a year if it's not set.
            $unknownYear = false;

            if (!$dateParts['year']) {
                $object->BDAY = self::DEFAULT_YEAR . '-' . $dateParts['month'] . '-' . $dateParts['date'];

                $unknownYear = true;
            }

            // Create event.
            $event = $calendar->add('VEVENT', [
                'SUMMARY' => sprintf($this->format, $object->FN->getValue()),
                'DTSTART' => new \DateTime($object->BDAY->getValue()),
                'RRULE'   => 'FREQ=YEARLY',
                'TRANSP'  => 'TRANSPARENT',
            ]);

            // add VALUE=date
            $event->DTSTART['VALUE'] = 'DATE';

            // Add X-SABRE-BDAY property.
            if ($unknownYear) {
                $event->add('X-SABRE-BDAY', 'BDAY', [
                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
                    'X-SABRE-VCARD-FN'  => $object->FN->getValue(),
                    'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
                ]);
            } else {
                $event->add('X-SABRE-BDAY', 'BDAY', [
                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
                    'X-SABRE-VCARD-FN'  => $object->FN->getValue(),
                ]);
            }

        }

        return $calendar;

    }

}
<?php

namespace Sabre\VObject;

use
    InvalidArgumentException;

/**
 * This is the CLI interface for sabre-vobject.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Cli {

    /**
     * No output.
     *
     * @var bool
     */
    protected $quiet = false;

    /**
     * Help display.
     *
     * @var bool
     */
    protected $showHelp = false;

    /**
     * Wether to spit out 'mimedir' or 'json' format.
     *
     * @var string
     */
    protected $format;

    /**
     * JSON pretty print.
     *
     * @var bool
     */
    protected $pretty;

    /**
     * Source file.
     *
     * @var string
     */
    protected $inputPath;

    /**
     * Destination file.
     *
     * @var string
     */
    protected $outputPath;

    /**
     * output stream.
     *
     * @var resource
     */
    protected $stdout;

    /**
     * stdin.
     *
     * @var resource
     */
    protected $stdin;

    /**
     * stderr.
     *
     * @var resource
     */
    protected $stderr;

    /**
     * Input format (one of json or mimedir).
     *
     * @var string
     */
    protected $inputFormat;

    /**
     * Makes the parser less strict.
     *
     * @var bool
     */
    protected $forgiving = false;

    /**
     * Main function.
     *
     * @return int
     */
    function main(array $argv) {

        // @codeCoverageIgnoreStart
        // We cannot easily test this, so we'll skip it. Pretty basic anyway.

        if (!$this->stderr) {
            $this->stderr = fopen('php://stderr', 'w');
        }
        if (!$this->stdout) {
            $this->stdout = fopen('php://stdout', 'w');
        }
        if (!$this->stdin) {
            $this->stdin = fopen('php://stdin', 'r');
        }

        // @codeCoverageIgnoreEnd


        try {

            list($options, $positional) = $this->parseArguments($argv);

            if (isset($options['q'])) {
                $this->quiet = true;
            }
            $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION));

            foreach ($options as $name => $value) {

                switch ($name) {

                    case 'q' :
                        // Already handled earlier.
                        break;
                    case 'h' :
                    case 'help' :
                        $this->showHelp();
                        return 0;
                        break;
                    case 'format' :
                        switch ($value) {

                            // jcard/jcal documents
                            case 'jcard' :
                            case 'jcal' :

                            // specific document versions
                            case 'vcard21' :
                            case 'vcard30' :
                            case 'vcard40' :
                            case 'icalendar20' :

                            // specific formats
                            case 'json' :
                            case 'mimedir' :

                            // icalendar/vcad
                            case 'icalendar' :
                            case 'vcard' :
                                $this->format = $value;
                                break;

                            default :
                                throw new InvalidArgumentException('Unknown format: ' . $value);

                        }
                        break;
                    case 'pretty' :
                        if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
                            $this->pretty = true;
                        }
                        break;
                    case 'forgiving' :
                        $this->forgiving = true;
                        break;
                    case 'inputformat' :
                        switch ($value) {
                            // json formats
                            case 'jcard' :
                            case 'jcal' :
                            case 'json' :
                                $this->inputFormat = 'json';
                                break;

                            // mimedir formats
                            case 'mimedir' :
                            case 'icalendar' :
                            case 'vcard' :
                            case 'vcard21' :
                            case 'vcard30' :
                            case 'vcard40' :
                            case 'icalendar20' :

                                $this->inputFormat = 'mimedir';
                                break;

                            default :
                                throw new InvalidArgumentException('Unknown format: ' . $value);

                        }
                        break;
                    default :
                        throw new InvalidArgumentException('Unknown option: ' . $name);

                }

            }

            if (count($positional) === 0) {
                $this->showHelp();
                return 1;
            }

            if (count($positional) === 1) {
                throw new InvalidArgumentException('Inputfile is a required argument');
            }

            if (count($positional) > 3) {
                throw new InvalidArgumentException('Too many arguments');
            }

            if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
                throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
            }

        } catch (InvalidArgumentException $e) {
            $this->showHelp();
            $this->log('Error: ' . $e->getMessage(), 'red');
            return 1;
        }

        $command = $positional[0];

        $this->inputPath = $positional[1];
        $this->outputPath = isset($positional[2]) ? $positional[2] : '-';

        if ($this->outputPath !== '-') {
            $this->stdout = fopen($this->outputPath, 'w');
        }

        if (!$this->inputFormat) {
            if (substr($this->inputPath, -5) === '.json') {
                $this->inputFormat = 'json';
            } else {
                $this->inputFormat = 'mimedir';
            }
        }
        if (!$this->format) {
            if (substr($this->outputPath, -5) === '.json') {
                $this->format = 'json';
            } else {
                $this->format = 'mimedir';
            }
        }


        $realCode = 0;

        try {

            while ($input = $this->readInput()) {

                $returnCode = $this->$command($input);
                if ($returnCode !== 0) $realCode = $returnCode;

            }

        } catch (EofException $e) {
            // end of file
        } catch (\Exception $e) {
            $this->log('Error: ' . $e->getMessage(), 'red');
            return 2;
        }

        return $realCode;

    }

    /**
     * Shows the help message.
     *
     * @return void
     */
    protected function showHelp() {

        $this->log('Usage:', 'yellow');
        $this->log("  vobject [options] command [arguments]");
        $this->log('');
        $this->log('Options:', 'yellow');
        $this->log($this->colorize('green', '  -q            ') . "Don't output anything.");
        $this->log($this->colorize('green', '  -help -h      ') . "Display this help message.");
        $this->log($this->colorize('green', '  --format      ') . "Convert to a specific format. Must be one of: vcard, vcard21,");
        $this->log($this->colorize('green', '  --forgiving   ') . "Makes the parser less strict.");
        $this->log("                vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.");
        $this->log($this->colorize('green', '  --inputformat ') . "If the input format cannot be guessed from the extension, it");
        $this->log("                must be specified here.");
        // Only PHP 5.4 and up
        if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
            $this->log($this->colorize('green', '  --pretty      ') . "json pretty-print.");
        }
        $this->log('');
        $this->log('Commands:', 'yellow');
        $this->log($this->colorize('green', '  validate') . ' source_file              Validates a file for correctness.');
        $this->log($this->colorize('green', '  repair') . ' source_file [output_file]  Repairs a file.');
        $this->log($this->colorize('green', '  convert') . ' source_file [output_file] Converts a file.');
        $this->log($this->colorize('green', '  color') . ' source_file                 Colorize a file, useful for debbugging.');
        $this->log(
        <<<HELP

If source_file is set as '-', STDIN will be used.
If output_file is omitted, STDOUT will be used.
All other output is sent to STDERR.

HELP
        );

        $this->log('Examples:', 'yellow');
        $this->log('   vobject convert contact.vcf contact.json');
        $this->log('   vobject convert --format=vcard40 old.vcf new.vcf');
        $this->log('   vobject convert --inputformat=json --format=mimedir - -');
        $this->log('   vobject color calendar.ics');
        $this->log('');
        $this->log('https://github.com/fruux/sabre-vobject', 'purple');

    }

    /**
     * Validates a VObject file.
     *
     * @param Component $vObj
     *
     * @return int
     */
    protected function validate(Component $vObj) {

        $returnCode = 0;

        switch ($vObj->name) {
            case 'VCALENDAR' :
                $this->log("iCalendar: " . (string)$vObj->VERSION);
                break;
            case 'VCARD' :
                $this->log("vCard: " . (string)$vObj->VERSION);
                break;
        }

        $warnings = $vObj->validate();
        if (!count($warnings)) {
            $this->log("  No warnings!");
        } else {

            $levels = [
                1 => 'REPAIRED',
                2 => 'WARNING',
                3 => 'ERROR',
            ];
            $returnCode = 2;
            foreach ($warnings as $warn) {

                $extra = '';
                if ($warn['node'] instanceof Property) {
                    $extra = ' (property: "' . $warn['node']->name . '")';
                }
                $this->log("  [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);

            }

        }

        return $returnCode;

    }

    /**
     * Repairs a VObject file.
     *
     * @param Component $vObj
     *
     * @return int
     */
    protected function repair(Component $vObj) {

        $returnCode = 0;

        switch ($vObj->name) {
            case 'VCALENDAR' :
                $this->log("iCalendar: " . (string)$vObj->VERSION);
                break;
            case 'VCARD' :
                $this->log("vCard: " . (string)$vObj->VERSION);
                break;
        }

        $warnings = $vObj->validate(Node::REPAIR);
        if (!count($warnings)) {
            $this->log("  No warnings!");
        } else {

            $levels = [
                1 => 'REPAIRED',
                2 => 'WARNING',
                3 => 'ERROR',
            ];
            $returnCode = 2;
            foreach ($warnings as $warn) {

                $extra = '';
                if ($warn['node'] instanceof Property) {
                    $extra = ' (property: "' . $warn['node']->name . '")';
                }
                $this->log("  [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);

            }

        }
        fwrite($this->stdout, $vObj->serialize());

        return $returnCode;

    }

    /**
     * Converts a vObject file to a new format.
     *
     * @param Component $vObj
     *
     * @return int
     */
    protected function convert($vObj) {

        $json = false;
        $convertVersion = null;
        $forceInput = null;

        switch ($this->format) {
            case 'json' :
                $json = true;
                if ($vObj->name === 'VCARD') {
                    $convertVersion = Document::VCARD40;
                }
                break;
            case 'jcard' :
                $json = true;
                $forceInput = 'VCARD';
                $convertVersion = Document::VCARD40;
                break;
            case 'jcal' :
                $json = true;
                $forceInput = 'VCALENDAR';
                break;
            case 'mimedir' :
            case 'icalendar' :
            case 'icalendar20' :
            case 'vcard' :
                break;
            case 'vcard21' :
                $convertVersion = Document::VCARD21;
                break;
            case 'vcard30' :
                $convertVersion = Document::VCARD30;
                break;
            case 'vcard40' :
                $convertVersion = Document::VCARD40;
                break;

        }

        if ($forceInput && $vObj->name !== $forceInput) {
            throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
        }
        if ($convertVersion) {
            $vObj = $vObj->convert($convertVersion);
        }
        if ($json) {
            $jsonOptions = 0;
            if ($this->pretty) {
                $jsonOptions = JSON_PRETTY_PRINT;
            }
            fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
        } else {
            fwrite($this->stdout, $vObj->serialize());
        }

        return 0;

    }

    /**
     * Colorizes a file.
     *
     * @param Component $vObj
     *
     * @return int
     */
    protected function color($vObj) {

        fwrite($this->stdout, $this->serializeComponent($vObj));

    }

    /**
     * Returns an ansi color string for a color name.
     *
     * @param string $color
     *
     * @return string
     */
    protected function colorize($color, $str, $resetTo = 'default') {

        $colors = [
            'cyan'    => '1;36',
            'red'     => '1;31',
            'yellow'  => '1;33',
            'blue'    => '0;34',
            'green'   => '0;32',
            'default' => '0',
            'purple'  => '0;35',
        ];
        return "\033[" . $colors[$color] . 'm' . $str . "\033[" . $colors[$resetTo] . "m";

    }

    /**
     * Writes out a string in specific color.
     *
     * @param string $color
     * @param string $str
     *
     * @return void
     */
    protected function cWrite($color, $str) {

        fwrite($this->stdout, $this->colorize($color, $str));

    }

    protected function serializeComponent(Component $vObj) {

        $this->cWrite('cyan', 'BEGIN');
        $this->cWrite('red', ':');
        $this->cWrite('yellow', $vObj->name . "\n");

        /**
         * Gives a component a 'score' for sorting purposes.
         *
         * This is solely used by the childrenSort method.
         *
         * A higher score means the item will be lower in the list.
         * To avoid score collisions, each "score category" has a reasonable
         * space to accomodate elements. The $key is added to the $score to
         * preserve the original relative order of elements.
         *
         * @param int $key
         * @param array $array
         *
         * @return int
         */
        $sortScore = function($key, $array) {

            if ($array[$key] instanceof Component) {

                // We want to encode VTIMEZONE first, this is a personal
                // preference.
                if ($array[$key]->name === 'VTIMEZONE') {
                    $score = 300000000;
                    return $score + $key;
                } else {
                    $score = 400000000;
                    return $score + $key;
                }
            } else {
                // Properties get encoded first
                // VCARD version 4.0 wants the VERSION property to appear first
                if ($array[$key] instanceof Property) {
                    if ($array[$key]->name === 'VERSION') {
                        $score = 100000000;
                        return $score + $key;
                    } else {
                        // All other properties
                        $score = 200000000;
                        return $score + $key;
                    }
                }
            }

        };

        $children = $vObj->children();
        $tmp = $children;
        uksort(
            $children,
            function($a, $b) use ($sortScore, $tmp) {

                $sA = $sortScore($a, $tmp);
                $sB = $sortScore($b, $tmp);

                return $sA - $sB;

            }
        );

        foreach ($children as $child) {
            if ($child instanceof Component) {
                $this->serializeComponent($child);
            } else {
                $this->serializeProperty($child);
            }
        }

        $this->cWrite('cyan', 'END');
        $this->cWrite('red', ':');
        $this->cWrite('yellow', $vObj->name . "\n");

    }

    /**
     * Colorizes a property.
     *
     * @param Property $property
     *
     * @return void
     */
    protected function serializeProperty(Property $property) {

        if ($property->group) {
            $this->cWrite('default', $property->group);
            $this->cWrite('red', '.');
        }

        $this->cWrite('yellow', $property->name);

        foreach ($property->parameters as $param) {

            $this->cWrite('red', ';');
            $this->cWrite('blue', $param->serialize());

        }
        $this->cWrite('red', ':');

        if ($property instanceof Property\Binary) {

            $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');

        } else {

            $parts = $property->getParts();
            $first1 = true;
            // Looping through property values
            foreach ($parts as $part) {
                if ($first1) {
                    $first1 = false;
                } else {
                    $this->cWrite('red', $property->delimiter);
                }
                $first2 = true;
                // Looping through property sub-values
                foreach ((array)$part as $subPart) {
                    if ($first2) {
                        $first2 = false;
                    } else {
                        // The sub-value delimiter is always comma
                        $this->cWrite('red', ',');
                    }

                    $subPart = strtr(
                        $subPart,
                        [
                            '\\' => $this->colorize('purple', '\\\\', 'green'),
                            ';'  => $this->colorize('purple', '\;', 'green'),
                            ','  => $this->colorize('purple', '\,', 'green'),
                            "\n" => $this->colorize('purple', "\\n\n\t", 'green'),
                            "\r" => "",
                        ]
                    );

                    $this->cWrite('green', $subPart);
                }
            }

        }
        $this->cWrite("default", "\n");

    }

    /**
     * Parses the list of arguments.
     *
     * @param array $argv
     *
     * @return void
     */
    protected function parseArguments(array $argv) {

        $positional = [];
        $options = [];

        for ($ii = 0; $ii < count($argv); $ii++) {

            // Skipping the first argument.
            if ($ii === 0) continue;

            $v = $argv[$ii];

            if (substr($v, 0, 2) === '--') {
                // This is a long-form option.
                $optionName = substr($v, 2);
                $optionValue = true;
                if (strpos($optionName, '=')) {
                    list($optionName, $optionValue) = explode('=', $optionName);
                }
                $options[$optionName] = $optionValue;
            } elseif (substr($v, 0, 1) === '-' && strlen($v) > 1) {
                // This is a short-form option.
                foreach (str_split(substr($v, 1)) as $option) {
                    $options[$option] = true;
                }

            } else {

                $positional[] = $v;

            }

        }

        return [$options, $positional];

    }

    protected $parser;

    /**
     * Reads the input file.
     *
     * @return Component
     */
    protected function readInput() {

        if (!$this->parser) {
            if ($this->inputPath !== '-') {
                $this->stdin = fopen($this->inputPath, 'r');
            }

            if ($this->inputFormat === 'mimedir') {
                $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
            } else {
                $this->parser = new Parser\Json($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
            }
        }

        return $this->parser->parse();

    }

    /**
     * Sends a message to STDERR.
     *
     * @param string $msg
     *
     * @return void
     */
    protected function log($msg, $color = 'default') {

        if (!$this->quiet) {
            if ($color !== 'default') {
                $msg = $this->colorize($color, $msg);
            }
            fwrite($this->stderr, $msg . "\n");
        }

    }

}
<?php

namespace Sabre\VObject\Component;

use Sabre\VObject;

/**
 * The Available sub-component.
 *
 * This component adds functionality to a component, specific for AVAILABLE
 * components.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Ivan Enderlin
 * @license http://sabre.io/license/ Modified BSD License
 */
class Available extends VObject\Component {

    /**
     * Returns the 'effective start' and 'effective end' of this VAVAILABILITY
     * component.
     *
     * We use the DTSTART and DTEND or DURATION to determine this.
     *
     * The returned value is an array containing DateTimeImmutable instances.
     * If either the start or end is 'unbounded' its value will be null
     * instead.
     *
     * @return array
     */
    function getEffectiveStartEnd() {

        $effectiveStart = $this->DTSTART->getDateTime();
        if (isset($this->DTEND)) {
            $effectiveEnd = $this->DTEND->getDateTime();
        } else {
            $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION));
        }

        return [$effectiveStart, $effectiveEnd];

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'UID'     => 1,
            'DTSTART' => 1,
            'DTSTAMP' => 1,

            'DTEND'    => '?',
            'DURATION' => '?',

            'CREATED'       => '?',
            'DESCRIPTION'   => '?',
            'LAST-MODIFIED' => '?',
            'RECURRENCE-ID' => '?',
            'RRULE'         => '?',
            'SUMMARY'       => '?',

            'CATEGORIES' => '*',
            'COMMENT'    => '*',
            'CONTACT'    => '*',
            'EXDATE'     => '*',
            'RDATE'      => '*',

            'AVAILABLE' => '*',
        ];

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *   Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
     *   Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on).
     *   2 - A warning.
     *   3 - An error.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $result = parent::validate($options);

        if (isset($this->DTEND) && isset($this->DURATION)) {
            $result[] = [
                'level'   => 3,
                'message' => 'DTEND and DURATION cannot both be present',
                'node'    => $this
            ];
        }

        return $result;

    }
}
<?php

namespace Sabre\VObject\Component;

use DateTimeImmutable;
use DateTimeInterface;
use Sabre\VObject;
use Sabre\VObject\InvalidDataException;

/**
 * VAlarm component.
 *
 * This component contains some additional functionality specific for VALARMs.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VAlarm extends VObject\Component {

    /**
     * Returns a DateTime object when this alarm is going to trigger.
     *
     * This ignores repeated alarm, only the first trigger is returned.
     *
     * @return DateTimeImmutable
     */
    function getEffectiveTriggerTime() {

        $trigger = $this->TRIGGER;
        if (!isset($trigger['VALUE']) || strtoupper($trigger['VALUE']) === 'DURATION') {
            $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER);
            $related = (isset($trigger['RELATED']) && strtoupper($trigger['RELATED']) == 'END') ? 'END' : 'START';

            $parentComponent = $this->parent;
            if ($related === 'START') {

                if ($parentComponent->name === 'VTODO') {
                    $propName = 'DUE';
                } else {
                    $propName = 'DTSTART';
                }

                $effectiveTrigger = $parentComponent->$propName->getDateTime();
                $effectiveTrigger = $effectiveTrigger->add($triggerDuration);
            } else {
                if ($parentComponent->name === 'VTODO') {
                    $endProp = 'DUE';
                } elseif ($parentComponent->name === 'VEVENT') {
                    $endProp = 'DTEND';
                } else {
                    throw new InvalidDataException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT');
                }

                if (isset($parentComponent->$endProp)) {
                    $effectiveTrigger = $parentComponent->$endProp->getDateTime();
                    $effectiveTrigger = $effectiveTrigger->add($triggerDuration);
                } elseif (isset($parentComponent->DURATION)) {
                    $effectiveTrigger = $parentComponent->DTSTART->getDateTime();
                    $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION);
                    $effectiveTrigger = $effectiveTrigger->add($duration);
                    $effectiveTrigger = $effectiveTrigger->add($triggerDuration);
                } else {
                    $effectiveTrigger = $parentComponent->DTSTART->getDateTime();
                    $effectiveTrigger = $effectiveTrigger->add($triggerDuration);
                }
            }
        } else {
            $effectiveTrigger = $trigger->getDateTime();
        }
        return $effectiveTrigger;

    }

    /**
     * Returns true or false depending on if the event falls in the specified
     * time-range. This is used for filtering purposes.
     *
     * The rules used to determine if an event falls within the specified
     * time-range is based on the CalDAV specification.
     *
     * @param DateTime $start
     * @param DateTime $end
     *
     * @return bool
     */
    function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {

        $effectiveTrigger = $this->getEffectiveTriggerTime();

        if (isset($this->DURATION)) {
            $duration = VObject\DateTimeParser::parseDuration($this->DURATION);
            $repeat = (string)$this->REPEAT;
            if (!$repeat) {
                $repeat = 1;
            }

            $period = new \DatePeriod($effectiveTrigger, $duration, (int)$repeat);

            foreach ($period as $occurrence) {

                if ($start <= $occurrence && $end > $occurrence) {
                    return true;
                }
            }
            return false;
        } else {
            return ($start <= $effectiveTrigger && $end > $effectiveTrigger);
        }

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'ACTION'  => 1,
            'TRIGGER' => 1,

            'DURATION' => '?',
            'REPEAT'   => '?',

            'ATTACH' => '?',
        ];

    }

}
<?php

namespace Sabre\VObject\Component;

use DateTimeInterface;
use Sabre\VObject;

/**
 * The VAvailability component.
 *
 * This component adds functionality to a component, specific for VAVAILABILITY
 * components.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Ivan Enderlin
 * @license http://sabre.io/license/ Modified BSD License
 */
class VAvailability extends VObject\Component {

    /**
     * Returns true or false depending on if the event falls in the specified
     * time-range. This is used for filtering purposes.
     *
     * The rules used to determine if an event falls within the specified
     * time-range is based on:
     *
     * https://tools.ietf.org/html/draft-daboo-calendar-availability-05#section-3.1
     *
     * @param DateTimeInterface $start
     * @param DateTimeInterface $end
     *
     * @return bool
     */
    function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {

        list($effectiveStart, $effectiveEnd) = $this->getEffectiveStartEnd();
        return (
            (is_null($effectiveStart) || $start < $effectiveEnd) &&
            (is_null($effectiveEnd) || $end > $effectiveStart)
        );

    }

    /**
     * Returns the 'effective start' and 'effective end' of this VAVAILABILITY
     * component.
     *
     * We use the DTSTART and DTEND or DURATION to determine this.
     *
     * The returned value is an array containing DateTimeImmutable instances.
     * If either the start or end is 'unbounded' its value will be null
     * instead.
     *
     * @return array
     */
    function getEffectiveStartEnd() {

        $effectiveStart = null;
        $effectiveEnd = null;

        if (isset($this->DTSTART)) {
            $effectiveStart = $this->DTSTART->getDateTime();
        }
        if (isset($this->DTEND)) {
            $effectiveEnd = $this->DTEND->getDateTime();
        } elseif ($effectiveStart && isset($this->DURATION)) {
            $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION));
        }

        return [$effectiveStart, $effectiveEnd];

    }


    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'UID'     => 1,
            'DTSTAMP' => 1,

            'BUSYTYPE'      => '?',
            'CLASS'         => '?',
            'CREATED'       => '?',
            'DESCRIPTION'   => '?',
            'DTSTART'       => '?',
            'LAST-MODIFIED' => '?',
            'ORGANIZER'     => '?',
            'PRIORITY'      => '?',
            'SEQUENCE'      => '?',
            'SUMMARY'       => '?',
            'URL'           => '?',
            'DTEND'         => '?',
            'DURATION'      => '?',

            'CATEGORIES' => '*',
            'COMMENT'    => '*',
            'CONTACT'    => '*',
        ];

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *   Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
     *   Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on).
     *   2 - A warning.
     *   3 - An error.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $result = parent::validate($options);

        if (isset($this->DTEND) && isset($this->DURATION)) {
            $result[] = [
                'level'   => 3,
                'message' => 'DTEND and DURATION cannot both be present',
                'node'    => $this
            ];
        }

        return $result;

    }
}
<?php

namespace Sabre\VObject\Component;

use DateTimeInterface;
use DateTimeZone;
use Sabre\VObject;
use Sabre\VObject\Component;
use Sabre\VObject\InvalidDataException;
use Sabre\VObject\Property;
use Sabre\VObject\Recur\EventIterator;
use Sabre\VObject\Recur\NoInstancesException;

/**
 * The VCalendar component.
 *
 * This component adds functionality to a component, specific for a VCALENDAR.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VCalendar extends VObject\Document {

    /**
     * The default name for this component.
     *
     * This should be 'VCALENDAR' or 'VCARD'.
     *
     * @var string
     */
    static $defaultName = 'VCALENDAR';

    /**
     * This is a list of components, and which classes they should map to.
     *
     * @var array
     */
    static $componentMap = [
        'VCALENDAR'     => 'Sabre\\VObject\\Component\\VCalendar',
        'VALARM'        => 'Sabre\\VObject\\Component\\VAlarm',
        'VEVENT'        => 'Sabre\\VObject\\Component\\VEvent',
        'VFREEBUSY'     => 'Sabre\\VObject\\Component\\VFreeBusy',
        'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability',
        'AVAILABLE'     => 'Sabre\\VObject\\Component\\Available',
        'VJOURNAL'      => 'Sabre\\VObject\\Component\\VJournal',
        'VTIMEZONE'     => 'Sabre\\VObject\\Component\\VTimeZone',
        'VTODO'         => 'Sabre\\VObject\\Component\\VTodo',
    ];

    /**
     * List of value-types, and which classes they map to.
     *
     * @var array
     */
    static $valueMap = [
        'BINARY'      => 'Sabre\\VObject\\Property\\Binary',
        'BOOLEAN'     => 'Sabre\\VObject\\Property\\Boolean',
        'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
        'DATE'        => 'Sabre\\VObject\\Property\\ICalendar\\Date',
        'DATE-TIME'   => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'DURATION'    => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
        'FLOAT'       => 'Sabre\\VObject\\Property\\FloatValue',
        'INTEGER'     => 'Sabre\\VObject\\Property\\IntegerValue',
        'PERIOD'      => 'Sabre\\VObject\\Property\\ICalendar\\Period',
        'RECUR'       => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
        'TEXT'        => 'Sabre\\VObject\\Property\\Text',
        'TIME'        => 'Sabre\\VObject\\Property\\Time',
        'UNKNOWN'     => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
        'URI'         => 'Sabre\\VObject\\Property\\Uri',
        'UTC-OFFSET'  => 'Sabre\\VObject\\Property\\UtcOffset',
    ];

    /**
     * List of properties, and which classes they map to.
     *
     * @var array
     */
    static $propertyMap = [
        // Calendar properties
        'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText',
        'METHOD'   => 'Sabre\\VObject\\Property\\FlatText',
        'PRODID'   => 'Sabre\\VObject\\Property\\FlatText',
        'VERSION'  => 'Sabre\\VObject\\Property\\FlatText',

        // Component properties
        'ATTACH'           => 'Sabre\\VObject\\Property\\Uri',
        'CATEGORIES'       => 'Sabre\\VObject\\Property\\Text',
        'CLASS'            => 'Sabre\\VObject\\Property\\FlatText',
        'COMMENT'          => 'Sabre\\VObject\\Property\\FlatText',
        'DESCRIPTION'      => 'Sabre\\VObject\\Property\\FlatText',
        'GEO'              => 'Sabre\\VObject\\Property\\FloatValue',
        'LOCATION'         => 'Sabre\\VObject\\Property\\FlatText',
        'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue',
        'PRIORITY'         => 'Sabre\\VObject\\Property\\IntegerValue',
        'RESOURCES'        => 'Sabre\\VObject\\Property\\Text',
        'STATUS'           => 'Sabre\\VObject\\Property\\FlatText',
        'SUMMARY'          => 'Sabre\\VObject\\Property\\FlatText',

        // Date and Time Component Properties
        'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'DTEND'     => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'DUE'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'DTSTART'   => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'DURATION'  => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
        'FREEBUSY'  => 'Sabre\\VObject\\Property\\ICalendar\\Period',
        'TRANSP'    => 'Sabre\\VObject\\Property\\FlatText',

        // Time Zone Component Properties
        'TZID'         => 'Sabre\\VObject\\Property\\FlatText',
        'TZNAME'       => 'Sabre\\VObject\\Property\\FlatText',
        'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset',
        'TZOFFSETTO'   => 'Sabre\\VObject\\Property\\UtcOffset',
        'TZURL'        => 'Sabre\\VObject\\Property\\Uri',

        // Relationship Component Properties
        'ATTENDEE'      => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
        'CONTACT'       => 'Sabre\\VObject\\Property\\FlatText',
        'ORGANIZER'     => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
        'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'RELATED-TO'    => 'Sabre\\VObject\\Property\\FlatText',
        'URL'           => 'Sabre\\VObject\\Property\\Uri',
        'UID'           => 'Sabre\\VObject\\Property\\FlatText',

        // Recurrence Component Properties
        'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'RDATE'  => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'RRULE'  => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
        'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545

        // Alarm Component Properties
        'ACTION'  => 'Sabre\\VObject\\Property\\FlatText',
        'REPEAT'  => 'Sabre\\VObject\\Property\\IntegerValue',
        'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',

        // Change Management Component Properties
        'CREATED'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'DTSTAMP'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'SEQUENCE'      => 'Sabre\\VObject\\Property\\IntegerValue',

        // Request Status
        'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text',

        // Additions from draft-daboo-valarm-extensions-04
        'ALARM-AGENT'   => 'Sabre\\VObject\\Property\\Text',
        'ACKNOWLEDGED'  => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
        'PROXIMITY'     => 'Sabre\\VObject\\Property\\Text',
        'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean',

        // Additions from draft-daboo-calendar-availability-05
        'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text',

    ];

    /**
     * Returns the current document type.
     *
     * @return int
     */
    function getDocumentType() {

        return self::ICALENDAR20;

    }

    /**
     * Returns a list of all 'base components'. For instance, if an Event has
     * a recurrence rule, and one instance is overridden, the overridden event
     * will have the same UID, but will be excluded from this list.
     *
     * VTIMEZONE components will always be excluded.
     *
     * @param string $componentName filter by component name
     *
     * @return VObject\Component[]
     */
    function getBaseComponents($componentName = null) {

        $isBaseComponent = function($component) {

            if (!$component instanceof VObject\Component) {
                return false;
            }
            if ($component->name === 'VTIMEZONE') {
                return false;
            }
            if (isset($component->{'RECURRENCE-ID'})) {
                return false;
            }
            return true;

        };

        if ($componentName) {
            // Early exit
            return array_filter(
                $this->select($componentName),
                $isBaseComponent
            );
        }

        $components = [];
        foreach ($this->children as $childGroup) {

            foreach ($childGroup as $child) {

                if (!$child instanceof Component) {
                    // If one child is not a component, they all are so we skip
                    // the entire group.
                    continue 2;
                }
                if ($isBaseComponent($child)) {
                    $components[] = $child;
                }

            }

        }
        return $components;

    }

    /**
     * Returns the first component that is not a VTIMEZONE, and does not have
     * an RECURRENCE-ID.
     *
     * If there is no such component, null will be returned.
     *
     * @param string $componentName filter by component name
     *
     * @return VObject\Component|null
     */
    function getBaseComponent($componentName = null) {

        $isBaseComponent = function($component) {

            if (!$component instanceof VObject\Component) {
                return false;
            }
            if ($component->name === 'VTIMEZONE') {
                return false;
            }
            if (isset($component->{'RECURRENCE-ID'})) {
                return false;
            }
            return true;

        };

        if ($componentName) {
            foreach ($this->select($componentName) as $child) {
                if ($isBaseComponent($child)) {
                    return $child;
                }
            }
            return null;
        }

        // Searching all components
        foreach ($this->children as $childGroup) {
            foreach ($childGroup as $child) {
                if ($isBaseComponent($child)) {
                    return $child;
                }
            }

        }
        return null;

    }

    /**
     * Expand all events in this VCalendar object and return a new VCalendar
     * with the expanded events.
     *
     * If this calendar object, has events with recurrence rules, this method
     * can be used to expand the event into multiple sub-events.
     *
     * Each event will be stripped from it's recurrence information, and only
     * the instances of the event in the specified timerange will be left
     * alone.
     *
     * In addition, this method will cause timezone information to be stripped,
     * and normalized to UTC.
     *
     * @param DateTimeInterface $start
     * @param DateTimeInterface $end
     * @param DateTimeZone $timeZone reference timezone for floating dates and
     *                     times.
     * @return VCalendar
     */
    function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone = null) {

        $newChildren = [];
        $recurringEvents = [];

        if (!$timeZone) {
            $timeZone = new DateTimeZone('UTC');
        }

        $stripTimezones = function(Component $component) use ($timeZone, &$stripTimezones) {

            foreach ($component->children() as $componentChild) {
                if ($componentChild instanceof Property\ICalendar\DateTime && $componentChild->hasTime()) {

                    $dt = $componentChild->getDateTimes($timeZone);
                    // We only need to update the first timezone, because
                    // setDateTimes will match all other timezones to the
                    // first.
                    $dt[0] = $dt[0]->setTimeZone(new DateTimeZone('UTC'));
                    $componentChild->setDateTimes($dt);
                } elseif ($componentChild instanceof Component) {
                    $stripTimezones($componentChild);
                }

            }
            return $component;

        };

        foreach ($this->children() as $child) {

            if ($child instanceof Property && $child->name !== 'PRODID') {
                // We explictly want to ignore PRODID, because we want to
                // overwrite it with our own.
                $newChildren[] = clone $child;
            } elseif ($child instanceof Component && $child->name !== 'VTIMEZONE') {

                // We're also stripping all VTIMEZONE objects because we're
                // converting everything to UTC.
                if ($child->name === 'VEVENT' && (isset($child->{'RECURRENCE-ID'}) || isset($child->RRULE) || isset($child->RDATE))) {
                    // Handle these a bit later.
                    $uid = (string)$child->UID;
                    if (!$uid) {
                        throw new InvalidDataException('Every VEVENT object must have a UID property');
                    }
                    if (isset($recurringEvents[$uid])) {
                        $recurringEvents[$uid][] = clone $child;
                    } else {
                        $recurringEvents[$uid] = [clone $child];
                    }
                } elseif ($child->name === 'VEVENT' && $child->isInTimeRange($start, $end)) {
                    $newChildren[] = $stripTimezones(clone $child);
                }

            }

        }

        foreach ($recurringEvents as $events) {

            try {
                $it = new EventIterator($events, $timeZone);

            } catch (NoInstancesException $e) {
                // This event is recurring, but it doesn't have a single
                // instance. We are skipping this event from the output
                // entirely.
                continue;
            }
            $it->fastForward($start);

            while ($it->valid() && $it->getDTStart() < $end) {

                if ($it->getDTEnd() > $start) {

                    $newChildren[] = $stripTimezones($it->getEventObject());

                }
                $it->next();

            }

        }

        return new self($newChildren);

    }

    /**
     * This method should return a list of default property values.
     *
     * @return array
     */
    protected function getDefaults() {

        return [
            'VERSION'  => '2.0',
            'PRODID'   => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
            'CALSCALE' => 'GREGORIAN',
        ];

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'PRODID'  => 1,
            'VERSION' => 1,

            'CALSCALE' => '?',
            'METHOD'   => '?',
        ];

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *   Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
     *   Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on).
     *   2 - A warning.
     *   3 - An error.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $warnings = parent::validate($options);

        if ($ver = $this->VERSION) {
            if ((string)$ver !== '2.0') {
                $warnings[] = [
                    'level'   => 3,
                    'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
                    'node'    => $this,
                ];
            }

        }

        $uidList = [];
        $componentsFound = 0;
        $componentTypes = [];

        foreach ($this->children() as $child) {
            if ($child instanceof Component) {
                $componentsFound++;

                if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) {
                    continue;
                }
                $componentTypes[] = $child->name;

                $uid = (string)$child->UID;
                $isMaster = isset($child->{'RECURRENCE-ID'}) ? 0 : 1;
                if (isset($uidList[$uid])) {
                    $uidList[$uid]['count']++;
                    if ($isMaster && $uidList[$uid]['hasMaster']) {
                        $warnings[] = [
                            'level'   => 3,
                            'message' => 'More than one master object was found for the object with UID ' . $uid,
                            'node'    => $this,
                        ];
                    }
                    $uidList[$uid]['hasMaster'] += $isMaster;
                } else {
                    $uidList[$uid] = [
                        'count'     => 1,
                        'hasMaster' => $isMaster,
                    ];
                }

            }
        }

        if ($componentsFound === 0) {
            $warnings[] = [
                'level'   => 3,
                'message' => 'An iCalendar object must have at least 1 component.',
                'node'    => $this,
            ];
        }

        if ($options & self::PROFILE_CALDAV) {
            if (count($uidList) > 1) {
                $warnings[] = [
                    'level'   => 3,
                    'message' => 'A calendar object on a CalDAV server may only have components with the same UID.',
                    'node'    => $this,
                ];
            }
            if (count($componentTypes) === 0) {
                $warnings[] = [
                    'level'   => 3,
                    'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).',
                    'node'    => $this,
                ];
            }
            if (count(array_unique($componentTypes)) > 1) {
                $warnings[] = [
                    'level'   => 3,
                    'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).',
                    'node'    => $this,
                ];
            }

            if (isset($this->METHOD)) {
                $warnings[] = [
                    'level'   => 3,
                    'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.',
                    'node'    => $this,
                ];
            }
        }

        return $warnings;

    }

    /**
     * Returns all components with a specific UID value.
     *
     * @return array
     */
    function getByUID($uid) {

        return array_filter($this->getComponents(), function($item) use ($uid) {

            if (!$itemUid = $item->select('UID')) {
                return false;
            }
            $itemUid = current($itemUid)->getValue();
            return $uid === $itemUid;

        });

    }


}
<?php

namespace Sabre\VObject\Component;

use Sabre\VObject;
use Sabre\Xml;

/**
 * The VCard component.
 *
 * This component represents the BEGIN:VCARD and END:VCARD found in every
 * vcard.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VCard extends VObject\Document {

    /**
     * The default name for this component.
     *
     * This should be 'VCALENDAR' or 'VCARD'.
     *
     * @var string
     */
    static $defaultName = 'VCARD';

    /**
     * Caching the version number.
     *
     * @var int
     */
    private $version = null;

    /**
     * This is a list of components, and which classes they should map to.
     *
     * @var array
     */
    static $componentMap = [
        'VCARD' => 'Sabre\\VObject\\Component\\VCard',
    ];

    /**
     * List of value-types, and which classes they map to.
     *
     * @var array
     */
    static $valueMap = [
        'BINARY'           => 'Sabre\\VObject\\Property\\Binary',
        'BOOLEAN'          => 'Sabre\\VObject\\Property\\Boolean',
        'CONTENT-ID'       => 'Sabre\\VObject\\Property\\FlatText',   // vCard 2.1 only
        'DATE'             => 'Sabre\\VObject\\Property\\VCard\\Date',
        'DATE-TIME'        => 'Sabre\\VObject\\Property\\VCard\\DateTime',
        'DATE-AND-OR-TIME' => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime', // vCard only
        'FLOAT'            => 'Sabre\\VObject\\Property\\FloatValue',
        'INTEGER'          => 'Sabre\\VObject\\Property\\IntegerValue',
        'LANGUAGE-TAG'     => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
        'TIMESTAMP'        => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
        'TEXT'             => 'Sabre\\VObject\\Property\\Text',
        'TIME'             => 'Sabre\\VObject\\Property\\Time',
        'UNKNOWN'          => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
        'URI'              => 'Sabre\\VObject\\Property\\Uri',
        'URL'              => 'Sabre\\VObject\\Property\\Uri', // vCard 2.1 only
        'UTC-OFFSET'       => 'Sabre\\VObject\\Property\\UtcOffset',
    ];

    /**
     * List of properties, and which classes they map to.
     *
     * @var array
     */
    static $propertyMap = [

        // vCard 2.1 properties and up
        'N'      => 'Sabre\\VObject\\Property\\Text',
        'FN'     => 'Sabre\\VObject\\Property\\FlatText',
        'PHOTO'  => 'Sabre\\VObject\\Property\\Binary',
        'BDAY'   => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
        'ADR'    => 'Sabre\\VObject\\Property\\Text',
        'LABEL'  => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
        'TEL'    => 'Sabre\\VObject\\Property\\FlatText',
        'EMAIL'  => 'Sabre\\VObject\\Property\\FlatText',
        'MAILER' => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0
        'GEO'    => 'Sabre\\VObject\\Property\\FlatText',
        'TITLE'  => 'Sabre\\VObject\\Property\\FlatText',
        'ROLE'   => 'Sabre\\VObject\\Property\\FlatText',
        'LOGO'   => 'Sabre\\VObject\\Property\\Binary',
        // 'AGENT'   => 'Sabre\\VObject\\Property\\',      // Todo: is an embedded vCard. Probably rare, so
                                 // not supported at the moment
        'ORG'     => 'Sabre\\VObject\\Property\\Text',
        'NOTE'    => 'Sabre\\VObject\\Property\\FlatText',
        'REV'     => 'Sabre\\VObject\\Property\\VCard\\TimeStamp',
        'SOUND'   => 'Sabre\\VObject\\Property\\FlatText',
        'URL'     => 'Sabre\\VObject\\Property\\Uri',
        'UID'     => 'Sabre\\VObject\\Property\\FlatText',
        'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
        'KEY'     => 'Sabre\\VObject\\Property\\FlatText',
        'TZ'      => 'Sabre\\VObject\\Property\\Text',

        // vCard 3.0 properties
        'CATEGORIES'  => 'Sabre\\VObject\\Property\\Text',
        'SORT-STRING' => 'Sabre\\VObject\\Property\\FlatText',
        'PRODID'      => 'Sabre\\VObject\\Property\\FlatText',
        'NICKNAME'    => 'Sabre\\VObject\\Property\\Text',
        'CLASS'       => 'Sabre\\VObject\\Property\\FlatText', // Removed in vCard 4.0

        // rfc2739 properties
        'FBURL'     => 'Sabre\\VObject\\Property\\Uri',
        'CAPURI'    => 'Sabre\\VObject\\Property\\Uri',
        'CALURI'    => 'Sabre\\VObject\\Property\\Uri',
        'CALADRURI' => 'Sabre\\VObject\\Property\\Uri',

        // rfc4770 properties
        'IMPP' => 'Sabre\\VObject\\Property\\Uri',

        // vCard 4.0 properties
        'SOURCE'       => 'Sabre\\VObject\\Property\\Uri',
        'XML'          => 'Sabre\\VObject\\Property\\FlatText',
        'ANNIVERSARY'  => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',
        'CLIENTPIDMAP' => 'Sabre\\VObject\\Property\\Text',
        'LANG'         => 'Sabre\\VObject\\Property\\VCard\\LanguageTag',
        'GENDER'       => 'Sabre\\VObject\\Property\\Text',
        'KIND'         => 'Sabre\\VObject\\Property\\FlatText',
        'MEMBER'       => 'Sabre\\VObject\\Property\\Uri',
        'RELATED'      => 'Sabre\\VObject\\Property\\Uri',

        // rfc6474 properties
        'BIRTHPLACE' => 'Sabre\\VObject\\Property\\FlatText',
        'DEATHPLACE' => 'Sabre\\VObject\\Property\\FlatText',
        'DEATHDATE'  => 'Sabre\\VObject\\Property\\VCard\\DateAndOrTime',

        // rfc6715 properties
        'EXPERTISE'     => 'Sabre\\VObject\\Property\\FlatText',
        'HOBBY'         => 'Sabre\\VObject\\Property\\FlatText',
        'INTEREST'      => 'Sabre\\VObject\\Property\\FlatText',
        'ORG-DIRECTORY' => 'Sabre\\VObject\\Property\\FlatText',

    ];

    /**
     * Returns the current document type.
     *
     * @return int
     */
    function getDocumentType() {

        if (!$this->version) {

            $version = (string)$this->VERSION;

            switch ($version) {
                case '2.1' :
                    $this->version = self::VCARD21;
                    break;
                case '3.0' :
                    $this->version = self::VCARD30;
                    break;
                case '4.0' :
                    $this->version = self::VCARD40;
                    break;
                default :
                    // We don't want to cache the version if it's unknown,
                    // because we might get a version property in a bit.
                    return self::UNKNOWN;
            }
        }

        return $this->version;

    }

    /**
     * Converts the document to a different vcard version.
     *
     * Use one of the VCARD constants for the target. This method will return
     * a copy of the vcard in the new version.
     *
     * At the moment the only supported conversion is from 3.0 to 4.0.
     *
     * If input and output version are identical, a clone is returned.
     *
     * @param int $target
     *
     * @return VCard
     */
    function convert($target) {

        $converter = new VObject\VCardConverter();
        return $converter->convert($this, $target);

    }

    /**
     * VCards with version 2.1, 3.0 and 4.0 are found.
     *
     * If the VCARD doesn't know its version, 2.1 is assumed.
     */
    const DEFAULT_VERSION = self::VCARD21;

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on)
     *   2 - An inconsequential issue
     *   3 - A severe issue.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $warnings = [];

        $versionMap = [
            self::VCARD21 => '2.1',
            self::VCARD30 => '3.0',
            self::VCARD40 => '4.0',
        ];

        $version = $this->select('VERSION');
        if (count($version) === 1) {
            $version = (string)$this->VERSION;
            if ($version !== '2.1' && $version !== '3.0' && $version !== '4.0') {
                $warnings[] = [
                    'level'   => 3,
                    'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.',
                    'node'    => $this,
                ];
                if ($options & self::REPAIR) {
                    $this->VERSION = $versionMap[self::DEFAULT_VERSION];
                }
            }
            if ($version === '2.1' && ($options & self::PROFILE_CARDDAV)) {
                $warnings[] = [
                    'level'   => 3,
                    'message' => 'CardDAV servers are not allowed to accept vCard 2.1.',
                    'node'    => $this,
                ];
            }

        }
        $uid = $this->select('UID');
        if (count($uid) === 0) {
            if ($options & self::PROFILE_CARDDAV) {
                // Required for CardDAV
                $warningLevel = 3;
                $message = 'vCards on CardDAV servers MUST have a UID property.';
            } else {
                // Not required for regular vcards
                $warningLevel = 2;
                $message = 'Adding a UID to a vCard property is recommended.';
            }
            if ($options & self::REPAIR) {
                $this->UID = VObject\UUIDUtil::getUUID();
                $warningLevel = 1;
            }
            $warnings[] = [
                'level'   => $warningLevel,
                'message' => $message,
                'node'    => $this,
            ];
        }

        $fn = $this->select('FN');
        if (count($fn) !== 1) {

            $repaired = false;
            if (($options & self::REPAIR) && count($fn) === 0) {
                // We're going to try to see if we can use the contents of the
                // N property.
                if (isset($this->N)) {
                    $value = explode(';', (string)$this->N);
                    if (isset($value[1]) && $value[1]) {
                        $this->FN = $value[1] . ' ' . $value[0];
                    } else {
                        $this->FN = $value[0];
                    }
                    $repaired = true;

                // Otherwise, the ORG property may work
                } elseif (isset($this->ORG)) {
                    $this->FN = (string)$this->ORG;
                    $repaired = true;
                }

            }
            $warnings[] = [
                'level'   => $repaired ? 1 : 3,
                'message' => 'The FN property must appear in the VCARD component exactly 1 time',
                'node'    => $this,
            ];
        }

        return array_merge(
            parent::validate($options),
            $warnings
        );

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'ADR'          => '*',
            'ANNIVERSARY'  => '?',
            'BDAY'         => '?',
            'CALADRURI'    => '*',
            'CALURI'       => '*',
            'CATEGORIES'   => '*',
            'CLIENTPIDMAP' => '*',
            'EMAIL'        => '*',
            'FBURL'        => '*',
            'IMPP'         => '*',
            'GENDER'       => '?',
            'GEO'          => '*',
            'KEY'          => '*',
            'KIND'         => '?',
            'LANG'         => '*',
            'LOGO'         => '*',
            'MEMBER'       => '*',
            'N'            => '?',
            'NICKNAME'     => '*',
            'NOTE'         => '*',
            'ORG'          => '*',
            'PHOTO'        => '*',
            'PRODID'       => '?',
            'RELATED'      => '*',
            'REV'          => '?',
            'ROLE'         => '*',
            'SOUND'        => '*',
            'SOURCE'       => '*',
            'TEL'          => '*',
            'TITLE'        => '*',
            'TZ'           => '*',
            'URL'          => '*',
            'VERSION'      => '1',
            'XML'          => '*',

            // FN is commented out, because it's already handled by the
            // validate function, which may also try to repair it.
            // 'FN'           => '+',
            'UID' => '?',
        ];

    }

    /**
     * Returns a preferred field.
     *
     * VCards can indicate wether a field such as ADR, TEL or EMAIL is
     * preferred by specifying TYPE=PREF (vcard 2.1, 3) or PREF=x (vcard 4, x
     * being a number between 1 and 100).
     *
     * If neither of those parameters are specified, the first is returned, if
     * a field with that name does not exist, null is returned.
     *
     * @param string $fieldName
     *
     * @return VObject\Property|null
     */
    function preferred($propertyName) {

        $preferred = null;
        $lastPref = 101;
        foreach ($this->select($propertyName) as $field) {

            $pref = 101;
            if (isset($field['TYPE']) && $field['TYPE']->has('PREF')) {
                $pref = 1;
            } elseif (isset($field['PREF'])) {
                $pref = $field['PREF']->getValue();
            }

            if ($pref < $lastPref || is_null($preferred)) {
                $preferred = $field;
                $lastPref = $pref;
            }

        }
        return $preferred;

    }

    /**
     * Returns a property with a specific TYPE value (ADR, TEL, or EMAIL).
     *
     * This function will return null if the property does not exist. If there are
     * multiple properties with the same TYPE value, only one will be returned.
     *
     * @param string $propertyName
     * @param string $type
     *
     * @return VObject\Property|null
     */
    function getByType($propertyName, $type) {
        foreach ($this->select($propertyName) as $field) {
            if (isset($field['TYPE']) && $field['TYPE']->has($type)) {
                return $field;
            }
        }
    }

    /**
     * This method should return a list of default property values.
     *
     * @return array
     */
    protected function getDefaults() {

        return [
            'VERSION' => '4.0',
            'PRODID'  => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
            'UID'     => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
        ];

    }

    /**
     * This method returns an array, with the representation as it should be
     * encoded in json. This is used to create jCard or jCal documents.
     *
     * @return array
     */
    function jsonSerialize() {

        // A vcard does not have sub-components, so we're overriding this
        // method to remove that array element.
        $properties = [];

        foreach ($this->children() as $child) {
            $properties[] = $child->jsonSerialize();
        }

        return [
            strtolower($this->name),
            $properties,
        ];

    }

    /**
     * This method serializes the data into XML. This is used to create xCard or
     * xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $propertiesByGroup = [];

        foreach ($this->children() as $property) {

            $group = $property->group;

            if (!isset($propertiesByGroup[$group])) {
                $propertiesByGroup[$group] = [];
            }

            $propertiesByGroup[$group][] = $property;

        }

        $writer->startElement(strtolower($this->name));

        foreach ($propertiesByGroup as $group => $properties) {

            if (!empty($group)) {

                $writer->startElement('group');
                $writer->writeAttribute('name', strtolower($group));

            }

            foreach ($properties as $property) {
                switch ($property->name) {

                    case 'VERSION':
                        continue;

                    case 'XML':
                        $value = $property->getParts();
                        $fragment = new Xml\Element\XmlFragment($value[0]);
                        $writer->write($fragment);
                        break;

                    default:
                        $property->xmlSerialize($writer);
                        break;

                }
            }

            if (!empty($group)) {
                $writer->endElement();
            }

        }

        $writer->endElement();

    }

    /**
     * Returns the default class for a property name.
     *
     * @param string $propertyName
     *
     * @return string
     */
    function getClassNameForPropertyName($propertyName) {

        $className = parent::getClassNameForPropertyName($propertyName);

        // In vCard 4, BINARY no longer exists, and we need URI instead.
        if ($className == 'Sabre\\VObject\\Property\\Binary' && $this->getDocumentType() === self::VCARD40) {
            return 'Sabre\\VObject\\Property\\Uri';
        }
        return $className;

    }

}
<?php

namespace Sabre\VObject\Component;

use DateTimeInterface;
use Sabre\VObject;
use Sabre\VObject\Recur\EventIterator;
use Sabre\VObject\Recur\NoInstancesException;

/**
 * VEvent component.
 *
 * This component contains some additional functionality specific for VEVENT's.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VEvent extends VObject\Component {

    /**
     * Returns true or false depending on if the event falls in the specified
     * time-range. This is used for filtering purposes.
     *
     * The rules used to determine if an event falls within the specified
     * time-range is based on the CalDAV specification.
     *
     * @param DateTimeInterface $start
     * @param DateTimeInterface $end
     *
     * @return bool
     */
    function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {

        if ($this->RRULE) {

            try {

                $it = new EventIterator($this, null, $start->getTimezone());

            } catch (NoInstancesException $e) {

                // If we've catched this exception, there are no instances
                // for the event that fall into the specified time-range.
                return false;

            }

            $it->fastForward($start);

            // We fast-forwarded to a spot where the end-time of the
            // recurrence instance exceeded the start of the requested
            // time-range.
            //
            // If the starttime of the recurrence did not exceed the
            // end of the time range as well, we have a match.
            return ($it->getDTStart() < $end && $it->getDTEnd() > $start);

        }

        $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone());
        if (isset($this->DTEND)) {

            // The DTEND property is considered non inclusive. So for a 3 day
            // event in july, dtstart and dtend would have to be July 1st and
            // July 4th respectively.
            //
            // See:
            // http://tools.ietf.org/html/rfc5545#page-54
            $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone());

        } elseif (isset($this->DURATION)) {
            $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION));
        } elseif (!$this->DTSTART->hasTime()) {
            $effectiveEnd = $effectiveStart->modify('+1 day');
        } else {
            $effectiveEnd = $effectiveStart;
        }
        return (
            ($start < $effectiveEnd) && ($end > $effectiveStart)
        );

    }

    /**
     * This method should return a list of default property values.
     *
     * @return array
     */
    protected function getDefaults() {

        return [
            'UID'     => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
            'DTSTAMP' => date('Ymd\\THis\\Z'),
        ];

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        $hasMethod = isset($this->parent->METHOD);
        return [
            'UID'           => 1,
            'DTSTAMP'       => 1,
            'DTSTART'       => $hasMethod ? '?' : '1',
            'CLASS'         => '?',
            'CREATED'       => '?',
            'DESCRIPTION'   => '?',
            'GEO'           => '?',
            'LAST-MODIFIED' => '?',
            'LOCATION'      => '?',
            'ORGANIZER'     => '?',
            'PRIORITY'      => '?',
            'SEQUENCE'      => '?',
            'STATUS'        => '?',
            'SUMMARY'       => '?',
            'TRANSP'        => '?',
            'URL'           => '?',
            'RECURRENCE-ID' => '?',
            'RRULE'         => '?',
            'DTEND'         => '?',
            'DURATION'      => '?',

            'ATTACH'         => '*',
            'ATTENDEE'       => '*',
            'CATEGORIES'     => '*',
            'COMMENT'        => '*',
            'CONTACT'        => '*',
            'EXDATE'         => '*',
            'REQUEST-STATUS' => '*',
            'RELATED-TO'     => '*',
            'RESOURCES'      => '*',
            'RDATE'          => '*',
        ];

    }

}
<?php

namespace Sabre\VObject\Component;

use DateTimeInterface;
use Sabre\VObject;

/**
 * The VFreeBusy component.
 *
 * This component adds functionality to a component, specific for VFREEBUSY
 * components.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VFreeBusy extends VObject\Component {

    /**
     * Checks based on the contained FREEBUSY information, if a timeslot is
     * available.
     *
     * @param DateTimeInterface $start
     * @param DateTimeInterface $end
     *
     * @return bool
     */
    function isFree(DateTimeInterface $start, DatetimeInterface $end) {

        foreach ($this->select('FREEBUSY') as $freebusy) {

            // We are only interested in FBTYPE=BUSY (the default),
            // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE.
            if (isset($freebusy['FBTYPE']) && strtoupper(substr((string)$freebusy['FBTYPE'], 0, 4)) !== 'BUSY') {
                continue;
            }

            // The freebusy component can hold more than 1 value, separated by
            // commas.
            $periods = explode(',', (string)$freebusy);

            foreach ($periods as $period) {
                // Every period is formatted as [start]/[end]. The start is an
                // absolute UTC time, the end may be an absolute UTC time, or
                // duration (relative) value.
                list($busyStart, $busyEnd) = explode('/', $period);

                $busyStart = VObject\DateTimeParser::parse($busyStart);
                $busyEnd = VObject\DateTimeParser::parse($busyEnd);
                if ($busyEnd instanceof \DateInterval) {
                    $busyEnd = $busyStart->add($busyEnd);
                }

                if ($start < $busyEnd && $end > $busyStart) {
                    return false;
                }

            }

        }

        return true;

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'UID'     => 1,
            'DTSTAMP' => 1,

            'CONTACT'   => '?',
            'DTSTART'   => '?',
            'DTEND'     => '?',
            'ORGANIZER' => '?',
            'URL'       => '?',

            'ATTENDEE'       => '*',
            'COMMENT'        => '*',
            'FREEBUSY'       => '*',
            'REQUEST-STATUS' => '*',
        ];

    }

}
<?php

namespace Sabre\VObject\Component;

use DateTimeInterface;
use Sabre\VObject;

/**
 * VJournal component.
 *
 * This component contains some additional functionality specific for VJOURNALs.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VJournal extends VObject\Component {

    /**
     * Returns true or false depending on if the event falls in the specified
     * time-range. This is used for filtering purposes.
     *
     * The rules used to determine if an event falls within the specified
     * time-range is based on the CalDAV specification.
     *
     * @param DateTimeInterface $start
     * @param DateTimeInterface $end
     *
     * @return bool
     */
    function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {

        $dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null;
        if ($dtstart) {
            $effectiveEnd = $dtstart;
            if (!$this->DTSTART->hasTime()) {
                $effectiveEnd = $effectiveEnd->modify('+1 day');
            }

            return ($start <= $effectiveEnd && $end > $dtstart);

        }
        return false;

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'UID'     => 1,
            'DTSTAMP' => 1,

            'CLASS'         => '?',
            'CREATED'       => '?',
            'DTSTART'       => '?',
            'LAST-MODIFIED' => '?',
            'ORGANIZER'     => '?',
            'RECURRENCE-ID' => '?',
            'SEQUENCE'      => '?',
            'STATUS'        => '?',
            'SUMMARY'       => '?',
            'URL'           => '?',

            'RRULE' => '?',

            'ATTACH'      => '*',
            'ATTENDEE'    => '*',
            'CATEGORIES'  => '*',
            'COMMENT'     => '*',
            'CONTACT'     => '*',
            'DESCRIPTION' => '*',
            'EXDATE'      => '*',
            'RELATED-TO'  => '*',
            'RDATE'       => '*',
        ];

    }

    /**
     * This method should return a list of default property values.
     *
     * @return array
     */
    protected function getDefaults() {

        return [
            'UID'     => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
            'DTSTAMP' => date('Ymd\\THis\\Z'),
        ];

    }
}
<?php

namespace Sabre\VObject\Component;

use Sabre\VObject;

/**
 * The VTimeZone component.
 *
 * This component adds functionality to a component, specific for VTIMEZONE
 * components.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VTimeZone extends VObject\Component {

    /**
     * Returns the PHP DateTimeZone for this VTIMEZONE component.
     *
     * If we can't accurately determine the timezone, this method will return
     * UTC.
     *
     * @return \DateTimeZone
     */
    function getTimeZone() {

        return VObject\TimeZoneUtil::getTimeZone((string)$this->TZID, $this->root);

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'TZID' => 1,

            'LAST-MODIFIED' => '?',
            'TZURL'         => '?',

            // At least 1 STANDARD or DAYLIGHT must appear.
            //
            // The validator is not specific yet to pick this up, so these
            // rules are too loose.
            'STANDARD' => '*',
            'DAYLIGHT' => '*',
        ];

    }

}
<?php

namespace Sabre\VObject\Component;

use DateTimeInterface;
use Sabre\VObject;

/**
 * VTodo component.
 *
 * This component contains some additional functionality specific for VTODOs.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VTodo extends VObject\Component {

    /**
     * Returns true or false depending on if the event falls in the specified
     * time-range. This is used for filtering purposes.
     *
     * The rules used to determine if an event falls within the specified
     * time-range is based on the CalDAV specification.
     *
     * @param DateTimeInterface $start
     * @param DateTimeInterface $end
     *
     * @return bool
     */
    function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) {

        $dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null;
        $duration = isset($this->DURATION) ? VObject\DateTimeParser::parseDuration($this->DURATION) : null;
        $due = isset($this->DUE) ? $this->DUE->getDateTime() : null;
        $completed = isset($this->COMPLETED) ? $this->COMPLETED->getDateTime() : null;
        $created = isset($this->CREATED) ? $this->CREATED->getDateTime() : null;

        if ($dtstart) {
            if ($duration) {
                $effectiveEnd = $dtstart->add($duration);
                return $start <= $effectiveEnd && $end > $dtstart;
            } elseif ($due) {
                return
                    ($start < $due || $start <= $dtstart) &&
                    ($end > $dtstart || $end >= $due);
            } else {
                return $start <= $dtstart && $end > $dtstart;
            }
        }
        if ($due) {
            return ($start < $due && $end >= $due);
        }
        if ($completed && $created) {
            return
                ($start <= $created || $start <= $completed) &&
                ($end >= $created || $end >= $completed);
        }
        if ($completed) {
            return ($start <= $completed && $end >= $completed);
        }
        if ($created) {
            return ($end > $created);
        }
        return true;

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * @var array
     */
    function getValidationRules() {

        return [
            'UID'     => 1,
            'DTSTAMP' => 1,

            'CLASS'         => '?',
            'COMPLETED'     => '?',
            'CREATED'       => '?',
            'DESCRIPTION'   => '?',
            'DTSTART'       => '?',
            'GEO'           => '?',
            'LAST-MODIFIED' => '?',
            'LOCATION'      => '?',
            'ORGANIZER'     => '?',
            'PERCENT'       => '?',
            'PRIORITY'      => '?',
            'RECURRENCE-ID' => '?',
            'SEQUENCE'      => '?',
            'STATUS'        => '?',
            'SUMMARY'       => '?',
            'URL'           => '?',

            'RRULE'    => '?',
            'DUE'      => '?',
            'DURATION' => '?',

            'ATTACH'         => '*',
            'ATTENDEE'       => '*',
            'CATEGORIES'     => '*',
            'COMMENT'        => '*',
            'CONTACT'        => '*',
            'EXDATE'         => '*',
            'REQUEST-STATUS' => '*',
            'RELATED-TO'     => '*',
            'RESOURCES'      => '*',
            'RDATE'          => '*',
        ];

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on)
     *   2 - An inconsequential issue
     *   3 - A severe issue.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $result = parent::validate($options);
        if (isset($this->DUE) && isset($this->DTSTART)) {

            $due = $this->DUE;
            $dtStart = $this->DTSTART;

            if ($due->getValueType() !== $dtStart->getValueType()) {

                $result[] = [
                    'level'   => 3,
                    'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART',
                    'node'    => $due,
                ];

            } elseif ($due->getDateTime() < $dtStart->getDateTime()) {

                $result[] = [
                    'level'   => 3,
                    'message' => 'DUE must occur after DTSTART',
                    'node'    => $due,
                ];

            }

        }

        return $result;

    }

    /**
     * This method should return a list of default property values.
     *
     * @return array
     */
    protected function getDefaults() {

        return [
            'UID'     => 'sabre-vobject-' . VObject\UUIDUtil::getUUID(),
            'DTSTAMP' => date('Ymd\\THis\\Z'),
        ];

    }

}
<?php

namespace Sabre\VObject;

use Sabre\Xml;

/**
 * Component.
 *
 * A component represents a group of properties, such as VCALENDAR, VEVENT, or
 * VCARD.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Component extends Node {

    /**
     * Component name.
     *
     * This will contain a string such as VEVENT, VTODO, VCALENDAR, VCARD.
     *
     * @var string
     */
    public $name;

    /**
     * A list of properties and/or sub-components.
     *
     * @var array
     */
    protected $children = [];

    /**
     * Creates a new component.
     *
     * You can specify the children either in key=>value syntax, in which case
     * properties will automatically be created, or you can just pass a list of
     * Component and Property object.
     *
     * By default, a set of sensible values will be added to the component. For
     * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
     * ensure that this does not happen, set $defaults to false.
     *
     * @param Document $root
     * @param string $name such as VCALENDAR, VEVENT.
     * @param array $children
     * @param bool $defaults
     *
     * @return void
     */
    function __construct(Document $root, $name, array $children = [], $defaults = true) {

        $this->name = strtoupper($name);
        $this->root = $root;

        if ($defaults) {
            // This is a terribly convoluted way to do this, but this ensures
            // that the order of properties as they are specified in both
            // defaults and the childrens list, are inserted in the object in a
            // natural way.
            $list = $this->getDefaults();
            $nodes = [];
            foreach ($children as $key => $value) {
                if ($value instanceof Node) {
                    if (isset($list[$value->name])) {
                        unset($list[$value->name]);
                    }
                    $nodes[] = $value;
                } else {
                    $list[$key] = $value;
                }
            }
            foreach ($list as $key => $value) {
                $this->add($key, $value);
            }
            foreach ($nodes as $node) {
                $this->add($node);
            }
        } else {
            foreach ($children as $k => $child) {
                if ($child instanceof Node) {
                    // Component or Property
                    $this->add($child);
                } else {

                    // Property key=>value
                    $this->add($k, $child);
                }
            }
        }

    }

    /**
     * Adds a new property or component, and returns the new item.
     *
     * This method has 3 possible signatures:
     *
     * add(Component $comp) // Adds a new component
     * add(Property $prop)  // Adds a new property
     * add($name, $value, array $parameters = []) // Adds a new property
     * add($name, array $children = []) // Adds a new component
     * by name.
     *
     * @return Node
     */
    function add() {

        $arguments = func_get_args();

        if ($arguments[0] instanceof Node) {
            if (isset($arguments[1])) {
                throw new \InvalidArgumentException('The second argument must not be specified, when passing a VObject Node');
            }
            $arguments[0]->parent = $this;
            $newNode = $arguments[0];

        } elseif (is_string($arguments[0])) {

            $newNode = call_user_func_array([$this->root, 'create'], $arguments);

        } else {

            throw new \InvalidArgumentException('The first argument must either be a \\Sabre\\VObject\\Node or a string');

        }

        $name = $newNode->name;
        if (isset($this->children[$name])) {
            $this->children[$name][] = $newNode;
        } else {
            $this->children[$name] = [$newNode];
        }
        return $newNode;

    }

    /**
     * This method removes a component or property from this component.
     *
     * You can either specify the item by name (like DTSTART), in which case
     * all properties/components with that name will be removed, or you can
     * pass an instance of a property or component, in which case only that
     * exact item will be removed.
     *
     * @param string|Property|Component $item
     * @return void
     */
    function remove($item) {

        if (is_string($item)) {
            // If there's no dot in the name, it's an exact property name and
            // we can just wipe out all those properties.
            //
            if (strpos($item, '.') === false) {
                unset($this->children[strtoupper($item)]);
                return;
            }
            // If there was a dot, we need to ask select() to help us out and
            // then we just call remove recursively.
            foreach ($this->select($item) as $child) {

                $this->remove($child);

            }
        } else {
            foreach ($this->select($item->name) as $k => $child) {
                if ($child === $item) {
                    unset($this->children[$item->name][$k]);
                    return;
                }
            }
        }

        throw new \InvalidArgumentException('The item you passed to remove() was not a child of this component');

    }

    /**
     * Returns a flat list of all the properties and components in this
     * component.
     *
     * @return array
     */
    function children() {

        $result = [];
        foreach ($this->children as $childGroup) {
            $result = array_merge($result, $childGroup);
        }
        return $result;

    }

    /**
     * This method only returns a list of sub-components. Properties are
     * ignored.
     *
     * @return array
     */
    function getComponents() {

        $result = [];

        foreach ($this->children as $childGroup) {
            foreach ($childGroup as $child) {
                if ($child instanceof self) {
                    $result[] = $child;
                }
            }
        }
        return $result;

    }

    /**
     * Returns an array with elements that match the specified name.
     *
     * This function is also aware of MIME-Directory groups (as they appear in
     * vcards). This means that if a property is grouped as "HOME.EMAIL", it
     * will also be returned when searching for just "EMAIL". If you want to
     * search for a property in a specific group, you can select on the entire
     * string ("HOME.EMAIL"). If you want to search on a specific property that
     * has not been assigned a group, specify ".EMAIL".
     *
     * @param string $name
     * @return array
     */
    function select($name) {

        $group = null;
        $name = strtoupper($name);
        if (strpos($name, '.') !== false) {
            list($group, $name) = explode('.', $name, 2);
        }
        if ($name === '') $name = null;

        if (!is_null($name)) {

            $result = isset($this->children[$name]) ? $this->children[$name] : [];

            if (is_null($group)) {
                return $result;
            } else {
                // If we have a group filter as well, we need to narrow it down
                // more.
                return array_filter(
                    $result,
                    function($child) use ($group) {

                        return $child instanceof Property && strtoupper($child->group) === $group;

                    }
                );
            }

        }

        // If we got to this point, it means there was no 'name' specified for
        // searching, implying that this is a group-only search.
        $result = [];
        foreach ($this->children as $childGroup) {

            foreach ($childGroup as $child) {

                if ($child instanceof Property && strtoupper($child->group) === $group) {
                    $result[] = $child;
                }

            }

        }
        return $result;

    }

    /**
     * Turns the object back into a serialized blob.
     *
     * @return string
     */
    function serialize() {

        $str = "BEGIN:" . $this->name . "\r\n";

        /**
         * Gives a component a 'score' for sorting purposes.
         *
         * This is solely used by the childrenSort method.
         *
         * A higher score means the item will be lower in the list.
         * To avoid score collisions, each "score category" has a reasonable
         * space to accomodate elements. The $key is added to the $score to
         * preserve the original relative order of elements.
         *
         * @param int $key
         * @param array $array
         *
         * @return int
         */
        $sortScore = function($key, $array) {

            if ($array[$key] instanceof Component) {

                // We want to encode VTIMEZONE first, this is a personal
                // preference.
                if ($array[$key]->name === 'VTIMEZONE') {
                    $score = 300000000;
                    return $score + $key;
                } else {
                    $score = 400000000;
                    return $score + $key;
                }
            } else {
                // Properties get encoded first
                // VCARD version 4.0 wants the VERSION property to appear first
                if ($array[$key] instanceof Property) {
                    if ($array[$key]->name === 'VERSION') {
                        $score = 100000000;
                        return $score + $key;
                    } else {
                        // All other properties
                        $score = 200000000;
                        return $score + $key;
                    }
                }
            }

        };

        $children = $this->children();
        $tmp = $children;
        uksort(
            $children,
            function($a, $b) use ($sortScore, $tmp) {

                $sA = $sortScore($a, $tmp);
                $sB = $sortScore($b, $tmp);

                return $sA - $sB;

            }
        );

        foreach ($children as $child) $str .= $child->serialize();
        $str .= "END:" . $this->name . "\r\n";

        return $str;

    }

    /**
     * This method returns an array, with the representation as it should be
     * encoded in JSON. This is used to create jCard or jCal documents.
     *
     * @return array
     */
    function jsonSerialize() {

        $components = [];
        $properties = [];

        foreach ($this->children as $childGroup) {
            foreach ($childGroup as $child) {
                if ($child instanceof self) {
                    $components[] = $child->jsonSerialize();
                } else {
                    $properties[] = $child->jsonSerialize();
                }
            }
        }

        return [
            strtolower($this->name),
            $properties,
            $components
        ];

    }

    /**
     * This method serializes the data into XML. This is used to create xCard or
     * xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $components = [];
        $properties = [];

        foreach ($this->children as $childGroup) {
            foreach ($childGroup as $child) {
                if ($child instanceof self) {
                    $components[] = $child;
                } else {
                    $properties[] = $child;
                }
            }
        }

        $writer->startElement(strtolower($this->name));

        if (!empty($properties)) {

            $writer->startElement('properties');

            foreach ($properties as $property) {
                $property->xmlSerialize($writer);
            }

            $writer->endElement();

        }

        if (!empty($components)) {

            $writer->startElement('components');

            foreach ($components as $component) {
                $component->xmlSerialize($writer);
            }

            $writer->endElement();
        }

        $writer->endElement();

    }

    /**
     * This method should return a list of default property values.
     *
     * @return array
     */
    protected function getDefaults() {

        return [];

    }

    /* Magic property accessors {{{ */

    /**
     * Using 'get' you will either get a property or component.
     *
     * If there were no child-elements found with the specified name,
     * null is returned.
     *
     * To use this, this may look something like this:
     *
     * $event = $calendar->VEVENT;
     *
     * @param string $name
     *
     * @return Property
     */
    function __get($name) {

        if ($name === 'children') {

            throw new \RuntimeException('Starting sabre/vobject 4.0 the children property is now protected. You should use the children() method instead');

        }

        $matches = $this->select($name);
        if (count($matches) === 0) {
            return;
        } else {
            $firstMatch = current($matches);
            /** @var $firstMatch Property */
            $firstMatch->setIterator(new ElementList(array_values($matches)));
            return $firstMatch;
        }

    }

    /**
     * This method checks if a sub-element with the specified name exists.
     *
     * @param string $name
     *
     * @return bool
     */
    function __isset($name) {

        $matches = $this->select($name);
        return count($matches) > 0;

    }

    /**
     * Using the setter method you can add properties or subcomponents.
     *
     * You can either pass a Component, Property
     * object, or a string to automatically create a Property.
     *
     * If the item already exists, it will be removed. If you want to add
     * a new item with the same name, always use the add() method.
     *
     * @param string $name
     * @param mixed $value
     *
     * @return void
     */
    function __set($name, $value) {

        $name = strtoupper($name);
        $this->remove($name);
        if ($value instanceof self || $value instanceof Property) {
            $this->add($value);
        } else {
            $this->add($name, $value);
        }
    }

    /**
     * Removes all properties and components within this component with the
     * specified name.
     *
     * @param string $name
     *
     * @return void
     */
    function __unset($name) {

        $this->remove($name);

    }

    /* }}} */

    /**
     * This method is automatically called when the object is cloned.
     * Specifically, this will ensure all child elements are also cloned.
     *
     * @return void
     */
    function __clone() {

        foreach ($this->children as $childName => $childGroup) {
            foreach ($childGroup as $key => $child) {
                $clonedChild = clone $child;
                $clonedChild->parent = $this;
                $clonedChild->root = $this->root;
                $this->children[$childName][$key] = $clonedChild;
            }
        }

    }

    /**
     * A simple list of validation rules.
     *
     * This is simply a list of properties, and how many times they either
     * must or must not appear.
     *
     * Possible values per property:
     *   * 0 - Must not appear.
     *   * 1 - Must appear exactly once.
     *   * + - Must appear at least once.
     *   * * - Can appear any number of times.
     *   * ? - May appear, but not more than once.
     *
     * It is also possible to specify defaults and severity levels for
     * violating the rule.
     *
     * See the VEVENT implementation for getValidationRules for a more complex
     * example.
     *
     * @var array
     */
    function getValidationRules() {

        return [];

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *   Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
     *   Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on).
     *   2 - A warning.
     *   3 - An error.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $rules = $this->getValidationRules();
        $defaults = $this->getDefaults();

        $propertyCounters = [];

        $messages = [];

        foreach ($this->children() as $child) {
            $name = strtoupper($child->name);
            if (!isset($propertyCounters[$name])) {
                $propertyCounters[$name] = 1;
            } else {
                $propertyCounters[$name]++;
            }
            $messages = array_merge($messages, $child->validate($options));
        }

        foreach ($rules as $propName => $rule) {

            switch ($rule) {
                case '0' :
                    if (isset($propertyCounters[$propName])) {
                        $messages[] = [
                            'level'   => 3,
                            'message' => $propName . ' MUST NOT appear in a ' . $this->name . ' component',
                            'node'    => $this,
                        ];
                    }
                    break;
                case '1' :
                    if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] !== 1) {
                        $repaired = false;
                        if ($options & self::REPAIR && isset($defaults[$propName])) {
                            $this->add($propName, $defaults[$propName]);
                            $repaired = true;
                        }
                        $messages[] = [
                            'level'   => $repaired ? 1 : 3,
                            'message' => $propName . ' MUST appear exactly once in a ' . $this->name . ' component',
                            'node'    => $this,
                        ];
                    }
                    break;
                case '+' :
                    if (!isset($propertyCounters[$propName]) || $propertyCounters[$propName] < 1) {
                        $messages[] = [
                            'level'   => 3,
                            'message' => $propName . ' MUST appear at least once in a ' . $this->name . ' component',
                            'node'    => $this,
                        ];
                    }
                    break;
                case '*' :
                    break;
                case '?' :
                    if (isset($propertyCounters[$propName]) && $propertyCounters[$propName] > 1) {
                        $messages[] = [
                            'level'   => 3,
                            'message' => $propName . ' MUST NOT appear more than once in a ' . $this->name . ' component',
                            'node'    => $this,
                        ];
                    }
                    break;

            }

        }
        return $messages;

    }

    /**
     * Call this method on a document if you're done using it.
     *
     * It's intended to remove all circular references, so PHP can easily clean
     * it up.
     *
     * @return void
     */
    function destroy() {

        parent::destroy();
        foreach ($this->children as $childGroup) {
            foreach ($childGroup as $child) {
                $child->destroy();
            }
        }
        $this->children = [];

    }

}
<?php

namespace Sabre\VObject;

use DateInterval;
use DateTimeImmutable;
use DateTimeZone;

/**
 * DateTimeParser.
 *
 * This class is responsible for parsing the several different date and time
 * formats iCalendar and vCards have.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class DateTimeParser {

    /**
     * Parses an iCalendar (rfc5545) formatted datetime and returns a
     * DateTimeImmutable object.
     *
     * Specifying a reference timezone is optional. It will only be used
     * if the non-UTC format is used. The argument is used as a reference, the
     * returned DateTimeImmutable object will still be in the UTC timezone.
     *
     * @param string $dt
     * @param DateTimeZone $tz
     *
     * @return DateTimeImmutable
     */
    static function parseDateTime($dt, DateTimeZone $tz = null) {

        // Format is YYYYMMDD + "T" + hhmmss
        $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])T([0-2][0-9])([0-5][0-9])([0-5][0-9])([Z]?)$/', $dt, $matches);

        if (!$result) {
            throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: ' . $dt);
        }

        if ($matches[7] === 'Z' || is_null($tz)) {
            $tz = new DateTimeZone('UTC');
        }

        try {
            $date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3] . ' ' . $matches[4] . ':' . $matches[5] . ':' . $matches[6], $tz);
        } catch (\Exception $e) {
            throw new InvalidDataException('The supplied iCalendar datetime value is incorrect: ' . $dt);
        }

        return $date;

    }

    /**
     * Parses an iCalendar (rfc5545) formatted date and returns a DateTimeImmutable object.
     *
     * @param string $date
     * @param DateTimeZone $tz
     *
     * @return DateTimeImmutable
     */
    static function parseDate($date, DateTimeZone $tz = null) {

        // Format is YYYYMMDD
        $result = preg_match('/^([0-9]{4})([0-1][0-9])([0-3][0-9])$/', $date, $matches);

        if (!$result) {
            throw new InvalidDataException('The supplied iCalendar date value is incorrect: ' . $date);
        }

        if (is_null($tz)) {
            $tz = new DateTimeZone('UTC');
        }

        try {
            $date = new DateTimeImmutable($matches[1] . '-' . $matches[2] . '-' . $matches[3], $tz);
        } catch (\Exception $e) {
            throw new InvalidDataException('The supplied iCalendar date value is incorrect: ' . $date);
        }

        return $date;

    }

    /**
     * Parses an iCalendar (RFC5545) formatted duration value.
     *
     * This method will either return a DateTimeInterval object, or a string
     * suitable for strtotime or DateTime::modify.
     *
     * @param string $duration
     * @param bool $asString
     *
     * @return DateInterval|string
     */
    static function parseDuration($duration, $asString = false) {

        $result = preg_match('/^(?<plusminus>\+|-)?P((?<week>\d+)W)?((?<day>\d+)D)?(T((?<hour>\d+)H)?((?<minute>\d+)M)?((?<second>\d+)S)?)?$/', $duration, $matches);
        if (!$result) {
            throw new InvalidDataException('The supplied iCalendar duration value is incorrect: ' . $duration);
        }

        if (!$asString) {

            $invert = false;

            if ($matches['plusminus'] === '-') {
                $invert = true;
            }

            $parts = [
                'week',
                'day',
                'hour',
                'minute',
                'second',
            ];

            foreach ($parts as $part) {
                $matches[$part] = isset($matches[$part]) && $matches[$part] ? (int)$matches[$part] : 0;
            }

            // We need to re-construct the $duration string, because weeks and
            // days are not supported by DateInterval in the same string.
            $duration = 'P';
            $days = $matches['day'];

            if ($matches['week']) {
                $days += $matches['week'] * 7;
            }

            if ($days) {
                $duration .= $days . 'D';
            }

            if ($matches['minute'] || $matches['second'] || $matches['hour']) {

                $duration .= 'T';

                if ($matches['hour']) {
                    $duration .= $matches['hour'] . 'H';
                }

                if ($matches['minute']) {
                    $duration .= $matches['minute'] . 'M';
                }

                if ($matches['second']) {
                    $duration .= $matches['second'] . 'S';
                }

            }

            if ($duration === 'P') {
                $duration = 'PT0S';
            }

            $iv = new DateInterval($duration);

            if ($invert) {
                $iv->invert = true;
            }

            return $iv;

        }

        $parts = [
            'week',
            'day',
            'hour',
            'minute',
            'second',
        ];

        $newDur = '';

        foreach ($parts as $part) {
            if (isset($matches[$part]) && $matches[$part]) {
                $newDur .= ' ' . $matches[$part] . ' ' . $part . 's';
            }
        }

        $newDur = ($matches['plusminus'] === '-' ? '-' : '+') . trim($newDur);

        if ($newDur === '+') {
            $newDur = '+0 seconds';
        };

        return $newDur;

    }

    /**
     * Parses either a Date or DateTime, or Duration value.
     *
     * @param string $date
     * @param DateTimeZone|string $referenceTz
     *
     * @return DateTimeImmutable|DateInterval
     */
    static function parse($date, $referenceTz = null) {

        if ($date[0] === 'P' || ($date[0] === '-' && $date[1] === 'P')) {
            return self::parseDuration($date);
        } elseif (strlen($date) === 8) {
            return self::parseDate($date, $referenceTz);
        } else {
            return self::parseDateTime($date, $referenceTz);
        }

    }

    /**
     * This method parses a vCard date and or time value.
     *
     * This can be used for the DATE, DATE-TIME, TIMESTAMP and
     * DATE-AND-OR-TIME value.
     *
     * This method returns an array, not a DateTime value.
     *
     * The elements in the array are in the following order:
     * year, month, date, hour, minute, second, timezone
     *
     * Almost any part of the string may be omitted. It's for example legal to
     * just specify seconds, leave out the year, etc.
     *
     * Timezone is either returned as 'Z' or as '+0800'
     *
     * For any non-specified values null is returned.
     *
     * List of date formats that are supported:
     * YYYY
     * YYYY-MM
     * YYYYMMDD
     * --MMDD
     * ---DD
     *
     * YYYY-MM-DD
     * --MM-DD
     * ---DD
     *
     * List of supported time formats:
     *
     * HH
     * HHMM
     * HHMMSS
     * -MMSS
     * --SS
     *
     * HH
     * HH:MM
     * HH:MM:SS
     * -MM:SS
     * --SS
     *
     * A full basic-format date-time string looks like :
     * 20130603T133901
     *
     * A full extended-format date-time string looks like :
     * 2013-06-03T13:39:01
     *
     * Times may be postfixed by a timezone offset. This can be either 'Z' for
     * UTC, or a string like -0500 or +1100.
     *
     * @param string $date
     *
     * @return array
     */
    static function parseVCardDateTime($date) {

        $regex = '/^
            (?:  # date part
                (?:
                    (?: (?<year> [0-9]{4}) (?: -)?| --)
                    (?<month> [0-9]{2})?
                |---)
                (?<date> [0-9]{2})?
            )?
            (?:T  # time part
                (?<hour> [0-9]{2} | -)
                (?<minute> [0-9]{2} | -)?
                (?<second> [0-9]{2})?

                (?: \.[0-9]{3})? # milliseconds
                (?P<timezone> # timezone offset

                    Z | (?: \+|-)(?: [0-9]{4})

                )?

            )?
            $/x';

        if (!preg_match($regex, $date, $matches)) {

            // Attempting to parse the extended format.
            $regex = '/^
                (?: # date part
                    (?: (?<year> [0-9]{4}) - | -- )
                    (?<month> [0-9]{2}) -
                    (?<date> [0-9]{2})
                )?
                (?:T # time part

                    (?: (?<hour> [0-9]{2}) : | -)
                    (?: (?<minute> [0-9]{2}) : | -)?
                    (?<second> [0-9]{2})?

                    (?: \.[0-9]{3})? # milliseconds
                    (?P<timezone> # timezone offset

                        Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})

                    )?

                )?
                $/x';

            if (!preg_match($regex, $date, $matches)) {
                throw new InvalidDataException('Invalid vCard date-time string: ' . $date);
            }

        }
        $parts = [
            'year',
            'month',
            'date',
            'hour',
            'minute',
            'second',
            'timezone'
        ];

        $result = [];
        foreach ($parts as $part) {

            if (empty($matches[$part])) {
                $result[$part] = null;
            } elseif ($matches[$part] === '-' || $matches[$part] === '--') {
                $result[$part] = null;
            } else {
                $result[$part] = $matches[$part];
            }

        }

        return $result;

    }

    /**
     * This method parses a vCard TIME value.
     *
     * This method returns an array, not a DateTime value.
     *
     * The elements in the array are in the following order:
     * hour, minute, second, timezone
     *
     * Almost any part of the string may be omitted. It's for example legal to
     * just specify seconds, leave out the hour etc.
     *
     * Timezone is either returned as 'Z' or as '+08:00'
     *
     * For any non-specified values null is returned.
     *
     * List of supported time formats:
     *
     * HH
     * HHMM
     * HHMMSS
     * -MMSS
     * --SS
     *
     * HH
     * HH:MM
     * HH:MM:SS
     * -MM:SS
     * --SS
     *
     * A full basic-format time string looks like :
     * 133901
     *
     * A full extended-format time string looks like :
     * 13:39:01
     *
     * Times may be postfixed by a timezone offset. This can be either 'Z' for
     * UTC, or a string like -0500 or +11:00.
     *
     * @param string $date
     *
     * @return array
     */
    static function parseVCardTime($date) {

        $regex = '/^
            (?<hour> [0-9]{2} | -)
            (?<minute> [0-9]{2} | -)?
            (?<second> [0-9]{2})?

            (?: \.[0-9]{3})? # milliseconds
            (?P<timezone> # timezone offset

                Z | (?: \+|-)(?: [0-9]{4})

            )?
            $/x';


        if (!preg_match($regex, $date, $matches)) {

            // Attempting to parse the extended format.
            $regex = '/^
                (?: (?<hour> [0-9]{2}) : | -)
                (?: (?<minute> [0-9]{2}) : | -)?
                (?<second> [0-9]{2})?

                (?: \.[0-9]{3})? # milliseconds
                (?P<timezone> # timezone offset

                    Z | (?: \+|-)(?: [0-9]{2}:[0-9]{2})

                )?
                $/x';

            if (!preg_match($regex, $date, $matches)) {
                throw new InvalidDataException('Invalid vCard time string: ' . $date);
            }

        }
        $parts = [
            'hour',
            'minute',
            'second',
            'timezone'
        ];

        $result = [];
        foreach ($parts as $part) {

            if (empty($matches[$part])) {
                $result[$part] = null;
            } elseif ($matches[$part] === '-') {
                $result[$part] = null;
            } else {
                $result[$part] = $matches[$part];
            }

        }

        return $result;

    }

    /**
     * This method parses a vCard date and or time value.
     *
     * This can be used for the DATE, DATE-TIME and
     * DATE-AND-OR-TIME value.
     *
     * This method returns an array, not a DateTime value.
     * The elements in the array are in the following order:
     *     year, month, date, hour, minute, second, timezone
     * Almost any part of the string may be omitted. It's for example legal to
     * just specify seconds, leave out the year, etc.
     *
     * Timezone is either returned as 'Z' or as '+0800'
     *
     * For any non-specified values null is returned.
     *
     * List of date formats that are supported:
     *     20150128
     *     2015-01
     *     --01
     *     --0128
     *     ---28
     *
     * List of supported time formats:
     *     13
     *     1353
     *     135301
     *     -53
     *     -5301
     *     --01 (unreachable, see the tests)
     *     --01Z
     *     --01+1234
     *
     * List of supported date-time formats:
     *     20150128T13
     *     --0128T13
     *     ---28T13
     *     ---28T1353
     *     ---28T135301
     *     ---28T13Z
     *     ---28T13+1234
     *
     * See the regular expressions for all the possible patterns.
     *
     * Times may be postfixed by a timezone offset. This can be either 'Z' for
     * UTC, or a string like -0500 or +1100.
     *
     * @param string $date
     *
     * @return array
     */
    static function parseVCardDateAndOrTime($date) {

        // \d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d
        $valueDate = '/^(?J)(?:' .
                         '(?<year>\d{4})(?<month>\d\d)(?<date>\d\d)' .
                         '|(?<year>\d{4})-(?<month>\d\d)' .
                         '|--(?<month>\d\d)(?<date>\d\d)?' .
                         '|---(?<date>\d\d)' .
                         ')$/';

        // (\d\d(\d\d(\d\d)?)?|-\d\d(\d\d)?|--\d\d)(Z|[+\-]\d\d(\d\d)?)?
        $valueTime = '/^(?J)(?:' .
                         '((?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?' .
                         '|-(?<minute>\d\d)(?<second>\d\d)?' .
                         '|--(?<second>\d\d))' .
                         '(?<timezone>(Z|[+\-]\d\d(\d\d)?))?' .
                         ')$/';

        // (\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?(Z|[+\-]\d\d(\d\d?)?
        $valueDateTime = '/^(?:' .
                         '((?<year0>\d{4})(?<month0>\d\d)(?<date0>\d\d)' .
                         '|--(?<month1>\d\d)(?<date1>\d\d)' .
                         '|---(?<date2>\d\d))' .
                         'T' .
                         '(?<hour>\d\d)((?<minute>\d\d)(?<second>\d\d)?)?' .
                         '(?<timezone>(Z|[+\-]\d\d(\d\d?)))?' .
                         ')$/';

        // date-and-or-time is date | date-time | time
        // in this strict order.

        if (0 === preg_match($valueDate, $date, $matches)
            && 0 === preg_match($valueDateTime, $date, $matches)
            && 0 === preg_match($valueTime, $date, $matches)) {
            throw new InvalidDataException('Invalid vCard date-time string: ' . $date);
        }

        $parts = [
            'year'     => null,
            'month'    => null,
            'date'     => null,
            'hour'     => null,
            'minute'   => null,
            'second'   => null,
            'timezone' => null
        ];

        // The $valueDateTime expression has a bug with (?J) so we simulate it.
        $parts['date0'] = &$parts['date'];
        $parts['date1'] = &$parts['date'];
        $parts['date2'] = &$parts['date'];
        $parts['month0'] = &$parts['month'];
        $parts['month1'] = &$parts['month'];
        $parts['year0'] = &$parts['year'];

        foreach ($parts as $part => &$value) {
            if (!empty($matches[$part])) {
                $value = $matches[$part];
            }
        }

        unset($parts['date0']);
        unset($parts['date1']);
        unset($parts['date2']);
        unset($parts['month0']);
        unset($parts['month1']);
        unset($parts['year0']);

        return $parts;

    }
}
<?php

namespace Sabre\VObject;

/**
 * Document.
 *
 * A document is just like a component, except that it's also the top level
 * element.
 *
 * Both a VCALENDAR and a VCARD are considered documents.
 *
 * This class also provides a registry for document types.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class Document extends Component {

    /**
     * Unknown document type.
     */
    const UNKNOWN = 1;

    /**
     * vCalendar 1.0.
     */
    const VCALENDAR10 = 2;

    /**
     * iCalendar 2.0.
     */
    const ICALENDAR20 = 3;

    /**
     * vCard 2.1.
     */
    const VCARD21 = 4;

    /**
     * vCard 3.0.
     */
    const VCARD30 = 5;

    /**
     * vCard 4.0.
     */
    const VCARD40 = 6;

    /**
     * The default name for this component.
     *
     * This should be 'VCALENDAR' or 'VCARD'.
     *
     * @var string
     */
    static $defaultName;

    /**
     * List of properties, and which classes they map to.
     *
     * @var array
     */
    static $propertyMap = [];

    /**
     * List of components, along with which classes they map to.
     *
     * @var array
     */
    static $componentMap = [];

    /**
     * List of value-types, and which classes they map to.
     *
     * @var array
     */
    static $valueMap = [];

    /**
     * Creates a new document.
     *
     * We're changing the default behavior slightly here. First, we don't want
     * to have to specify a name (we already know it), and we want to allow
     * children to be specified in the first argument.
     *
     * But, the default behavior also works.
     *
     * So the two sigs:
     *
     * new Document(array $children = [], $defaults = true);
     * new Document(string $name, array $children = [], $defaults = true)
     *
     * @return void
     */
    function __construct() {

        $args = func_get_args();
        if (count($args) === 0 || is_array($args[0])) {
            array_unshift($args, $this, static::$defaultName);
            call_user_func_array(['parent', '__construct'], $args);
        } else {
            array_unshift($args, $this);
            call_user_func_array(['parent', '__construct'], $args);
        }

    }

    /**
     * Returns the current document type.
     *
     * @return int
     */
    function getDocumentType() {

        return self::UNKNOWN;

    }

    /**
     * Creates a new component or property.
     *
     * If it's a known component, we will automatically call createComponent.
     * otherwise, we'll assume it's a property and call createProperty instead.
     *
     * @param string $name
     * @param string $arg1,... Unlimited number of args
     *
     * @return mixed
     */
    function create($name) {

        if (isset(static::$componentMap[strtoupper($name)])) {

            return call_user_func_array([$this, 'createComponent'], func_get_args());

        } else {

            return call_user_func_array([$this, 'createProperty'], func_get_args());

        }

    }

    /**
     * Creates a new component.
     *
     * This method automatically searches for the correct component class, based
     * on its name.
     *
     * You can specify the children either in key=>value syntax, in which case
     * properties will automatically be created, or you can just pass a list of
     * Component and Property object.
     *
     * By default, a set of sensible values will be added to the component. For
     * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To
     * ensure that this does not happen, set $defaults to false.
     *
     * @param string $name
     * @param array $children
     * @param bool $defaults
     *
     * @return Component
     */
    function createComponent($name, array $children = null, $defaults = true) {

        $name = strtoupper($name);
        $class = 'Sabre\\VObject\\Component';

        if (isset(static::$componentMap[$name])) {
            $class = static::$componentMap[$name];
        }
        if (is_null($children)) $children = [];
        return new $class($this, $name, $children, $defaults);

    }

    /**
     * Factory method for creating new properties.
     *
     * This method automatically searches for the correct property class, based
     * on its name.
     *
     * You can specify the parameters either in key=>value syntax, in which case
     * parameters will automatically be created, or you can just pass a list of
     * Parameter objects.
     *
     * @param string $name
     * @param mixed $value
     * @param array $parameters
     * @param string $valueType Force a specific valuetype, such as URI or TEXT
     *
     * @return Property
     */
    function createProperty($name, $value = null, array $parameters = null, $valueType = null) {

        // If there's a . in the name, it means it's prefixed by a groupname.
        if (($i = strpos($name, '.')) !== false) {
            $group = substr($name, 0, $i);
            $name = strtoupper(substr($name, $i + 1));
        } else {
            $name = strtoupper($name);
            $group = null;
        }

        $class = null;

        if ($valueType) {
            // The valueType argument comes first to figure out the correct
            // class.
            $class = $this->getClassNameForPropertyValue($valueType);
        }

        if (is_null($class)) {
            // If a VALUE parameter is supplied, we should use that.
            if (isset($parameters['VALUE'])) {
                $class = $this->getClassNameForPropertyValue($parameters['VALUE']);
                if (is_null($class)) {
                    throw new InvalidDataException('Unsupported VALUE parameter for ' . $name . ' property. You supplied "' . $parameters['VALUE'] . '"');
                }
            }
            else {
                $class = $this->getClassNameForPropertyName($name);
            }
        }
        if (is_null($parameters)) $parameters = [];

        return new $class($this, $name, $value, $parameters, $group);

    }

    /**
     * This method returns a full class-name for a value parameter.
     *
     * For instance, DTSTART may have VALUE=DATE. In that case we will look in
     * our valueMap table and return the appropriate class name.
     *
     * This method returns null if we don't have a specialized class.
     *
     * @param string $valueParam
     * @return string|null
     */
    function getClassNameForPropertyValue($valueParam) {

        $valueParam = strtoupper($valueParam);
        if (isset(static::$valueMap[$valueParam])) {
            return static::$valueMap[$valueParam];
        }

    }

    /**
     * Returns the default class for a property name.
     *
     * @param string $propertyName
     *
     * @return string
     */
    function getClassNameForPropertyName($propertyName) {

        if (isset(static::$propertyMap[$propertyName])) {
            return static::$propertyMap[$propertyName];
        } else {
            return 'Sabre\\VObject\\Property\\Unknown';
        }

    }

}
<?php

namespace Sabre\VObject;

use ArrayIterator;
use LogicException;

/**
 * VObject ElementList.
 *
 * This class represents a list of elements. Lists are the result of queries,
 * such as doing $vcalendar->vevent where there's multiple VEVENT objects.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ElementList extends ArrayIterator {


    /* {{{ ArrayAccess Interface */

    /**
     * Sets an item through ArrayAccess.
     *
     * @param int $offset
     * @param mixed $value
     *
     * @return void
     */
    function offsetSet($offset, $value) {

        throw new LogicException('You can not add new objects to an ElementList');

    }

    /**
     * Sets an item through ArrayAccess.
     *
     * This method just forwards the request to the inner iterator
     *
     * @param int $offset
     *
     * @return void
     */
    function offsetUnset($offset) {

        throw new LogicException('You can not remove objects from an ElementList');

    }

    /* }}} */

}
<?php

namespace Sabre\VObject;

/**
 * Exception thrown by parser when the end of the stream has been reached,
 * before this was expected.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class EofException extends ParseException {

}
<?php

namespace Sabre\VObject;

/**
 * FreeBusyData is a helper class that manages freebusy information.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class FreeBusyData {

    /**
     * Start timestamp
     *
     * @var int
     */
    protected $start;

    /**
     * End timestamp
     *
     * @var int
     */
    protected $end;

    /**
     * A list of free-busy times.
     *
     * @var array
     */
    protected $data;

    function __construct($start, $end) {

        $this->start = $start;
        $this->end = $end;
        $this->data = [];

        $this->data[] = [
            'start' => $this->start,
            'end'   => $this->end,
            'type'  => 'FREE',
        ];

    }

    /**
     * Adds free or busytime to the data.
     *
     * @param int $start
     * @param int $end
     * @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE
     * @return void
     */
    function add($start, $end, $type) {

        if ($start > $this->end || $end < $this->start) {

            // This new data is outside our timerange.
            return;

        }

        if ($start < $this->start) {
            // The item starts before our requested time range
            $start = $this->start;
        }
        if ($end > $this->end) {
            // The item ends after our requested time range
            $end = $this->end;
        }

        // Finding out where we need to insert the new item.
        $currentIndex = 0;
        while ($start > $this->data[$currentIndex]['end']) {
            $currentIndex++;
        }

        // The standard insertion point will be one _after_ the first
        // overlapping item.
        $insertStartIndex = $currentIndex + 1;

        $newItem = [
            'start' => $start,
            'end'   => $end,
            'type'  => $type,
        ];

        $preceedingItem = $this->data[$insertStartIndex - 1];
        if ($this->data[$insertStartIndex - 1]['start'] === $start) {
            // The old item starts at the exact same point as the new item.
            $insertStartIndex--;
        }

        // Now we know where to insert the item, we need to know where it
        // starts overlapping with items on the tail end. We need to start
        // looking one item before the insertStartIndex, because it's possible
        // that the new item 'sits inside' the previous old item.
        if ($insertStartIndex > 0) {
            $currentIndex = $insertStartIndex - 1;
        } else {
            $currentIndex = 0;
        }

        while ($end > $this->data[$currentIndex]['end']) {

            $currentIndex++;

        }

        // What we are about to insert into the array
        $newItems = [
            $newItem
        ];

        // This is the amount of items that are completely overwritten by the
        // new item.
        $itemsToDelete = $currentIndex - $insertStartIndex;
        if ($this->data[$currentIndex]['end'] <= $end) $itemsToDelete++;

        // If itemsToDelete was -1, it means that the newly inserted item is
        // actually sitting inside an existing one. This means we need to split
        // the item at the current position in two and insert the new item in
        // between.
        if ($itemsToDelete === -1) {
            $itemsToDelete = 0;
            if ($newItem['end'] < $preceedingItem['end']) {
                $newItems[] = [
                    'start' => $newItem['end'] + 1,
                    'end'   => $preceedingItem['end'],
                    'type'  => $preceedingItem['type']
                ];
            }
        }

        array_splice(
            $this->data,
            $insertStartIndex,
            $itemsToDelete,
            $newItems
        );

        $doMerge = false;
        $mergeOffset = $insertStartIndex;
        $mergeItem = $newItem;
        $mergeDelete = 1;

        if (isset($this->data[$insertStartIndex - 1])) {
            // Updating the start time of the previous item.
            $this->data[$insertStartIndex - 1]['end'] = $start;

            // If the previous and the current are of the same type, we can
            // merge them into one item.
            if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) {
                $doMerge = true;
                $mergeOffset--;
                $mergeDelete++;
                $mergeItem['start'] = $this->data[$insertStartIndex - 1]['start'];
            }
        }
        if (isset($this->data[$insertStartIndex + 1])) {
            // Updating the start time of the next item.
            $this->data[$insertStartIndex + 1]['start'] = $end;

            // If the next and the current are of the same type, we can
            // merge them into one item.
            if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) {
                $doMerge = true;
                $mergeDelete++;
                $mergeItem['end'] = $this->data[$insertStartIndex + 1]['end'];
            }

        }
        if ($doMerge) {
            array_splice(
                $this->data,
                $mergeOffset,
                $mergeDelete,
                [$mergeItem]
            );
        }

    }

    function getData() {

        return $this->data;

    }

}
<?php

namespace Sabre\VObject;

use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Recur\EventIterator;
use Sabre\VObject\Recur\NoInstancesException;

/**
 * This class helps with generating FREEBUSY reports based on existing sets of
 * objects.
 *
 * It only looks at VEVENT and VFREEBUSY objects from the sourcedata, and
 * generates a single VFREEBUSY object.
 *
 * VFREEBUSY components are described in RFC5545, The rules for what should
 * go in a single freebusy report is taken from RFC4791, section 7.10.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class FreeBusyGenerator {

    /**
     * Input objects.
     *
     * @var array
     */
    protected $objects = [];

    /**
     * Start of range.
     *
     * @var DateTimeInterface|null
     */
    protected $start;

    /**
     * End of range.
     *
     * @var DateTimeInterface|null
     */
    protected $end;

    /**
     * VCALENDAR object.
     *
     * @var Document
     */
    protected $baseObject;

    /**
     * Reference timezone.
     *
     * When we are calculating busy times, and we come across so-called
     * floating times (times without a timezone), we use the reference timezone
     * instead.
     *
     * This is also used for all-day events.
     *
     * This defaults to UTC.
     *
     * @var DateTimeZone
     */
    protected $timeZone;

    /**
     * A VAVAILABILITY document.
     *
     * If this is set, it's information will be included when calculating
     * freebusy time.
     *
     * @var Document
     */
    protected $vavailability;

    /**
     * Creates the generator.
     *
     * Check the setTimeRange and setObjects methods for details about the
     * arguments.
     *
     * @param DateTimeInterface $start
     * @param DateTimeInterface $end
     * @param mixed $objects
     * @param DateTimeZone $timeZone
     */
    function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) {

        $this->setTimeRange($start, $end);

        if ($objects) {
            $this->setObjects($objects);
        }
        if (is_null($timeZone)) {
            $timeZone = new DateTimeZone('UTC');
        }
        $this->setTimeZone($timeZone);

    }

    /**
     * Sets the VCALENDAR object.
     *
     * If this is set, it will not be generated for you. You are responsible
     * for setting things like the METHOD, CALSCALE, VERSION, etc..
     *
     * The VFREEBUSY object will be automatically added though.
     *
     * @param Document $vcalendar
     * @return void
     */
    function setBaseObject(Document $vcalendar) {

        $this->baseObject = $vcalendar;

    }

    /**
     * Sets a VAVAILABILITY document.
     *
     * @param Document $vcalendar
     * @return void
     */
    function setVAvailability(Document $vcalendar) {

        $this->vavailability = $vcalendar;

    }

    /**
     * Sets the input objects.
     *
     * You must either specify a valendar object as a string, or as the parse
     * Component.
     * It's also possible to specify multiple objects as an array.
     *
     * @param mixed $objects
     *
     * @return void
     */
    function setObjects($objects) {

        if (!is_array($objects)) {
            $objects = [$objects];
        }

        $this->objects = [];
        foreach ($objects as $object) {

            if (is_string($object) || is_resource($object)) {
                $this->objects[] = Reader::read($object);
            } elseif ($object instanceof Component) {
                $this->objects[] = $object;
            } else {
                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
            }

        }

    }

    /**
     * Sets the time range.
     *
     * Any freebusy object falling outside of this time range will be ignored.
     *
     * @param DateTimeInterface $start
     * @param DateTimeInterface $end
     *
     * @return void
     */
    function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) {

        if (!$start) {
            $start = new DateTimeImmutable(Settings::$minDate);
        }
        if (!$end) {
            $end = new DateTimeImmutable(Settings::$maxDate);
        }
        $this->start = $start;
        $this->end = $end;

    }

    /**
     * Sets the reference timezone for floating times.
     *
     * @param DateTimeZone $timeZone
     *
     * @return void
     */
    function setTimeZone(DateTimeZone $timeZone) {

        $this->timeZone = $timeZone;

    }

    /**
     * Parses the input data and returns a correct VFREEBUSY object, wrapped in
     * a VCALENDAR.
     *
     * @return Component
     */
    function getResult() {

        $fbData = new FreeBusyData(
            $this->start->getTimeStamp(),
            $this->end->getTimeStamp()
        );
        if ($this->vavailability) {

            $this->calculateAvailability($fbData, $this->vavailability);

        }

        $this->calculateBusy($fbData, $this->objects);

        return $this->generateFreeBusyCalendar($fbData);


    }

    /**
     * This method takes a VAVAILABILITY component and figures out all the
     * available times.
     *
     * @param FreeBusyData $fbData
     * @param VCalendar $vavailability
     * @return void
     */
    protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) {

        $vavailComps = iterator_to_array($vavailability->VAVAILABILITY);
        usort(
            $vavailComps,
            function($a, $b) {

                // We need to order the components by priority. Priority 1
                // comes first, up until priority 9. Priority 0 comes after
                // priority 9. No priority implies priority 0.
                //
                // Yes, I'm serious.
                $priorityA = isset($a->PRIORITY) ? (int)$a->PRIORITY->getValue() : 0;
                $priorityB = isset($b->PRIORITY) ? (int)$b->PRIORITY->getValue() : 0;

                if ($priorityA === 0) $priorityA = 10;
                if ($priorityB === 0) $priorityB = 10;

                return $priorityA - $priorityB;

            }
        );

        // Now we go over all the VAVAILABILITY components and figure if
        // there's any we don't need to consider.
        //
        // This is can be because of one of two reasons: either the
        // VAVAILABILITY component falls outside the time we are interested in,
        // or a different VAVAILABILITY component with a higher priority has
        // already completely covered the time-range.
        $old = $vavailComps;
        $new = [];

        foreach ($old as $vavail) {

            list($compStart, $compEnd) = $vavail->getEffectiveStartEnd();

            // We don't care about datetimes that are earlier or later than the
            // start and end of the freebusy report, so this gets normalized
            // first.
            if (is_null($compStart) || $compStart < $this->start) {
                $compStart = $this->start;
            }
            if (is_null($compEnd) || $compEnd > $this->end) {
                $compEnd = $this->end;
            }

            // If the item fell out of the timerange, we can just skip it.
            if ($compStart > $this->end || $compEnd < $this->start) {
                continue;
            }

            // Going through our existing list of components to see if there's
            // a higher priority component that already fully covers this one.
            foreach ($new as $higherVavail) {

                list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
                if (
                    (is_null($higherStart) || $higherStart < $compStart) &&
                    (is_null($higherEnd) || $higherEnd > $compEnd)
                ) {

                    // Component is fully covered by a higher priority
                    // component. We can skip this component.
                    continue 2;

                }

            }

            // We're keeping it!
            $new[] = $vavail;

        }

        // Lastly, we need to traverse the remaining components and fill in the
        // freebusydata slots.
        //
        // We traverse the components in reverse, because we want the higher
        // priority components to override the lower ones.
        foreach (array_reverse($new) as $vavail) {

            $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
            list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd();

            // Making the component size no larger than the requested free-busy
            // report range.
            if (!$vavailStart || $vavailStart < $this->start) {
                $vavailStart = $this->start;
            }
            if (!$vavailEnd || $vavailEnd > $this->end) {
                $vavailEnd = $this->end;
            }

            // Marking the entire time range of the VAVAILABILITY component as
            // busy.
            $fbData->add(
                $vavailStart->getTimeStamp(),
                $vavailEnd->getTimeStamp(),
                $busyType
            );

            // Looping over the AVAILABLE components.
            if (isset($vavail->AVAILABLE)) foreach ($vavail->AVAILABLE as $available) {

                list($availStart, $availEnd) = $available->getEffectiveStartEnd();
                $fbData->add(
                    $availStart->getTimeStamp(),
                    $availEnd->getTimeStamp(),
                    'FREE'
                );

                if ($available->RRULE) {
                    // Our favourite thing: recurrence!!

                    $rruleIterator = new Recur\RRuleIterator(
                        $available->RRULE->getValue(),
                        $availStart
                    );
                    $rruleIterator->fastForward($vavailStart);

                    $startEndDiff = $availStart->diff($availEnd);

                    while ($rruleIterator->valid()) {

                        $recurStart = $rruleIterator->current();
                        $recurEnd = $recurStart->add($startEndDiff);

                        if ($recurStart > $vavailEnd) {
                            // We're beyond the legal timerange.
                            break;
                        }

                        if ($recurEnd > $vavailEnd) {
                            // Truncating the end if it exceeds the
                            // VAVAILABILITY end.
                            $recurEnd = $vavailEnd;
                        }

                        $fbData->add(
                            $recurStart->getTimeStamp(),
                            $recurEnd->getTimeStamp(),
                            'FREE'
                        );

                        $rruleIterator->next();

                    }
                }

            }

        }

    }

    /**
     * This method takes an array of iCalendar objects and applies its busy
     * times on fbData.
     *
     * @param FreeBusyData $fbData
     * @param VCalendar[] $objects
     */
    protected function calculateBusy(FreeBusyData $fbData, array $objects) {

        foreach ($objects as $key => $object) {

            foreach ($object->getBaseComponents() as $component) {

                switch ($component->name) {

                    case 'VEVENT' :

                        $FBTYPE = 'BUSY';
                        if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
                            break;
                        }
                        if (isset($component->STATUS)) {
                            $status = strtoupper($component->STATUS);
                            if ($status === 'CANCELLED') {
                                break;
                            }
                            if ($status === 'TENTATIVE') {
                                $FBTYPE = 'BUSY-TENTATIVE';
                            }
                        }

                        $times = [];

                        if ($component->RRULE) {
                            try {
                                $iterator = new EventIterator($object, (string)$component->UID, $this->timeZone);
                            } catch (NoInstancesException $e) {
                                // This event is recurring, but it doesn't have a single
                                // instance. We are skipping this event from the output
                                // entirely.
                                unset($this->objects[$key]);
                                continue;
                            }

                            if ($this->start) {
                                $iterator->fastForward($this->start);
                            }

                            $maxRecurrences = Settings::$maxRecurrences;

                            while ($iterator->valid() && --$maxRecurrences) {

                                $startTime = $iterator->getDTStart();
                                if ($this->end && $startTime > $this->end) {
                                    break;
                                }
                                $times[] = [
                                    $iterator->getDTStart(),
                                    $iterator->getDTEnd(),
                                ];

                                $iterator->next();

                            }

                        } else {

                            $startTime = $component->DTSTART->getDateTime($this->timeZone);
                            if ($this->end && $startTime > $this->end) {
                                break;
                            }
                            $endTime = null;
                            if (isset($component->DTEND)) {
                                $endTime = $component->DTEND->getDateTime($this->timeZone);
                            } elseif (isset($component->DURATION)) {
                                $duration = DateTimeParser::parseDuration((string)$component->DURATION);
                                $endTime = clone $startTime;
                                $endTime = $endTime->add($duration);
                            } elseif (!$component->DTSTART->hasTime()) {
                                $endTime = clone $startTime;
                                $endTime = $endTime->modify('+1 day');
                            } else {
                                // The event had no duration (0 seconds)
                                break;
                            }

                            $times[] = [$startTime, $endTime];

                        }

                        foreach ($times as $time) {

                            if ($this->end && $time[0] > $this->end) break;
                            if ($this->start && $time[1] < $this->start) break;

                            $fbData->add(
                                $time[0]->getTimeStamp(),
                                $time[1]->getTimeStamp(),
                                $FBTYPE
                            );
                        }
                        break;

                    case 'VFREEBUSY' :
                        foreach ($component->FREEBUSY as $freebusy) {

                            $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY';

                            // Skipping intervals marked as 'free'
                            if ($fbType === 'FREE')
                                continue;

                            $values = explode(',', $freebusy);
                            foreach ($values as $value) {
                                list($startTime, $endTime) = explode('/', $value);
                                $startTime = DateTimeParser::parseDateTime($startTime);

                                if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') {
                                    $duration = DateTimeParser::parseDuration($endTime);
                                    $endTime = clone $startTime;
                                    $endTime = $endTime->add($duration);
                                } else {
                                    $endTime = DateTimeParser::parseDateTime($endTime);
                                }

                                if ($this->start && $this->start > $endTime) continue;
                                if ($this->end && $this->end < $startTime) continue;
                                $fbData->add(
                                    $startTime->getTimeStamp(),
                                    $endTime->getTimeStamp(),
                                    $fbType
                                );

                            }


                        }
                        break;

                }


            }

        }

    }

    /**
     * This method takes a FreeBusyData object and generates the VCALENDAR
     * object associated with it.
     *
     * @return VCalendar
     */
    protected function generateFreeBusyCalendar(FreeBusyData $fbData) {

        if ($this->baseObject) {
            $calendar = $this->baseObject;
        } else {
            $calendar = new VCalendar();
        }

        $vfreebusy = $calendar->createComponent('VFREEBUSY');
        $calendar->add($vfreebusy);

        if ($this->start) {
            $dtstart = $calendar->createProperty('DTSTART');
            $dtstart->setDateTime($this->start);
            $vfreebusy->add($dtstart);
        }
        if ($this->end) {
            $dtend = $calendar->createProperty('DTEND');
            $dtend->setDateTime($this->end);
            $vfreebusy->add($dtend);
        }

        $tz = new \DateTimeZone('UTC');
        $dtstamp = $calendar->createProperty('DTSTAMP');
        $dtstamp->setDateTime(new DateTimeImmutable('now', $tz));
        $vfreebusy->add($dtstamp);

        foreach ($fbData->getData() as $busyTime) {

            $busyType = strtoupper($busyTime['type']);

            // Ignoring all the FREE parts, because those are already assumed.
            if ($busyType === 'FREE') {
                continue;
            }

            $busyTime[0] = new \DateTimeImmutable('@' . $busyTime['start'], $tz);
            $busyTime[1] = new \DateTimeImmutable('@' . $busyTime['end'], $tz);

            $prop = $calendar->createProperty(
                'FREEBUSY',
                $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
            );

            // Only setting FBTYPE if it's not BUSY, because BUSY is the
            // default anyway.
            if ($busyType !== 'BUSY') {
                $prop['FBTYPE'] = $busyType;
            }
            $vfreebusy->add($prop);

        }

        return $calendar;


    }

}
<?php

namespace Sabre\VObject;

/**
 * This exception is thrown whenever an invalid value is found anywhere in a
 * iCalendar or vCard object.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class InvalidDataException extends \Exception {
}
<?php

namespace Sabre\VObject\ITip;

use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Reader;
use Sabre\VObject\Recur\EventIterator;

/**
 * The ITip\Broker class is a utility class that helps with processing
 * so-called iTip messages.
 *
 * iTip is defined in rfc5546, stands for iCalendar Transport-Independent
 * Interoperability Protocol, and describes the underlying mechanism for
 * using iCalendar for scheduling for for example through email (also known as
 * IMip) and CalDAV Scheduling.
 *
 * This class helps by:
 *
 * 1. Creating individual invites based on an iCalendar event for each
 *    attendee.
 * 2. Generating invite updates based on an iCalendar update. This may result
 *    in new invites, updates and cancellations for attendees, if that list
 *    changed.
 * 3. On the receiving end, it can create a local iCalendar event based on
 *    a received invite.
 * 4. It can also process an invite update on a local event, ensuring that any
 *    overridden properties from attendees are retained.
 * 5. It can create a accepted or declined iTip reply based on an invite.
 * 6. It can process a reply from an invite and update an events attendee
 *     status based on a reply.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Broker {

    /**
     * This setting determines whether the rules for the SCHEDULE-AGENT
     * parameter should be followed.
     *
     * This is a parameter defined on ATTENDEE properties, introduced by RFC
     * 6638. This parameter allows a caldav client to tell the server 'Don't do
     * any scheduling operations'.
     *
     * If this setting is turned on, any attendees with SCHEDULE-AGENT set to
     * CLIENT will be ignored. This is the desired behavior for a CalDAV
     * server, but if you're writing an iTip application that doesn't deal with
     * CalDAV, you may want to ignore this parameter.
     *
     * @var bool
     */
    public $scheduleAgentServerRules = true;

    /**
     * The broker will try during 'parseEvent' figure out whether the change
     * was significant.
     *
     * It uses a few different ways to do this. One of these ways is seeing if
     * certain properties changed values. This list of specified here.
     *
     * This list is taken from:
     * * http://tools.ietf.org/html/rfc5546#section-2.1.4
     *
     * @var string[]
     */
    public $significantChangeProperties = [
        'DTSTART',
        'DTEND',
        'DURATION',
        'DUE',
        'RRULE',
        'RDATE',
        'EXDATE',
        'STATUS',
    ];

    /**
     * This method is used to process an incoming itip message.
     *
     * Examples:
     *
     * 1. A user is an attendee to an event. The organizer sends an updated
     * meeting using a new iTip message with METHOD:REQUEST. This function
     * will process the message and update the attendee's event accordingly.
     *
     * 2. The organizer cancelled the event using METHOD:CANCEL. We will update
     * the users event to state STATUS:CANCELLED.
     *
     * 3. An attendee sent a reply to an invite using METHOD:REPLY. We can
     * update the organizers event to update the ATTENDEE with its correct
     * PARTSTAT.
     *
     * The $existingObject is updated in-place. If there is no existing object
     * (because it's a new invite for example) a new object will be created.
     *
     * If an existing object does not exist, and the method was CANCEL or
     * REPLY, the message effectively gets ignored, and no 'existingObject'
     * will be created.
     *
     * The updated $existingObject is also returned from this function.
     *
     * If the iTip message was not supported, we will always return false.
     *
     * @param Message $itipMessage
     * @param VCalendar $existingObject
     *
     * @return VCalendar|null
     */
    function processMessage(Message $itipMessage, VCalendar $existingObject = null) {

        // We only support events at the moment.
        if ($itipMessage->component !== 'VEVENT') {
            return false;
        }

        switch ($itipMessage->method) {

            case 'REQUEST' :
                return $this->processMessageRequest($itipMessage, $existingObject);

            case 'CANCEL' :
                return $this->processMessageCancel($itipMessage, $existingObject);

            case 'REPLY' :
                return $this->processMessageReply($itipMessage, $existingObject);

            default :
                // Unsupported iTip message
                return;

        }

        return $existingObject;

    }

    /**
     * This function parses a VCALENDAR object and figure out if any messages
     * need to be sent.
     *
     * A VCALENDAR object will be created from the perspective of either an
     * attendee, or an organizer. You must pass a string identifying the
     * current user, so we can figure out who in the list of attendees or the
     * organizer we are sending this message on behalf of.
     *
     * It's possible to specify the current user as an array, in case the user
     * has more than one identifying href (such as multiple emails).
     *
     * It $oldCalendar is specified, it is assumed that the operation is
     * updating an existing event, which means that we need to look at the
     * differences between events, and potentially send old attendees
     * cancellations, and current attendees updates.
     *
     * If $calendar is null, but $oldCalendar is specified, we treat the
     * operation as if the user has deleted an event. If the user was an
     * organizer, this means that we need to send cancellation notices to
     * people. If the user was an attendee, we need to make sure that the
     * organizer gets the 'declined' message.
     *
     * @param VCalendar|string $calendar
     * @param string|array $userHref
     * @param VCalendar|string $oldCalendar
     *
     * @return array
     */
    function parseEvent($calendar = null, $userHref, $oldCalendar = null) {

        if ($oldCalendar) {
            if (is_string($oldCalendar)) {
                $oldCalendar = Reader::read($oldCalendar);
            }
            if (!isset($oldCalendar->VEVENT)) {
                // We only support events at the moment
                return [];
            }

            $oldEventInfo = $this->parseEventInfo($oldCalendar);
        } else {
            $oldEventInfo = [
                'organizer'             => null,
                'significantChangeHash' => '',
                'attendees'             => [],
            ];
        }

        $userHref = (array)$userHref;

        if (!is_null($calendar)) {

            if (is_string($calendar)) {
                $calendar = Reader::read($calendar);
            }
            if (!isset($calendar->VEVENT)) {
                // We only support events at the moment
                return [];
            }
            $eventInfo = $this->parseEventInfo($calendar);
            if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) {
                // If there were no attendees on either side of the equation,
                // we don't need to do anything.
                return [];
            }
            if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) {
                // There was no organizer before or after the change.
                return [];
            }

            $baseCalendar = $calendar;

            // If the new object didn't have an organizer, the organizer
            // changed the object from a scheduling object to a non-scheduling
            // object. We just copy the info from the old object.
            if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) {
                $eventInfo['organizer'] = $oldEventInfo['organizer'];
                $eventInfo['organizerName'] = $oldEventInfo['organizerName'];
            }

        } else {
            // The calendar object got deleted, we need to process this as a
            // cancellation / decline.
            if (!$oldCalendar) {
                // No old and no new calendar, there's no thing to do.
                return [];
            }

            $eventInfo = $oldEventInfo;

            if (in_array($eventInfo['organizer'], $userHref)) {
                // This is an organizer deleting the event.
                $eventInfo['attendees'] = [];
                // Increasing the sequence, but only if the organizer deleted
                // the event.
                $eventInfo['sequence']++;
            } else {
                // This is an attendee deleting the event.
                foreach ($eventInfo['attendees'] as $key => $attendee) {
                    if (in_array($attendee['href'], $userHref)) {
                        $eventInfo['attendees'][$key]['instances'] = ['master' =>
                            ['id' => 'master', 'partstat' => 'DECLINED']
                        ];
                    }
                }
            }
            $baseCalendar = $oldCalendar;

        }

        if (in_array($eventInfo['organizer'], $userHref)) {
            return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo);
        } elseif ($oldCalendar) {
            // We need to figure out if the user is an attendee, but we're only
            // doing so if there's an oldCalendar, because we only want to
            // process updates, not creation of new events.
            foreach ($eventInfo['attendees'] as $attendee) {
                if (in_array($attendee['href'], $userHref)) {
                    return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']);
                }
            }
        }
        return [];

    }

    /**
     * Processes incoming REQUEST messages.
     *
     * This is message from an organizer, and is either a new event
     * invite, or an update to an existing one.
     *
     *
     * @param Message $itipMessage
     * @param VCalendar $existingObject
     *
     * @return VCalendar|null
     */
    protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) {

        if (!$existingObject) {
            // This is a new invite, and we're just going to copy over
            // all the components from the invite.
            $existingObject = new VCalendar();
            foreach ($itipMessage->message->getComponents() as $component) {
                $existingObject->add(clone $component);
            }
        } else {
            // We need to update an existing object with all the new
            // information. We can just remove all existing components
            // and create new ones.
            foreach ($existingObject->getComponents() as $component) {
                $existingObject->remove($component);
            }
            foreach ($itipMessage->message->getComponents() as $component) {
                $existingObject->add(clone $component);
            }
        }
        return $existingObject;

    }

    /**
     * Processes incoming CANCEL messages.
     *
     * This is a message from an organizer, and means that either an
     * attendee got removed from an event, or an event got cancelled
     * altogether.
     *
     * @param Message $itipMessage
     * @param VCalendar $existingObject
     *
     * @return VCalendar|null
     */
    protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) {

        if (!$existingObject) {
            // The event didn't exist in the first place, so we're just
            // ignoring this message.
        } else {
            foreach ($existingObject->VEVENT as $vevent) {
                $vevent->STATUS = 'CANCELLED';
                $vevent->SEQUENCE = $itipMessage->sequence;
            }
        }
        return $existingObject;

    }

    /**
     * Processes incoming REPLY messages.
     *
     * The message is a reply. This is for example an attendee telling
     * an organizer he accepted the invite, or declined it.
     *
     * @param Message $itipMessage
     * @param VCalendar $existingObject
     *
     * @return VCalendar|null
     */
    protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) {

        // A reply can only be processed based on an existing object.
        // If the object is not available, the reply is ignored.
        if (!$existingObject) {
            return;
        }
        $instances = [];
        $requestStatus = '2.0';

        // Finding all the instances the attendee replied to.
        foreach ($itipMessage->message->VEVENT as $vevent) {
            $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
            $attendee = $vevent->ATTENDEE;
            $instances[$recurId] = $attendee['PARTSTAT']->getValue();
            if (isset($vevent->{'REQUEST-STATUS'})) {
                $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue();
                list($requestStatus) = explode(';', $requestStatus);
            }
        }

        // Now we need to loop through the original organizer event, to find
        // all the instances where we have a reply for.
        $masterObject = null;
        foreach ($existingObject->VEVENT as $vevent) {
            $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
            if ($recurId === 'master') {
                $masterObject = $vevent;
            }
            if (isset($instances[$recurId])) {
                $attendeeFound = false;
                if (isset($vevent->ATTENDEE)) {
                    foreach ($vevent->ATTENDEE as $attendee) {
                        if ($attendee->getValue() === $itipMessage->sender) {
                            $attendeeFound = true;
                            $attendee['PARTSTAT'] = $instances[$recurId];
                            $attendee['SCHEDULE-STATUS'] = $requestStatus;
                            // Un-setting the RSVP status, because we now know
                            // that the attendee already replied.
                            unset($attendee['RSVP']);
                            break;
                        }
                    }
                }
                if (!$attendeeFound) {
                    // Adding a new attendee. The iTip documentation calls this
                    // a party crasher.
                    $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, [
                        'PARTSTAT' => $instances[$recurId]
                    ]);
                    if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName;
                }
                unset($instances[$recurId]);
            }
        }

        if (!$masterObject) {
            // No master object, we can't add new instances.
            return;
        }
        // If we got replies to instances that did not exist in the
        // original list, it means that new exceptions must be created.
        foreach ($instances as $recurId => $partstat) {

            $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid);
            $found = false;
            $iterations = 1000;
            do {

                $newObject = $recurrenceIterator->getEventObject();
                $recurrenceIterator->next();

                if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue() === $recurId) {
                    $found = true;
                }
                $iterations--;

            } while ($recurrenceIterator->valid() && !$found && $iterations);

            // Invalid recurrence id. Skipping this object.
            if (!$found) continue;

            unset(
                $newObject->RRULE,
                $newObject->EXDATE,
                $newObject->RDATE
            );
            $attendeeFound = false;
            if (isset($newObject->ATTENDEE)) {
                foreach ($newObject->ATTENDEE as $attendee) {
                    if ($attendee->getValue() === $itipMessage->sender) {
                        $attendeeFound = true;
                        $attendee['PARTSTAT'] = $partstat;
                        break;
                    }
                }
            }
            if (!$attendeeFound) {
                // Adding a new attendee
                $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, [
                    'PARTSTAT' => $partstat
                ]);
                if ($itipMessage->senderName) {
                    $attendee['CN'] = $itipMessage->senderName;
                }
            }
            $existingObject->add($newObject);

        }
        return $existingObject;

    }

    /**
     * This method is used in cases where an event got updated, and we
     * potentially need to send emails to attendees to let them know of updates
     * in the events.
     *
     * We will detect which attendees got added, which got removed and create
     * specific messages for these situations.
     *
     * @param VCalendar $calendar
     * @param array $eventInfo
     * @param array $oldEventInfo
     *
     * @return array
     */
    protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) {

        // Merging attendee lists.
        $attendees = [];
        foreach ($oldEventInfo['attendees'] as $attendee) {
            $attendees[$attendee['href']] = [
                'href'         => $attendee['href'],
                'oldInstances' => $attendee['instances'],
                'newInstances' => [],
                'name'         => $attendee['name'],
                'forceSend'    => null,
            ];
        }
        foreach ($eventInfo['attendees'] as $attendee) {
            if (isset($attendees[$attendee['href']])) {
                $attendees[$attendee['href']]['name'] = $attendee['name'];
                $attendees[$attendee['href']]['newInstances'] = $attendee['instances'];
                $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend'];
            } else {
                $attendees[$attendee['href']] = [
                    'href'         => $attendee['href'],
                    'oldInstances' => [],
                    'newInstances' => $attendee['instances'],
                    'name'         => $attendee['name'],
                    'forceSend'    => $attendee['forceSend'],
                ];
            }
        }

        $messages = [];

        foreach ($attendees as $attendee) {

            // An organizer can also be an attendee. We should not generate any
            // messages for those.
            if ($attendee['href'] === $eventInfo['organizer']) {
                continue;
            }

            $message = new Message();
            $message->uid = $eventInfo['uid'];
            $message->component = 'VEVENT';
            $message->sequence = $eventInfo['sequence'];
            $message->sender = $eventInfo['organizer'];
            $message->senderName = $eventInfo['organizerName'];
            $message->recipient = $attendee['href'];
            $message->recipientName = $attendee['name'];

            if (!$attendee['newInstances']) {

                // If there are no instances the attendee is a part of, it
                // means the attendee was removed and we need to send him a
                // CANCEL.
                $message->method = 'CANCEL';

                // Creating the new iCalendar body.
                $icalMsg = new VCalendar();
                $icalMsg->METHOD = $message->method;
                $event = $icalMsg->add('VEVENT', [
                    'UID'      => $message->uid,
                    'SEQUENCE' => $message->sequence,
                ]);
                if (isset($calendar->VEVENT->SUMMARY)) {
                    $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue());
                }
                $event->add(clone $calendar->VEVENT->DTSTART);
                if (isset($calendar->VEVENT->DTEND)) {
                    $event->add(clone $calendar->VEVENT->DTEND);
                } elseif (isset($calendar->VEVENT->DURATION)) {
                    $event->add(clone $calendar->VEVENT->DURATION);
                }
                $org = $event->add('ORGANIZER', $eventInfo['organizer']);
                if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName'];
                $event->add('ATTENDEE', $attendee['href'], [
                    'CN' => $attendee['name'],
                ]);
                $message->significantChange = true;

            } else {

                // The attendee gets the updated event body
                $message->method = 'REQUEST';

                // Creating the new iCalendar body.
                $icalMsg = new VCalendar();
                $icalMsg->METHOD = $message->method;

                foreach ($calendar->select('VTIMEZONE') as $timezone) {
                    $icalMsg->add(clone $timezone);
                }

                // We need to find out that this change is significant. If it's
                // not, systems may opt to not send messages.
                //
                // We do this based on the 'significantChangeHash' which is
                // some value that changes if there's a certain set of
                // properties changed in the event, or simply if there's a
                // difference in instances that the attendee is invited to.

                $message->significantChange =
                    $attendee['forceSend'] === 'REQUEST' ||
                    array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) ||
                    $oldEventInfo['significantChangeHash'] !== $eventInfo['significantChangeHash'];

                foreach ($attendee['newInstances'] as $instanceId => $instanceInfo) {

                    $currentEvent = clone $eventInfo['instances'][$instanceId];
                    if ($instanceId === 'master') {

                        // We need to find a list of events that the attendee
                        // is not a part of to add to the list of exceptions.
                        $exceptions = [];
                        foreach ($eventInfo['instances'] as $instanceId => $vevent) {
                            if (!isset($attendee['newInstances'][$instanceId])) {
                                $exceptions[] = $instanceId;
                            }
                        }

                        // If there were exceptions, we need to add it to an
                        // existing EXDATE property, if it exists.
                        if ($exceptions) {
                            if (isset($currentEvent->EXDATE)) {
                                $currentEvent->EXDATE->setParts(array_merge(
                                    $currentEvent->EXDATE->getParts(),
                                    $exceptions
                                ));
                            } else {
                                $currentEvent->EXDATE = $exceptions;
                            }
                        }

                        // Cleaning up any scheduling information that
                        // shouldn't be sent along.
                        unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']);
                        unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']);

                        foreach ($currentEvent->ATTENDEE as $attendee) {
                            unset($attendee['SCHEDULE-FORCE-SEND']);
                            unset($attendee['SCHEDULE-STATUS']);

                            // We're adding PARTSTAT=NEEDS-ACTION to ensure that
                            // iOS shows an "Inbox Item"
                            if (!isset($attendee['PARTSTAT'])) {
                                $attendee['PARTSTAT'] = 'NEEDS-ACTION';
                            }

                        }

                    }

                    $icalMsg->add($currentEvent);

                }

            }

            $message->message = $icalMsg;
            $messages[] = $message;

        }

        return $messages;

    }

    /**
     * Parse an event update for an attendee.
     *
     * This function figures out if we need to send a reply to an organizer.
     *
     * @param VCalendar $calendar
     * @param array $eventInfo
     * @param array $oldEventInfo
     * @param string $attendee
     *
     * @return Message[]
     */
    protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) {

        if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent'] === 'CLIENT') {
            return [];
        }

        // Don't bother generating messages for events that have already been
        // cancelled.
        if ($eventInfo['status'] === 'CANCELLED') {
            return [];
        }

        $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ?
            $oldEventInfo['attendees'][$attendee]['instances'] :
            [];

        $instances = [];
        foreach ($oldInstances as $instance) {

            $instances[$instance['id']] = [
                'id'        => $instance['id'],
                'oldstatus' => $instance['partstat'],
                'newstatus' => null,
            ];

        }
        foreach ($eventInfo['attendees'][$attendee]['instances'] as $instance) {

            if (isset($instances[$instance['id']])) {
                $instances[$instance['id']]['newstatus'] = $instance['partstat'];
            } else {
                $instances[$instance['id']] = [
                    'id'        => $instance['id'],
                    'oldstatus' => null,
                    'newstatus' => $instance['partstat'],
                ];
            }

        }

        // We need to also look for differences in EXDATE. If there are new
        // items in EXDATE, it means that an attendee deleted instances of an
        // event, which means we need to send DECLINED specifically for those
        // instances.
        // We only need to do that though, if the master event is not declined.
        if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') {
            foreach ($eventInfo['exdate'] as $exDate) {

                if (!in_array($exDate, $oldEventInfo['exdate'])) {
                    if (isset($instances[$exDate])) {
                        $instances[$exDate]['newstatus'] = 'DECLINED';
                    } else {
                        $instances[$exDate] = [
                            'id'        => $exDate,
                            'oldstatus' => null,
                            'newstatus' => 'DECLINED',
                        ];
                    }
                }

            }
        }

        // Gathering a few extra properties for each instance.
        foreach ($instances as $recurId => $instanceInfo) {

            if (isset($eventInfo['instances'][$recurId])) {
                $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART;
            } else {
                $instances[$recurId]['dtstart'] = $recurId;
            }

        }

        $message = new Message();
        $message->uid = $eventInfo['uid'];
        $message->method = 'REPLY';
        $message->component = 'VEVENT';
        $message->sequence = $eventInfo['sequence'];
        $message->sender = $attendee;
        $message->senderName = $eventInfo['attendees'][$attendee]['name'];
        $message->recipient = $eventInfo['organizer'];
        $message->recipientName = $eventInfo['organizerName'];

        $icalMsg = new VCalendar();
        $icalMsg->METHOD = 'REPLY';

        $hasReply = false;

        foreach ($instances as $instance) {

            if ($instance['oldstatus'] == $instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') {
                // Skip
                continue;
            }

            $event = $icalMsg->add('VEVENT', [
                'UID'      => $message->uid,
                'SEQUENCE' => $message->sequence,
            ]);
            $summary = isset($calendar->VEVENT->SUMMARY) ? $calendar->VEVENT->SUMMARY->getValue() : '';
            // Adding properties from the correct source instance
            if (isset($eventInfo['instances'][$instance['id']])) {
                $instanceObj = $eventInfo['instances'][$instance['id']];
                $event->add(clone $instanceObj->DTSTART);
                if (isset($instanceObj->DTEND)) {
                    $event->add(clone $instanceObj->DTEND);
                } elseif (isset($instanceObj->DURATION)) {
                    $event->add(clone $instanceObj->DURATION);
                }
                if (isset($instanceObj->SUMMARY)) {
                    $event->add('SUMMARY', $instanceObj->SUMMARY->getValue());
                } elseif ($summary) {
                    $event->add('SUMMARY', $summary);
                }
            } else {
                // This branch of the code is reached, when a reply is
                // generated for an instance of a recurring event, through the
                // fact that the instance has disappeared by showing up in
                // EXDATE
                $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
                // Treat is as a DATE field
                if (strlen($instance['id']) <= 8) {
                    $event->add('DTSTART', $dt, ['VALUE' => 'DATE']);
                } else {
                    $event->add('DTSTART', $dt);
                }
                if ($summary) {
                    $event->add('SUMMARY', $summary);
                }
            }
            if ($instance['id'] !== 'master') {
                $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']);
                // Treat is as a DATE field
                if (strlen($instance['id']) <= 8) {
                    $event->add('RECURRENCE-ID', $dt, ['VALUE' => 'DATE']);
                } else {
                    $event->add('RECURRENCE-ID', $dt);
                }
            }
            $organizer = $event->add('ORGANIZER', $message->recipient);
            if ($message->recipientName) {
                $organizer['CN'] = $message->recipientName;
            }
            $attendee = $event->add('ATTENDEE', $message->sender, [
                'PARTSTAT' => $instance['newstatus']
            ]);
            if ($message->senderName) {
                $attendee['CN'] = $message->senderName;
            }
            $hasReply = true;

        }

        if ($hasReply) {
            $message->message = $icalMsg;
            return [$message];
        } else {
            return [];
        }

    }

    /**
     * Returns attendee information and information about instances of an
     * event.
     *
     * Returns an array with the following keys:
     *
     * 1. uid
     * 2. organizer
     * 3. organizerName
     * 4. organizerScheduleAgent
     * 5. organizerForceSend
     * 6. instances
     * 7. attendees
     * 8. sequence
     * 9. exdate
     * 10. timezone - strictly the timezone on which the recurrence rule is
     *                based on.
     * 11. significantChangeHash
     * 12. status
     * @param VCalendar $calendar
     *
     * @return array
     */
    protected function parseEventInfo(VCalendar $calendar = null) {

        $uid = null;
        $organizer = null;
        $organizerName = null;
        $organizerForceSend = null;
        $sequence = null;
        $timezone = null;
        $status = null;
        $organizerScheduleAgent = 'SERVER';

        $significantChangeHash = '';

        // Now we need to collect a list of attendees, and which instances they
        // are a part of.
        $attendees = [];

        $instances = [];
        $exdate = [];

        foreach ($calendar->VEVENT as $vevent) {

            if (is_null($uid)) {
                $uid = $vevent->UID->getValue();
            } else {
                if ($uid !== $vevent->UID->getValue()) {
                    throw new ITipException('If a calendar contained more than one event, they must have the same UID.');
                }
            }

            if (!isset($vevent->DTSTART)) {
                throw new ITipException('An event MUST have a DTSTART property.');
            }

            if (isset($vevent->ORGANIZER)) {
                if (is_null($organizer)) {
                    $organizer = $vevent->ORGANIZER->getNormalizedValue();
                    $organizerName = isset($vevent->ORGANIZER['CN']) ? $vevent->ORGANIZER['CN'] : null;
                } else {
                    if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) {
                        throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.');
                    }
                }
                $organizerForceSend =
                    isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ?
                    strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) :
                    null;
                $organizerScheduleAgent =
                    isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ?
                    strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) :
                    'SERVER';
            }
            if (is_null($sequence) && isset($vevent->SEQUENCE)) {
                $sequence = $vevent->SEQUENCE->getValue();
            }
            if (isset($vevent->EXDATE)) {
                foreach ($vevent->select('EXDATE') as $val) {
                    $exdate = array_merge($exdate, $val->getParts());
                }
                sort($exdate);
            }
            if (isset($vevent->STATUS)) {
                $status = strtoupper($vevent->STATUS->getValue());
            }

            $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master';
            if (is_null($timezone)) {
                if ($recurId === 'master') {
                    $timezone = $vevent->DTSTART->getDateTime()->getTimeZone();
                } else {
                    $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone();
                }
            }
            if (isset($vevent->ATTENDEE)) {
                foreach ($vevent->ATTENDEE as $attendee) {

                    if ($this->scheduleAgentServerRules &&
                        isset($attendee['SCHEDULE-AGENT']) &&
                        strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT'
                    ) {
                        continue;
                    }
                    $partStat =
                        isset($attendee['PARTSTAT']) ?
                        strtoupper($attendee['PARTSTAT']) :
                        'NEEDS-ACTION';

                    $forceSend =
                        isset($attendee['SCHEDULE-FORCE-SEND']) ?
                        strtoupper($attendee['SCHEDULE-FORCE-SEND']) :
                        null;


                    if (isset($attendees[$attendee->getNormalizedValue()])) {
                        $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = [
                            'id'         => $recurId,
                            'partstat'   => $partStat,
                            'force-send' => $forceSend,
                        ];
                    } else {
                        $attendees[$attendee->getNormalizedValue()] = [
                            'href'      => $attendee->getNormalizedValue(),
                            'instances' => [
                                $recurId => [
                                    'id'       => $recurId,
                                    'partstat' => $partStat,
                                ],
                            ],
                            'name'      => isset($attendee['CN']) ? (string)$attendee['CN'] : null,
                            'forceSend' => $forceSend,
                        ];
                    }

                }
                $instances[$recurId] = $vevent;

            }

            foreach ($this->significantChangeProperties as $prop) {
                if (isset($vevent->$prop)) {
                    $propertyValues = $vevent->select($prop);

                    $significantChangeHash .= $prop . ':';

                    if ($prop === 'EXDATE') {

                        $significantChangeHash .= implode(',', $exdate) . ';';

                    } else {

                        foreach ($propertyValues as $val) {
                            $significantChangeHash .= $val->getValue() . ';';
                        }

                    }
                }
            }

        }
        $significantChangeHash = md5($significantChangeHash);

        return compact(
            'uid',
            'organizer',
            'organizerName',
            'organizerScheduleAgent',
            'organizerForceSend',
            'instances',
            'attendees',
            'sequence',
            'exdate',
            'timezone',
            'significantChangeHash',
            'status'
        );

    }

}
<?php

namespace Sabre\VObject\ITip;

use Exception;

/**
 * This message is emitted in case of serious problems with iTip messages.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ITipException extends Exception {
}
<?php

namespace Sabre\VObject\ITip;

/**
 * This class represents an iTip message.
 *
 * A message holds all the information relevant to the message, including the
 * object itself.
 *
 * It should for the most part be treated as immutable.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Message {

    /**
     * The object's UID.
     *
     * @var string
     */
    public $uid;

    /**
     * The component type, such as VEVENT.
     *
     * @var string
     */
    public $component;

    /**
     * Contains the ITip method, which is something like REQUEST, REPLY or
     * CANCEL.
     *
     * @var string
     */
    public $method;

    /**
     * The current sequence number for the event.
     *
     * @var int
     */
    public $sequence;

    /**
     * The senders' email address.
     *
     * Note that this does not imply that this has to be used in a From: field
     * if the message is sent by email. It may also be populated in Reply-To:
     * or not at all.
     *
     * @var string
     */
    public $sender;

    /**
     * The name of the sender. This is often populated from a CN parameter from
     * either the ORGANIZER or ATTENDEE, depending on the message.
     *
     * @var string|null
     */
    public $senderName;

    /**
     * The recipient's email address.
     *
     * @var string
     */
    public $recipient;

    /**
     * The name of the recipient. This is usually populated with the CN
     * parameter from the ATTENDEE or ORGANIZER property, if it's available.
     *
     * @var string|null
     */
    public $recipientName;

    /**
     * After the message has been delivered, this should contain a string such
     * as : 1.1;Sent or 1.2;Delivered.
     *
     * In case of a failure, this will hold the error status code.
     *
     * See:
     * http://tools.ietf.org/html/rfc6638#section-7.3
     *
     * @var string
     */
    public $scheduleStatus;

    /**
     * The iCalendar / iTip body.
     *
     * @var \Sabre\VObject\Component\VCalendar
     */
    public $message;

    /**
     * This will be set to true, if the iTip broker considers the change
     * 'significant'.
     *
     * In practice, this means that we'll only mark it true, if for instance
     * DTSTART changed. This allows systems to only send iTip messages when
     * significant changes happened. This is especially useful for iMip, as
     * normally a ton of messages may be generated for normal calendar use.
     *
     * To see the list of properties that are considered 'significant', check
     * out Sabre\VObject\ITip\Broker::$significantChangeProperties.
     *
     * @var bool
     */
    public $significantChange = true;

    /**
     * Returns the schedule status as a string.
     *
     * For example:
     * 1.2
     *
     * @return mixed bool|string
     */
    function getScheduleStatus() {

        if (!$this->scheduleStatus) {

            return false;

        } else {

            list($scheduleStatus) = explode(';', $this->scheduleStatus);
            return $scheduleStatus;

        }

    }

}
<?php

namespace Sabre\VObject\ITip;

/**
 * SameOrganizerForAllComponentsException.
 *
 * This exception is emitted when an event is encountered with more than one
 * component (e.g.: exceptions), but the organizer is not identical in every
 * component.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class SameOrganizerForAllComponentsException extends ITipException {

}
<?php

namespace Sabre\VObject;

use Sabre\Xml;

/**
 * A node is the root class for every element in an iCalendar of vCard object.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class Node
    implements \IteratorAggregate,
               \ArrayAccess,
               \Countable,
               \JsonSerializable,
               Xml\XmlSerializable {

    /**
     * The following constants are used by the validate() method.
     *
     * If REPAIR is set, the validator will attempt to repair any broken data
     * (if possible).
     */
    const REPAIR = 1;

    /**
     * If this option is set, the validator will operate on the vcards on the
     * assumption that the vcards need to be valid for CardDAV.
     *
     * This means for example that the UID is required, whereas it is not for
     * regular vcards.
     */
    const PROFILE_CARDDAV = 2;

    /**
     * If this option is set, the validator will operate on iCalendar objects
     * on the assumption that the vcards need to be valid for CalDAV.
     *
     * This means for example that calendars can only contain objects with
     * identical component types and UIDs.
     */
    const PROFILE_CALDAV = 4;

    /**
     * Reference to the parent object, if this is not the top object.
     *
     * @var Node
     */
    public $parent;

    /**
     * Iterator override.
     *
     * @var ElementList
     */
    protected $iterator = null;

    /**
     * The root document.
     *
     * @var Component
     */
    protected $root;

    /**
     * Serializes the node into a mimedir format.
     *
     * @return string
     */
    abstract function serialize();

    /**
     * This method returns an array, with the representation as it should be
     * encoded in JSON. This is used to create jCard or jCal documents.
     *
     * @return array
     */
    abstract function jsonSerialize();

    /**
     * This method serializes the data into XML. This is used to create xCard or
     * xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    abstract function xmlSerialize(Xml\Writer $writer);

    /**
     * Call this method on a document if you're done using it.
     *
     * It's intended to remove all circular references, so PHP can easily clean
     * it up.
     *
     * @return void
     */
    function destroy() {

        $this->parent = null;
        $this->root = null;

    }

    /* {{{ IteratorAggregator interface */

    /**
     * Returns the iterator for this object.
     *
     * @return ElementList
     */
    function getIterator() {

        if (!is_null($this->iterator)) {
            return $this->iterator;
        }

        return new ElementList([$this]);

    }

    /**
     * Sets the overridden iterator.
     *
     * Note that this is not actually part of the iterator interface
     *
     * @param ElementList $iterator
     *
     * @return void
     */
    function setIterator(ElementList $iterator) {

        $this->iterator = $iterator;

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on)
     *   2 - An inconsequential issue
     *   3 - A severe issue.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        return [];

    }

    /* }}} */

    /* {{{ Countable interface */

    /**
     * Returns the number of elements.
     *
     * @return int
     */
    function count() {

        $it = $this->getIterator();
        return $it->count();

    }

    /* }}} */

    /* {{{ ArrayAccess Interface */


    /**
     * Checks if an item exists through ArrayAccess.
     *
     * This method just forwards the request to the inner iterator
     *
     * @param int $offset
     *
     * @return bool
     */
    function offsetExists($offset) {

        $iterator = $this->getIterator();
        return $iterator->offsetExists($offset);

    }

    /**
     * Gets an item through ArrayAccess.
     *
     * This method just forwards the request to the inner iterator
     *
     * @param int $offset
     *
     * @return mixed
     */
    function offsetGet($offset) {

        $iterator = $this->getIterator();
        return $iterator->offsetGet($offset);

    }

    /**
     * Sets an item through ArrayAccess.
     *
     * This method just forwards the request to the inner iterator
     *
     * @param int $offset
     * @param mixed $value
     *
     * @return void
     */
    function offsetSet($offset, $value) {

        $iterator = $this->getIterator();
        $iterator->offsetSet($offset, $value);

    // @codeCoverageIgnoreStart
    //
    // This method always throws an exception, so we ignore the closing
    // brace
    }
    // @codeCoverageIgnoreEnd

    /**
     * Sets an item through ArrayAccess.
     *
     * This method just forwards the request to the inner iterator
     *
     * @param int $offset
     *
     * @return void
     */
    function offsetUnset($offset) {

        $iterator = $this->getIterator();
        $iterator->offsetUnset($offset);

    // @codeCoverageIgnoreStart
    //
    // This method always throws an exception, so we ignore the closing
    // brace
    }
    // @codeCoverageIgnoreEnd

    /* }}} */
}
<?php

namespace Sabre\VObject;

use ArrayIterator;
use Sabre\Xml;

/**
 * VObject Parameter.
 *
 * This class represents a parameter. A parameter is always tied to a property.
 * In the case of:
 *   DTSTART;VALUE=DATE:20101108
 * VALUE=DATE would be the parameter name and value.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Parameter extends Node {

    /**
     * Parameter name.
     *
     * @var string
     */
    public $name;

    /**
     * vCard 2.1 allows parameters to be encoded without a name.
     *
     * We can deduce the parameter name based on it's value.
     *
     * @var bool
     */
    public $noName = false;

    /**
     * Parameter value.
     *
     * @var string
     */
    protected $value;

    /**
     * Sets up the object.
     *
     * It's recommended to use the create:: factory method instead.
     *
     * @param string $name
     * @param string $value
     */
    function __construct(Document $root, $name, $value = null) {

        $this->name = strtoupper($name);
        $this->root = $root;
        if (is_null($name)) {
            $this->noName = true;
            $this->name = static::guessParameterNameByValue($value);
        }

        // If guessParameterNameByValue() returns an empty string
        // above, we're actually dealing with a parameter that has no value.
        // In that case we have to move the value to the name.
        if ($this->name === '') {
            $this->noName = false;
            $this->name = strtoupper($value);
        } else {
            $this->setValue($value);
        }

    }

    /**
     * Try to guess property name by value, can be used for vCard 2.1 nameless parameters.
     *
     * Figuring out what the name should have been. Note that a ton of
     * these are rather silly in 2014 and would probably rarely be
     * used, but we like to be complete.
     *
     * @param string $value
     *
     * @return string
     */
    static function guessParameterNameByValue($value) {
        switch (strtoupper($value)) {

            // Encodings
            case '7-BIT' :
            case 'QUOTED-PRINTABLE' :
            case 'BASE64' :
                $name = 'ENCODING';
                break;

            // Common types
            case 'WORK' :
            case 'HOME' :
            case 'PREF' :

            // Delivery Label Type
            case 'DOM' :
            case 'INTL' :
            case 'POSTAL' :
            case 'PARCEL' :

            // Telephone types
            case 'VOICE' :
            case 'FAX' :
            case 'MSG' :
            case 'CELL' :
            case 'PAGER' :
            case 'BBS' :
            case 'MODEM' :
            case 'CAR' :
            case 'ISDN' :
            case 'VIDEO' :

            // EMAIL types (lol)
            case 'AOL' :
            case 'APPLELINK' :
            case 'ATTMAIL' :
            case 'CIS' :
            case 'EWORLD' :
            case 'INTERNET' :
            case 'IBMMAIL' :
            case 'MCIMAIL' :
            case 'POWERSHARE' :
            case 'PRODIGY' :
            case 'TLX' :
            case 'X400' :

            // Photo / Logo format types
            case 'GIF' :
            case 'CGM' :
            case 'WMF' :
            case 'BMP' :
            case 'DIB' :
            case 'PICT' :
            case 'TIFF' :
            case 'PDF' :
            case 'PS' :
            case 'JPEG' :
            case 'MPEG' :
            case 'MPEG2' :
            case 'AVI' :
            case 'QTIME' :

            // Sound Digital Audio Type
            case 'WAVE' :
            case 'PCM' :
            case 'AIFF' :

            // Key types
            case 'X509' :
            case 'PGP' :
                $name = 'TYPE';
                break;

            // Value types
            case 'INLINE' :
            case 'URL' :
            case 'CONTENT-ID' :
            case 'CID' :
                $name = 'VALUE';
                break;

            default:
                $name = '';
        }

        return $name;
    }

    /**
     * Updates the current value.
     *
     * This may be either a single, or multiple strings in an array.
     *
     * @param string|array $value
     *
     * @return void
     */
    function setValue($value) {

        $this->value = $value;

    }

    /**
     * Returns the current value.
     *
     * This method will always return a string, or null. If there were multiple
     * values, it will automatically concatenate them (separated by comma).
     *
     * @return string|null
     */
    function getValue() {

        if (is_array($this->value)) {
            return implode(',', $this->value);
        } else {
            return $this->value;
        }

    }

    /**
     * Sets multiple values for this parameter.
     *
     * @param array $value
     *
     * @return void
     */
    function setParts(array $value) {

        $this->value = $value;

    }

    /**
     * Returns all values for this parameter.
     *
     * If there were no values, an empty array will be returned.
     *
     * @return array
     */
    function getParts() {

        if (is_array($this->value)) {
            return $this->value;
        } elseif (is_null($this->value)) {
            return [];
        } else {
            return [$this->value];
        }

    }

    /**
     * Adds a value to this parameter.
     *
     * If the argument is specified as an array, all items will be added to the
     * parameter value list.
     *
     * @param string|array $part
     *
     * @return void
     */
    function addValue($part) {

        if (is_null($this->value)) {
            $this->value = $part;
        } else {
            $this->value = array_merge((array)$this->value, (array)$part);
        }

    }

    /**
     * Checks if this parameter contains the specified value.
     *
     * This is a case-insensitive match. It makes sense to call this for for
     * instance the TYPE parameter, to see if it contains a keyword such as
     * 'WORK' or 'FAX'.
     *
     * @param string $value
     *
     * @return bool
     */
    function has($value) {

        return in_array(
            strtolower($value),
            array_map('strtolower', (array)$this->value)
        );

    }

    /**
     * Turns the object back into a serialized blob.
     *
     * @return string
     */
    function serialize() {

        $value = $this->getParts();

        if (count($value) === 0) {
            return $this->name . '=';
        }

        if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {

            return implode(';', $value);

        }

        return $this->name . '=' . array_reduce(
            $value,
            function($out, $item) {

                if (!is_null($out)) $out .= ',';

                // If there's no special characters in the string, we'll use the simple
                // format.
                //
                // The list of special characters is defined as:
                //
                // Any character except CONTROL, DQUOTE, ";", ":", ","
                //
                // by the iCalendar spec:
                // https://tools.ietf.org/html/rfc5545#section-3.1
                //
                // And we add ^ to that because of:
                // https://tools.ietf.org/html/rfc6868
                //
                // But we've found that iCal (7.0, shipped with OSX 10.9)
                // severaly trips on + characters not being quoted, so we
                // added + as well.
                if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
                    return $out . $item;
                } else {
                    // Enclosing in double-quotes, and using RFC6868 for encoding any
                    // special characters
                    $out .= '"' . strtr(
                        $item,
                        [
                            '^'  => '^^',
                            "\n" => '^n',
                            '"'  => '^\'',
                        ]
                    ) . '"';
                    return $out;
                }

            }
        );

    }

    /**
     * This method returns an array, with the representation as it should be
     * encoded in JSON. This is used to create jCard or jCal documents.
     *
     * @return array
     */
    function jsonSerialize() {

        return $this->value;

    }

    /**
     * This method serializes the data into XML. This is used to create xCard or
     * xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        foreach (explode(',', $this->value) as $value) {
            $writer->writeElement('text', $value);
        }

    }

    /**
     * Called when this object is being cast to a string.
     *
     * @return string
     */
    function __toString() {

        return (string)$this->getValue();

    }

    /**
     * Returns the iterator for this object.
     *
     * @return ElementList
     */
    function getIterator() {

        if (!is_null($this->iterator))
            return $this->iterator;

        return $this->iterator = new ArrayIterator((array)$this->value);

    }

}
<?php

namespace Sabre\VObject;

/**
 * Exception thrown by Reader if an invalid object was attempted to be parsed.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ParseException extends \Exception {
}
<?php

namespace Sabre\VObject\Parser;

use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\EofException;
use Sabre\VObject\ParseException;

/**
 * Json Parser.
 *
 * This parser parses both the jCal and jCard formats.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Json extends Parser {

    /**
     * The input data.
     *
     * @var array
     */
    protected $input;

    /**
     * Root component.
     *
     * @var Document
     */
    protected $root;

    /**
     * This method starts the parsing process.
     *
     * If the input was not supplied during construction, it's possible to pass
     * it here instead.
     *
     * If either input or options are not supplied, the defaults will be used.
     *
     * @param resource|string|array|null $input
     * @param int $options
     *
     * @return Sabre\VObject\Document
     */
    function parse($input = null, $options = 0) {

        if (!is_null($input)) {
            $this->setInput($input);
        }
        if (is_null($this->input)) {
            throw new EofException('End of input stream, or no input supplied');
        }

        if (0 !== $options) {
            $this->options = $options;
        }

        switch ($this->input[0]) {
            case 'vcalendar' :
                $this->root = new VCalendar([], false);
                break;
            case 'vcard' :
                $this->root = new VCard([], false);
                break;
            default :
                throw new ParseException('The root component must either be a vcalendar, or a vcard');

        }
        foreach ($this->input[1] as $prop) {
            $this->root->add($this->parseProperty($prop));
        }
        if (isset($this->input[2])) foreach ($this->input[2] as $comp) {
            $this->root->add($this->parseComponent($comp));
        }

        // Resetting the input so we can throw an feof exception the next time.
        $this->input = null;

        return $this->root;

    }

    /**
     * Parses a component.
     *
     * @param array $jComp
     *
     * @return \Sabre\VObject\Component
     */
    function parseComponent(array $jComp) {

        // We can remove $self from PHP 5.4 onward.
        $self = $this;

        $properties = array_map(
            function($jProp) use ($self) {
                return $self->parseProperty($jProp);
            },
            $jComp[1]
        );

        if (isset($jComp[2])) {

            $components = array_map(
                function($jComp) use ($self) {
                    return $self->parseComponent($jComp);
                },
                $jComp[2]
            );

        } else $components = [];

        return $this->root->createComponent(
            $jComp[0],
            array_merge($properties, $components),
            $defaults = false
        );

    }

    /**
     * Parses properties.
     *
     * @param array $jProp
     *
     * @return \Sabre\VObject\Property
     */
    function parseProperty(array $jProp) {

        list(
            $propertyName,
            $parameters,
            $valueType
        ) = $jProp;

        $propertyName = strtoupper($propertyName);

        // This is the default class we would be using if we didn't know the
        // value type. We're using this value later in this function.
        $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName);

        $parameters = (array)$parameters;

        $value = array_slice($jProp, 3);

        $valueType = strtoupper($valueType);

        if (isset($parameters['group'])) {
            $propertyName = $parameters['group'] . '.' . $propertyName;
            unset($parameters['group']);
        }

        $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType);
        $prop->setJsonValue($value);

        // We have to do something awkward here. FlatText as well as Text
        // represents TEXT values. We have to normalize these here. In the
        // future we can get rid of FlatText once we're allowed to break BC
        // again.
        if ($defaultPropertyClass === 'Sabre\VObject\Property\FlatText') {
            $defaultPropertyClass = 'Sabre\VObject\Property\Text';
        }

        // If the value type we received (e.g.: TEXT) was not the default value
        // type for the given property (e.g.: BDAY), we need to add a VALUE=
        // parameter.
        if ($defaultPropertyClass !== get_class($prop)) {
            $prop["VALUE"] = $valueType;
        }

        return $prop;

    }

    /**
     * Sets the input data.
     *
     * @param resource|string|array $input
     *
     * @return void
     */
    function setInput($input) {

        if (is_resource($input)) {
            $input = stream_get_contents($input);
        }
        if (is_string($input)) {
            $input = json_decode($input);
        }
        $this->input = $input;

    }

}
<?php

namespace Sabre\VObject\Parser;

use Sabre\VObject\Component;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Document;
use Sabre\VObject\EofException;
use Sabre\VObject\ParseException;

/**
 * MimeDir parser.
 *
 * This class parses iCalendar 2.0 and vCard 2.1, 3.0 and 4.0 files. This
 * parser will return one of the following two objects from the parse method:
 *
 * Sabre\VObject\Component\VCalendar
 * Sabre\VObject\Component\VCard
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class MimeDir extends Parser {

    /**
     * The input stream.
     *
     * @var resource
     */
    protected $input;

    /**
     * Root component.
     *
     * @var Component
     */
    protected $root;

    /**
     * By default all input will be assumed to be UTF-8.
     *
     * However, both iCalendar and vCard might be encoded using different
     * character sets. The character set is usually set in the mime-type.
     *
     * If this is the case, use setEncoding to specify that a different
     * encoding will be used. If this is set, the parser will automatically
     * convert all incoming data to UTF-8.
     *
     * @var string
     */
    protected $charset = 'UTF-8';

    /**
     * The list of character sets we support when decoding.
     *
     * This would be a const expression but for now we need to support PHP 5.5
     */
    protected static $SUPPORTED_CHARSETS = [
        'UTF-8',
        'ISO-8859-1',
        'Windows-1252',
    ];

    /**
     * Parses an iCalendar or vCard file.
     *
     * Pass a stream or a string. If null is parsed, the existing buffer is
     * used.
     *
     * @param string|resource|null $input
     * @param int $options
     *
     * @return Sabre\VObject\Document
     */
    function parse($input = null, $options = 0) {

        $this->root = null;

        if (!is_null($input)) {
            $this->setInput($input);
        }

        if (0 !== $options) {
            $this->options = $options;
        }

        $this->parseDocument();

        return $this->root;

    }

    /**
     * By default all input will be assumed to be UTF-8.
     *
     * However, both iCalendar and vCard might be encoded using different
     * character sets. The character set is usually set in the mime-type.
     *
     * If this is the case, use setEncoding to specify that a different
     * encoding will be used. If this is set, the parser will automatically
     * convert all incoming data to UTF-8.
     *
     * @param string $charset
     */
    function setCharset($charset) {

        if (!in_array($charset, self::$SUPPORTED_CHARSETS)) {
            throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: ' . implode(', ', self::$SUPPORTED_CHARSETS) . ')');
        }
        $this->charset = $charset;

    }

    /**
     * Sets the input buffer. Must be a string or stream.
     *
     * @param resource|string $input
     *
     * @return void
     */
    function setInput($input) {

        // Resetting the parser
        $this->lineIndex = 0;
        $this->startLine = 0;

        if (is_string($input)) {
            // Convering to a stream.
            $stream = fopen('php://temp', 'r+');
            fwrite($stream, $input);
            rewind($stream);
            $this->input = $stream;
        } elseif (is_resource($input)) {
            $this->input = $input;
        } else {
            throw new \InvalidArgumentException('This parser can only read from strings or streams.');
        }

    }

    /**
     * Parses an entire document.
     *
     * @return void
     */
    protected function parseDocument() {

        $line = $this->readLine();

        // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
        // It's 0xEF 0xBB 0xBF in UTF-8 hex.
        if (3 <= strlen($line)
            && ord($line[0]) === 0xef
            && ord($line[1]) === 0xbb
            && ord($line[2]) === 0xbf) {
            $line = substr($line, 3);
        }

        switch (strtoupper($line)) {
            case 'BEGIN:VCALENDAR' :
                $class = VCalendar::$componentMap['VCALENDAR'];
                break;
            case 'BEGIN:VCARD' :
                $class = VCard::$componentMap['VCARD'];
                break;
            default :
                throw new ParseException('This parser only supports VCARD and VCALENDAR files');
        }

        $this->root = new $class([], false);

        while (true) {

            // Reading until we hit END:
            $line = $this->readLine();
            if (strtoupper(substr($line, 0, 4)) === 'END:') {
                break;
            }
            $result = $this->parseLine($line);
            if ($result) {
                $this->root->add($result);
            }

        }

        $name = strtoupper(substr($line, 4));
        if ($name !== $this->root->name) {
            throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"');
        }

    }

    /**
     * Parses a line, and if it hits a component, it will also attempt to parse
     * the entire component.
     *
     * @param string $line Unfolded line
     *
     * @return Node
     */
    protected function parseLine($line) {

        // Start of a new component
        if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {

            $component = $this->root->createComponent(substr($line, 6), [], false);

            while (true) {

                // Reading until we hit END:
                $line = $this->readLine();
                if (strtoupper(substr($line, 0, 4)) === 'END:') {
                    break;
                }
                $result = $this->parseLine($line);
                if ($result) {
                    $component->add($result);
                }

            }

            $name = strtoupper(substr($line, 4));
            if ($name !== $component->name) {
                throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"');
            }

            return $component;

        } else {

            // Property reader
            $property = $this->readProperty($line);
            if (!$property) {
                // Ignored line
                return false;
            }
            return $property;

        }

    }

    /**
     * We need to look ahead 1 line every time to see if we need to 'unfold'
     * the next line.
     *
     * If that was not the case, we store it here.
     *
     * @var null|string
     */
    protected $lineBuffer;

    /**
     * The real current line number.
     */
    protected $lineIndex = 0;

    /**
     * In the case of unfolded lines, this property holds the line number for
     * the start of the line.
     *
     * @var int
     */
    protected $startLine = 0;

    /**
     * Contains a 'raw' representation of the current line.
     *
     * @var string
     */
    protected $rawLine;

    /**
     * Reads a single line from the buffer.
     *
     * This method strips any newlines and also takes care of unfolding.
     *
     * @throws \Sabre\VObject\EofException
     *
     * @return string
     */
    protected function readLine() {

        if (!is_null($this->lineBuffer)) {
            $rawLine = $this->lineBuffer;
            $this->lineBuffer = null;
        } else {
            do {
                $eof = feof($this->input);

                $rawLine = fgets($this->input);

                if ($eof || (feof($this->input) && $rawLine === false)) {
                    throw new EofException('End of document reached prematurely');
                }
                if ($rawLine === false) {
                    throw new ParseException('Error reading from input stream');
                }
                $rawLine = rtrim($rawLine, "\r\n");
            } while ($rawLine === ''); // Skipping empty lines
            $this->lineIndex++;
        }
        $line = $rawLine;

        $this->startLine = $this->lineIndex;

        // Looking ahead for folded lines.
        while (true) {

            $nextLine = rtrim(fgets($this->input), "\r\n");
            $this->lineIndex++;
            if (!$nextLine) {
                break;
            }
            if ($nextLine[0] === "\t" || $nextLine[0] === " ") {
                $line .= substr($nextLine, 1);
                $rawLine .= "\n " . substr($nextLine, 1);
            } else {
                $this->lineBuffer = $nextLine;
                break;
            }

        }
        $this->rawLine = $rawLine;
        return $line;

    }

    /**
     * Reads a property or component from a line.
     *
     * @return void
     */
    protected function readProperty($line) {

        if ($this->options & self::OPTION_FORGIVING) {
            $propNameToken = 'A-Z0-9\-\._\\/';
        } else {
            $propNameToken = 'A-Z0-9\-\.';
        }

        $paramNameToken = 'A-Z0-9\-';
        $safeChar = '^";:,';
        $qSafeChar = '^"';

        $regex = "/
            ^(?P<name> [$propNameToken]+ ) (?=[;:])        # property name
            |
            (?<=:)(?P<propValue> .+)$                      # property value
            |
            ;(?P<paramName> [$paramNameToken]+) (?=[=;:])  # parameter name
            |
            (=|,)(?P<paramValue>                           # parameter value
                (?: [$safeChar]*) |
                \"(?: [$qSafeChar]+)\"
            ) (?=[;:,])
            /xi";

        //echo $regex, "\n"; die();
        preg_match_all($regex, $line, $matches,  PREG_SET_ORDER);

        $property = [
            'name'       => null,
            'parameters' => [],
            'value'      => null
        ];

        $lastParam = null;

        /**
         * Looping through all the tokens.
         *
         * Note that we are looping through them in reverse order, because if a
         * sub-pattern matched, the subsequent named patterns will not show up
         * in the result.
         */
        foreach ($matches as $match) {

            if (isset($match['paramValue'])) {
                if ($match['paramValue'] && $match['paramValue'][0] === '"') {
                    $value = substr($match['paramValue'], 1, -1);
                } else {
                    $value = $match['paramValue'];
                }

                $value = $this->unescapeParam($value);

                if (is_null($lastParam)) {
                    throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
                }
                if (is_null($property['parameters'][$lastParam])) {
                    $property['parameters'][$lastParam] = $value;
                } elseif (is_array($property['parameters'][$lastParam])) {
                    $property['parameters'][$lastParam][] = $value;
                } else {
                    $property['parameters'][$lastParam] = [
                        $property['parameters'][$lastParam],
                        $value
                    ];
                }
                continue;
            }
            if (isset($match['paramName'])) {
                $lastParam = strtoupper($match['paramName']);
                if (!isset($property['parameters'][$lastParam])) {
                    $property['parameters'][$lastParam] = null;
                }
                continue;
            }
            if (isset($match['propValue'])) {
                $property['value'] = $match['propValue'];
                continue;
            }
            if (isset($match['name']) && $match['name']) {
                $property['name'] = strtoupper($match['name']);
                continue;
            }

            // @codeCoverageIgnoreStart
            throw new \LogicException('This code should not be reachable');
            // @codeCoverageIgnoreEnd

        }

        if (is_null($property['value'])) {
            $property['value'] = '';
        }
        if (!$property['name']) {
            if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
                return false;
            }
            throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
        }

        // vCard 2.1 states that parameters may appear without a name, and only
        // a value. We can deduce the value based on it's name.
        //
        // Our parser will get those as parameters without a value instead, so
        // we're filtering these parameters out first.
        $namedParameters = [];
        $namelessParameters = [];

        foreach ($property['parameters'] as $name => $value) {
            if (!is_null($value)) {
                $namedParameters[$name] = $value;
            } else {
                $namelessParameters[] = $name;
            }
        }

        $propObj = $this->root->createProperty($property['name'], null, $namedParameters);

        foreach ($namelessParameters as $namelessParameter) {
            $propObj->add(null, $namelessParameter);
        }

        if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
            $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
        } else {
            $charset = $this->charset;
            if ($this->root->getDocumentType() === Document::VCARD21 && isset($propObj['CHARSET'])) {
                // vCard 2.1 allows the character set to be specified per property.
                $charset = (string)$propObj['CHARSET'];
            }
            switch ($charset) {
                case 'UTF-8' :
                    break;
                case 'ISO-8859-1' :
                    $property['value'] = utf8_encode($property['value']);
                    break;
                case 'Windows-1252' :
                    $property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset);
                    break;
                default :
                    throw new ParseException('Unsupported CHARSET: ' . $propObj['CHARSET']);
            }
            $propObj->setRawMimeDirValue($property['value']);
        }

        return $propObj;

    }

    /**
     * Unescapes a property value.
     *
     * vCard 2.1 says:
     *   * Semi-colons must be escaped in some property values, specifically
     *     ADR, ORG and N.
     *   * Semi-colons must be escaped in parameter values, because semi-colons
     *     are also use to separate values.
     *   * No mention of escaping backslashes with another backslash.
     *   * newlines are not escaped either, instead QUOTED-PRINTABLE is used to
     *     span values over more than 1 line.
     *
     * vCard 3.0 says:
     *   * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be
     *     escaped, all time time.
     *   * Comma's are used for delimeters in multiple values
     *   * (rfc2426) Adds to to this that the semi-colon MUST also be escaped,
     *     as in some properties semi-colon is used for separators.
     *   * Properties using semi-colons: N, ADR, GEO, ORG
     *   * Both ADR and N's individual parts may be broken up further with a
     *     comma.
     *   * Properties using commas: NICKNAME, CATEGORIES
     *
     * vCard 4.0 (rfc6350) says:
     *   * Commas must be escaped.
     *   * Semi-colons may be escaped, an unescaped semi-colon _may_ be a
     *     delimiter, depending on the property.
     *   * Backslashes must be escaped
     *   * Newlines must be escaped as either \N or \n.
     *   * Some compound properties may contain multiple parts themselves, so a
     *     comma within a semi-colon delimited property may also be unescaped
     *     to denote multiple parts _within_ the compound property.
     *   * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP.
     *   * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID.
     *
     * Even though the spec says that commas must always be escaped, the
     * example for GEO in Section 6.5.2 seems to violate this.
     *
     * iCalendar 2.0 (rfc5545) says:
     *   * Commas or semi-colons may be used as delimiters, depending on the
     *     property.
     *   * Commas, semi-colons, backslashes, newline (\N or \n) are always
     *     escaped, unless they are delimiters.
     *   * Colons shall not be escaped.
     *   * Commas can be considered the 'default delimiter' and is described as
     *     the delimiter in cases where the order of the multiple values is
     *     insignificant.
     *   * Semi-colons are described as the delimiter for 'structured values'.
     *     They are specifically used in Semi-colons are used as a delimiter in
     *     REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however.
     *
     * Now for the parameters
     *
     * If delimiter is not set (null) this method will just return a string.
     * If it's a comma or a semi-colon the string will be split on those
     * characters, and always return an array.
     *
     * @param string $input
     * @param string $delimiter
     *
     * @return string|string[]
     */
    static function unescapeValue($input, $delimiter = ';') {

        $regex = '#  (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
        if ($delimiter) {
            $regex .= ' | (' . $delimiter . ')';
        }
        $regex .= ') #x';

        $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

        $resultArray = [];
        $result = '';

        foreach ($matches as $match) {

            switch ($match) {
                case '\\\\' :
                    $result .= '\\';
                    break;
                case '\N' :
                case '\n' :
                    $result .= "\n";
                    break;
                case '\;' :
                    $result .= ';';
                    break;
                case '\,' :
                    $result .= ',';
                    break;
                case $delimiter :
                    $resultArray[] = $result;
                    $result = '';
                    break;
                default :
                    $result .= $match;
                    break;

            }

        }

        $resultArray[] = $result;
        return $delimiter ? $resultArray : $result;

    }

    /**
     * Unescapes a parameter value.
     *
     * vCard 2.1:
     *   * Does not mention a mechanism for this. In addition, double quotes
     *     are never used to wrap values.
     *   * This means that parameters can simply not contain colons or
     *     semi-colons.
     *
     * vCard 3.0 (rfc2425, rfc2426):
     *   * Parameters _may_ be surrounded by double quotes.
     *   * If this is not the case, semi-colon, colon and comma may simply not
     *     occur (the comma used for multiple parameter values though).
     *   * If it is surrounded by double-quotes, it may simply not contain
     *     double-quotes.
     *   * This means that a parameter can in no case encode double-quotes, or
     *     newlines.
     *
     * vCard 4.0 (rfc6350)
     *   * Behavior seems to be identical to vCard 3.0
     *
     * iCalendar 2.0 (rfc5545)
     *   * Behavior seems to be identical to vCard 3.0
     *
     * Parameter escaping mechanism (rfc6868) :
     *   * This rfc describes a new way to escape parameter values.
     *   * New-line is encoded as ^n
     *   * ^ is encoded as ^^.
     *   * " is encoded as ^'
     *
     * @param string $input
     *
     * @return void
     */
    private function unescapeParam($input) {

        return
            preg_replace_callback(
                '#(\^(\^|n|\'))#',
                function($matches) {
                    switch ($matches[2]) {
                        case 'n' :
                            return "\n";
                        case '^' :
                            return '^';
                        case '\'' :
                            return '"';

                    // @codeCoverageIgnoreStart
                    }
                    // @codeCoverageIgnoreEnd
                },
                $input
            );
    }

    /**
     * Gets the full quoted printable value.
     *
     * We need a special method for this, because newlines have both a meaning
     * in vCards, and in QuotedPrintable.
     *
     * This method does not do any decoding.
     *
     * @return string
     */
    private function extractQuotedPrintableValue() {

        // We need to parse the raw line again to get the start of the value.
        //
        // We are basically looking for the first colon (:), but we need to
        // skip over the parameters first, as they may contain one.
        $regex = '/^
            (?: [^:])+ # Anything but a colon
            (?: "[^"]")* # A parameter in double quotes
            : # start of the value we really care about
            (.*)$
        /xs';

        preg_match($regex, $this->rawLine, $matches);

        $value = $matches[1];
        // Removing the first whitespace character from every line. Kind of
        // like unfolding, but we keep the newline.
        $value = str_replace("\n ", "\n", $value);

        // Microsoft products don't always correctly fold lines, they may be
        // missing a whitespace. So if 'forgiving' is turned on, we will take
        // those as well.
        if ($this->options & self::OPTION_FORGIVING) {
            while (substr($value, -1) === '=') {
                // Reading the line
                $this->readLine();
                // Grabbing the raw form
                $value .= "\n" . $this->rawLine;
            }
        }

        return $value;

    }

}
<?php

namespace Sabre\VObject\Parser;

/**
 * Abstract parser.
 *
 * This class serves as a base-class for the different parsers.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class Parser {

    /**
     * Turning on this option makes the parser more forgiving.
     *
     * In the case of the MimeDir parser, this means that the parser will
     * accept slashes and underscores in property names, and it will also
     * attempt to fix Microsoft vCard 2.1's broken line folding.
     */
    const OPTION_FORGIVING = 1;

    /**
     * If this option is turned on, any lines we cannot parse will be ignored
     * by the reader.
     */
    const OPTION_IGNORE_INVALID_LINES = 2;

    /**
     * Bitmask of parser options.
     *
     * @var int
     */
    protected $options;

    /**
     * Creates the parser.
     *
     * Optionally, it's possible to parse the input stream here.
     *
     * @param mixed $input
     * @param int $options Any parser options (OPTION constants).
     *
     * @return void
     */
    function __construct($input = null, $options = 0) {

        if (!is_null($input)) {
            $this->setInput($input);
        }
        $this->options = $options;
    }

    /**
     * This method starts the parsing process.
     *
     * If the input was not supplied during construction, it's possible to pass
     * it here instead.
     *
     * If either input or options are not supplied, the defaults will be used.
     *
     * @param mixed $input
     * @param int $options
     *
     * @return array
     */
    abstract function parse($input = null, $options = 0);

    /**
     * Sets the input data.
     *
     * @param mixed $input
     *
     * @return void
     */
    abstract function setInput($input);

}
<?php

namespace Sabre\VObject\Parser\XML\Element;

use Sabre\Xml as SabreXml;

/**
 * Our own sabre/xml key-value element.
 *
 * It just removes the clark notation.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Ivan Enderlin
 * @license http://sabre.io/license/ Modified BSD License
 */
class KeyValue extends SabreXml\Element\KeyValue {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called staticly, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param XML\Reader $reader
     *
     * @return mixed
     */
    static function xmlDeserialize(SabreXml\Reader $reader) {

        // If there's no children, we don't do anything.
        if ($reader->isEmptyElement) {
            $reader->next();
            return [];
        }

        $values = [];
        $reader->read();

        do {

            if ($reader->nodeType === SabreXml\Reader::ELEMENT) {

                $name = $reader->localName;
                $values[$name] = $reader->parseCurrentElement()['value'];

            } else {
                $reader->read();
            }

        } while ($reader->nodeType !== SabreXml\Reader::END_ELEMENT);

        $reader->read();

        return $values;

    }

}
<?php

namespace Sabre\VObject\Parser;

use Sabre\VObject\Component;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\EofException;
use Sabre\VObject\ParseException;
use Sabre\Xml as SabreXml;

/**
 * XML Parser.
 *
 * This parser parses both the xCal and xCard formats.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Ivan Enderlin
 * @license http://sabre.io/license/ Modified BSD License
 */
class XML extends Parser {

    const XCAL_NAMESPACE = 'urn:ietf:params:xml:ns:icalendar-2.0';
    const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0';

    /**
     * The input data.
     *
     * @var array
     */
    protected $input;

    /**
     * A pointer/reference to the input.
     *
     * @var array
     */
    private $pointer;

    /**
     * Document, root component.
     *
     * @var Sabre\VObject\Document
     */
    protected $root;

    /**
     * Creates the parser.
     *
     * Optionally, it's possible to parse the input stream here.
     *
     * @param mixed $input
     * @param int $options Any parser options (OPTION constants).
     *
     * @return void
     */
    function __construct($input = null, $options = 0) {

        if (0 === $options) {
            $options = parent::OPTION_FORGIVING;
        }

        parent::__construct($input, $options);

    }

    /**
     * Parse xCal or xCard.
     *
     * @param resource|string $input
     * @param int $options
     *
     * @throws \Exception
     *
     * @return Sabre\VObject\Document
     */
    function parse($input = null, $options = 0) {

        if (!is_null($input)) {
            $this->setInput($input);
        }

        if (0 !== $options) {
            $this->options = $options;
        }

        if (is_null($this->input)) {
            throw new EofException('End of input stream, or no input supplied');
        }

        switch ($this->input['name']) {

            case '{' . self::XCAL_NAMESPACE . '}icalendar':
                $this->root = new VCalendar([], false);
                $this->pointer = &$this->input['value'][0];
                $this->parseVCalendarComponents($this->root);
                break;

            case '{' . self::XCARD_NAMESPACE . '}vcards':
                foreach ($this->input['value'] as &$vCard) {

                    $this->root = new VCard(['version' => '4.0'], false);
                    $this->pointer = &$vCard;
                    $this->parseVCardComponents($this->root);

                    // We just parse the first <vcard /> element.
                    break;

                }
                break;

            default:
                throw new ParseException('Unsupported XML standard');

        }

        return $this->root;
    }

    /**
     * Parse a xCalendar component.
     *
     * @param Component $parentComponent
     *
     * @return void
     */
    protected function parseVCalendarComponents(Component $parentComponent) {

        foreach ($this->pointer['value'] ?: [] as $children) {

            switch (static::getTagName($children['name'])) {

                case 'properties':
                    $this->pointer = &$children['value'];
                    $this->parseProperties($parentComponent);
                    break;

                case 'components':
                    $this->pointer = &$children;
                    $this->parseComponent($parentComponent);
                    break;
            }
        }

    }

    /**
     * Parse a xCard component.
     *
     * @param Component $parentComponent
     *
     * @return void
     */
    protected function parseVCardComponents(Component $parentComponent) {

        $this->pointer = &$this->pointer['value'];
        $this->parseProperties($parentComponent);

    }

    /**
     * Parse xCalendar and xCard properties.
     *
     * @param Component $parentComponent
     * @param string  $propertyNamePrefix
     *
     * @return void
     */
    protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '') {

        foreach ($this->pointer ?: [] as $xmlProperty) {

            list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);

            $propertyName = $tagName;
            $propertyValue = [];
            $propertyParameters = [];
            $propertyType = 'text';

            // A property which is not part of the standard.
            if ($namespace !== self::XCAL_NAMESPACE
                && $namespace !== self::XCARD_NAMESPACE) {

                $propertyName = 'xml';
                $value = '<' . $tagName . ' xmlns="' . $namespace . '"';

                foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) {
                    $value .= ' ' . $attributeName . '="' . str_replace('"', '\"', $attributeValue) . '"';
                }

                $value .= '>' . $xmlProperty['value'] . '</' . $tagName . '>';

                $propertyValue = [$value];

                $this->createProperty(
                    $parentComponent,
                    $propertyName,
                    $propertyParameters,
                    $propertyType,
                    $propertyValue
                );

                continue;
            }

            // xCard group.
            if ($propertyName === 'group') {

                if (!isset($xmlProperty['attributes']['name'])) {
                    continue;
                }

                $this->pointer = &$xmlProperty['value'];
                $this->parseProperties(
                    $parentComponent,
                    strtoupper($xmlProperty['attributes']['name']) . '.'
                );

                continue;

            }

            // Collect parameters.
            foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) {

                if (!is_array($xmlPropertyChild)
                    || 'parameters' !== static::getTagName($xmlPropertyChild['name']))
                    continue;

                $xmlParameters = $xmlPropertyChild['value'];

                foreach ($xmlParameters as $xmlParameter) {

                    $propertyParameterValues = [];

                    foreach ($xmlParameter['value'] as $xmlParameterValues) {
                        $propertyParameterValues[] = $xmlParameterValues['value'];
                    }

                    $propertyParameters[static::getTagName($xmlParameter['name'])]
                        = implode(',', $propertyParameterValues);

                }

                array_splice($xmlProperty['value'], $i, 1);

            }

            $propertyNameExtended = ($this->root instanceof VCalendar
                                      ? 'xcal'
                                      : 'xcard') . ':' . $propertyName;

            switch ($propertyNameExtended) {

                case 'xcal:geo':
                    $propertyType = 'float';
                    $propertyValue['latitude'] = 0;
                    $propertyValue['longitude'] = 0;

                    foreach ($xmlProperty['value'] as $xmlRequestChild) {
                        $propertyValue[static::getTagName($xmlRequestChild['name'])]
                            = $xmlRequestChild['value'];
                    }
                    break;

                case 'xcal:request-status':
                    $propertyType = 'text';

                    foreach ($xmlProperty['value'] as $xmlRequestChild) {
                        $propertyValue[static::getTagName($xmlRequestChild['name'])]
                            = $xmlRequestChild['value'];
                    }
                    break;

                case 'xcal:freebusy':
                    $propertyType = 'freebusy';
                    // We don't break because we only want to set
                    // another property type.

                case 'xcal:categories':
                case 'xcal:resources':
                case 'xcal:exdate':
                    foreach ($xmlProperty['value'] as $specialChild) {
                        $propertyValue[static::getTagName($specialChild['name'])]
                            = $specialChild['value'];
                    }
                    break;

                case 'xcal:rdate':
                    $propertyType = 'date-time';

                    foreach ($xmlProperty['value'] as $specialChild) {

                        $tagName = static::getTagName($specialChild['name']);

                        if ('period' === $tagName) {

                            $propertyParameters['value'] = 'PERIOD';
                            $propertyValue[] = implode('/', $specialChild['value']);

                        }
                        else {
                            $propertyValue[] = $specialChild['value'];
                        }
                    }
                    break;

                default:
                    $propertyType = static::getTagName($xmlProperty['value'][0]['name']);

                    foreach ($xmlProperty['value'] as $value) {
                        $propertyValue[] = $value['value'];
                    }

                    if ('date' === $propertyType) {
                        $propertyParameters['value'] = 'DATE';
                    }
                    break;
            }

            $this->createProperty(
                $parentComponent,
                $propertyNamePrefix . $propertyName,
                $propertyParameters,
                $propertyType,
                $propertyValue
            );

        }

    }

    /**
     * Parse a component.
     *
     * @param Component $parentComponent
     *
     * @return void
     */
    protected function parseComponent(Component $parentComponent) {

        $components = $this->pointer['value'] ?: [];

        foreach ($components as $component) {

            $componentName = static::getTagName($component['name']);
            $currentComponent = $this->root->createComponent(
                $componentName,
                null,
                false
            );

            $this->pointer = &$component;
            $this->parseVCalendarComponents($currentComponent);

            $parentComponent->add($currentComponent);

        }

    }

    /**
     * Create a property.
     *
     * @param Component $parentComponent
     * @param string $name
     * @param array $parameters
     * @param string $type
     * @param mixed $value
     *
     * @return void
     */
    protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value) {

        $property = $this->root->createProperty(
            $name,
            null,
            $parameters,
            $type
        );
        $parentComponent->add($property);
        $property->setXmlValue($value);

    }

    /**
     * Sets the input data.
     *
     * @param resource|string $input
     *
     * @return void
     */
    function setInput($input) {

        if (is_resource($input)) {
            $input = stream_get_contents($input);
        }

        if (is_string($input)) {

            $reader = new SabreXml\Reader();
            $reader->elementMap['{' . self::XCAL_NAMESPACE . '}period']
                = 'Sabre\VObject\Parser\XML\Element\KeyValue';
            $reader->elementMap['{' . self::XCAL_NAMESPACE . '}recur']
                = 'Sabre\VObject\Parser\XML\Element\KeyValue';
            $reader->xml($input);
            $input = $reader->parse();

        }

        $this->input = $input;

    }

    /**
     * Get tag name from a Clark notation.
     *
     * @param string $clarkedTagName
     *
     * @return string
     */
    protected static function getTagName($clarkedTagName) {

        list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName);
        return $tagName;

    }
}
<?php

namespace Sabre\VObject;

/**
 * PHPUnit Assertions
 *
 * This trait can be added to your unittest to make it easier to test iCalendar
 * and/or vCards.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
trait PHPUnitAssertions {

    /**
     * This method tests wether two vcards or icalendar objects are
     * semantically identical.
     *
     * It supports objects being supplied as strings, streams or
     * Sabre\VObject\Component instances.
     *
     * PRODID is removed from both objects as this is often changes and would
     * just get in the way.
     *
     * CALSCALE will automatically get removed if it's set to GREGORIAN.
     *
     * Any property that has the value **ANY** will be treated as a wildcard.
     *
     * @param resource|string|Component $expected
     * @param resource|string|Component $actual
     * @param string $message
     */
    function assertVObjectEqualsVObject($expected, $actual, $message = '') {

        $self = $this;
        $getObj = function($input) use ($self) {

            if (is_resource($input)) {
                $input = stream_get_contents($input);
            }
            if (is_string($input)) {
                $input = Reader::read($input);
            }
            if (!$input instanceof Component) {
                $this->fail('Input must be a string, stream or VObject component');
            }
            unset($input->PRODID);
            if ($input instanceof Component\VCalendar && (string)$input->CALSCALE === 'GREGORIAN') {
                unset($input->CALSCALE);
            }
            return $input;

        };

        $expected = $getObj($expected)->serialize();
        $actual = $getObj($actual)->serialize();

        // Finding wildcards in expected.
        preg_match_all('|^([A-Z]+):\\*\\*ANY\\*\\*\r$|m', $expected, $matches, PREG_SET_ORDER);

        foreach ($matches as $match) {

            $actual = preg_replace(
                '|^' . preg_quote($match[1], '|') . ':(.*)\r$|m',
                $match[1] . ':**ANY**' . "\r",
                $actual
            );

        }

        $this->assertEquals(
            $expected,
            $actual,
            $message
        );

    }


}
<?php

namespace Sabre\VObject\Property;

use Sabre\VObject\Property;

/**
 * BINARY property.
 *
 * This object represents BINARY values.
 *
 * Binary values are most commonly used by the iCalendar ATTACH property, and
 * the vCard PHOTO property.
 *
 * This property will transparently encode and decode to base64.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Binary extends Property {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = null;

    /**
     * Updates the current value.
     *
     * This may be either a single, or multiple strings in an array.
     *
     * @param string|array $value
     *
     * @return void
     */
    function setValue($value) {

        if (is_array($value)) {

            if (count($value) === 1) {
                $this->value = $value[0];
            } else {
                throw new \InvalidArgumentException('The argument must either be a string or an array with only one child');
            }

        } else {

            $this->value = $value;

        }

    }

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->value = base64_decode($val);

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return base64_encode($this->value);

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'BINARY';

    }

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        return [base64_encode($this->getValue())];

    }

    /**
     * Sets the json value, as it would appear in a jCard or jCal object.
     *
     * The value must always be an array.
     *
     * @param array $value
     *
     * @return void
     */
    function setJsonValue(array $value) {

        $value = array_map('base64_decode', $value);
        parent::setJsonValue($value);

    }

}
<?php

namespace Sabre\VObject\Property;

use
    Sabre\VObject\Property;

/**
 * Boolean property.
 *
 * This object represents BOOLEAN values. These are always the case-insenstive
 * string TRUE or FALSE.
 *
 * Automatic conversion to PHP's true and false are done.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Boolean extends Property {

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $val = strtoupper($val) === 'TRUE' ? true : false;
        $this->setValue($val);

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return $this->value ? 'TRUE' : 'FALSE';

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'BOOLEAN';

    }

    /**
     * Hydrate data from a XML subtree, as it would appear in a xCard or xCal
     * object.
     *
     * @param array $value
     *
     * @return void
     */
    function setXmlValue(array $value) {

        $value = array_map(
            function($value) {
                return 'true' === $value;
            },
            $value
        );
        parent::setXmlValue($value);

    }

}
<?php

namespace Sabre\VObject\Property;

/**
 * FlatText property.
 *
 * This object represents certain TEXT values.
 *
 * Specifically, this property is used for text values where there is only 1
 * part. Semi-colons and colons will be de-escaped when deserializing, but if
 * any semi-colons or commas appear without a backslash, we will not assume
 * that they are delimiters.
 *
 * vCard 2.1 specifically has a whole bunch of properties where this may
 * happen, as it only defines a delimiter for a few properties.
 *
 * vCard 4.0 states something similar. An unescaped semi-colon _may_ be a
 * delimiter, depending on the property.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class FlatText extends Text {

    /**
     * Field separator.
     *
     * @var string
     */
    public $delimiter = ',';

    /**
     * Sets the value as a quoted-printable encoded string.
     *
     * Overriding this so we're not splitting on a ; delimiter.
     *
     * @param string $val
     *
     * @return void
     */
    function setQuotedPrintableValue($val) {

        $val = quoted_printable_decode($val);
        $this->setValue($val);

    }

}
<?php

namespace Sabre\VObject\Property;

use Sabre\VObject\Property;
use Sabre\Xml;

/**
 * Float property.
 *
 * This object represents FLOAT values. These can be 1 or more floating-point
 * numbers.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class FloatValue extends Property {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = ';';

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $val = explode($this->delimiter, $val);
        foreach ($val as &$item) {
            $item = (float)$item;
        }
        $this->setParts($val);

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return implode(
            $this->delimiter,
            $this->getParts()
        );

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'FLOAT';

    }

    /**
     * Returns the value, in the format it should be encoded for JSON.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        $val = array_map('floatval', $this->getParts());

        // Special-casing the GEO property.
        //
        // See:
        // http://tools.ietf.org/html/draft-ietf-jcardcal-jcal-04#section-3.4.1.2
        if ($this->name === 'GEO') {
            return [$val];
        }

        return $val;

    }

    /**
     * Hydrate data from a XML subtree, as it would appear in a xCard or xCal
     * object.
     *
     * @param array $value
     *
     * @return void
     */
    function setXmlValue(array $value) {

        $value = array_map('floatval', $value);
        parent::setXmlValue($value);

    }

    /**
     * This method serializes only the value of a property. This is used to
     * create xCard or xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    protected function xmlSerializeValue(Xml\Writer $writer) {

        // Special-casing the GEO property.
        //
        // See:
        // http://tools.ietf.org/html/rfc6321#section-3.4.1.2
        if ($this->name === 'GEO') {

            $value = array_map('floatval', $this->getParts());

            $writer->writeElement('latitude', $value[0]);
            $writer->writeElement('longitude', $value[1]);

        }
        else {
            parent::xmlSerializeValue($writer);
        }

    }

}
<?php

namespace Sabre\VObject\Property\ICalendar;

use
    Sabre\VObject\Property\Text;

/**
 * CalAddress property.
 *
 * This object encodes CAL-ADDRESS values, as defined in rfc5545
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class CalAddress extends Text {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = null;

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'CAL-ADDRESS';

    }

    /**
     * This returns a normalized form of the value.
     *
     * This is primarily used right now to turn mixed-cased schemes in user
     * uris to lower-case.
     *
     * Evolution in particular tends to encode mailto: as MAILTO:.
     *
     * @return string
     */
    function getNormalizedValue() {

        $input = $this->getValue();
        if (!strpos($input, ':')) {
            return $input;
        }
        list($schema, $everythingElse) = explode(':', $input, 2);
        return strtolower($schema) . ':' . $everythingElse;

    }
}
<?php

namespace Sabre\VObject\Property\ICalendar;

/**
 * DateTime property.
 *
 * This object represents DATE values, as defined here:
 *
 * http://tools.ietf.org/html/rfc5545#section-3.3.5
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Date extends DateTime {

}
<?php

namespace Sabre\VObject\Property\ICalendar;

use DateTimeInterface;
use DateTimeZone;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\InvalidDataException;
use Sabre\VObject\Property;
use Sabre\VObject\TimeZoneUtil;

/**
 * DateTime property.
 *
 * This object represents DATE-TIME values, as defined here:
 *
 * http://tools.ietf.org/html/rfc5545#section-3.3.4
 *
 * This particular object has a bit of hackish magic that it may also in some
 * cases represent a DATE value. This is because it's a common usecase to be
 * able to change a DATE-TIME into a DATE.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class DateTime extends Property {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = ',';

    /**
     * Sets a multi-valued property.
     *
     * You may also specify DateTime objects here.
     *
     * @param array $parts
     *
     * @return void
     */
    function setParts(array $parts) {

        if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) {
            $this->setDateTimes($parts);
        } else {
            parent::setParts($parts);
        }

    }

    /**
     * Updates the current value.
     *
     * This may be either a single, or multiple strings in an array.
     *
     * Instead of strings, you may also use DateTime here.
     *
     * @param string|array|DateTimeInterface $value
     *
     * @return void
     */
    function setValue($value) {

        if (is_array($value) && isset($value[0]) && $value[0] instanceof DateTimeInterface) {
            $this->setDateTimes($value);
        } elseif ($value instanceof DateTimeInterface) {
            $this->setDateTimes([$value]);
        } else {
            parent::setValue($value);
        }

    }

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->setValue(explode($this->delimiter, $val));

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return implode($this->delimiter, $this->getParts());

    }

    /**
     * Returns true if this is a DATE-TIME value, false if it's a DATE.
     *
     * @return bool
     */
    function hasTime() {

        return strtoupper((string)$this['VALUE']) !== 'DATE';

    }

    /**
     * Returns true if this is a floating DATE or DATE-TIME.
     *
     * Note that DATE is always floating.
     */
    function isFloating() {

        return
            !$this->hasTime() ||
            (
                !isset($this['TZID']) &&
                strpos($this->getValue(), 'Z') === false
            );

    }

    /**
     * Returns a date-time value.
     *
     * Note that if this property contained more than 1 date-time, only the
     * first will be returned. To get an array with multiple values, call
     * getDateTimes.
     *
     * If no timezone information is known, because it's either an all-day
     * property or floating time, we will use the DateTimeZone argument to
     * figure out the exact date.
     *
     * @param DateTimeZone $timeZone
     *
     * @return DateTimeImmutable
     */
    function getDateTime(DateTimeZone $timeZone = null) {

        $dt = $this->getDateTimes($timeZone);
        if (!$dt) return;

        return $dt[0];

    }

    /**
     * Returns multiple date-time values.
     *
     * If no timezone information is known, because it's either an all-day
     * property or floating time, we will use the DateTimeZone argument to
     * figure out the exact date.
     *
     * @param DateTimeZone $timeZone
     *
     * @return DateTimeImmutable[]
     * @return \DateTime[]
     */
    function getDateTimes(DateTimeZone $timeZone = null) {

        // Does the property have a TZID?
        $tzid = $this['TZID'];

        if ($tzid) {
            $timeZone = TimeZoneUtil::getTimeZone((string)$tzid, $this->root);
        }

        $dts = [];
        foreach ($this->getParts() as $part) {
            $dts[] = DateTimeParser::parse($part, $timeZone);
        }
        return $dts;

    }

    /**
     * Sets the property as a DateTime object.
     *
     * @param DateTimeInterface $dt
     * @param bool isFloating If set to true, timezones will be ignored.
     *
     * @return void
     */
    function setDateTime(DateTimeInterface $dt, $isFloating = false) {

        $this->setDateTimes([$dt], $isFloating);

    }

    /**
     * Sets the property as multiple date-time objects.
     *
     * The first value will be used as a reference for the timezones, and all
     * the otehr values will be adjusted for that timezone
     *
     * @param DateTimeInterface[] $dt
     * @param bool isFloating If set to true, timezones will be ignored.
     *
     * @return void
     */
    function setDateTimes(array $dt, $isFloating = false) {

        $values = [];

        if ($this->hasTime()) {

            $tz = null;
            $isUtc = false;

            foreach ($dt as $d) {

                if ($isFloating) {
                    $values[] = $d->format('Ymd\\THis');
                    continue;
                }
                if (is_null($tz)) {
                    $tz = $d->getTimeZone();
                    $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z', '+00:00']);
                    if (!$isUtc) {
                        $this->offsetSet('TZID', $tz->getName());
                    }
                } else {
                    $d = $d->setTimeZone($tz);
                }

                if ($isUtc) {
                    $values[] = $d->format('Ymd\\THis\\Z');
                } else {
                    $values[] = $d->format('Ymd\\THis');
                }

            }
            if ($isUtc || $isFloating) {
                $this->offsetUnset('TZID');
            }

        } else {

            foreach ($dt as $d) {

                $values[] = $d->format('Ymd');

            }
            $this->offsetUnset('TZID');

        }

        $this->value = $values;

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return $this->hasTime() ? 'DATE-TIME' : 'DATE';

    }

    /**
     * Returns the value, in the format it should be encoded for JSON.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        $dts = $this->getDateTimes();
        $hasTime = $this->hasTime();
        $isFloating = $this->isFloating();

        $tz = $dts[0]->getTimeZone();
        $isUtc = $isFloating ? false : in_array($tz->getName(), ['UTC', 'GMT', 'Z']);

        return array_map(
            function(DateTimeInterface $dt) use ($hasTime, $isUtc) {

                if ($hasTime) {
                    return $dt->format('Y-m-d\\TH:i:s') . ($isUtc ? 'Z' : '');
                } else {
                    return $dt->format('Y-m-d');
                }

            },
            $dts
        );

    }

    /**
     * Sets the json value, as it would appear in a jCard or jCal object.
     *
     * The value must always be an array.
     *
     * @param array $value
     *
     * @return void
     */
    function setJsonValue(array $value) {

        // dates and times in jCal have one difference to dates and times in
        // iCalendar. In jCal date-parts are separated by dashes, and
        // time-parts are separated by colons. It makes sense to just remove
        // those.
        $this->setValue(
            array_map(
                function($item) {

                    return strtr($item, [':' => '', '-' => '']);

                },
                $value
            )
        );

    }

    /**
     * We need to intercept offsetSet, because it may be used to alter the
     * VALUE from DATE-TIME to DATE or vice-versa.
     *
     * @param string $name
     * @param mixed $value
     *
     * @return void
     */
    function offsetSet($name, $value) {

        parent::offsetSet($name, $value);
        if (strtoupper($name) !== 'VALUE') {
            return;
        }

        // This will ensure that dates are correctly encoded.
        $this->setDateTimes($this->getDateTimes());

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on)
     *   2 - An inconsequential issue
     *   3 - A severe issue.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $messages = parent::validate($options);
        $valueType = $this->getValueType();
        $values = $this->getParts();
        try {
            foreach ($values as $value) {
                switch ($valueType) {
                    case 'DATE' :
                        DateTimeParser::parseDate($value);
                        break;
                    case 'DATE-TIME' :
                        DateTimeParser::parseDateTime($value);
                        break;
                }
            }
        } catch (InvalidDataException $e) {
            $messages[] = [
                'level'   => 3,
                'message' => 'The supplied value (' . $value . ') is not a correct ' . $valueType,
                'node'    => $this,
            ];
        }
        return $messages;

    }
}
<?php

namespace Sabre\VObject\Property\ICalendar;

use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Property;

/**
 * Duration property.
 *
 * This object represents DURATION values, as defined here:
 *
 * http://tools.ietf.org/html/rfc5545#section-3.3.6
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Duration extends Property {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = ',';

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->setValue(explode($this->delimiter, $val));

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return implode($this->delimiter, $this->getParts());

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'DURATION';

    }

    /**
     * Returns a DateInterval representation of the Duration property.
     *
     * If the property has more than one value, only the first is returned.
     *
     * @return \DateInterval
     */
    function getDateInterval() {

        $parts = $this->getParts();
        $value = $parts[0];
        return DateTimeParser::parseDuration($value);

    }

}
<?php

namespace Sabre\VObject\Property\ICalendar;

use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Property;
use Sabre\Xml;

/**
 * Period property.
 *
 * This object represents PERIOD values, as defined here:
 *
 * http://tools.ietf.org/html/rfc5545#section-3.8.2.6
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Period extends Property {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = ',';

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->setValue(explode($this->delimiter, $val));

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return implode($this->delimiter, $this->getParts());

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'PERIOD';

    }

    /**
     * Sets the json value, as it would appear in a jCard or jCal object.
     *
     * The value must always be an array.
     *
     * @param array $value
     *
     * @return void
     */
    function setJsonValue(array $value) {

        $value = array_map(
            function($item) {

                return strtr(implode('/', $item), [':' => '', '-' => '']);

            },
            $value
        );
        parent::setJsonValue($value);

    }

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        $return = [];
        foreach ($this->getParts() as $item) {

            list($start, $end) = explode('/', $item, 2);

            $start = DateTimeParser::parseDateTime($start);

            // This is a duration value.
            if ($end[0] === 'P') {
                $return[] = [
                    $start->format('Y-m-d\\TH:i:s'),
                    $end
                ];
            } else {
                $end = DateTimeParser::parseDateTime($end);
                $return[] = [
                    $start->format('Y-m-d\\TH:i:s'),
                    $end->format('Y-m-d\\TH:i:s'),
                ];
            }

        }

        return $return;

    }

    /**
     * This method serializes only the value of a property. This is used to
     * create xCard or xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    protected function xmlSerializeValue(Xml\Writer $writer) {

        $writer->startElement(strtolower($this->getValueType()));
        $value = $this->getJsonValue();
        $writer->writeElement('start', $value[0][0]);

        if ($value[0][1][0] === 'P') {
            $writer->writeElement('duration', $value[0][1]);
        }
        else {
            $writer->writeElement('end', $value[0][1]);
        }

        $writer->endElement();

    }

}
<?php

namespace Sabre\VObject\Property\ICalendar;

use Sabre\VObject\Property;
use Sabre\Xml;

/**
 * Recur property.
 *
 * This object represents RECUR properties.
 * These values are just used for RRULE and the now deprecated EXRULE.
 *
 * The RRULE property may look something like this:
 *
 * RRULE:FREQ=MONTHLY;BYDAY=1,2,3;BYHOUR=5.
 *
 * This property exposes this as a key=>value array that is accessible using
 * getParts, and may be set using setParts.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Recur extends Property {

    /**
     * Updates the current value.
     *
     * This may be either a single, or multiple strings in an array.
     *
     * @param string|array $value
     *
     * @return void
     */
    function setValue($value) {

        // If we're getting the data from json, we'll be receiving an object
        if ($value instanceof \StdClass) {
            $value = (array)$value;
        }

        if (is_array($value)) {
            $newVal = [];
            foreach ($value as $k => $v) {

                if (is_string($v)) {
                    $v = strtoupper($v);

                    // The value had multiple sub-values
                    if (strpos($v, ',') !== false) {
                        $v = explode(',', $v);
                    }
                    if (strcmp($k, 'until') === 0) {
                        $v = strtr($v, [':' => '', '-' => '']);
                    }
                } elseif (is_array($v)) {
                    $v = array_map('strtoupper', $v);
                }

                $newVal[strtoupper($k)] = $v;
            }
            $this->value = $newVal;
        } elseif (is_string($value)) {
            $this->value = self::stringToArray($value);
        } else {
            throw new \InvalidArgumentException('You must either pass a string, or a key=>value array');
        }

    }

    /**
     * Returns the current value.
     *
     * This method will always return a singular value. If this was a
     * multi-value object, some decision will be made first on how to represent
     * it as a string.
     *
     * To get the correct multi-value version, use getParts.
     *
     * @return string
     */
    function getValue() {

        $out = [];
        foreach ($this->value as $key => $value) {
            $out[] = $key . '=' . (is_array($value) ? implode(',', $value) : $value);
        }
        return strtoupper(implode(';', $out));

    }

    /**
     * Sets a multi-valued property.
     *
     * @param array $parts
     * @return void
     */
    function setParts(array $parts) {

        $this->setValue($parts);

    }

    /**
     * Returns a multi-valued property.
     *
     * This method always returns an array, if there was only a single value,
     * it will still be wrapped in an array.
     *
     * @return array
     */
    function getParts() {

        return $this->value;

    }

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->setValue($val);

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return $this->getValue();

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'RECUR';

    }

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        $values = [];
        foreach ($this->getParts() as $k => $v) {
            if (strcmp($k, 'UNTIL') === 0) {
                $date = new DateTime($this->root, null, $v);
                $values[strtolower($k)] = $date->getJsonValue()[0];
            } elseif (strcmp($k, 'COUNT') === 0) {
                $values[strtolower($k)] = intval($v);
            } else {
                $values[strtolower($k)] = $v;
            }
        }
        return [$values];

    }

    /**
     * This method serializes only the value of a property. This is used to
     * create xCard or xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    protected function xmlSerializeValue(Xml\Writer $writer) {

        $valueType = strtolower($this->getValueType());

        foreach ($this->getJsonValue() as $value) {
            $writer->writeElement($valueType, $value);
        }

    }

    /**
     * Parses an RRULE value string, and turns it into a struct-ish array.
     *
     * @param string $value
     *
     * @return array
     */
    static function stringToArray($value) {

        $value = strtoupper($value);
        $newValue = [];
        foreach (explode(';', $value) as $part) {

            // Skipping empty parts.
            if (empty($part)) {
                continue;
            }
            list($partName, $partValue) = explode('=', $part);

            // The value itself had multiple values..
            if (strpos($partValue, ',') !== false) {
                $partValue = explode(',', $partValue);
            }
            $newValue[$partName] = $partValue;

        }

        return $newValue;
    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on)
     *   2 - An inconsequential issue
     *   3 - A severe issue.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $repair = ($options & self::REPAIR);

        $warnings = parent::validate($options);
        $values = $this->getParts();

        foreach ($values as $key => $value) {

            if ($value === '') {
                $warnings[] = [
                    'level'   => $repair ? 1 : 3,
                    'message' => 'Invalid value for ' . $key . ' in ' . $this->name,
                    'node'    => $this
                ];
                if ($repair) {
                    unset($values[$key]);
                }
            } elseif ($key == 'BYMONTH') {
                $byMonth = (array)$value;
                foreach ($byMonth as $i => $v) {
                    if (!is_numeric($v) || (int)$v < 1 || (int)$v > 12) {
                        $warnings[] = [
                            'level'   => $repair ? 1 : 3,
                            'message' => 'BYMONTH in RRULE must have value(s) between 1 and 12!',
                            'node'    => $this
                        ];
                        if ($repair) {
                            if (is_array($value)) {
                                unset($values[$key][$i]);
                            } else {
                                unset($values[$key]);
                            }
                        }
                    }
                }
                // if there is no valid entry left, remove the whole value
                if (is_array($value) && empty($values[$key])) {
                    unset($values[$key]);
                }
            } elseif ($key == 'BYWEEKNO') {
                $byWeekNo = (array)$value;
                foreach ($byWeekNo as $i => $v) {
                    if (!is_numeric($v) || (int)$v < -53 || (int)$v == 0 || (int)$v > 53) {
                        $warnings[] = [
                            'level'   => $repair ? 1 : 3,
                            'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!',
                            'node'    => $this
                        ];
                        if ($repair) {
                            if (is_array($value)) {
                                unset($values[$key][$i]);
                            } else {
                                unset($values[$key]);
                            }
                        }
                    }
                }
                // if there is no valid entry left, remove the whole value
                if (is_array($value) && empty($values[$key])) {
                    unset($values[$key]);
                }
            } elseif ($key == 'BYYEARDAY') {
                $byYearDay = (array)$value;
                foreach ($byYearDay as $i => $v) {
                    if (!is_numeric($v) || (int)$v < -366 || (int)$v == 0 || (int)$v > 366) {
                        $warnings[] = [
                            'level'   => $repair ? 1 : 3,
                            'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!',
                            'node'    => $this
                        ];
                        if ($repair) {
                            if (is_array($value)) {
                                unset($values[$key][$i]);
                            } else {
                                unset($values[$key]);
                            }
                        }
                    }
                }
                // if there is no valid entry left, remove the whole value
                if (is_array($value) && empty($values[$key])) {
                    unset($values[$key]);
                }
            }

        }
        if (!isset($values['FREQ'])) {
            $warnings[] = [
                'level'   => $repair ? 1 : 3,
                'message' => 'FREQ is required in ' . $this->name,
                'node'    => $this
            ];
            if ($repair) {
                $this->parent->remove($this);
            }
        }
        if ($repair) {
            $this->setValue($values);
        }

        return $warnings;

    }

}
<?php

namespace Sabre\VObject\Property;

use
    Sabre\VObject\Property;

/**
 * Integer property.
 *
 * This object represents INTEGER values. These are always a single integer.
 * They may be preceeded by either + or -.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class IntegerValue extends Property {

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->setValue((int)$val);

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return $this->value;

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'INTEGER';

    }

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        return [(int)$this->getValue()];

    }

    /**
     * Hydrate data from a XML subtree, as it would appear in a xCard or xCal
     * object.
     *
     * @param array $value
     *
     * @return void
     */
    function setXmlValue(array $value) {

        $value = array_map('intval', $value);
        parent::setXmlValue($value);

    }
}
<?php

namespace Sabre\VObject\Property;

use Sabre\VObject\Component;
use Sabre\VObject\Document;
use Sabre\VObject\Parser\MimeDir;
use Sabre\VObject\Property;
use Sabre\Xml;

/**
 * Text property.
 *
 * This object represents TEXT values.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Text extends Property {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string
     */
    public $delimiter = ',';

    /**
     * List of properties that are considered 'structured'.
     *
     * @var array
     */
    protected $structuredValues = [
        // vCard
        'N',
        'ADR',
        'ORG',
        'GENDER',
        'CLIENTPIDMAP',

        // iCalendar
        'REQUEST-STATUS',
    ];

    /**
     * Some text components have a minimum number of components.
     *
     * N must for instance be represented as 5 components, separated by ;, even
     * if the last few components are unused.
     *
     * @var array
     */
    protected $minimumPropertyValues = [
        'N'   => 5,
        'ADR' => 7,
    ];

    /**
     * Creates the property.
     *
     * You can specify the parameters either in key=>value syntax, in which case
     * parameters will automatically be created, or you can just pass a list of
     * Parameter objects.
     *
     * @param Component $root The root document
     * @param string $name
     * @param string|array|null $value
     * @param array $parameters List of parameters
     * @param string $group The vcard property group
     *
     * @return void
     */
    function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) {

        // There's two types of multi-valued text properties:
        // 1. multivalue properties.
        // 2. structured value properties
        //
        // The former is always separated by a comma, the latter by semi-colon.
        if (in_array($name, $this->structuredValues)) {
            $this->delimiter = ';';
        }

        parent::__construct($root, $name, $value, $parameters, $group);

    }

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->setValue(MimeDir::unescapeValue($val, $this->delimiter));

    }

    /**
     * Sets the value as a quoted-printable encoded string.
     *
     * @param string $val
     *
     * @return void
     */
    function setQuotedPrintableValue($val) {

        $val = quoted_printable_decode($val);

        // Quoted printable only appears in vCard 2.1, and the only character
        // that may be escaped there is ;. So we are simply splitting on just
        // that.
        //
        // We also don't have to unescape \\, so all we need to look for is a ;
        // that's not preceeded with a \.
        $regex = '# (?<!\\\\) ; #x';
        $matches = preg_split($regex, $val);
        $this->setValue($matches);

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        $val = $this->getParts();

        if (isset($this->minimumPropertyValues[$this->name])) {
            $val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
        }

        foreach ($val as &$item) {

            if (!is_array($item)) {
                $item = [$item];
            }

            foreach ($item as &$subItem) {
                $subItem = strtr(
                    $subItem,
                    [
                        '\\' => '\\\\',
                        ';'  => '\;',
                        ','  => '\,',
                        "\n" => '\n',
                        "\r" => "",
                    ]
                );
            }
            $item = implode(',', $item);

        }

        return implode($this->delimiter, $val);

    }

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        // Structured text values should always be returned as a single
        // array-item. Multi-value text should be returned as multiple items in
        // the top-array.
        if (in_array($this->name, $this->structuredValues)) {
            return [$this->getParts()];
        }
        return $this->getParts();

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'TEXT';

    }

    /**
     * Turns the object back into a serialized blob.
     *
     * @return string
     */
    function serialize() {

        // We need to kick in a special type of encoding, if it's a 2.1 vcard.
        if ($this->root->getDocumentType() !== Document::VCARD21) {
            return parent::serialize();
        }

        $val = $this->getParts();

        if (isset($this->minimumPropertyValues[$this->name])) {
            $val = array_pad($val, $this->minimumPropertyValues[$this->name], '');
        }

        // Imploding multiple parts into a single value, and splitting the
        // values with ;.
        if (count($val) > 1) {
            foreach ($val as $k => $v) {
                $val[$k] = str_replace(';', '\;', $v);
            }
            $val = implode(';', $val);
        } else {
            $val = $val[0];
        }

        $str = $this->name;
        if ($this->group) $str = $this->group . '.' . $this->name;
        foreach ($this->parameters as $param) {

            if ($param->getValue() === 'QUOTED-PRINTABLE') {
                continue;
            }
            $str .= ';' . $param->serialize();

        }



        // If the resulting value contains a \n, we must encode it as
        // quoted-printable.
        if (strpos($val, "\n") !== false) {

            $str .= ';ENCODING=QUOTED-PRINTABLE:';
            $lastLine = $str;
            $out = null;

            // The PHP built-in quoted-printable-encode does not correctly
            // encode newlines for us. Specifically, the \r\n sequence must in
            // vcards be encoded as =0D=OA and we must insert soft-newlines
            // every 75 bytes.
            for ($ii = 0;$ii < strlen($val);$ii++) {
                $ord = ord($val[$ii]);
                // These characters are encoded as themselves.
                if ($ord >= 32 && $ord <= 126) {
                    $lastLine .= $val[$ii];
                } else {
                    $lastLine .= '=' . strtoupper(bin2hex($val[$ii]));
                }
                if (strlen($lastLine) >= 75) {
                    // Soft line break
                    $out .= $lastLine . "=\r\n ";
                    $lastLine = null;
                }

            }
            if (!is_null($lastLine)) $out .= $lastLine . "\r\n";
            return $out;

        } else {
            $str .= ':' . $val;
            $out = '';
            while (strlen($str) > 0) {
                if (strlen($str) > 75) {
                    $out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n";
                    $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8');
                } else {
                    $out .= $str . "\r\n";
                    $str = '';
                    break;
                }
            }

            return $out;

        }

    }

    /**
     * This method serializes only the value of a property. This is used to
     * create xCard or xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    protected function xmlSerializeValue(Xml\Writer $writer) {

        $values = $this->getParts();

        $map = function($items) use ($values, $writer) {
            foreach ($items as $i => $item) {
                $writer->writeElement(
                    $item,
                    !empty($values[$i]) ? $values[$i] : null
                );
            }
        };

        switch ($this->name) {

            // Special-casing the REQUEST-STATUS property.
            //
            // See:
            // http://tools.ietf.org/html/rfc6321#section-3.4.1.3
            case 'REQUEST-STATUS':
                $writer->writeElement('code', $values[0]);
                $writer->writeElement('description', $values[1]);

                if (isset($values[2])) {
                    $writer->writeElement('data', $values[2]);
                }
                break;

            case 'N':
                $map([
                    'surname',
                    'given',
                    'additional',
                    'prefix',
                    'suffix'
                ]);
                break;

            case 'GENDER':
                $map([
                    'sex',
                    'text'
                ]);
                break;

            case 'ADR':
                $map([
                    'pobox',
                    'ext',
                    'street',
                    'locality',
                    'region',
                    'code',
                    'country'
                ]);
                break;

            case 'CLIENTPIDMAP':
                $map([
                    'sourceid',
                    'uri'
                ]);
                break;

            default:
                parent::xmlSerializeValue($writer);
        }

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   - Node::REPAIR - If something is broken, and automatic repair may
     *                    be attempted.
     *
     * An array is returned with warnings.
     *
     * Every item in the array has the following properties:
     *    * level - (number between 1 and 3 with severity information)
     *    * message - (human readable message)
     *    * node - (reference to the offending node)
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $warnings = parent::validate($options);

        if (isset($this->minimumPropertyValues[$this->name])) {

            $minimum = $this->minimumPropertyValues[$this->name];
            $parts = $this->getParts();
            if (count($parts) < $minimum) {
                $warnings[] = [
                    'level'   => $options & self::REPAIR ? 1 : 3,
                    'message' => 'The ' . $this->name . ' property must have at least ' . $minimum . ' values. It only has ' . count($parts),
                    'node'    => $this,
                ];
                if ($options & self::REPAIR) {
                    $parts = array_pad($parts, $minimum, '');
                    $this->setParts($parts);
                }
            }

        }
        return $warnings;

    }
}
<?php

namespace Sabre\VObject\Property;

use Sabre\VObject\DateTimeParser;

/**
 * Time property.
 *
 * This object encodes TIME values.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Time extends Text {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = null;

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'TIME';

    }

    /**
     * Sets the JSON value, as it would appear in a jCard or jCal object.
     *
     * The value must always be an array.
     *
     * @param array $value
     *
     * @return void
     */
    function setJsonValue(array $value) {

        // Removing colons from value.
        $value = str_replace(
            ':',
            '',
            $value
        );

        if (count($value) === 1) {
            $this->setValue(reset($value));
        } else {
            $this->setValue($value);
        }

    }

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        $parts = DateTimeParser::parseVCardTime($this->getValue());
        $timeStr = '';

        // Hour
        if (!is_null($parts['hour'])) {
            $timeStr .= $parts['hour'];

            if (!is_null($parts['minute'])) {
                $timeStr .= ':';
            }
        } else {
            // We know either minute or second _must_ be set, so we insert a
            // dash for an empty value.
            $timeStr .= '-';
        }

        // Minute
        if (!is_null($parts['minute'])) {
            $timeStr .= $parts['minute'];

            if (!is_null($parts['second'])) {
                $timeStr .= ':';
            }
        } else {
            if (isset($parts['second'])) {
                // Dash for empty minute
                $timeStr .= '-';
            }
        }

        // Second
        if (!is_null($parts['second'])) {
            $timeStr .= $parts['second'];
        }

        // Timezone
        if (!is_null($parts['timezone'])) {
            if ($parts['timezone'] === 'Z') {
                $timeStr .= 'Z';
            } else {
                $timeStr .=
                    preg_replace('/([0-9]{2})([0-9]{2})$/', '$1:$2', $parts['timezone']);
            }
        }

        return [$timeStr];

    }

    /**
     * Hydrate data from a XML subtree, as it would appear in a xCard or xCal
     * object.
     *
     * @param array $value
     *
     * @return void
     */
    function setXmlValue(array $value) {

        $value = array_map(
            function($value) {
                return str_replace(':', '', $value);
            },
            $value
        );
        parent::setXmlValue($value);

    }

}
<?php

namespace Sabre\VObject\Property;

/**
 * Unknown property.
 *
 * This object represents any properties not recognized by the parser.
 * This type of value has been introduced by the jCal, jCard specs.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Unknown extends Text {

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        return [$this->getRawMimeDirValue()];

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'UNKNOWN';

    }

}
<?php

namespace Sabre\VObject\Property;

use Sabre\VObject\Parameter;
use Sabre\VObject\Property;

/**
 * URI property.
 *
 * This object encodes URI values. vCard 2.1 calls these URL.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Uri extends Text {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = null;

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'URI';

    }

    /**
     * Returns an iterable list of children.
     *
     * @return array
     */
    function parameters() {

        $parameters = parent::parameters();
        if (!isset($parameters['VALUE']) && in_array($this->name, ['URL', 'PHOTO'])) {
            // If we are encoding a URI value, and this URI value has no
            // VALUE=URI parameter, we add it anyway.
            //
            // This is not required by any spec, but both Apple iCal and Apple
            // AddressBook (at least in version 10.8) will trip over this if
            // this is not set, and so it improves compatibility.
            //
            // See Issue #227 and #235
            $parameters['VALUE'] = new Parameter($this->root, 'VALUE', 'URI');
        }
        return $parameters;

    }

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        // Normally we don't need to do any type of unescaping for these
        // properties, however.. we've noticed that Google Contacts
        // specifically escapes the colon (:) with a blackslash. While I have
        // no clue why they thought that was a good idea, I'm unescaping it
        // anyway.
        //
        // Good thing backslashes are not allowed in urls. Makes it easy to
        // assume that a backslash is always intended as an escape character.
        if ($this->name === 'URL') {
            $regex = '#  (?: (\\\\ (?: \\\\ | : ) ) ) #x';
            $matches = preg_split($regex, $val, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
            $newVal = '';
            foreach ($matches as $match) {
                switch ($match) {
                    case '\:' :
                        $newVal .= ':';
                        break;
                    default :
                        $newVal .= $match;
                        break;
                }
            }
            $this->value = $newVal;
        } else {
            $this->value = strtr($val, ['\,' => ',']);
        }

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        if (is_array($this->value)) {
            $value = $this->value[0];
        } else {
            $value = $this->value;
        }

        return strtr($value, [',' => '\,']);

    }

}
<?php

namespace Sabre\VObject\Property;

/**
 * UtcOffset property.
 *
 * This object encodes UTC-OFFSET values.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class UtcOffset extends Text {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = null;

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'UTC-OFFSET';

    }

    /**
     * Sets the JSON value, as it would appear in a jCard or jCal object.
     *
     * The value must always be an array.
     *
     * @param array $value
     *
     * @return void
     */
    function setJsonValue(array $value) {

        $value = array_map(
            function($value) {
                return str_replace(':', '', $value);
            },
            $value
        );
        parent::setJsonValue($value);

    }

    /**
     * Returns the value, in the format it should be encoded for JSON.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        return array_map(
            function($value) {
                return substr($value, 0, -2) . ':' .
                       substr($value, -2);
            },
            parent::getJsonValue()
        );

    }
}
<?php

namespace Sabre\VObject\Property\VCard;

/**
 * Date property.
 *
 * This object encodes vCard DATE values.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Date extends DateAndOrTime {

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'DATE';

    }

    /**
     * Sets the property as a DateTime object.
     *
     * @param \DateTimeInterface $dt
     *
     * @return void
     */
    function setDateTime(\DateTimeInterface $dt) {

        $this->value = $dt->format('Ymd');

    }

}
<?php

namespace Sabre\VObject\Property\VCard;

use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\InvalidDataException;
use Sabre\VObject\Property;
use Sabre\Xml;

/**
 * DateAndOrTime property.
 *
 * This object encodes DATE-AND-OR-TIME values.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class DateAndOrTime extends Property {

    /**
     * Field separator.
     *
     * @var null|string
     */
    public $delimiter = null;

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'DATE-AND-OR-TIME';

    }

    /**
     * Sets a multi-valued property.
     *
     * You may also specify DateTimeInterface objects here.
     *
     * @param array $parts
     *
     * @return void
     */
    function setParts(array $parts) {

        if (count($parts) > 1) {
            throw new \InvalidArgumentException('Only one value allowed');
        }
        if (isset($parts[0]) && $parts[0] instanceof DateTimeInterface) {
            $this->setDateTime($parts[0]);
        } else {
            parent::setParts($parts);
        }

    }

    /**
     * Updates the current value.
     *
     * This may be either a single, or multiple strings in an array.
     *
     * Instead of strings, you may also use DateTimeInterface here.
     *
     * @param string|array|DateTimeInterface $value
     *
     * @return void
     */
    function setValue($value) {

        if ($value instanceof DateTimeInterface) {
            $this->setDateTime($value);
        } else {
            parent::setValue($value);
        }

    }

    /**
     * Sets the property as a DateTime object.
     *
     * @param DateTimeInterface $dt
     *
     * @return void
     */
    function setDateTime(DateTimeInterface $dt) {

        $tz = $dt->getTimeZone();
        $isUtc = in_array($tz->getName(), ['UTC', 'GMT', 'Z']);

        if ($isUtc) {
            $value = $dt->format('Ymd\\THis\\Z');
        } else {
            // Calculating the offset.
            $value = $dt->format('Ymd\\THisO');
        }

        $this->value = $value;

    }

    /**
     * Returns a date-time value.
     *
     * Note that if this property contained more than 1 date-time, only the
     * first will be returned. To get an array with multiple values, call
     * getDateTimes.
     *
     * If no time was specified, we will always use midnight (in the default
     * timezone) as the time.
     *
     * If parts of the date were omitted, such as the year, we will grab the
     * current values for those. So at the time of writing, if the year was
     * omitted, we would have filled in 2014.
     *
     * @return DateTimeImmutable
     */
    function getDateTime() {

        $now = new DateTime();

        $tzFormat = $now->getTimezone()->getOffset($now) === 0 ? '\\Z' : 'O';
        $nowParts = DateTimeParser::parseVCardDateTime($now->format('Ymd\\This' . $tzFormat));

        $dateParts = DateTimeParser::parseVCardDateTime($this->getValue());

        // This sets all the missing parts to the current date/time.
        // So if the year was missing for a birthday, we're making it 'this
        // year'.
        foreach ($dateParts as $k => $v) {
            if (is_null($v)) {
                $dateParts[$k] = $nowParts[$k];
            }
        }
        return new DateTimeImmutable("$dateParts[year]-$dateParts[month]-$dateParts[date] $dateParts[hour]:$dateParts[minute]:$dateParts[second] $dateParts[timezone]");

    }

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        $parts = DateTimeParser::parseVCardDateTime($this->getValue());

        $dateStr = '';

        // Year
        if (!is_null($parts['year'])) {

            $dateStr .= $parts['year'];

            if (!is_null($parts['month'])) {
                // If a year and a month is set, we need to insert a separator
                // dash.
                $dateStr .= '-';
            }

        } else {

            if (!is_null($parts['month']) || !is_null($parts['date'])) {
                // Inserting two dashes
                $dateStr .= '--';
            }

        }

        // Month
        if (!is_null($parts['month'])) {

            $dateStr .= $parts['month'];

            if (isset($parts['date'])) {
                // If month and date are set, we need the separator dash.
                $dateStr .= '-';
            }

        } elseif (isset($parts['date'])) {
            // If the month is empty, and a date is set, we need a 'empty
            // dash'
            $dateStr .= '-';
        }

        // Date
        if (!is_null($parts['date'])) {
            $dateStr .= $parts['date'];
        }


        // Early exit if we don't have a time string.
        if (is_null($parts['hour']) && is_null($parts['minute']) && is_null($parts['second'])) {
            return [$dateStr];
        }

        $dateStr .= 'T';

        // Hour
        if (!is_null($parts['hour'])) {

            $dateStr .= $parts['hour'];

            if (!is_null($parts['minute'])) {
                $dateStr .= ':';
            }

        } else {
            // We know either minute or second _must_ be set, so we insert a
            // dash for an empty value.
            $dateStr .= '-';
        }

        // Minute
        if (!is_null($parts['minute'])) {

            $dateStr .= $parts['minute'];

            if (!is_null($parts['second'])) {
                $dateStr .= ':';
            }

        } elseif (isset($parts['second'])) {
            // Dash for empty minute
            $dateStr .= '-';
        }

        // Second
        if (!is_null($parts['second'])) {
            $dateStr .= $parts['second'];
        }

        // Timezone
        if (!is_null($parts['timezone'])) {
            $dateStr .= $parts['timezone'];
        }

        return [$dateStr];

    }

    /**
     * This method serializes only the value of a property. This is used to
     * create xCard or xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    protected function xmlSerializeValue(Xml\Writer $writer) {

        $valueType = strtolower($this->getValueType());
        $parts = DateTimeParser::parseVCardDateAndOrTime($this->getValue());
        $value = '';

        // $d = defined
        $d = function($part) use ($parts) {
            return !is_null($parts[$part]);
        };

        // $r = read
        $r = function($part) use ($parts) {
            return $parts[$part];
        };

        // From the Relax NG Schema.
        //
        // # 4.3.1
        // value-date = element date {
        //     xsd:string { pattern = "\d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d" }
        //   }
        if (($d('year') || $d('month') || $d('date'))
            && (!$d('hour') && !$d('minute') && !$d('second') && !$d('timezone'))) {

            if ($d('year') && $d('month') && $d('date')) {
                $value .= $r('year') . $r('month') . $r('date');
            } elseif ($d('year') && $d('month') && !$d('date')) {
                $value .= $r('year') . '-' . $r('month');
            } elseif (!$d('year') && $d('month')) {
                $value .= '--' . $r('month') . $r('date');
            } elseif (!$d('year') && !$d('month') && $d('date')) {
                $value .= '---' . $r('date');
            }

        // # 4.3.2
        // value-time = element time {
        //     xsd:string { pattern = "(\d\d(\d\d(\d\d)?)?|-\d\d(\d\d?)|--\d\d)"
        //                          ~ "(Z|[+\-]\d\d(\d\d)?)?" }
        //   }
        } elseif ((!$d('year') && !$d('month') && !$d('date'))
                  && ($d('hour') || $d('minute') || $d('second'))) {

            if ($d('hour')) {
                $value .= $r('hour') . $r('minute') . $r('second');
            } elseif ($d('minute')) {
                $value .= '-' . $r('minute') . $r('second');
            } elseif ($d('second')) {
                $value .= '--' . $r('second');
            }

            $value .= $r('timezone');

        // # 4.3.3
        // value-date-time = element date-time {
        //     xsd:string { pattern = "(\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?"
        //                          ~ "(Z|[+\-]\d\d(\d\d)?)?" }
        //   }
        } elseif ($d('date') && $d('hour')) {

            if ($d('year') && $d('month') && $d('date')) {
                $value .= $r('year') . $r('month') . $r('date');
            } elseif (!$d('year') && $d('month') && $d('date')) {
                $value .= '--' . $r('month') . $r('date');
            } elseif (!$d('year') && !$d('month') && $d('date')) {
                $value .= '---' . $r('date');
            }

            $value .= 'T' . $r('hour') . $r('minute') . $r('second') .
                      $r('timezone');

        }

        $writer->writeElement($valueType, $value);

    }

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->setValue($val);

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return implode($this->delimiter, $this->getParts());

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   Node::REPAIR - May attempt to automatically repair the problem.
     *
     * This method returns an array with detected problems.
     * Every element has the following properties:
     *
     *  * level - problem level.
     *  * message - A human-readable string describing the issue.
     *  * node - A reference to the problematic node.
     *
     * The level means:
     *   1 - The issue was repaired (only happens if REPAIR was turned on)
     *   2 - An inconsequential issue
     *   3 - A severe issue.
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $messages = parent::validate($options);
        $value = $this->getValue();

        try {
            DateTimeParser::parseVCardDateTime($value);
        } catch (InvalidDataException $e) {
            $messages[] = [
                'level'   => 3,
                'message' => 'The supplied value (' . $value . ') is not a correct DATE-AND-OR-TIME property',
                'node'    => $this,
            ];
        }

        return $messages;

    }
}
<?php

namespace Sabre\VObject\Property\VCard;

/**
 * DateTime property.
 *
 * This object encodes DATE-TIME values for vCards.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class DateTime extends DateAndOrTime {

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'DATE-TIME';

    }

}
<?php

namespace Sabre\VObject\Property\VCard;

use
    Sabre\VObject\Property;

/**
 * LanguageTag property.
 *
 * This object represents LANGUAGE-TAG values as used in vCards.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class LanguageTag extends Property {

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    function setRawMimeDirValue($val) {

        $this->setValue($val);

    }

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    function getRawMimeDirValue() {

        return $this->getValue();

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'LANGUAGE-TAG';

    }

}
<?php

namespace Sabre\VObject\Property\VCard;

use Sabre\VObject\DateTimeParser;
use Sabre\VObject\Property\Text;
use Sabre\Xml;

/**
 * TimeStamp property.
 *
 * This object encodes TIMESTAMP values.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class TimeStamp extends Text {

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = null;

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    function getValueType() {

        return 'TIMESTAMP';

    }

    /**
     * Returns the value, in the format it should be encoded for json.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        $parts = DateTimeParser::parseVCardDateTime($this->getValue());

        $dateStr =
            $parts['year'] . '-' .
            $parts['month'] . '-' .
            $parts['date'] . 'T' .
            $parts['hour'] . ':' .
            $parts['minute'] . ':' .
            $parts['second'];

        // Timezone
        if (!is_null($parts['timezone'])) {
            $dateStr .= $parts['timezone'];
        }

        return [$dateStr];

    }

    /**
     * This method serializes only the value of a property. This is used to
     * create xCard or xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    protected function xmlSerializeValue(Xml\Writer $writer) {

        // xCard is the only XML and JSON format that has the same date and time
        // format than vCard.
        $valueType = strtolower($this->getValueType());
        $writer->writeElement($valueType, $this->getValue());

    }
}
<?php

namespace Sabre\VObject;

use Sabre\Xml;

/**
 * Property.
 *
 * A property is always in a KEY:VALUE structure, and may optionally contain
 * parameters.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
abstract class Property extends Node {

    /**
     * Property name.
     *
     * This will contain a string such as DTSTART, SUMMARY, FN.
     *
     * @var string
     */
    public $name;

    /**
     * Property group.
     *
     * This is only used in vcards
     *
     * @var string
     */
    public $group;

    /**
     * List of parameters.
     *
     * @var array
     */
    public $parameters = [];

    /**
     * Current value.
     *
     * @var mixed
     */
    protected $value;

    /**
     * In case this is a multi-value property. This string will be used as a
     * delimiter.
     *
     * @var string|null
     */
    public $delimiter = ';';

    /**
     * Creates the generic property.
     *
     * Parameters must be specified in key=>value syntax.
     *
     * @param Component $root The root document
     * @param string $name
     * @param string|array|null $value
     * @param array $parameters List of parameters
     * @param string $group The vcard property group
     *
     * @return void
     */
    function __construct(Component $root, $name, $value = null, array $parameters = [], $group = null) {

        $this->name = $name;
        $this->group = $group;

        $this->root = $root;

        foreach ($parameters as $k => $v) {
            $this->add($k, $v);
        }

        if (!is_null($value)) {
            $this->setValue($value);
        }

    }

    /**
     * Updates the current value.
     *
     * This may be either a single, or multiple strings in an array.
     *
     * @param string|array $value
     *
     * @return void
     */
    function setValue($value) {

        $this->value = $value;

    }

    /**
     * Returns the current value.
     *
     * This method will always return a singular value. If this was a
     * multi-value object, some decision will be made first on how to represent
     * it as a string.
     *
     * To get the correct multi-value version, use getParts.
     *
     * @return string
     */
    function getValue() {

        if (is_array($this->value)) {
            if (count($this->value) == 0) {
                return;
            } elseif (count($this->value) === 1) {
                return $this->value[0];
            } else {
                return $this->getRawMimeDirValue();
            }
        } else {
            return $this->value;
        }

    }

    /**
     * Sets a multi-valued property.
     *
     * @param array $parts
     *
     * @return void
     */
    function setParts(array $parts) {

        $this->value = $parts;

    }

    /**
     * Returns a multi-valued property.
     *
     * This method always returns an array, if there was only a single value,
     * it will still be wrapped in an array.
     *
     * @return array
     */
    function getParts() {

        if (is_null($this->value)) {
            return [];
        } elseif (is_array($this->value)) {
            return $this->value;
        } else {
            return [$this->value];
        }

    }

    /**
     * Adds a new parameter.
     *
     * If a parameter with same name already existed, the values will be
     * combined.
     * If nameless parameter is added, we try to guess it's name.
     *
     * @param string $name
     * @param string|null|array $value
     */
    function add($name, $value = null) {
        $noName = false;
        if ($name === null) {
            $name = Parameter::guessParameterNameByValue($value);
            $noName = true;
        }

        if (isset($this->parameters[strtoupper($name)])) {
            $this->parameters[strtoupper($name)]->addValue($value);
        }
        else {
            $param = new Parameter($this->root, $name, $value);
            $param->noName = $noName;
            $this->parameters[$param->name] = $param;
        }
    }

    /**
     * Returns an iterable list of children.
     *
     * @return array
     */
    function parameters() {

        return $this->parameters;

    }

    /**
     * Returns the type of value.
     *
     * This corresponds to the VALUE= parameter. Every property also has a
     * 'default' valueType.
     *
     * @return string
     */
    abstract function getValueType();

    /**
     * Sets a raw value coming from a mimedir (iCalendar/vCard) file.
     *
     * This has been 'unfolded', so only 1 line will be passed. Unescaping is
     * not yet done, but parameters are not included.
     *
     * @param string $val
     *
     * @return void
     */
    abstract function setRawMimeDirValue($val);

    /**
     * Returns a raw mime-dir representation of the value.
     *
     * @return string
     */
    abstract function getRawMimeDirValue();

    /**
     * Turns the object back into a serialized blob.
     *
     * @return string
     */
    function serialize() {

        $str = $this->name;
        if ($this->group) $str = $this->group . '.' . $this->name;

        foreach ($this->parameters() as $param) {

            $str .= ';' . $param->serialize();

        }

        $str .= ':' . $this->getRawMimeDirValue();

        $out = '';
        while (strlen($str) > 0) {
            if (strlen($str) > 75) {
                $out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n";
                $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8');
            } else {
                $out .= $str . "\r\n";
                $str = '';
                break;
            }
        }

        return $out;

    }

    /**
     * Returns the value, in the format it should be encoded for JSON.
     *
     * This method must always return an array.
     *
     * @return array
     */
    function getJsonValue() {

        return $this->getParts();

    }

    /**
     * Sets the JSON value, as it would appear in a jCard or jCal object.
     *
     * The value must always be an array.
     *
     * @param array $value
     *
     * @return void
     */
    function setJsonValue(array $value) {

        if (count($value) === 1) {
            $this->setValue(reset($value));
        } else {
            $this->setValue($value);
        }

    }

    /**
     * This method returns an array, with the representation as it should be
     * encoded in JSON. This is used to create jCard or jCal documents.
     *
     * @return array
     */
    function jsonSerialize() {

        $parameters = [];

        foreach ($this->parameters as $parameter) {
            if ($parameter->name === 'VALUE') {
                continue;
            }
            $parameters[strtolower($parameter->name)] = $parameter->jsonSerialize();
        }
        // In jCard, we need to encode the property-group as a separate 'group'
        // parameter.
        if ($this->group) {
            $parameters['group'] = $this->group;
        }

        return array_merge(
            [
                strtolower($this->name),
                (object)$parameters,
                strtolower($this->getValueType()),
            ],
            $this->getJsonValue()
        );
    }

    /**
     * Hydrate data from a XML subtree, as it would appear in a xCard or xCal
     * object.
     *
     * @param array $value
     *
     * @return void
     */
    function setXmlValue(array $value) {

        $this->setJsonValue($value);

    }

    /**
     * This method serializes the data into XML. This is used to create xCard or
     * xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $parameters = [];

        foreach ($this->parameters as $parameter) {

            if ($parameter->name === 'VALUE') {
                continue;
            }

            $parameters[] = $parameter;

        }

        $writer->startElement(strtolower($this->name));

        if (!empty($parameters)) {

            $writer->startElement('parameters');

            foreach ($parameters as $parameter) {

                $writer->startElement(strtolower($parameter->name));
                $writer->write($parameter);
                $writer->endElement();

            }

            $writer->endElement();

        }

        $this->xmlSerializeValue($writer);
        $writer->endElement();

    }

    /**
     * This method serializes only the value of a property. This is used to
     * create xCard or xCal documents.
     *
     * @param Xml\Writer $writer  XML writer.
     *
     * @return void
     */
    protected function xmlSerializeValue(Xml\Writer $writer) {

        $valueType = strtolower($this->getValueType());

        foreach ($this->getJsonValue() as $values) {
            foreach ((array)$values as $value) {
                $writer->writeElement($valueType, $value);
            }
        }

    }

    /**
     * Called when this object is being cast to a string.
     *
     * If the property only had a single value, you will get just that. In the
     * case the property had multiple values, the contents will be escaped and
     * combined with ,.
     *
     * @return string
     */
    function __toString() {

        return (string)$this->getValue();

    }

    /* ArrayAccess interface {{{ */

    /**
     * Checks if an array element exists.
     *
     * @param mixed $name
     *
     * @return bool
     */
    function offsetExists($name) {

        if (is_int($name)) return parent::offsetExists($name);

        $name = strtoupper($name);

        foreach ($this->parameters as $parameter) {
            if ($parameter->name == $name) return true;
        }
        return false;

    }

    /**
     * Returns a parameter.
     *
     * If the parameter does not exist, null is returned.
     *
     * @param string $name
     *
     * @return Node
     */
    function offsetGet($name) {

        if (is_int($name)) return parent::offsetGet($name);
        $name = strtoupper($name);

        if (!isset($this->parameters[$name])) {
            return;
        }

        return $this->parameters[$name];

    }

    /**
     * Creates a new parameter.
     *
     * @param string $name
     * @param mixed $value
     *
     * @return void
     */
    function offsetSet($name, $value) {

        if (is_int($name)) {
            parent::offsetSet($name, $value);
            // @codeCoverageIgnoreStart
            // This will never be reached, because an exception is always
            // thrown.
            return;
            // @codeCoverageIgnoreEnd
        }

        $param = new Parameter($this->root, $name, $value);
        $this->parameters[$param->name] = $param;

    }

    /**
     * Removes one or more parameters with the specified name.
     *
     * @param string $name
     *
     * @return void
     */
    function offsetUnset($name) {

        if (is_int($name)) {
            parent::offsetUnset($name);
            // @codeCoverageIgnoreStart
            // This will never be reached, because an exception is always
            // thrown.
            return;
            // @codeCoverageIgnoreEnd
        }

        unset($this->parameters[strtoupper($name)]);

    }
    /* }}} */

    /**
     * This method is automatically called when the object is cloned.
     * Specifically, this will ensure all child elements are also cloned.
     *
     * @return void
     */
    function __clone() {

        foreach ($this->parameters as $key => $child) {
            $this->parameters[$key] = clone $child;
            $this->parameters[$key]->parent = $this;
        }

    }

    /**
     * Validates the node for correctness.
     *
     * The following options are supported:
     *   - Node::REPAIR - If something is broken, and automatic repair may
     *                    be attempted.
     *
     * An array is returned with warnings.
     *
     * Every item in the array has the following properties:
     *    * level - (number between 1 and 3 with severity information)
     *    * message - (human readable message)
     *    * node - (reference to the offending node)
     *
     * @param int $options
     *
     * @return array
     */
    function validate($options = 0) {

        $warnings = [];

        // Checking if our value is UTF-8
        if (!StringUtil::isUTF8($this->getRawMimeDirValue())) {

            $oldValue = $this->getRawMimeDirValue();
            $level = 3;
            if ($options & self::REPAIR) {
                $newValue = StringUtil::convertToUTF8($oldValue);
                if (true || StringUtil::isUTF8($newValue)) {
                    $this->setRawMimeDirValue($newValue);
                    $level = 1;
                }

            }


            if (preg_match('%([\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', $oldValue, $matches)) {
                $message = 'Property contained a control character (0x' . bin2hex($matches[1]) . ')';
            } else {
                $message = 'Property is not valid UTF-8! ' . $oldValue;
            }

            $warnings[] = [
                'level'   => $level,
                'message' => $message,
                'node'    => $this,
            ];
        }

        // Checking if the propertyname does not contain any invalid bytes.
        if (!preg_match('/^([A-Z0-9-]+)$/', $this->name)) {
            $warnings[] = [
                'level'   => $options & self::REPAIR ? 1 : 3,
                'message' => 'The propertyname: ' . $this->name . ' contains invalid characters. Only A-Z, 0-9 and - are allowed',
                'node'    => $this,
            ];
            if ($options & self::REPAIR) {
                // Uppercasing and converting underscores to dashes.
                $this->name = strtoupper(
                    str_replace('_', '-', $this->name)
                );
                // Removing every other invalid character
                $this->name = preg_replace('/([^A-Z0-9-])/u', '', $this->name);

            }

        }

        if ($encoding = $this->offsetGet('ENCODING')) {

            if ($this->root->getDocumentType() === Document::VCARD40) {
                $warnings[] = [
                    'level'   => 3,
                    'message' => 'ENCODING parameter is not valid in vCard 4.',
                    'node'    => $this
                ];
            } else {

                $encoding = (string)$encoding;

                $allowedEncoding = [];

                switch ($this->root->getDocumentType()) {
                    case Document::ICALENDAR20 :
                        $allowedEncoding = ['8BIT', 'BASE64'];
                        break;
                    case Document::VCARD21 :
                        $allowedEncoding = ['QUOTED-PRINTABLE', 'BASE64', '8BIT'];
                        break;
                    case Document::VCARD30 :
                        $allowedEncoding = ['B'];
                        break;

                }
                if ($allowedEncoding && !in_array(strtoupper($encoding), $allowedEncoding)) {
                    $warnings[] = [
                        'level'   => 3,
                        'message' => 'ENCODING=' . strtoupper($encoding) . ' is not valid for this document type.',
                        'node'    => $this
                    ];
                }
            }

        }

        // Validating inner parameters
        foreach ($this->parameters as $param) {
            $warnings = array_merge($warnings, $param->validate($options));
        }

        return $warnings;

    }

    /**
     * Call this method on a document if you're done using it.
     *
     * It's intended to remove all circular references, so PHP can easily clean
     * it up.
     *
     * @return void
     */
    function destroy() {

        parent::destroy();
        foreach ($this->parameters as $param) {
            $param->destroy();
        }
        $this->parameters = [];

    }

}
<?php

namespace Sabre\VObject;

/**
 * iCalendar/vCard/jCal/jCard/xCal/xCard reader object.
 *
 * This object provides a few (static) convenience methods to quickly access
 * the parsers.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Reader {

    /**
     * If this option is passed to the reader, it will be less strict about the
     * validity of the lines.
     */
    const OPTION_FORGIVING = 1;

    /**
     * If this option is turned on, any lines we cannot parse will be ignored
     * by the reader.
     */
    const OPTION_IGNORE_INVALID_LINES = 2;

    /**
     * Parses a vCard or iCalendar object, and returns the top component.
     *
     * The options argument is a bitfield. Pass any of the OPTIONS constant to
     * alter the parsers' behaviour.
     *
     * You can either supply a string, or a readable stream for input.
     *
     * @param string|resource $data
     * @param int $options
     * @param string $charset
     * @return Document
     */
    static function read($data, $options = 0, $charset = 'UTF-8') {

        $parser = new Parser\MimeDir();
        $parser->setCharset($charset);
        $result = $parser->parse($data, $options);

        return $result;

    }

    /**
     * Parses a jCard or jCal object, and returns the top component.
     *
     * The options argument is a bitfield. Pass any of the OPTIONS constant to
     * alter the parsers' behaviour.
     *
     * You can either a string, a readable stream, or an array for it's input.
     * Specifying the array is useful if json_decode was already called on the
     * input.
     *
     * @param string|resource|array $data
     * @param int $options
     *
     * @return Document
     */
    static function readJson($data, $options = 0) {

        $parser = new Parser\Json();
        $result = $parser->parse($data, $options);

        return $result;

    }

    /**
     * Parses a xCard or xCal object, and returns the top component.
     *
     * The options argument is a bitfield. Pass any of the OPTIONS constant to
     * alter the parsers' behaviour.
     *
     * You can either supply a string, or a readable stream for input.
     *
     * @param string|resource $data
     * @param int $options
     *
     * @return Document
     */
    static function readXML($data, $options = 0) {

        $parser = new Parser\XML();
        $result = $parser->parse($data, $options);

        return $result;

    }

}
<?php

namespace Sabre\VObject\Recur;

use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use InvalidArgumentException;
use Sabre\VObject\Component;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\Settings;

/**
 * This class is used to determine new for a recurring event, when the next
 * events occur.
 *
 * This iterator may loop infinitely in the future, therefore it is important
 * that if you use this class, you set hard limits for the amount of iterations
 * you want to handle.
 *
 * Note that currently there is not full support for the entire iCalendar
 * specification, as it's very complex and contains a lot of permutations
 * that's not yet used very often in software.
 *
 * For the focus has been on features as they actually appear in Calendaring
 * software, but this may well get expanded as needed / on demand
 *
 * The following RRULE properties are supported
 *   * UNTIL
 *   * INTERVAL
 *   * COUNT
 *   * FREQ=DAILY
 *     * BYDAY
 *     * BYHOUR
 *     * BYMONTH
 *   * FREQ=WEEKLY
 *     * BYDAY
 *     * BYHOUR
 *     * WKST
 *   * FREQ=MONTHLY
 *     * BYMONTHDAY
 *     * BYDAY
 *     * BYSETPOS
 *   * FREQ=YEARLY
 *     * BYMONTH
 *     * BYYEARDAY
 *     * BYWEEKNO
 *     * BYMONTHDAY (only if BYMONTH is also set)
 *     * BYDAY (only if BYMONTH is also set)
 *
 * Anything beyond this is 'undefined', which means that it may get ignored, or
 * you may get unexpected results. The effect is that in some applications the
 * specified recurrence may look incorrect, or is missing.
 *
 * The recurrence iterator also does not yet support THISANDFUTURE.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class EventIterator implements \Iterator {

    /**
     * Reference timeZone for floating dates and times.
     *
     * @var DateTimeZone
     */
    protected $timeZone;

    /**
     * True if we're iterating an all-day event.
     *
     * @var bool
     */
    protected $allDay = false;

    /**
     * Creates the iterator.
     *
     * There's three ways to set up the iterator.
     *
     * 1. You can pass a VCALENDAR component and a UID.
     * 2. You can pass an array of VEVENTs (all UIDS should match).
     * 3. You can pass a single VEVENT component.
     *
     * Only the second method is recomended. The other 1 and 3 will be removed
     * at some point in the future.
     *
     * The $uid parameter is only required for the first method.
     *
     * @param Component|array $input
     * @param string|null $uid
     * @param DateTimeZone $timeZone Reference timezone for floating dates and
     *                               times.
     */
    function __construct($input, $uid = null, DateTimeZone $timeZone = null) {

        if (is_null($timeZone)) {
            $timeZone = new DateTimeZone('UTC');
        }
        $this->timeZone = $timeZone;

        if (is_array($input)) {
            $events = $input;
        } elseif ($input instanceof VEvent) {
            // Single instance mode.
            $events = [$input];
        } else {
            // Calendar + UID mode.
            $uid = (string)$uid;
            if (!$uid) {
                throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor');
            }
            if (!isset($input->VEVENT)) {
                throw new InvalidArgumentException('No events found in this calendar');
            }
            $events = $input->getByUID($uid);

        }

        foreach ($events as $vevent) {

            if (!isset($vevent->{'RECURRENCE-ID'})) {

                $this->masterEvent = $vevent;

            } else {

                $this->exceptions[
                    $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
                ] = true;
                $this->overriddenEvents[] = $vevent;

            }

        }

        if (!$this->masterEvent) {
            // No base event was found. CalDAV does allow cases where only
            // overridden instances are stored.
            //
            // In this particular case, we're just going to grab the first
            // event and use that instead. This may not always give the
            // desired result.
            if (!count($this->overriddenEvents)) {
                throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid);
            }
            $this->masterEvent = array_shift($this->overriddenEvents);
        }

        $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
        $this->allDay = !$this->masterEvent->DTSTART->hasTime();

        if (isset($this->masterEvent->EXDATE)) {

            foreach ($this->masterEvent->EXDATE as $exDate) {

                foreach ($exDate->getDateTimes($this->timeZone) as $dt) {
                    $this->exceptions[$dt->getTimeStamp()] = true;
                }

            }

        }

        if (isset($this->masterEvent->DTEND)) {
            $this->eventDuration =
                $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
                $this->startDate->getTimeStamp();
        } elseif (isset($this->masterEvent->DURATION)) {
            $duration = $this->masterEvent->DURATION->getDateInterval();
            $end = clone $this->startDate;
            $end = $end->add($duration);
            $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
        } elseif ($this->allDay) {
            $this->eventDuration = 3600 * 24;
        } else {
            $this->eventDuration = 0;
        }

        if (isset($this->masterEvent->RDATE)) {
            $this->recurIterator = new RDateIterator(
                $this->masterEvent->RDATE->getParts(),
                $this->startDate
            );
        } elseif (isset($this->masterEvent->RRULE)) {
            $this->recurIterator = new RRuleIterator(
                $this->masterEvent->RRULE->getParts(),
                $this->startDate
            );
        } else {
            $this->recurIterator = new RRuleIterator(
                [
                    'FREQ'  => 'DAILY',
                    'COUNT' => 1,
                ],
                $this->startDate
            );
        }

        $this->rewind();
        if (!$this->valid()) {
            throw new NoInstancesException('This recurrence rule does not generate any valid instances');
        }

    }

    /**
     * Returns the date for the current position of the iterator.
     *
     * @return DateTimeImmutable
     */
    function current() {

        if ($this->currentDate) {
            return clone $this->currentDate;
        }

    }

    /**
     * This method returns the start date for the current iteration of the
     * event.
     *
     * @return DateTimeImmutable
     */
    function getDtStart() {

        if ($this->currentDate) {
            return clone $this->currentDate;
        }

    }

    /**
     * This method returns the end date for the current iteration of the
     * event.
     *
     * @return DateTimeImmutable
     */
    function getDtEnd() {

        if (!$this->valid()) {
            return;
        }
        $end = clone $this->currentDate;
        return $end->modify('+' . $this->eventDuration . ' seconds');

    }

    /**
     * Returns a VEVENT for the current iterations of the event.
     *
     * This VEVENT will have a recurrence id, and it's DTSTART and DTEND
     * altered.
     *
     * @return VEvent
     */
    function getEventObject() {

        if ($this->currentOverriddenEvent) {
            return $this->currentOverriddenEvent;
        }

        $event = clone $this->masterEvent;

        // Ignoring the following block, because PHPUnit's code coverage
        // ignores most of these lines, and this messes with our stats.
        //
        // @codeCoverageIgnoreStart
        unset(
            $event->RRULE,
            $event->EXDATE,
            $event->RDATE,
            $event->EXRULE,
            $event->{'RECURRENCE-ID'}
        );
        // @codeCoverageIgnoreEnd

        $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating());
        if (isset($event->DTEND)) {
            $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating());
        }
        $recurid = clone $event->DTSTART;
        $recurid->name = 'RECURRENCE-ID';
        $event->add($recurid);
        return $event;

    }

    /**
     * Returns the current position of the iterator.
     *
     * This is for us simply a 0-based index.
     *
     * @return int
     */
    function key() {

        // The counter is always 1 ahead.
        return $this->counter - 1;

    }

    /**
     * This is called after next, to see if the iterator is still at a valid
     * position, or if it's at the end.
     *
     * @return bool
     */
    function valid() {

        if ($this->counter > Settings::$maxRecurrences && Settings::$maxRecurrences !== -1) {
            throw new MaxInstancesExceededException('Recurring events are only allowed to generate ' . Settings::$maxRecurrences);
        }
        return !!$this->currentDate;

    }

    /**
     * Sets the iterator back to the starting point.
     */
    function rewind() {

        $this->recurIterator->rewind();
        // re-creating overridden event index.
        $index = [];
        foreach ($this->overriddenEvents as $key => $event) {
            $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
            $index[$stamp][] = $key;
        }
        krsort($index);
        $this->counter = 0;
        $this->overriddenEventsIndex = $index;
        $this->currentOverriddenEvent = null;

        $this->nextDate = null;
        $this->currentDate = clone $this->startDate;

        $this->next();

    }

    /**
     * Advances the iterator with one step.
     *
     * @return void
     */
    function next() {

        $this->currentOverriddenEvent = null;
        $this->counter++;
        if ($this->nextDate) {
            // We had a stored value.
            $nextDate = $this->nextDate;
            $this->nextDate = null;
        } else {
            // We need to ask rruleparser for the next date.
            // We need to do this until we find a date that's not in the
            // exception list.
            do {
                if (!$this->recurIterator->valid()) {
                    $nextDate = null;
                    break;
                }
                $nextDate = $this->recurIterator->current();
                $this->recurIterator->next();
            } while (isset($this->exceptions[$nextDate->getTimeStamp()]));

        }


        // $nextDate now contains what rrule thinks is the next one, but an
        // overridden event may cut ahead.
        if ($this->overriddenEventsIndex) {

            $offsets = end($this->overriddenEventsIndex);
            $timestamp = key($this->overriddenEventsIndex);
            $offset = end($offsets);
            if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
                // Overridden event comes first.
                $this->currentOverriddenEvent = $this->overriddenEvents[$offset];

                // Putting the rrule next date aside.
                $this->nextDate = $nextDate;
                $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);

                // Ensuring that this item will only be used once.
                array_pop($this->overriddenEventsIndex[$timestamp]);
                if (!$this->overriddenEventsIndex[$timestamp]) {
                    array_pop($this->overriddenEventsIndex);
                }

                // Exit point!
                return;

            }

        }

        $this->currentDate = $nextDate;

    }

    /**
     * Quickly jump to a date in the future.
     *
     * @param DateTimeInterface $dateTime
     */
    function fastForward(DateTimeInterface $dateTime) {

        while ($this->valid() && $this->getDtEnd() <= $dateTime) {
            $this->next();
        }

    }

    /**
     * Returns true if this recurring event never ends.
     *
     * @return bool
     */
    function isInfinite() {

        return $this->recurIterator->isInfinite();

    }

    /**
     * RRULE parser.
     *
     * @var RRuleIterator
     */
    protected $recurIterator;

    /**
     * The duration, in seconds, of the master event.
     *
     * We use this to calculate the DTEND for subsequent events.
     */
    protected $eventDuration;

    /**
     * A reference to the main (master) event.
     *
     * @var VEVENT
     */
    protected $masterEvent;

    /**
     * List of overridden events.
     *
     * @var array
     */
    protected $overriddenEvents = [];

    /**
     * Overridden event index.
     *
     * Key is timestamp, value is the list of indexes of the item in the $overriddenEvent
     * property.
     *
     * @var array
     */
    protected $overriddenEventsIndex;

    /**
     * A list of recurrence-id's that are either part of EXDATE, or are
     * overridden.
     *
     * @var array
     */
    protected $exceptions = [];

    /**
     * Internal event counter.
     *
     * @var int
     */
    protected $counter;

    /**
     * The very start of the iteration process.
     *
     * @var DateTimeImmutable
     */
    protected $startDate;

    /**
     * Where we are currently in the iteration process.
     *
     * @var DateTimeImmutable
     */
    protected $currentDate;

    /**
     * The next date from the rrule parser.
     *
     * Sometimes we need to temporary store the next date, because an
     * overridden event came before.
     *
     * @var DateTimeImmutable
     */
    protected $nextDate;

    /**
     * The event that overwrites the current iteration
     *
     * @var VEVENT
     */
    protected $currentOverriddenEvent;

}
<?php

namespace Sabre\VObject\Recur;

use Exception;

/**
 * This exception will get thrown when a recurrence rule generated more than
 * the maximum number of instances.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
 */
class MaxInstancesExceededException extends Exception {
}
<?php

namespace Sabre\VObject\Recur;

use Exception;

/**
 * This exception gets thrown when a recurrence iterator produces 0 instances.
 *
 * This may happen when every occurence in a rrule is also in EXDATE.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
 */
class NoInstancesException extends Exception {

}
<?php

namespace Sabre\VObject\Recur;

use DateTimeInterface;
use Iterator;
use Sabre\VObject\DateTimeParser;

/**
 * RRuleParser.
 *
 * This class receives an RRULE string, and allows you to iterate to get a list
 * of dates in that recurrence.
 *
 * For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain
 * 5 items, one for each day.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class RDateIterator implements Iterator {

    /**
     * Creates the Iterator.
     *
     * @param string|array $rrule
     * @param DateTimeInterface $start
     */
    function __construct($rrule, DateTimeInterface $start) {

        $this->startDate = $start;
        $this->parseRDate($rrule);
        $this->currentDate = clone $this->startDate;

    }

    /* Implementation of the Iterator interface {{{ */

    function current() {

        if (!$this->valid()) return;
        return clone $this->currentDate;

    }

    /**
     * Returns the current item number.
     *
     * @return int
     */
    function key() {

        return $this->counter;

    }

    /**
     * Returns whether the current item is a valid item for the recurrence
     * iterator.
     *
     * @return bool
     */
    function valid() {

        return ($this->counter <= count($this->dates));

    }

    /**
     * Resets the iterator.
     *
     * @return void
     */
    function rewind() {

        $this->currentDate = clone $this->startDate;
        $this->counter = 0;

    }

    /**
     * Goes on to the next iteration.
     *
     * @return void
     */
    function next() {

        $this->counter++;
        if (!$this->valid()) return;

        $this->currentDate =
            DateTimeParser::parse(
                $this->dates[$this->counter - 1],
                $this->startDate->getTimezone()
            );

    }

    /* End of Iterator implementation }}} */

    /**
     * Returns true if this recurring event never ends.
     *
     * @return bool
     */
    function isInfinite() {

        return false;

    }

    /**
     * This method allows you to quickly go to the next occurrence after the
     * specified date.
     *
     * @param DateTimeInterface $dt
     *
     * @return void
     */
    function fastForward(DateTimeInterface $dt) {

        while ($this->valid() && $this->currentDate < $dt) {
            $this->next();
        }

    }

    /**
     * The reference start date/time for the rrule.
     *
     * All calculations are based on this initial date.
     *
     * @var DateTimeInterface
     */
    protected $startDate;

    /**
     * The date of the current iteration. You can get this by calling
     * ->current().
     *
     * @var DateTimeInterface
     */
    protected $currentDate;

    /**
     * The current item in the list.
     *
     * You can get this number with the key() method.
     *
     * @var int
     */
    protected $counter = 0;

    /* }}} */

    /**
     * This method receives a string from an RRULE property, and populates this
     * class with all the values.
     *
     * @param string|array $rrule
     *
     * @return void
     */
    protected function parseRDate($rdate) {

        if (is_string($rdate)) {
            $rdate = explode(',', $rdate);
        }

        $this->dates = $rdate;

    }

    /**
     * Array with the RRULE dates
     *
     * @var array
     */
    protected $dates = [];

}
<?php

namespace Sabre\VObject\Recur;

use DateTimeImmutable;
use DateTimeInterface;
use Iterator;
use Sabre\VObject\DateTimeParser;
use Sabre\VObject\InvalidDataException;
use Sabre\VObject\Property;

/**
 * RRuleParser.
 *
 * This class receives an RRULE string, and allows you to iterate to get a list
 * of dates in that recurrence.
 *
 * For instance, passing: FREQ=DAILY;LIMIT=5 will cause the iterator to contain
 * 5 items, one for each day.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class RRuleIterator implements Iterator {

    /**
     * Creates the Iterator.
     *
     * @param string|array $rrule
     * @param DateTimeInterface $start
     */
    function __construct($rrule, DateTimeInterface $start) {

        $this->startDate = $start;
        $this->parseRRule($rrule);
        $this->currentDate = clone $this->startDate;

    }

    /* Implementation of the Iterator interface {{{ */

    function current() {

        if (!$this->valid()) return;
        return clone $this->currentDate;

    }

    /**
     * Returns the current item number.
     *
     * @return int
     */
    function key() {

        return $this->counter;

    }

    /**
     * Returns whether the current item is a valid item for the recurrence
     * iterator. This will return false if we've gone beyond the UNTIL or COUNT
     * statements.
     *
     * @return bool
     */
    function valid() {

        if (!is_null($this->count)) {
            return $this->counter < $this->count;
        }
        return is_null($this->until) || $this->currentDate <= $this->until;

    }

    /**
     * Resets the iterator.
     *
     * @return void
     */
    function rewind() {

        $this->currentDate = clone $this->startDate;
        $this->counter = 0;

    }

    /**
     * Goes on to the next iteration.
     *
     * @return void
     */
    function next() {

        // Otherwise, we find the next event in the normal RRULE
        // sequence.
        switch ($this->frequency) {

            case 'hourly' :
                $this->nextHourly();
                break;

            case 'daily' :
                $this->nextDaily();
                break;

            case 'weekly' :
                $this->nextWeekly();
                break;

            case 'monthly' :
                $this->nextMonthly();
                break;

            case 'yearly' :
                $this->nextYearly();
                break;

        }
        $this->counter++;

    }

    /* End of Iterator implementation }}} */

    /**
     * Returns true if this recurring event never ends.
     *
     * @return bool
     */
    function isInfinite() {

        return !$this->count && !$this->until;

    }

    /**
     * This method allows you to quickly go to the next occurrence after the
     * specified date.
     *
     * @param DateTimeInterface $dt
     *
     * @return void
     */
    function fastForward(DateTimeInterface $dt) {

        while ($this->valid() && $this->currentDate < $dt) {
            $this->next();
        }

    }

    /**
     * The reference start date/time for the rrule.
     *
     * All calculations are based on this initial date.
     *
     * @var DateTimeInterface
     */
    protected $startDate;

    /**
     * The date of the current iteration. You can get this by calling
     * ->current().
     *
     * @var DateTimeInterface
     */
    protected $currentDate;

    /**
     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
     * yearly.
     *
     * @var string
     */
    protected $frequency;

    /**
     * The number of recurrences, or 'null' if infinitely recurring.
     *
     * @var int
     */
    protected $count;

    /**
     * The interval.
     *
     * If for example frequency is set to daily, interval = 2 would mean every
     * 2 days.
     *
     * @var int
     */
    protected $interval = 1;

    /**
     * The last instance of this recurrence, inclusively.
     *
     * @var DateTimeInterface|null
     */
    protected $until;

    /**
     * Which seconds to recur.
     *
     * This is an array of integers (between 0 and 60)
     *
     * @var array
     */
    protected $bySecond;

    /**
     * Which minutes to recur.
     *
     * This is an array of integers (between 0 and 59)
     *
     * @var array
     */
    protected $byMinute;

    /**
     * Which hours to recur.
     *
     * This is an array of integers (between 0 and 23)
     *
     * @var array
     */
    protected $byHour;

    /**
     * The current item in the list.
     *
     * You can get this number with the key() method.
     *
     * @var int
     */
    protected $counter = 0;

    /**
     * Which weekdays to recur.
     *
     * This is an array of weekdays
     *
     * This may also be preceeded by a positive or negative integer. If present,
     * this indicates the nth occurrence of a specific day within the monthly or
     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
     * the month, or year.
     *
     * @var array
     */
    protected $byDay;

    /**
     * Which days of the month to recur.
     *
     * This is an array of days of the months (1-31). The value can also be
     * negative. -5 for instance means the 5th last day of the month.
     *
     * @var array
     */
    protected $byMonthDay;

    /**
     * Which days of the year to recur.
     *
     * This is an array with days of the year (1 to 366). The values can also
     * be negative. For instance, -1 will always represent the last day of the
     * year. (December 31st).
     *
     * @var array
     */
    protected $byYearDay;

    /**
     * Which week numbers to recur.
     *
     * This is an array of integers from 1 to 53. The values can also be
     * negative. -1 will always refer to the last week of the year.
     *
     * @var array
     */
    protected $byWeekNo;

    /**
     * Which months to recur.
     *
     * This is an array of integers from 1 to 12.
     *
     * @var array
     */
    protected $byMonth;

    /**
     * Which items in an existing st to recur.
     *
     * These numbers work together with an existing by* rule. It specifies
     * exactly which items of the existing by-rule to filter.
     *
     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
     * used to recur the last workday of the month.
     *
     * This would be done by setting frequency to 'monthly', byDay to
     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
     *
     * @var array
     */
    protected $bySetPos;

    /**
     * When the week starts.
     *
     * @var string
     */
    protected $weekStart = 'MO';

    /* Functions that advance the iterator {{{ */

    /**
     * Does the processing for advancing the iterator for hourly frequency.
     *
     * @return void
     */
    protected function nextHourly() {

        $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' hours');

    }

    /**
     * Does the processing for advancing the iterator for daily frequency.
     *
     * @return void
     */
    protected function nextDaily() {

        if (!$this->byHour && !$this->byDay) {
            $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' days');
            return;
        }

        if (!empty($this->byHour)) {
            $recurrenceHours = $this->getHours();
        }

        if (!empty($this->byDay)) {
            $recurrenceDays = $this->getDays();
        }

        if (!empty($this->byMonth)) {
            $recurrenceMonths = $this->getMonths();
        }

        do {
            if ($this->byHour) {
                if ($this->currentDate->format('G') == '23') {
                    // to obey the interval rule
                    $this->currentDate = $this->currentDate->modify('+' . $this->interval - 1 . ' days');
                }

                $this->currentDate = $this->currentDate->modify('+1 hours');

            } else {
                $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' days');

            }

            // Current month of the year
            $currentMonth = $this->currentDate->format('n');

            // Current day of the week
            $currentDay = $this->currentDate->format('w');

            // Current hour of the day
            $currentHour = $this->currentDate->format('G');

        } while (
            ($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
            ($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
            ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
        );

    }

    /**
     * Does the processing for advancing the iterator for weekly frequency.
     *
     * @return void
     */
    protected function nextWeekly() {

        if (!$this->byHour && !$this->byDay) {
            $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' weeks');
            return;
        }

        if ($this->byHour) {
            $recurrenceHours = $this->getHours();
        }

        if ($this->byDay) {
            $recurrenceDays = $this->getDays();
        }

        // First day of the week:
        $firstDay = $this->dayMap[$this->weekStart];

        do {

            if ($this->byHour) {
                $this->currentDate = $this->currentDate->modify('+1 hours');
            } else {
                $this->currentDate = $this->currentDate->modify('+1 days');
            }

            // Current day of the week
            $currentDay = (int)$this->currentDate->format('w');

            // Current hour of the day
            $currentHour = (int)$this->currentDate->format('G');

            // We need to roll over to the next week
            if ($currentDay === $firstDay && (!$this->byHour || $currentHour == '0')) {
                $this->currentDate = $this->currentDate->modify('+' . $this->interval - 1 . ' weeks');

                // We need to go to the first day of this week, but only if we
                // are not already on this first day of this week.
                if ($this->currentDate->format('w') != $firstDay) {
                    $this->currentDate = $this->currentDate->modify('last ' . $this->dayNames[$this->dayMap[$this->weekStart]]);
                }
            }

            // We have a match
        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
    }

    /**
     * Does the processing for advancing the iterator for monthly frequency.
     *
     * @return void
     */
    protected function nextMonthly() {

        $currentDayOfMonth = $this->currentDate->format('j');
        if (!$this->byMonthDay && !$this->byDay) {

            // If the current day is higher than the 28th, rollover can
            // occur to the next month. We Must skip these invalid
            // entries.
            if ($currentDayOfMonth < 29) {
                $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' months');
            } else {
                $increase = 0;
                do {
                    $increase++;
                    $tempDate = clone $this->currentDate;
                    $tempDate = $tempDate->modify('+ ' . ($this->interval * $increase) . ' months');
                } while ($tempDate->format('j') != $currentDayOfMonth);
                $this->currentDate = $tempDate;
            }
            return;
        }

        while (true) {

            $occurrences = $this->getMonthlyOccurrences();

            foreach ($occurrences as $occurrence) {

                // The first occurrence thats higher than the current
                // day of the month wins.
                if ($occurrence > $currentDayOfMonth) {
                    break 2;
                }

            }

            // If we made it all the way here, it means there were no
            // valid occurrences, and we need to advance to the next
            // month.
            //
            // This line does not currently work in hhvm. Temporary workaround
            // follows:
            // $this->currentDate->modify('first day of this month');
            $this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
            // end of workaround
            $this->currentDate = $this->currentDate->modify('+ ' . $this->interval . ' months');

            // This goes to 0 because we need to start counting at the
            // beginning.
            $currentDayOfMonth = 0;

        }

        $this->currentDate = $this->currentDate->setDate(
            (int)$this->currentDate->format('Y'),
            (int)$this->currentDate->format('n'),
            (int)$occurrence
        );

    }

    /**
     * Does the processing for advancing the iterator for yearly frequency.
     *
     * @return void
     */
    protected function nextYearly() {

        $currentMonth = $this->currentDate->format('n');
        $currentYear = $this->currentDate->format('Y');
        $currentDayOfMonth = $this->currentDate->format('j');

        // No sub-rules, so we just advance by year
        if (empty($this->byMonth)) {

            // Unless it was a leap day!
            if ($currentMonth == 2 && $currentDayOfMonth == 29) {

                $counter = 0;
                do {
                    $counter++;
                    // Here we increase the year count by the interval, until
                    // we hit a date that's also in a leap year.
                    //
                    // We could just find the next interval that's dividable by
                    // 4, but that would ignore the rule that there's no leap
                    // year every year that's dividable by a 100, but not by
                    // 400. (1800, 1900, 2100). So we just rely on the datetime
                    // functions instead.
                    $nextDate = clone $this->currentDate;
                    $nextDate = $nextDate->modify('+ ' . ($this->interval * $counter) . ' years');
                } while ($nextDate->format('n') != 2);

                $this->currentDate = $nextDate;

                return;

            }

            if ($this->byWeekNo !== null) { // byWeekNo is an array with values from -53 to -1, or 1 to 53
                $dayOffsets = [];
                if ($this->byDay) {
                    foreach ($this->byDay as $byDay) {
                        $dayOffsets[] = $this->dayMap[$byDay];
                    }
                } else {   // default is Monday
                    $dayOffsets[] = 1;
                }

                $currentYear = $this->currentDate->format('Y');

                while (true) {
                    $checkDates = [];

                    // loop through all WeekNo and Days to check all the combinations
                    foreach ($this->byWeekNo as $byWeekNo) {
                        foreach ($dayOffsets as $dayOffset) {
                            $date = clone $this->currentDate;
                            $date->setISODate($currentYear, $byWeekNo, $dayOffset);

                            if ($date > $this->currentDate) {
                                $checkDates[] = $date;
                            }
                        }
                    }

                    if (count($checkDates) > 0) {
                        $this->currentDate = min($checkDates);
                        return;
                    }

                    // if there is no date found, check the next year
                    $currentYear += $this->interval;
                }
            }

            if ($this->byYearDay !== null) { // byYearDay is an array with values from -366 to -1, or 1 to 366
                $dayOffsets = [];
                if ($this->byDay) {
                    foreach ($this->byDay as $byDay) {
                        $dayOffsets[] = $this->dayMap[$byDay];
                    }
                } else {   // default is Monday-Sunday
                    $dayOffsets = [1,2,3,4,5,6,7];
                }

                $currentYear = $this->currentDate->format('Y');

                while (true) {
                    $checkDates = [];

                    // loop through all YearDay and Days to check all the combinations
                    foreach ($this->byYearDay as $byYearDay) {
                        $date = clone $this->currentDate;
                        $date->setDate($currentYear, 1, 1);
                        if ($byYearDay > 0) {
                            $date->add(new \DateInterval('P' . $byYearDay . 'D'));
                        } else {
                            $date->sub(new \DateInterval('P' . abs($byYearDay) . 'D'));
                        }

                        if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
                            $checkDates[] = $date;
                        }
                    }

                    if (count($checkDates) > 0) {
                        $this->currentDate = min($checkDates);
                        return;
                    }

                    // if there is no date found, check the next year
                    $currentYear += $this->interval;
                }
            }

            // The easiest form
            $this->currentDate = $this->currentDate->modify('+' . $this->interval . ' years');
            return;

        }

        $currentMonth = $this->currentDate->format('n');
        $currentYear = $this->currentDate->format('Y');
        $currentDayOfMonth = $this->currentDate->format('j');

        $advancedToNewMonth = false;

        // If we got a byDay or getMonthDay filter, we must first expand
        // further.
        if ($this->byDay || $this->byMonthDay) {

            while (true) {

                $occurrences = $this->getMonthlyOccurrences();

                foreach ($occurrences as $occurrence) {

                    // The first occurrence that's higher than the current
                    // day of the month wins.
                    // If we advanced to the next month or year, the first
                    // occurrence is always correct.
                    if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
                        break 2;
                    }

                }

                // If we made it here, it means we need to advance to
                // the next month or year.
                $currentDayOfMonth = 1;
                $advancedToNewMonth = true;
                do {

                    $currentMonth++;
                    if ($currentMonth > 12) {
                        $currentYear += $this->interval;
                        $currentMonth = 1;
                    }
                } while (!in_array($currentMonth, $this->byMonth));

                $this->currentDate = $this->currentDate->setDate(
                    (int)$currentYear,
                    (int)$currentMonth,
                    (int)$currentDayOfMonth
                );

            }

            // If we made it here, it means we got a valid occurrence
            $this->currentDate = $this->currentDate->setDate(
                (int)$currentYear,
                (int)$currentMonth,
                (int)$occurrence
            );
            return;

        } else {

            // These are the 'byMonth' rules, if there are no byDay or
            // byMonthDay sub-rules.
            do {

                $currentMonth++;
                if ($currentMonth > 12) {
                    $currentYear += $this->interval;
                    $currentMonth = 1;
                }
            } while (!in_array($currentMonth, $this->byMonth));
            $this->currentDate = $this->currentDate->setDate(
                (int)$currentYear,
                (int)$currentMonth,
                (int)$currentDayOfMonth
            );

            return;

        }

    }

    /* }}} */

    /**
     * This method receives a string from an RRULE property, and populates this
     * class with all the values.
     *
     * @param string|array $rrule
     *
     * @return void
     */
    protected function parseRRule($rrule) {

        if (is_string($rrule)) {
            $rrule = Property\ICalendar\Recur::stringToArray($rrule);
        }

        foreach ($rrule as $key => $value) {

            $key = strtoupper($key);
            switch ($key) {

                case 'FREQ' :
                    $value = strtolower($value);
                    if (!in_array(
                        $value,
                        ['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
                    )) {
                        throw new InvalidDataException('Unknown value for FREQ=' . strtoupper($value));
                    }
                    $this->frequency = $value;
                    break;

                case 'UNTIL' :
                    $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());

                    // In some cases events are generated with an UNTIL=
                    // parameter before the actual start of the event.
                    //
                    // Not sure why this is happening. We assume that the
                    // intention was that the event only recurs once.
                    //
                    // So we are modifying the parameter so our code doesn't
                    // break.
                    if ($this->until < $this->startDate) {
                        $this->until = $this->startDate;
                    }
                    break;

                case 'INTERVAL' :
                    // No break

                case 'COUNT' :
                    $val = (int)$value;
                    if ($val < 1) {
                        throw new InvalidDataException(strtoupper($key) . ' in RRULE must be a positive integer!');
                    }
                    $key = strtolower($key);
                    $this->$key = $val;
                    break;

                case 'BYSECOND' :
                    $this->bySecond = (array)$value;
                    break;

                case 'BYMINUTE' :
                    $this->byMinute = (array)$value;
                    break;

                case 'BYHOUR' :
                    $this->byHour = (array)$value;
                    break;

                case 'BYDAY' :
                    $value = (array)$value;
                    foreach ($value as $part) {
                        if (!preg_match('#^  (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
                            throw new InvalidDataException('Invalid part in BYDAY clause: ' . $part);
                        }
                    }
                    $this->byDay = $value;
                    break;

                case 'BYMONTHDAY' :
                    $this->byMonthDay = (array)$value;
                    break;

                case 'BYYEARDAY' :
                    $this->byYearDay = (array)$value;
                    foreach ($this->byYearDay as $byYearDay) {
                        if (!is_numeric($byYearDay) || (int)$byYearDay < -366 || (int)$byYearDay == 0 || (int)$byYearDay > 366) {
                            throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
                        }
                    }
                    break;

                case 'BYWEEKNO' :
                    $this->byWeekNo = (array)$value;
                    foreach ($this->byWeekNo as $byWeekNo) {
                        if (!is_numeric($byWeekNo) || (int)$byWeekNo < -53 || (int)$byWeekNo == 0 || (int)$byWeekNo > 53) {
                            throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
                        }
                    }
                    break;

                case 'BYMONTH' :
                    $this->byMonth = (array)$value;
                    foreach ($this->byMonth as $byMonth) {
                        if (!is_numeric($byMonth) || (int)$byMonth < 1 || (int)$byMonth > 12) {
                            throw new InvalidDataException('BYMONTH in RRULE must have value(s) betweeen 1 and 12!');
                        }
                    }
                    break;

                case 'BYSETPOS' :
                    $this->bySetPos = (array)$value;
                    break;

                case 'WKST' :
                    $this->weekStart = strtoupper($value);
                    break;

                default:
                    throw new InvalidDataException('Not supported: ' . strtoupper($key));

            }

        }

    }

    /**
     * Mappings between the day number and english day name.
     *
     * @var array
     */
    protected $dayNames = [
        0 => 'Sunday',
        1 => 'Monday',
        2 => 'Tuesday',
        3 => 'Wednesday',
        4 => 'Thursday',
        5 => 'Friday',
        6 => 'Saturday',
    ];

    /**
     * Returns all the occurrences for a monthly frequency with a 'byDay' or
     * 'byMonthDay' expansion for the current month.
     *
     * The returned list is an array of integers with the day of month (1-31).
     *
     * @return array
     */
    protected function getMonthlyOccurrences() {

        $startDate = clone $this->currentDate;

        $byDayResults = [];

        // Our strategy is to simply go through the byDays, advance the date to
        // that point and add it to the results.
        if ($this->byDay) foreach ($this->byDay as $day) {

            $dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];


            // Dayname will be something like 'wednesday'. Now we need to find
            // all wednesdays in this month.
            $dayHits = [];

            // workaround for missing 'first day of the month' support in hhvm
            $checkDate = new \DateTime($startDate->format('Y-m-1'));
            // workaround modify always advancing the date even if the current day is a $dayName in hhvm
            if ($checkDate->format('l') !== $dayName) {
                $checkDate = $checkDate->modify($dayName);
            }

            do {
                $dayHits[] = $checkDate->format('j');
                $checkDate = $checkDate->modify('next ' . $dayName);
            } while ($checkDate->format('n') === $startDate->format('n'));

            // So now we have 'all wednesdays' for month. It is however
            // possible that the user only really wanted the 1st, 2nd or last
            // wednesday.
            if (strlen($day) > 2) {
                $offset = (int)substr($day, 0, -2);

                if ($offset > 0) {
                    // It is possible that the day does not exist, such as a
                    // 5th or 6th wednesday of the month.
                    if (isset($dayHits[$offset - 1])) {
                        $byDayResults[] = $dayHits[$offset - 1];
                    }
                } else {

                    // if it was negative we count from the end of the array
                    // might not exist, fx. -5th tuesday
                    if (isset($dayHits[count($dayHits) + $offset])) {
                        $byDayResults[] = $dayHits[count($dayHits) + $offset];
                    }
                }
            } else {
                // There was no counter (first, second, last wednesdays), so we
                // just need to add the all to the list).
                $byDayResults = array_merge($byDayResults, $dayHits);

            }

        }

        $byMonthDayResults = [];
        if ($this->byMonthDay) foreach ($this->byMonthDay as $monthDay) {

            // Removing values that are out of range for this month
            if ($monthDay > $startDate->format('t') ||
                $monthDay < 0 - $startDate->format('t')) {
                    continue;
            }
            if ($monthDay > 0) {
                $byMonthDayResults[] = $monthDay;
            } else {
                // Negative values
                $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
            }
        }

        // If there was just byDay or just byMonthDay, they just specify our
        // (almost) final list. If both were provided, then byDay limits the
        // list.
        if ($this->byMonthDay && $this->byDay) {
            $result = array_intersect($byMonthDayResults, $byDayResults);
        } elseif ($this->byMonthDay) {
            $result = $byMonthDayResults;
        } else {
            $result = $byDayResults;
        }
        $result = array_unique($result);
        sort($result, SORT_NUMERIC);

        // The last thing that needs checking is the BYSETPOS. If it's set, it
        // means only certain items in the set survive the filter.
        if (!$this->bySetPos) {
            return $result;
        }

        $filteredResult = [];
        foreach ($this->bySetPos as $setPos) {

            if ($setPos < 0) {
                $setPos = count($result) + ($setPos + 1);
            }
            if (isset($result[$setPos - 1])) {
                $filteredResult[] = $result[$setPos - 1];
            }
        }

        sort($filteredResult, SORT_NUMERIC);
        return $filteredResult;

    }

    /**
     * Simple mapping from iCalendar day names to day numbers.
     *
     * @var array
     */
    protected $dayMap = [
        'SU' => 0,
        'MO' => 1,
        'TU' => 2,
        'WE' => 3,
        'TH' => 4,
        'FR' => 5,
        'SA' => 6,
    ];

    protected function getHours() {

        $recurrenceHours = [];
        foreach ($this->byHour as $byHour) {
            $recurrenceHours[] = $byHour;
        }

        return $recurrenceHours;
    }

    protected function getDays() {

        $recurrenceDays = [];
        foreach ($this->byDay as $byDay) {

            // The day may be preceeded with a positive (+n) or
            // negative (-n) integer. However, this does not make
            // sense in 'weekly' so we ignore it here.
            $recurrenceDays[] = $this->dayMap[substr($byDay, -2)];

        }

        return $recurrenceDays;
    }

    protected function getMonths() {

        $recurrenceMonths = [];
        foreach ($this->byMonth as $byMonth) {
            $recurrenceMonths[] = $byMonth;
        }

        return $recurrenceMonths;
    }
}
<?php

namespace Sabre\VObject;

/**
 * This class provides a list of global defaults for vobject.
 *
 * Some of these started to appear in various classes, so it made a bit more
 * sense to centralize them, so it's easier for user to find and change these.
 *
 * The global nature of them does mean that changing the settings for one
 * instance has a global influence.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Settings {

    /**
     * The minimum date we accept for various calculations with dates, such as
     * recurrences.
     *
     * The choice of 1900 is pretty arbitrary, but it covers most common
     * use-cases. In particular, it covers birthdates for virtually everyone
     * alive on earth, which is less than 5 people at the time of writing.
     */
    static $minDate = '1900-01-01';

    /**
     * The maximum date we accept for various calculations with dates, such as
     * recurrences.
     *
     * The choice of 2100 is pretty arbitrary, but should cover most
     * appointments made for many years to come.
     */
    static $maxDate = '2100-01-01';

    /**
     * The maximum number of recurrences that will be generated.
     *
     * This setting limits the maximum of recurring events that this library
     * generates in its recurrence iterators.
     *
     * This is a security measure. Without this, it would be possible to craft
     * specific events that recur many, many times, potentially DDOSing the
     * server.
     *
     * The default (3500) allows creation of a dialy event that goes on for 10
     * years, which is hopefully long enough for most.
     *
     * Set this value to -1 to disable this control altogether.
     */
    static $maxRecurrences = 3500;

}
<?php

namespace Sabre\VObject\Splitter;

use Sabre\VObject;
use Sabre\VObject\Component\VCalendar;

/**
 * Splitter.
 *
 * This class is responsible for splitting up iCalendar objects.
 *
 * This class expects a single VCALENDAR object with one or more
 * calendar-objects inside. Objects with identical UID's will be combined into
 * a single object.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Dominik Tobschall (http://tobschall.de/)
 * @author Armin Hackmann
 * @license http://sabre.io/license/ Modified BSD License
 */
class ICalendar implements SplitterInterface {

    /**
     * Timezones.
     *
     * @var array
     */
    protected $vtimezones = [];

    /**
     * iCalendar objects.
     *
     * @var array
     */
    protected $objects = [];

    /**
     * Constructor.
     *
     * The splitter should receive an readable file stream as it's input.
     *
     * @param resource $input
     * @param int $options Parser options, see the OPTIONS constants.
     */
    function __construct($input, $options = 0) {

        $data = VObject\Reader::read($input, $options);

        if (!$data instanceof VObject\Component\VCalendar) {
            throw new VObject\ParseException('Supplied input could not be parsed as VCALENDAR.');
        }

        foreach ($data->children() as $component) {
            if (!$component instanceof VObject\Component) {
                continue;
            }

            // Get all timezones
            if ($component->name === 'VTIMEZONE') {
                $this->vtimezones[(string)$component->TZID] = $component;
                continue;
            }

            // Get component UID for recurring Events search
            if (!$component->UID) {
                $component->UID = sha1(microtime()) . '-vobjectimport';
            }
            $uid = (string)$component->UID;

            // Take care of recurring events
            if (!array_key_exists($uid, $this->objects)) {
                $this->objects[$uid] = new VCalendar();
            }

            $this->objects[$uid]->add(clone $component);
        }

    }

    /**
     * Every time getNext() is called, a new object will be parsed, until we
     * hit the end of the stream.
     *
     * When the end is reached, null will be returned.
     *
     * @return Sabre\VObject\Component|null
     */
    function getNext() {

        if ($object = array_shift($this->objects)) {

            // create our baseobject
            $object->version = '2.0';
            $object->prodid = '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN';
            $object->calscale = 'GREGORIAN';

            // add vtimezone information to obj (if we have it)
            foreach ($this->vtimezones as $vtimezone) {
                $object->add($vtimezone);
            }

            return $object;

        } else {

            return;

        }

    }

}
<?php

namespace Sabre\VObject\Splitter;

/**
 * VObject splitter.
 *
 * The splitter is responsible for reading a large vCard or iCalendar object,
 * and splitting it into multiple objects.
 *
 * This is for example for Card and CalDAV, which require every event and vcard
 * to exist in their own objects, instead of one large one.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Dominik Tobschall (http://tobschall.de/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface SplitterInterface {

    /**
     * Constructor.
     *
     * The splitter should receive an readable file stream as it's input.
     *
     * @param resource $input
     */
    function __construct($input);

    /**
     * Every time getNext() is called, a new object will be parsed, until we
     * hit the end of the stream.
     *
     * When the end is reached, null will be returned.
     *
     * @return Sabre\VObject\Component|null
     */
    function getNext();

}
<?php

namespace Sabre\VObject\Splitter;

use Sabre\VObject;
use Sabre\VObject\Parser\MimeDir;

/**
 * Splitter.
 *
 * This class is responsible for splitting up VCard objects.
 *
 * It is assumed that the input stream contains 1 or more VCARD objects. This
 * class checks for BEGIN:VCARD and END:VCARD and parses each encountered
 * component individually.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Dominik Tobschall (http://tobschall.de/)
 * @author Armin Hackmann
 * @license http://sabre.io/license/ Modified BSD License
 */
class VCard implements SplitterInterface {

    /**
     * File handle.
     *
     * @var resource
     */
    protected $input;

    /**
     * Persistent parser.
     *
     * @var MimeDir
     */
    protected $parser;

    /**
     * Constructor.
     *
     * The splitter should receive an readable file stream as it's input.
     *
     * @param resource $input
     * @param int $options Parser options, see the OPTIONS constants.
     */
    function __construct($input, $options = 0) {

        $this->input = $input;
        $this->parser = new MimeDir($input, $options);

    }

    /**
     * Every time getNext() is called, a new object will be parsed, until we
     * hit the end of the stream.
     *
     * When the end is reached, null will be returned.
     *
     * @return Sabre\VObject\Component|null
     */
    function getNext() {

        try {
            $object = $this->parser->parse();

            if (!$object instanceof VObject\Component\VCard) {
                throw new VObject\ParseException('The supplied input contained non-VCARD data.');
            }

        } catch (VObject\EofException $e) {
            return;
        }

        return $object;

    }

}
<?php

namespace Sabre\VObject;

/**
 * Useful utilities for working with various strings.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class StringUtil {

    /**
     * Returns true or false depending on if a string is valid UTF-8.
     *
     * @param string $str
     *
     * @return bool
     */
    static function isUTF8($str) {

        // Control characters
        if (preg_match('%[\x00-\x08\x0B-\x0C\x0E\x0F]%', $str)) {
            return false;
        }

        return (bool)preg_match('%%u', $str);

    }

    /**
     * This method tries its best to convert the input string to UTF-8.
     *
     * Currently only ISO-5991-1 input and UTF-8 input is supported, but this
     * may be expanded upon if we receive other examples.
     *
     * @param string $str
     *
     * @return string
     */
    static function convertToUTF8($str) {

        $encoding = mb_detect_encoding($str, ['UTF-8', 'ISO-8859-1', 'WINDOWS-1252'], true);

        switch ($encoding) {
            case 'ISO-8859-1' :
                $newStr = utf8_encode($str);
                break;
            /* Unreachable code. Not sure yet how we can improve this
             * situation.
            case 'WINDOWS-1252' :
                $newStr = iconv('cp1252', 'UTF-8', $str);
                break;
             */
            default :
                 $newStr = $str;

        }

        // Removing any control characters
        return (preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', '', $newStr));

    }

}
<?php

/**
 * Microsoft exchange timezones
 * Source:
 * http://msdn.microsoft.com/en-us/library/ms988620%28v=exchg.65%29.aspx.
 *
 * Correct timezones deduced with help from:
 * http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
return [
    'Universal Coordinated Time'                               => 'UTC',
    'Casablanca, Monrovia'                                     => 'Africa/Casablanca',
    'Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London'   => 'Europe/Lisbon',
    'Greenwich Mean Time; Dublin, Edinburgh, London'           => 'Europe/London',
    'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna'         => 'Europe/Berlin',
    'Belgrade, Pozsony, Budapest, Ljubljana, Prague'           => 'Europe/Prague',
    'Brussels, Copenhagen, Madrid, Paris'                      => 'Europe/Paris',
    'Paris, Madrid, Brussels, Copenhagen'                      => 'Europe/Paris',
    'Prague, Central Europe'                                   => 'Europe/Prague',
    'Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb'        => 'Europe/Sarajevo',
    'West Central Africa'                                      => 'Africa/Luanda', // This was a best guess
    'Athens, Istanbul, Minsk'                                  => 'Europe/Athens',
    'Bucharest'                                                => 'Europe/Bucharest',
    'Cairo'                                                    => 'Africa/Cairo',
    'Harare, Pretoria'                                         => 'Africa/Harare',
    'Helsinki, Riga, Tallinn'                                  => 'Europe/Helsinki',
    'Israel, Jerusalem Standard Time'                          => 'Asia/Jerusalem',
    'Baghdad'                                                  => 'Asia/Baghdad',
    'Arab, Kuwait, Riyadh'                                     => 'Asia/Kuwait',
    'Moscow, St. Petersburg, Volgograd'                        => 'Europe/Moscow',
    'East Africa, Nairobi'                                     => 'Africa/Nairobi',
    'Tehran'                                                   => 'Asia/Tehran',
    'Abu Dhabi, Muscat'                                        => 'Asia/Muscat', // Best guess
    'Baku, Tbilisi, Yerevan'                                   => 'Asia/Baku',
    'Kabul'                                                    => 'Asia/Kabul',
    'Ekaterinburg'                                             => 'Asia/Yekaterinburg',
    'Islamabad, Karachi, Tashkent'                             => 'Asia/Karachi',
    'Kolkata, Chennai, Mumbai, New Delhi, India Standard Time' => 'Asia/Calcutta',
    'Kathmandu, Nepal'                                         => 'Asia/Kathmandu',
    'Almaty, Novosibirsk, North Central Asia'                  => 'Asia/Almaty',
    'Astana, Dhaka'                                            => 'Asia/Dhaka',
    'Sri Jayawardenepura, Sri Lanka'                           => 'Asia/Colombo',
    'Rangoon'                                                  => 'Asia/Rangoon',
    'Bangkok, Hanoi, Jakarta'                                  => 'Asia/Bangkok',
    'Krasnoyarsk'                                              => 'Asia/Krasnoyarsk',
    'Beijing, Chongqing, Hong Kong SAR, Urumqi'                => 'Asia/Shanghai',
    'Irkutsk, Ulaan Bataar'                                    => 'Asia/Irkutsk',
    'Kuala Lumpur, Singapore'                                  => 'Asia/Singapore',
    'Perth, Western Australia'                                 => 'Australia/Perth',
    'Taipei'                                                   => 'Asia/Taipei',
    'Osaka, Sapporo, Tokyo'                                    => 'Asia/Tokyo',
    'Seoul, Korea Standard time'                               => 'Asia/Seoul',
    'Yakutsk'                                                  => 'Asia/Yakutsk',
    'Adelaide, Central Australia'                              => 'Australia/Adelaide',
    'Darwin'                                                   => 'Australia/Darwin',
    'Brisbane, East Australia'                                 => 'Australia/Brisbane',
    'Canberra, Melbourne, Sydney, Hobart (year 2000 only)'     => 'Australia/Sydney',
    'Guam, Port Moresby'                                       => 'Pacific/Guam',
    'Hobart, Tasmania'                                         => 'Australia/Hobart',
    'Vladivostok'                                              => 'Asia/Vladivostok',
    'Magadan, Solomon Is., New Caledonia'                      => 'Asia/Magadan',
    'Auckland, Wellington'                                     => 'Pacific/Auckland',
    'Fiji Islands, Kamchatka, Marshall Is.'                    => 'Pacific/Fiji',
    'Nuku\'alofa, Tonga'                                       => 'Pacific/Tongatapu',
    'Azores'                                                   => 'Atlantic/Azores',
    'Cape Verde Is.'                                           => 'Atlantic/Cape_Verde',
    'Mid-Atlantic'                                             => 'America/Noronha',
    'Brasilia'                                                 => 'America/Sao_Paulo', // Best guess
    'Buenos Aires'                                             => 'America/Argentina/Buenos_Aires',
    'Greenland'                                                => 'America/Godthab',
    'Newfoundland'                                             => 'America/St_Johns',
    'Atlantic Time (Canada)'                                   => 'America/Halifax',
    'Caracas, La Paz'                                          => 'America/Caracas',
    'Santiago'                                                 => 'America/Santiago',
    'Bogota, Lima, Quito'                                      => 'America/Bogota',
    'Eastern Time (US & Canada)'                               => 'America/New_York',
    'Indiana (East)'                                           => 'America/Indiana/Indianapolis',
    'Central America'                                          => 'America/Guatemala',
    'Central Time (US & Canada)'                               => 'America/Chicago',
    'Mexico City, Tegucigalpa'                                 => 'America/Mexico_City',
    'Saskatchewan'                                             => 'America/Edmonton',
    'Arizona'                                                  => 'America/Phoenix',
    'Mountain Time (US & Canada)'                              => 'America/Denver', // Best guess
    'Pacific Time (US & Canada); Tijuana'                      => 'America/Los_Angeles', // Best guess
    'Alaska'                                                   => 'America/Anchorage',
    'Hawaii'                                                   => 'Pacific/Honolulu',
    'Midway Island, Samoa'                                     => 'Pacific/Midway',
    'Eniwetok, Kwajalein, Dateline Time'                       => 'Pacific/Kwajalein',
];
<?php

/**
 * The following list are timezone names that could be generated by
 * Lotus / Domino.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
return [
    'Dateline'               => 'Etc/GMT-12',
    'Samoa'                  => 'Pacific/Apia',
    'Hawaiian'               => 'Pacific/Honolulu',
    'Alaskan'                => 'America/Anchorage',
    'Pacific'                => 'America/Los_Angeles',
    'Pacific Standard Time'  => 'America/Los_Angeles',
    'Mexico Standard Time 2' => 'America/Chihuahua',
    'Mountain'               => 'America/Denver',
    // 'Mountain Standard Time' => 'America/Chihuahua', // conflict with windows timezones.
    'US Mountain'     => 'America/Phoenix',
    'Canada Central'  => 'America/Edmonton',
    'Central America' => 'America/Guatemala',
    'Central'         => 'America/Chicago',
    // 'Central Standard Time'  => 'America/Mexico_City', // conflict with windows timezones.
    'Mexico'            => 'America/Mexico_City',
    'Eastern'           => 'America/New_York',
    'SA Pacific'        => 'America/Bogota',
    'US Eastern'        => 'America/Indiana/Indianapolis',
    'Venezuela'         => 'America/Caracas',
    'Atlantic'          => 'America/Halifax',
    'Central Brazilian' => 'America/Manaus',
    'Pacific SA'        => 'America/Santiago',
    'SA Western'        => 'America/La_Paz',
    'Newfoundland'      => 'America/St_Johns',
    'Argentina'         => 'America/Argentina/Buenos_Aires',
    'E. South America'  => 'America/Belem',
    'Greenland'         => 'America/Godthab',
    'Montevideo'        => 'America/Montevideo',
    'SA Eastern'        => 'America/Belem',
    // 'Mid-Atlantic'           => 'Etc/GMT-2', // conflict with windows timezones.
    'Azores'            => 'Atlantic/Azores',
    'Cape Verde'        => 'Atlantic/Cape_Verde',
    'Greenwich'         => 'Atlantic/Reykjavik', // No I'm serious.. Greenwich is not GMT.
    'Morocco'           => 'Africa/Casablanca',
    'Central Europe'    => 'Europe/Prague',
    'Central European'  => 'Europe/Sarajevo',
    'Romance'           => 'Europe/Paris',
    'W. Central Africa' => 'Africa/Lagos', // Best guess
    'W. Europe'         => 'Europe/Amsterdam',
    'E. Europe'         => 'Europe/Minsk',
    'Egypt'             => 'Africa/Cairo',
    'FLE'               => 'Europe/Helsinki',
    'GTB'               => 'Europe/Athens',
    'Israel'            => 'Asia/Jerusalem',
    'Jordan'            => 'Asia/Amman',
    'Middle East'       => 'Asia/Beirut',
    'Namibia'           => 'Africa/Windhoek',
    'South Africa'      => 'Africa/Harare',
    'Arab'              => 'Asia/Kuwait',
    'Arabic'            => 'Asia/Baghdad',
    'E. Africa'         => 'Africa/Nairobi',
    'Georgian'          => 'Asia/Tbilisi',
    'Russian'           => 'Europe/Moscow',
    'Iran'              => 'Asia/Tehran',
    'Arabian'           => 'Asia/Muscat',
    'Armenian'          => 'Asia/Yerevan',
    'Azerbijan'         => 'Asia/Baku',
    'Caucasus'          => 'Asia/Yerevan',
    'Mauritius'         => 'Indian/Mauritius',
    'Afghanistan'       => 'Asia/Kabul',
    'Ekaterinburg'      => 'Asia/Yekaterinburg',
    'Pakistan'          => 'Asia/Karachi',
    'West Asia'         => 'Asia/Tashkent',
    'India'             => 'Asia/Calcutta',
    'Sri Lanka'         => 'Asia/Colombo',
    'Nepal'             => 'Asia/Kathmandu',
    'Central Asia'      => 'Asia/Dhaka',
    'N. Central Asia'   => 'Asia/Almaty',
    'Myanmar'           => 'Asia/Rangoon',
    'North Asia'        => 'Asia/Krasnoyarsk',
    'SE Asia'           => 'Asia/Bangkok',
    'China'             => 'Asia/Shanghai',
    'North Asia East'   => 'Asia/Irkutsk',
    'Singapore'         => 'Asia/Singapore',
    'Taipei'            => 'Asia/Taipei',
    'W. Australia'      => 'Australia/Perth',
    'Korea'             => 'Asia/Seoul',
    'Tokyo'             => 'Asia/Tokyo',
    'Yakutsk'           => 'Asia/Yakutsk',
    'AUS Central'       => 'Australia/Darwin',
    'Cen. Australia'    => 'Australia/Adelaide',
    'AUS Eastern'       => 'Australia/Sydney',
    'E. Australia'      => 'Australia/Brisbane',
    'Tasmania'          => 'Australia/Hobart',
    'Vladivostok'       => 'Asia/Vladivostok',
    'West Pacific'      => 'Pacific/Guam',
    'Central Pacific'   => 'Asia/Magadan',
    'Fiji'              => 'Pacific/Fiji',
    'New Zealand'       => 'Pacific/Auckland',
    'Tonga'             => 'Pacific/Tongatapu',
];
<?php

/**
 * A list of additional PHP timezones that are returned by
 * DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC)
 * valid for new DateTimeZone().
 *
 * This list does not include those timezone identifiers that we have to map to
 * a different identifier for some PHP versions (see php-workaround.php).
 *
 * Instead of using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC)
 * directly, we use this file because DateTimeZone::ALL_WITH_BC is not properly
 * supported by all PHP version and HHVM.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
return [
    'Africa/Asmera',
    'Africa/Timbuktu',
    'America/Argentina/ComodRivadavia',
    'America/Atka',
    'America/Buenos_Aires',
    'America/Catamarca',
    'America/Coral_Harbour',
    'America/Cordoba',
    'America/Ensenada',
    'America/Fort_Wayne',
    'America/Indianapolis',
    'America/Jujuy',
    'America/Knox_IN',
    'America/Louisville',
    'America/Mendoza',
    'America/Montreal',
    'America/Porto_Acre',
    'America/Rosario',
    'America/Shiprock',
    'America/Virgin',
    'Antarctica/South_Pole',
    'Asia/Ashkhabad',
    'Asia/Calcutta',
    'Asia/Chungking',
    'Asia/Dacca',
    'Asia/Istanbul',
    'Asia/Katmandu',
    'Asia/Macao',
    'Asia/Saigon',
    'Asia/Tel_Aviv',
    'Asia/Thimbu',
    'Asia/Ujung_Pandang',
    'Asia/Ulan_Bator',
    'Atlantic/Faeroe',
    'Atlantic/Jan_Mayen',
    'Australia/ACT',
    'Australia/Canberra',
    'Australia/LHI',
    'Australia/North',
    'Australia/NSW',
    'Australia/Queensland',
    'Australia/South',
    'Australia/Tasmania',
    'Australia/Victoria',
    'Australia/West',
    'Australia/Yancowinna',
    'Brazil/Acre',
    'Brazil/DeNoronha',
    'Brazil/East',
    'Brazil/West',
    'Canada/Atlantic',
    'Canada/Central',
    'Canada/East-Saskatchewan',
    'Canada/Eastern',
    'Canada/Mountain',
    'Canada/Newfoundland',
    'Canada/Pacific',
    'Canada/Saskatchewan',
    'Canada/Yukon',
    'CET',
    'Chile/Continental',
    'Chile/EasterIsland',
    'EET',
    'EST',
    'Etc/GMT',
    'Etc/GMT+0',
    'Etc/GMT+1',
    'Etc/GMT+10',
    'Etc/GMT+11',
    'Etc/GMT+12',
    'Etc/GMT+2',
    'Etc/GMT+3',
    'Etc/GMT+4',
    'Etc/GMT+5',
    'Etc/GMT+6',
    'Etc/GMT+7',
    'Etc/GMT+8',
    'Etc/GMT+9',
    'Etc/GMT-0',
    'Etc/GMT-1',
    'Etc/GMT-10',
    'Etc/GMT-11',
    'Etc/GMT-12',
    'Etc/GMT-13',
    'Etc/GMT-14',
    'Etc/GMT-2',
    'Etc/GMT-3',
    'Etc/GMT-4',
    'Etc/GMT-5',
    'Etc/GMT-6',
    'Etc/GMT-7',
    'Etc/GMT-8',
    'Etc/GMT-9',
    'Etc/GMT0',
    'Etc/Greenwich',
    'Etc/UCT',
    'Etc/Universal',
    'Etc/UTC',
    'Etc/Zulu',
    'Europe/Belfast',
    'Europe/Nicosia',
    'Europe/Tiraspol',
    'GB',
    'GMT',
    'GMT+0',
    'GMT-0',
    'HST',
    'MET',
    'Mexico/BajaNorte',
    'Mexico/BajaSur',
    'Mexico/General',
    'MST',
    'NZ',
    'Pacific/Ponape',
    'Pacific/Samoa',
    'Pacific/Truk',
    'Pacific/Yap',
    'PRC',
    'ROC',
    'ROK',
    'UCT',
    'US/Alaska',
    'US/Aleutian',
    'US/Arizona',
    'US/Central',
    'US/East-Indiana',
    'US/Eastern',
    'US/Hawaii',
    'US/Indiana-Starke',
    'US/Michigan',
    'US/Mountain',
    'US/Pacific',
    'US/Pacific-New',
    'US/Samoa',
    'WET',
];
<?php

/**
 * A list of PHP timezones that were supported until 5.5.9, removed in
 * PHP 5.5.10 and re-introduced in PHP 5.5.17.
 *
 * DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) returns them,
 * but they are invalid for new DateTimeZone(). Fixed in PHP 5.5.17.
 * https://bugs.php.net/bug.php?id=66985
 *
 * Some more info here:
 * http://evertpot.com/php-5-5-10-timezone-changes/
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
return [
    'CST6CDT'   => 'America/Chicago',
    'Cuba'      => 'America/Havana',
    'Egypt'     => 'Africa/Cairo',
    'Eire'      => 'Europe/Dublin',
    'EST5EDT'   => 'America/New_York',
    'Factory'   => 'UTC',
    'GB-Eire'   => 'Europe/London',
    'GMT0'      => 'UTC',
    'Greenwich' => 'UTC',
    'Hongkong'  => 'Asia/Hong_Kong',
    'Iceland'   => 'Atlantic/Reykjavik',
    'Iran'      => 'Asia/Tehran',
    'Israel'    => 'Asia/Jerusalem',
    'Jamaica'   => 'America/Jamaica',
    'Japan'     => 'Asia/Tokyo',
    'Kwajalein' => 'Pacific/Kwajalein',
    'Libya'     => 'Africa/Tripoli',
    'MST7MDT'   => 'America/Denver',
    'Navajo'    => 'America/Denver',
    'NZ-CHAT'   => 'Pacific/Chatham',
    'Poland'    => 'Europe/Warsaw',
    'Portugal'  => 'Europe/Lisbon',
    'PST8PDT'   => 'America/Los_Angeles',
    'Singapore' => 'Asia/Singapore',
    'Turkey'    => 'Europe/Istanbul',
    'Universal' => 'UTC',
    'W-SU'      => 'Europe/Moscow',
    'Zulu'      => 'UTC',
];
<?php

/**
 * Automatically generated timezone file
 *
 * Last update: 2016-08-24T17:35:38-04:00
 * Source: http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml
 *
 * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/).
 * @license http://sabre.io/license/ Modified BSD License
 */

return  [
  'AUS Central Standard Time'       => 'Australia/Darwin',
  'AUS Eastern Standard Time'       => 'Australia/Sydney',
  'Afghanistan Standard Time'       => 'Asia/Kabul',
  'Alaskan Standard Time'           => 'America/Anchorage',
  'Aleutian Standard Time'          => 'America/Adak',
  'Altai Standard Time'             => 'Asia/Barnaul',
  'Arab Standard Time'              => 'Asia/Riyadh',
  'Arabian Standard Time'           => 'Asia/Dubai',
  'Arabic Standard Time'            => 'Asia/Baghdad',
  'Argentina Standard Time'         => 'America/Buenos_Aires',
  'Astrakhan Standard Time'         => 'Europe/Astrakhan',
  'Atlantic Standard Time'          => 'America/Halifax',
  'Aus Central W. Standard Time'    => 'Australia/Eucla',
  'Azerbaijan Standard Time'        => 'Asia/Baku',
  'Azores Standard Time'            => 'Atlantic/Azores',
  'Bahia Standard Time'             => 'America/Bahia',
  'Bangladesh Standard Time'        => 'Asia/Dhaka',
  'Belarus Standard Time'           => 'Europe/Minsk',
  'Bougainville Standard Time'      => 'Pacific/Bougainville',
  'Canada Central Standard Time'    => 'America/Regina',
  'Cape Verde Standard Time'        => 'Atlantic/Cape_Verde',
  'Caucasus Standard Time'          => 'Asia/Yerevan',
  'Cen. Australia Standard Time'    => 'Australia/Adelaide',
  'Central America Standard Time'   => 'America/Guatemala',
  'Central Asia Standard Time'      => 'Asia/Almaty',
  'Central Brazilian Standard Time' => 'America/Cuiaba',
  'Central Europe Standard Time'    => 'Europe/Budapest',
  'Central European Standard Time'  => 'Europe/Warsaw',
  'Central Pacific Standard Time'   => 'Pacific/Guadalcanal',
  'Central Standard Time'           => 'America/Chicago',
  'Central Standard Time (Mexico)'  => 'America/Mexico_City',
  'Chatham Islands Standard Time'   => 'Pacific/Chatham',
  'China Standard Time'             => 'Asia/Shanghai',
  'Cuba Standard Time'              => 'America/Havana',
  'Dateline Standard Time'          => 'Etc/GMT+12',
  'E. Africa Standard Time'         => 'Africa/Nairobi',
  'E. Australia Standard Time'      => 'Australia/Brisbane',
  'E. Europe Standard Time'         => 'Europe/Chisinau',
  'E. South America Standard Time'  => 'America/Sao_Paulo',
  'Easter Island Standard Time'     => 'Pacific/Easter',
  'Eastern Standard Time'           => 'America/New_York',
  'Eastern Standard Time (Mexico)'  => 'America/Cancun',
  'Egypt Standard Time'             => 'Africa/Cairo',
  'Ekaterinburg Standard Time'      => 'Asia/Yekaterinburg',
  'FLE Standard Time'               => 'Europe/Kiev',
  'Fiji Standard Time'              => 'Pacific/Fiji',
  'GMT Standard Time'               => 'Europe/London',
  'GTB Standard Time'               => 'Europe/Bucharest',
  'Georgian Standard Time'          => 'Asia/Tbilisi',
  'Greenland Standard Time'         => 'America/Godthab',
  'Greenwich Standard Time'         => 'Atlantic/Reykjavik',
  'Haiti Standard Time'             => 'America/Port-au-Prince',
  'Hawaiian Standard Time'          => 'Pacific/Honolulu',
  'India Standard Time'             => 'Asia/Calcutta',
  'Iran Standard Time'              => 'Asia/Tehran',
  'Israel Standard Time'            => 'Asia/Jerusalem',
  'Jordan Standard Time'            => 'Asia/Amman',
  'Kaliningrad Standard Time'       => 'Europe/Kaliningrad',
  'Korea Standard Time'             => 'Asia/Seoul',
  'Libya Standard Time'             => 'Africa/Tripoli',
  'Line Islands Standard Time'      => 'Pacific/Kiritimati',
  'Lord Howe Standard Time'         => 'Australia/Lord_Howe',
  'Magadan Standard Time'           => 'Asia/Magadan',
  'Marquesas Standard Time'         => 'Pacific/Marquesas',
  'Mauritius Standard Time'         => 'Indian/Mauritius',
  'Middle East Standard Time'       => 'Asia/Beirut',
  'Montevideo Standard Time'        => 'America/Montevideo',
  'Morocco Standard Time'           => 'Africa/Casablanca',
  'Mountain Standard Time'          => 'America/Denver',
  'Mountain Standard Time (Mexico)' => 'America/Chihuahua',
  'Myanmar Standard Time'           => 'Asia/Rangoon',
  'N. Central Asia Standard Time'   => 'Asia/Novosibirsk',
  'Namibia Standard Time'           => 'Africa/Windhoek',
  'Nepal Standard Time'             => 'Asia/Katmandu',
  'New Zealand Standard Time'       => 'Pacific/Auckland',
  'Newfoundland Standard Time'      => 'America/St_Johns',
  'Norfolk Standard Time'           => 'Pacific/Norfolk',
  'North Asia East Standard Time'   => 'Asia/Irkutsk',
  'North Asia Standard Time'        => 'Asia/Krasnoyarsk',
  'North Korea Standard Time'       => 'Asia/Pyongyang',
  'Pacific SA Standard Time'        => 'America/Santiago',
  'Pacific Standard Time'           => 'America/Los_Angeles',
  'Pacific Standard Time (Mexico)'  => 'America/Tijuana',
  'Pakistan Standard Time'          => 'Asia/Karachi',
  'Paraguay Standard Time'          => 'America/Asuncion',
  'Romance Standard Time'           => 'Europe/Paris',
  'Russia Time Zone 10'             => 'Asia/Srednekolymsk',
  'Russia Time Zone 11'             => 'Asia/Kamchatka',
  'Russia Time Zone 3'              => 'Europe/Samara',
  'Russian Standard Time'           => 'Europe/Moscow',
  'SA Eastern Standard Time'        => 'America/Cayenne',
  'SA Pacific Standard Time'        => 'America/Bogota',
  'SA Western Standard Time'        => 'America/La_Paz',
  'SE Asia Standard Time'           => 'Asia/Bangkok',
  'Saint Pierre Standard Time'      => 'America/Miquelon',
  'Sakhalin Standard Time'          => 'Asia/Sakhalin',
  'Samoa Standard Time'             => 'Pacific/Apia',
  'Singapore Standard Time'         => 'Asia/Singapore',
  'South Africa Standard Time'      => 'Africa/Johannesburg',
  'Sri Lanka Standard Time'         => 'Asia/Colombo',
  'Syria Standard Time'             => 'Asia/Damascus',
  'Taipei Standard Time'            => 'Asia/Taipei',
  'Tasmania Standard Time'          => 'Australia/Hobart',
  'Tocantins Standard Time'         => 'America/Araguaina',
  'Tokyo Standard Time'             => 'Asia/Tokyo',
  'Tomsk Standard Time'             => 'Asia/Tomsk',
  'Tonga Standard Time'             => 'Pacific/Tongatapu',
  'Transbaikal Standard Time'       => 'Asia/Chita',
  'Turkey Standard Time'            => 'Europe/Istanbul',
  'Turks And Caicos Standard Time'  => 'America/Grand_Turk',
  'US Eastern Standard Time'        => 'America/Indianapolis',
  'US Mountain Standard Time'       => 'America/Phoenix',
  'UTC'                             => 'Etc/GMT',
  'UTC+12'                          => 'Etc/GMT-12',
  'UTC-02'                          => 'Etc/GMT+2',
  'UTC-08'                          => 'Etc/GMT+8',
  'UTC-09'                          => 'Etc/GMT+9',
  'UTC-11'                          => 'Etc/GMT+11',
  'Ulaanbaatar Standard Time'       => 'Asia/Ulaanbaatar',
  'Venezuela Standard Time'         => 'America/Caracas',
  'Vladivostok Standard Time'       => 'Asia/Vladivostok',
  'W. Australia Standard Time'      => 'Australia/Perth',
  'W. Central Africa Standard Time' => 'Africa/Lagos',
  'W. Europe Standard Time'         => 'Europe/Berlin',
  'W. Mongolia Standard Time'       => 'Asia/Hovd',
  'West Asia Standard Time'         => 'Asia/Tashkent',
  'West Bank Standard Time'         => 'Asia/Hebron',
  'West Pacific Standard Time'      => 'Pacific/Port_Moresby',
  'Yakutsk Standard Time'           => 'Asia/Yakutsk',
];
<?php

namespace Sabre\VObject;

/**
 * Time zone name translation.
 *
 * This file translates well-known time zone names into "Olson database" time zone names.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Frank Edelhaeuser (fedel@users.sourceforge.net)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class TimeZoneUtil {

    static $map = null;

    /**
     * List of microsoft exchange timezone ids.
     *
     * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
     */
    static $microsoftExchangeMap = [
        0  => 'UTC',
        31 => 'Africa/Casablanca',

        // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
        // I'm not even kidding.. We handle this special case in the
        // getTimeZone method.
        2  => 'Europe/Lisbon',
        1  => 'Europe/London',
        4  => 'Europe/Berlin',
        6  => 'Europe/Prague',
        3  => 'Europe/Paris',
        69 => 'Africa/Luanda', // This was a best guess
        7  => 'Europe/Athens',
        5  => 'Europe/Bucharest',
        49 => 'Africa/Cairo',
        50 => 'Africa/Harare',
        59 => 'Europe/Helsinki',
        27 => 'Asia/Jerusalem',
        26 => 'Asia/Baghdad',
        74 => 'Asia/Kuwait',
        51 => 'Europe/Moscow',
        56 => 'Africa/Nairobi',
        25 => 'Asia/Tehran',
        24 => 'Asia/Muscat', // Best guess
        54 => 'Asia/Baku',
        48 => 'Asia/Kabul',
        58 => 'Asia/Yekaterinburg',
        47 => 'Asia/Karachi',
        23 => 'Asia/Calcutta',
        62 => 'Asia/Kathmandu',
        46 => 'Asia/Almaty',
        71 => 'Asia/Dhaka',
        66 => 'Asia/Colombo',
        61 => 'Asia/Rangoon',
        22 => 'Asia/Bangkok',
        64 => 'Asia/Krasnoyarsk',
        45 => 'Asia/Shanghai',
        63 => 'Asia/Irkutsk',
        21 => 'Asia/Singapore',
        73 => 'Australia/Perth',
        75 => 'Asia/Taipei',
        20 => 'Asia/Tokyo',
        72 => 'Asia/Seoul',
        70 => 'Asia/Yakutsk',
        19 => 'Australia/Adelaide',
        44 => 'Australia/Darwin',
        18 => 'Australia/Brisbane',
        76 => 'Australia/Sydney',
        43 => 'Pacific/Guam',
        42 => 'Australia/Hobart',
        68 => 'Asia/Vladivostok',
        41 => 'Asia/Magadan',
        17 => 'Pacific/Auckland',
        40 => 'Pacific/Fiji',
        67 => 'Pacific/Tongatapu',
        29 => 'Atlantic/Azores',
        53 => 'Atlantic/Cape_Verde',
        30 => 'America/Noronha',
         8 => 'America/Sao_Paulo', // Best guess
        32 => 'America/Argentina/Buenos_Aires',
        60 => 'America/Godthab',
        28 => 'America/St_Johns',
         9 => 'America/Halifax',
        33 => 'America/Caracas',
        65 => 'America/Santiago',
        35 => 'America/Bogota',
        10 => 'America/New_York',
        34 => 'America/Indiana/Indianapolis',
        55 => 'America/Guatemala',
        11 => 'America/Chicago',
        37 => 'America/Mexico_City',
        36 => 'America/Edmonton',
        38 => 'America/Phoenix',
        12 => 'America/Denver', // Best guess
        13 => 'America/Los_Angeles', // Best guess
        14 => 'America/Anchorage',
        15 => 'Pacific/Honolulu',
        16 => 'Pacific/Midway',
        39 => 'Pacific/Kwajalein',
    ];

    /**
     * This method will try to find out the correct timezone for an iCalendar
     * date-time value.
     *
     * You must pass the contents of the TZID parameter, as well as the full
     * calendar.
     *
     * If the lookup fails, this method will return the default PHP timezone
     * (as configured using date_default_timezone_set, or the date.timezone ini
     * setting).
     *
     * Alternatively, if $failIfUncertain is set to true, it will throw an
     * exception if we cannot accurately determine the timezone.
     *
     * @param string $tzid
     * @param Sabre\VObject\Component $vcalendar
     *
     * @return DateTimeZone
     */
    static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {

        // First we will just see if the tzid is a support timezone identifier.
        //
        // The only exception is if the timezone starts with (. This is to
        // handle cases where certain microsoft products generate timezone
        // identifiers that for instance look like:
        //
        // (GMT+01.00) Sarajevo/Warsaw/Zagreb
        //
        // Since PHP 5.5.10, the first bit will be used as the timezone and
        // this method will return just GMT+01:00. This is wrong, because it
        // doesn't take DST into account.
        if ($tzid[0] !== '(') {

            // PHP has a bug that logs PHP warnings even it shouldn't:
            // https://bugs.php.net/bug.php?id=67881
            //
            // That's why we're checking if we'll be able to successfull instantiate
            // \DateTimeZone() before doing so. Otherwise we could simply instantiate
            // and catch the exception.
            $tzIdentifiers = \DateTimeZone::listIdentifiers();

            try {
                if (
                    (in_array($tzid, $tzIdentifiers)) ||
                    (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) ||
                    (in_array($tzid, self::getIdentifiersBC()))
                ) {
                    return new \DateTimeZone($tzid);
                }
            } catch (\Exception $e) {
            }

        }

        self::loadTzMaps();

        // Next, we check if the tzid is somewhere in our tzid map.
        if (isset(self::$map[$tzid])) {
            return new \DateTimeZone(self::$map[$tzid]);
        }

        // Some Microsoft products prefix the offset first, so let's strip that off
        // and see if it is our tzid map.  We don't want to check for this first just
        // in case there are overrides in our tzid map.
        if (preg_match('/^\((UTC|GMT)(\+|\-)[\d]{2}\:[\d]{2}\) (.*)/', $tzid, $matches)) {
            $tzidAlternate = $matches[3];
            if (isset(self::$map[$tzidAlternate])) {
                return new \DateTimeZone(self::$map[$tzidAlternate]);
            }
        }

        // Maybe the author was hyper-lazy and just included an offset. We
        // support it, but we aren't happy about it.
        if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {

            // Note that the path in the source will never be taken from PHP 5.5.10
            // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it
            // already gets returned early in this function. Once we drop support
            // for versions under PHP 5.5.10, this bit can be taken out of the
            // source.
            // @codeCoverageIgnoreStart
            return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2], 0, 2), '0'));
            // @codeCoverageIgnoreEnd
        }

        if ($vcalendar) {

            // If that didn't work, we will scan VTIMEZONE objects
            foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) {

                if ((string)$vtimezone->TZID === $tzid) {

                    // Some clients add 'X-LIC-LOCATION' with the olson name.
                    if (isset($vtimezone->{'X-LIC-LOCATION'})) {

                        $lic = (string)$vtimezone->{'X-LIC-LOCATION'};

                        // Libical generators may specify strings like
                        // "SystemV/EST5EDT". For those we must remove the
                        // SystemV part.
                        if (substr($lic, 0, 8) === 'SystemV/') {
                            $lic = substr($lic, 8);
                        }

                        return self::getTimeZone($lic, null, $failIfUncertain);

                    }
                    // Microsoft may add a magic number, which we also have an
                    // answer for.
                    if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
                        $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();

                        // 2 can mean both Europe/Lisbon and Europe/Sarajevo.
                        if ($cdoId === 2 && strpos((string)$vtimezone->TZID, 'Sarajevo') !== false) {
                            return new \DateTimeZone('Europe/Sarajevo');
                        }

                        if (isset(self::$microsoftExchangeMap[$cdoId])) {
                            return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
                        }
                    }

                }

            }

        }

        if ($failIfUncertain) {
            throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid);
        }

        // If we got all the way here, we default to UTC.
        return new \DateTimeZone(date_default_timezone_get());

    }

    /**
     * This method will load in all the tz mapping information, if it's not yet
     * done.
     */
    static function loadTzMaps() {

        if (!is_null(self::$map)) return;

        self::$map = array_merge(
            include __DIR__ . '/timezonedata/windowszones.php',
            include __DIR__ . '/timezonedata/lotuszones.php',
            include __DIR__ . '/timezonedata/exchangezones.php',
            include __DIR__ . '/timezonedata/php-workaround.php'
        );

    }

    /**
     * This method returns an array of timezone identifiers, that are supported
     * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers().
     *
     * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because:
     * - It's not supported by some PHP versions as well as HHVM.
     * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions.
     * (See timezonedata/php-bc.php and timezonedata php-workaround.php)
     *
     * @return array
     */
    static function getIdentifiersBC() {
        return include __DIR__ . '/timezonedata/php-bc.php';
    }

}
<?php

namespace Sabre\VObject;

/**
 * UUID Utility.
 *
 * This class has static methods to generate and validate UUID's.
 * UUIDs are used a decent amount within various *DAV standards, so it made
 * sense to include it.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class UUIDUtil {

    /**
     * Returns a pseudo-random v4 UUID.
     *
     * This function is based on a comment by Andrew Moore on php.net
     *
     * @see http://www.php.net/manual/en/function.uniqid.php#94959
     *
     * @return string
     */
    static function getUUID() {

        return sprintf(

            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',

            // 32 bits for "time_low"
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),

            // 16 bits for "time_mid"
            mt_rand(0, 0xffff),

            // 16 bits for "time_hi_and_version",
            // four most significant bits holds version number 4
            mt_rand(0, 0x0fff) | 0x4000,

            // 16 bits, 8 bits for "clk_seq_hi_res",
            // 8 bits for "clk_seq_low",
            // two most significant bits holds zero and one for variant DCE1.1
            mt_rand(0, 0x3fff) | 0x8000,

            // 48 bits for "node"
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
        );
    }

    /**
     * Checks if a string is a valid UUID.
     *
     * @param string $uuid
     *
     * @return bool
     */
    static function validateUUID($uuid) {

        return preg_match(
            '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i',
            $uuid
        ) !== 0;

    }

}
<?php

namespace Sabre\VObject;

/**
 * This utility converts vcards from one version to another.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class VCardConverter {

    /**
     * Converts a vCard object to a new version.
     *
     * targetVersion must be one of:
     *   Document::VCARD21
     *   Document::VCARD30
     *   Document::VCARD40
     *
     * Currently only 3.0 and 4.0 as input and output versions.
     *
     * 2.1 has some minor support for the input version, it's incomplete at the
     * moment though.
     *
     * If input and output version are identical, a clone is returned.
     *
     * @param Component\VCard $input
     * @param int $targetVersion
     */
    function convert(Component\VCard $input, $targetVersion) {

        $inputVersion = $input->getDocumentType();
        if ($inputVersion === $targetVersion) {
            return clone $input;
        }

        if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) {
            throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data');
        }
        if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) {
            throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version');
        }

        $newVersion = $targetVersion === Document::VCARD40 ? '4.0' : '3.0';

        $output = new Component\VCard([
            'VERSION' => $newVersion,
        ]);

        // We might have generated a default UID. Remove it!
        unset($output->UID);

        foreach ($input->children() as $property) {

            $this->convertProperty($input, $output, $property, $targetVersion);

        }

        return $output;

    }

    /**
     * Handles conversion of a single property.
     *
     * @param Component\VCard $input
     * @param Component\VCard $output
     * @param Property $property
     * @param int $targetVersion
     *
     * @return void
     */
    protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) {

        // Skipping these, those are automatically added.
        if (in_array($property->name, ['VERSION', 'PRODID'])) {
            return;
        }

        $parameters = $property->parameters();
        $valueType = null;
        if (isset($parameters['VALUE'])) {
            $valueType = $parameters['VALUE']->getValue();
            unset($parameters['VALUE']);
        }
        if (!$valueType) {
            $valueType = $property->getValueType();
        }
        $newProperty = $output->createProperty(
            $property->name,
            $property->getParts(),
            [], // parameters will get added a bit later.
            $valueType
        );


        if ($targetVersion === Document::VCARD30) {

            if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) {

                $newProperty = $this->convertUriToBinary($output, $newProperty);

            } elseif ($property instanceof Property\VCard\DateAndOrTime) {

                // In vCard 4, the birth year may be optional. This is not the
                // case for vCard 3. Apple has a workaround for this that
                // allows applications that support Apple's extension still
                // omit birthyears in vCard 3, but applications that do not
                // support this, will just use a random birthyear. We're
                // choosing 1604 for the birthyear, because that's what apple
                // uses.
                $parts = DateTimeParser::parseVCardDateTime($property->getValue());
                if (is_null($parts['year'])) {
                    $newValue = '1604-' . $parts['month'] . '-' . $parts['date'];
                    $newProperty->setValue($newValue);
                    $newProperty['X-APPLE-OMIT-YEAR'] = '1604';
                }

                if ($newProperty->name == 'ANNIVERSARY') {
                    // Microsoft non-standard anniversary
                    $newProperty->name = 'X-ANNIVERSARY';

                    // We also need to add a new apple property for the same
                    // purpose. This apple property needs a 'label' in the same
                    // group, so we first need to find a groupname that doesn't
                    // exist yet.
                    $x = 1;
                    while ($output->select('ITEM' . $x . '.')) {
                        $x++;
                    }
                    $output->add('ITEM' . $x . '.X-ABDATE', $newProperty->getValue(), ['VALUE' => 'DATE-AND-OR-TIME']);
                    $output->add('ITEM' . $x . '.X-ABLABEL', '_$!<Anniversary>!$_');
                }

            } elseif ($property->name === 'KIND') {

                switch (strtolower($property->getValue())) {
                    case 'org' :
                        // vCard 3.0 does not have an equivalent to KIND:ORG,
                        // but apple has an extension that means the same
                        // thing.
                        $newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY');
                        break;

                    case 'individual' :
                        // Individual is implicit, so we skip it.
                        return;

                    case 'group' :
                        // OS X addressbook property
                        $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP');
                        break;
                }


            }

        } elseif ($targetVersion === Document::VCARD40) {

            // These properties were removed in vCard 4.0
            if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) {
                return;
            }

            if ($property instanceof Property\Binary) {

                $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters);

            } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) {

                // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR',
                // then we're stripping the year from the vcard 4 value.
                $parts = DateTimeParser::parseVCardDateTime($property->getValue());
                if ($parts['year'] === $property['X-APPLE-OMIT-YEAR']->getValue()) {
                    $newValue = '--' . $parts['month'] . '-' . $parts['date'];
                    $newProperty->setValue($newValue);
                }

                // Regardless if the year matched or not, we do need to strip
                // X-APPLE-OMIT-YEAR.
                unset($parameters['X-APPLE-OMIT-YEAR']);

            }
            switch ($property->name) {
                case 'X-ABSHOWAS' :
                    if (strtoupper($property->getValue()) === 'COMPANY') {
                        $newProperty = $output->createProperty('KIND', 'ORG');
                    }
                    break;
                case 'X-ADDRESSBOOKSERVER-KIND' :
                    if (strtoupper($property->getValue()) === 'GROUP') {
                        $newProperty = $output->createProperty('KIND', 'GROUP');
                    }
                    break;
                case 'X-ANNIVERSARY' :
                    $newProperty->name = 'ANNIVERSARY';
                    // If we already have an anniversary property with the same
                    // value, ignore.
                    foreach ($output->select('ANNIVERSARY') as $anniversary) {
                        if ($anniversary->getValue() === $newProperty->getValue()) {
                            return;
                        }
                    }
                    break;
                case 'X-ABDATE' :
                    // Find out what the label was, if it exists.
                    if (!$property->group) {
                        break;
                    }
                    $label = $input->{$property->group . '.X-ABLABEL'};

                    // We only support converting anniversaries.
                    if (!$label || $label->getValue() !== '_$!<Anniversary>!$_') {
                        break;
                    }

                    // If we already have an anniversary property with the same
                    // value, ignore.
                    foreach ($output->select('ANNIVERSARY') as $anniversary) {
                        if ($anniversary->getValue() === $newProperty->getValue()) {
                            return;
                        }
                    }
                    $newProperty->name = 'ANNIVERSARY';
                    break;
                // Apple's per-property label system.
                case 'X-ABLABEL' :
                    if ($newProperty->getValue() === '_$!<Anniversary>!$_') {
                        // We can safely remove these, as they are converted to
                        // ANNIVERSARY properties.
                        return;
                    }
                    break;

            }

        }

        // set property group
        $newProperty->group = $property->group;

        if ($targetVersion === Document::VCARD40) {
            $this->convertParameters40($newProperty, $parameters);
        } else {
            $this->convertParameters30($newProperty, $parameters);
        }

        // Lastly, we need to see if there's a need for a VALUE parameter.
        //
        // We can do that by instantating a empty property with that name, and
        // seeing if the default valueType is identical to the current one.
        $tempProperty = $output->createProperty($newProperty->name);
        if ($tempProperty->getValueType() !== $newProperty->getValueType()) {
            $newProperty['VALUE'] = $newProperty->getValueType();
        }

        $output->add($newProperty);


    }

    /**
     * Converts a BINARY property to a URI property.
     *
     * vCard 4.0 no longer supports BINARY properties.
     *
     * @param Component\VCard $output
     * @param Property\Uri $property The input property.
     * @param $parameters List of parameters that will eventually be added to
     *                    the new property.
     *
     * @return Property\Uri
     */
    protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) {

        $value = $newProperty->getValue();
        $newProperty = $output->createProperty(
            $newProperty->name,
            null, // no value
            [], // no parameters yet
            'URI' // Forcing the BINARY type
        );

        $mimeType = 'application/octet-stream';

        // See if we can find a better mimetype.
        if (isset($parameters['TYPE'])) {

            $newTypes = [];
            foreach ($parameters['TYPE']->getParts() as $typePart) {
                if (in_array(
                    strtoupper($typePart),
                    ['JPEG', 'PNG', 'GIF']
                )) {
                    $mimeType = 'image/' . strtolower($typePart);
                } else {
                    $newTypes[] = $typePart;
                }
            }

            // If there were any parameters we're not converting to a
            // mime-type, we need to keep them.
            if ($newTypes) {
                $parameters['TYPE']->setParts($newTypes);
            } else {
                unset($parameters['TYPE']);
            }

        }

        $newProperty->setValue('data:' . $mimeType . ';base64,' . base64_encode($value));
        return $newProperty;

    }

    /**
     * Converts a URI property to a BINARY property.
     *
     * In vCard 4.0 attachments are encoded as data: uri. Even though these may
     * be valid in vCard 3.0 as well, we should convert those to BINARY if
     * possible, to improve compatibility.
     *
     * @param Component\VCard $output
     * @param Property\Uri $property The input property.
     *
     * @return Property\Binary|null
     */
    protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) {

        $value = $newProperty->getValue();

        // Only converting data: uris
        if (substr($value, 0, 5) !== 'data:') {
            return $newProperty;
        }

        $newProperty = $output->createProperty(
            $newProperty->name,
            null, // no value
            [], // no parameters yet
            'BINARY'
        );

        $mimeType = substr($value, 5, strpos($value, ',') - 5);
        if (strpos($mimeType, ';')) {
            $mimeType = substr($mimeType, 0, strpos($mimeType, ';'));
            $newProperty->setValue(base64_decode(substr($value, strpos($value, ',') + 1)));
        } else {
            $newProperty->setValue(substr($value, strpos($value, ',') + 1));
        }
        unset($value);

        $newProperty['ENCODING'] = 'b';
        switch ($mimeType) {

            case 'image/jpeg' :
                $newProperty['TYPE'] = 'JPEG';
                break;
            case 'image/png' :
                $newProperty['TYPE'] = 'PNG';
                break;
            case 'image/gif' :
                $newProperty['TYPE'] = 'GIF';
                break;

        }


        return $newProperty;

    }

    /**
     * Adds parameters to a new property for vCard 4.0.
     *
     * @param Property $newProperty
     * @param array $parameters
     *
     * @return void
     */
    protected function convertParameters40(Property $newProperty, array $parameters) {

        // Adding all parameters.
        foreach ($parameters as $param) {

            // vCard 2.1 allowed parameters with no name
            if ($param->noName) $param->noName = false;

            switch ($param->name) {

                // We need to see if there's any TYPE=PREF, because in vCard 4
                // that's now PREF=1.
                case 'TYPE' :
                    foreach ($param->getParts() as $paramPart) {

                        if (strtoupper($paramPart) === 'PREF') {
                            $newProperty->add('PREF', '1');
                        } else {
                            $newProperty->add($param->name, $paramPart);
                        }

                    }
                    break;
                // These no longer exist in vCard 4
                case 'ENCODING' :
                case 'CHARSET' :
                    break;

                default :
                    $newProperty->add($param->name, $param->getParts());
                    break;

            }

        }

    }

    /**
     * Adds parameters to a new property for vCard 3.0.
     *
     * @param Property $newProperty
     * @param array $parameters
     *
     * @return void
     */
    protected function convertParameters30(Property $newProperty, array $parameters) {

        // Adding all parameters.
        foreach ($parameters as $param) {

            // vCard 2.1 allowed parameters with no name
            if ($param->noName) $param->noName = false;

            switch ($param->name) {

                case 'ENCODING' :
                    // This value only existed in vCard 2.1, and should be
                    // removed for anything else.
                    if (strtoupper($param->getValue()) !== 'QUOTED-PRINTABLE') {
                        $newProperty->add($param->name, $param->getParts());
                    }
                    break;

                /*
                 * Converting PREF=1 to TYPE=PREF.
                 *
                 * Any other PREF numbers we'll drop.
                 */
                case 'PREF' :
                    if ($param->getValue() == '1') {
                        $newProperty->add('TYPE', 'PREF');
                    }
                    break;

                default :
                    $newProperty->add($param->name, $param->getParts());
                    break;

            }

        }

    }
}
<?php

namespace Sabre\VObject;

/**
 * This class contains the version number for the VObject package.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Version {

    /**
     * Full version number.
     */
    const VERSION = '4.1.2';

}
<?php

namespace Sabre\VObject;

use Sabre\Xml;

/**
 * iCalendar/vCard/jCal/jCard/xCal/xCard writer object.
 *
 * This object provides a few (static) convenience methods to quickly access
 * the serializers.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Ivan Enderlin
 * @license http://sabre.io/license/ Modified BSD License
 */
class Writer {

    /**
     * Serializes a vCard or iCalendar object.
     *
     * @param Component $component
     *
     * @return string
     */
    static function write(Component $component) {

        return $component->serialize();

    }

    /**
     * Serializes a jCal or jCard object.
     *
     * @param Component $component
     * @param int $options
     *
     * @return string
     */
    static function writeJson(Component $component, $options = 0) {

        return json_encode($component, $options);

    }

    /**
     * Serializes a xCal or xCard object.
     *
     * @param Component $component
     *
     * @return string
     */
    static function writeXml(Component $component) {

        $writer = new Xml\Writer();
        $writer->openMemory();
        $writer->setIndent(true);

        $writer->startDocument('1.0', 'utf-8');

        if ($component instanceof Component\VCalendar) {

            $writer->startElement('icalendar');
            $writer->writeAttribute('xmlns', Parser\Xml::XCAL_NAMESPACE);

        } else {

            $writer->startElement('vcards');
            $writer->writeAttribute('xmlns', Parser\Xml::XCARD_NAMESPACE);

        }

        $component->xmlSerialize($writer);

        $writer->endElement();

        return $writer->outputMemory();

    }

}
Copyright (C) 2011-2016 fruux GmbH (https://fruux.com/)

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * 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.
    * Neither the name Sabre 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
sabre/vobject
=============

The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545)
and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP.

The goal of the VObject library is to create a very complete library, with an easy to use API.


Installation
------------

Make sure you have [Composer][1] installed, and then run:

    composer require sabre/vobject "^4.0"

This package requires PHP 5.5. If you need the PHP 5.3/5.4 version of this package instead, use:


    composer require sabre/vobject "^3.4"


Usage
-----

* [Working with vCards](http://sabre.io/vobject/vcard/)
* [Working with iCalendar](http://sabre.io/vobject/icalendar/)



Build status
------------

| branch | status |
| ------ | ------ |
| master | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=master)](https://travis-ci.org/fruux/sabre-vobject) |
| 3.5    | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.5)](https://travis-ci.org/fruux/sabre-vobject) |
| 3.4    | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.4)](https://travis-ci.org/fruux/sabre-vobject) |
| 3.1    | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.1)](https://travis-ci.org/fruux/sabre-vobject) |
| 2.1    | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-vobject) |
| 2.0    | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-vobject) |



Support
-------

Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions.

Made at fruux
-------------

This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.

[1]: https://getcomposer.org/
# RELAX NG Schema for iCalendar in XML
# Extract from RFC6321.
# Erratum 3042 applied.
# Erratum 3050 applied.
# Erratum 3314 applied.

default namespace = "urn:ietf:params:xml:ns:icalendar-2.0"

# 3.2 Property Parameters

# 3.2.1 Alternate Text Representation

altrepparam = element altrep {
    value-uri
}

# 3.2.2 Common Name

cnparam = element cn {
    value-text
}

# 3.2.3 Calendar User Type

cutypeparam = element cutype {
    element text {
        "INDIVIDUAL" |
        "GROUP" |
        "RESOURCE" |
        "ROOM" |
        "UNKNOWN"
    }
}

# 3.2.4 Delegators

delfromparam = element delegated-from {
    value-cal-address+
}

# 3.2.5 Delegatees

deltoparam = element delegated-to {
    value-cal-address+
}

# 3.2.6 Directory Entry Reference

dirparam = element dir {
    value-uri
}

# 3.2.7 Inline Encoding

encodingparam = element encoding {
    element text {
        "8BIT" |
        "BASE64"
    }
}

# 3.2.8 Format Type

fmttypeparam = element fmttype {
    value-text
}

# 3.2.9 Free/Busy Time Type

fbtypeparam = element fbtype {
    element text {
        "FREE" |
        "BUSY" |
        "BUSY-UNAVAILABLE" |
        "BUSY-TENTATIVE"
    }
}

# 3.2.10 Language

languageparam = element language {
    value-text
}

# 3.2.11 Group or List Membership

memberparam = element member {
    value-cal-address+
}

# 3.2.12 Participation Status

partstatparam = element partstat {
    type-partstat-event |
    type-partstat-todo |
    type-partstat-jour
}

type-partstat-event = (
    element text {
        "NEEDS-ACTION" |
        "ACCEPTED" |
        "DECLINED" |
        "TENTATIVE" |
        "DELEGATED"
    }
)

type-partstat-todo = (
    element text {
        "NEEDS-ACTION" |
        "ACCEPTED" |
        "DECLINED" |
        "TENTATIVE" |
        "DELEGATED" |
        "COMPLETED" |
        "IN-PROCESS"
    }
)

type-partstat-jour = (
    element text {
        "NEEDS-ACTION" |
        "ACCEPTED" |
        "DECLINED"
    }
)

# 3.2.13 Recurrence Identifier Range

rangeparam = element range {
    element text {
        "THISANDFUTURE"
    }
}

# 3.2.14 Alarm Trigger Relationship

trigrelparam = element related {
    element text {
        "START" |
        "END"
    }
}

# 3.2.15 Relationship Type

reltypeparam = element reltype {
    element text {
        "PARENT" |
        "CHILD" |
        "SIBLING"
    }
}

# 3.2.16 Participation Role

roleparam = element role {
    element text {
        "CHAIR" |
        "REQ-PARTICIPANT" |
        "OPT-PARTICIPANT" |
        "NON-PARTICIPANT"
    }
}

# 3.2.17 RSVP Expectation

rsvpparam = element rsvp {
    value-boolean
}

# 3.2.18 Sent By

sentbyparam = element sent-by {
    value-cal-address
}

# 3.2.19 Time Zone Identifier

tzidparam = element tzid {
    value-text
}

# 3.3 Property Value Data Types

# 3.3.1 BINARY

value-binary =  element binary {
    xsd:string
}

# 3.3.2 BOOLEAN

value-boolean = element boolean {
    xsd:boolean
}

# 3.3.3 CAL-ADDRESS

value-cal-address = element cal-address {
    xsd:anyURI
}

# 3.3.4 DATE

pattern-date = xsd:string {
    pattern = "\d\d\d\d-\d\d-\d\d"
}

value-date = element date {
    pattern-date
}

# 3.3.5 DATE-TIME

pattern-date-time = xsd:string {
    pattern = "\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ?"
}

value-date-time = element date-time {
    pattern-date-time
}

# 3.3.6 DURATION

pattern-duration = xsd:string {
    pattern = "(+|-)?P(\d+W)|(\d+D)?"
            ~ "(T(\d+H(\d+M)?(\d+S)?)|"
            ~   "(\d+M(\d+S)?)|"
            ~   "(\d+S))?"
}

value-duration = element duration {
    pattern-duration
}

# 3.3.7 FLOAT

value-float = element float {
    xsd:float
}

# 3.3.8 INTEGER

value-integer = element integer {
    xsd:integer
}

# 3.3.9 PERIOD

value-period = element period {
    element start {
        pattern-date-time
    },
    (
        element end {
            pattern-date-time
        } |
        element duration {
            pattern-duration
        }
    )
}

# 3.3.10 RECUR

value-recur = element recur {
    type-freq,
    (type-until | type-count)?,
    element interval {
        xsd:positiveInteger
    }?,
    type-bysecond*,
    type-byminute*,
    type-byhour*,
    type-byday*,
    type-bymonthday*,
    type-byyearday*,
    type-byweekno*,
    type-bymonth*,
    type-bysetpos*,
    element wkst { type-weekday }?
}

type-freq = element freq {
    "SECONDLY" |
    "MINUTELY" |
    "HOURLY"   |
    "DAILY"    |
    "WEEKLY"   |
    "MONTHLY"  |
    "YEARLY"
}

type-until = element until {
    type-date |
    type-date-time
}

type-count = element count {
    xsd:positiveInteger
}

type-bysecond = element bysecond {
    xsd:nonNegativeInteger
}

type-byminute = element byminute {
    xsd:nonNegativeInteger
}

type-byhour = element byhour {
    xsd:nonNegativeInteger
}

type-weekday = (
    "SU" |
    "MO" |
    "TU" |
    "WE" |
    "TH" |
    "FR" |
    "SA"
)

type-byday = element byday {
    xsd:integer?,
    type-weekday
}

type-bymonthday = element bymonthday {
    xsd:integer
}

type-byyearday = element byyearday {
    xsd:integer
}

type-byweekno = element byweekno {
    xsd:integer
}

type-bymonth = element bymonth {
    xsd:positiveInteger
}

type-bysetpos = element bysetpos {
    xsd:integer
}

# 3.3.11 TEXT

value-text = element text {
    xsd:string
}

# 3.3.12 TIME

pattern-time = xsd:string {
    pattern = "\d\d:\d\d:\d\dZ?"
}

value-time = element time {
    pattern-time
}

# 3.3.13 URI

value-uri = element uri {
    xsd:anyURI
}

# 3.3.14 UTC-OFFSET

value-utc-offset = element utc-offset {
    xsd:string { pattern = "(+|-)\d\d:\d\d(:\d\d)?" }
}

# UNKNOWN

value-unknown = element unknown {
    xsd:string
}

# 3.4 iCalendar Stream

start = element icalendar {
    vcalendar+
}

# 3.6 Calendar Components

vcalendar = element vcalendar {
    type-calprops,
    type-component
}

type-calprops = element properties {
    property-prodid &
    property-version &
    property-calscale? &
    property-method?
}

type-component = element components {
    (
        component-vevent |
        component-vtodo |
        component-vjournal |
        component-vfreebusy |
        component-vtimezone
    )*
}

# 3.6.1 Event Component

component-vevent = element vevent {
    type-eventprop,
    element components {
        component-valarm+
    }?
}

type-eventprop = element properties {
    property-dtstamp &
    property-dtstart &
    property-uid &

    property-class? &
    property-created? &
    property-description? &
    property-geo? &
    property-last-mod? &
    property-location? &
    property-organizer? &
    property-priority? &
    property-seq? &
    property-status-event? &
    property-summary? &
    property-transp? &
    property-url? &
    property-recurid? &

    property-rrule? &

    (property-dtend | property-duration)? &

    property-attach* &
    property-attendee* &
    property-categories* &
    property-comment* &
    property-contact* &
    property-exdate* &
    property-rstatus* &
    property-related* &
    property-resources* &
    property-rdate*
}

# 3.6.2 To-do Component

component-vtodo = element vtodo {
    type-todoprop,
    element components {
        component-valarm+
    }?
}

type-todoprop = element properties {
    property-dtstamp &
    property-uid &

    property-class? &
    property-completed? &
    property-created? &
    property-description? &
    property-geo? &
    property-last-mod? &
    property-location? &
    property-organizer? &
    property-percent? &
    property-priority? &
    property-recurid? &
    property-seq? &
    property-status-todo? &
    property-summary? &
    property-url? &

    property-rrule? &

    (
        (property-dtstart?, property-dtend? ) |
        (property-dtstart, property-duration)?
    ) &

    property-attach* &
    property-attendee* &
    property-categories* &
    property-comment* &
    property-contact* &
    property-exdate* &
    property-rstatus* &
    property-related* &
    property-resources* &
    property-rdate*
}

# 3.6.3 Journal Component

component-vjournal = element vjournal {
    type-jourprop
}

type-jourprop = element properties {
    property-dtstamp &
    property-uid &

    property-class? &
    property-created? &
    property-dtstart? &
    property-last-mod? &
    property-organizer? &
    property-recurid? &
    property-seq? &
    property-status-jour? &
    property-summary? &
    property-url? &

    property-rrule? &

    property-attach* &
    property-attendee* &
    property-categories* &
    property-comment* &
    property-contact* &
    property-description? &
    property-exdate* &
    property-related* &
    property-rdate* &
    property-rstatus*
}

# 3.6.4 Free/Busy Component

component-vfreebusy = element vfreebusy {
    type-fbprop
}

type-fbprop = element properties {
    property-dtstamp &
    property-uid &

    property-contact? &
    property-dtstart? &
    property-dtend? &
    property-duration? &
    property-organizer? &
    property-url? &

    property-attendee* &
    property-comment* &
    property-freebusy* &
    property-rstatus*
}

# 3.6.5 Time Zone Component

component-vtimezone = element vtimezone {
    element properties {
        property-tzid &

        property-last-mod? &
        property-tzurl?
    },
    element components {
        (component-standard | component-daylight) &
        component-standard* &
        component-daylight*
    }
}

component-standard = element standard {
    type-tzprop
}

component-daylight = element daylight {
    type-tzprop
}

type-tzprop = element properties {
    property-dtstart &
    property-tzoffsetto &
    property-tzoffsetfrom &

    property-rrule? &

    property-comment* &
    property-rdate* &
    property-tzname*
}

# 3.6.6 Alarm Component

component-valarm = element valarm {
    type-audioprop | type-dispprop | type-emailprop
}

type-audioprop = element properties {
    property-action &

    property-trigger &

    (property-duration, property-repeat)? &

    property-attach?
}

type-emailprop = element properties {
    property-action &
    property-description &
    property-trigger &
    property-summary &

    property-attendee+ &

    (property-duration, property-repeat)? &

    property-attach*
}

type-dispprop = element properties {
    property-action &
    property-description &
    property-trigger &

    (property-duration, property-repeat)?
}

# 3.7 Calendar Properties

# 3.7.1 Calendar Scale

property-calscale = element calscale {

    element parameters { empty }?,

    element text { "GREGORIAN" }
}

# 3.7.2 Method

property-method = element method {

    element parameters { empty }?,

    value-text
}

# 3.7.3 Product Identifier

property-prodid = element prodid {

    element parameters { empty }?,

    value-text
}

# 3.7.4 Version

property-version = element version {

    element parameters { empty }?,

    element text { "2.0" }
}

# 3.8 Component Properties

# 3.8.1 Descriptive Component Properties

# 3.8.1.1 Attachment

property-attach = element attach {

    element parameters {
        fmttypeparam? &
        encodingparam?
    }?,

    value-uri | value-binary
}

# 3.8.1.2 Categories

property-categories = element categories {

    element parameters {
        languageparam? &
    }?,

    value-text+
}

# 3.8.1.3 Classification

property-class = element class {

    element parameters { empty }?,

    element text {
        "PUBLIC" |
        "PRIVATE" |
        "CONFIDENTIAL"
    }
}

# 3.8.1.4 Comment

property-comment = element comment {

    element parameters {
        altrepparam? &
        languageparam?
    }?,

    value-text
}

# 3.8.1.5 Description

property-description = element description {

    element parameters {
        altrepparam? &
        languageparam?
    }?,

    value-text
}

# 3.8.1.6 Geographic Position

property-geo = element geo {

    element parameters { empty }?,

    element latitude  { xsd:float },
    element longitude { xsd:float }
}

# 3.8.1.7 Location

property-location = element location {

    element parameters {

        altrepparam? &
        languageparam?
    }?,

    value-text
}

# 3.8.1.8 Percent Complete

property-percent = element percent-complete {

    element parameters { empty }?,

    value-integer
}

# 3.8.1.9 Priority

property-priority = element priority {

    element parameters { empty }?,

    value-integer
}

# 3.8.1.10 Resources

property-resources = element resources {

    element parameters {
        altrepparam? &
        languageparam?
    }?,

    value-text+
}

# 3.8.1.11 Status

property-status-event = element status {

    element parameters { empty }?,

    element text {
        "TENTATIVE" |
        "CONFIRMED" |
        "CANCELLED"
    }
}

property-status-todo = element status {

    element parameters { empty }?,

    element text {
        "NEEDS-ACTION" |
        "COMPLETED" |
        "IN-PROCESS" |
        "CANCELLED"
    }
}

property-status-jour = element status {

    element parameters { empty }?,

    element text {
        "DRAFT" |
        "FINAL" |
        "CANCELLED"
    }
}

# 3.8.1.12 Summary

property-summary = element summary {

    element parameters {
        altrepparam? &
        languageparam?
    }?,

    value-text
}

# 3.8.2 Date and Time Component Properties

# 3.8.2.1 Date/Time Completed

property-completed = element completed {

    element parameters { empty }?,

    value-date-time
}

# 3.8.2.2 Date/Time End

property-dtend = element dtend {

    element parameters {
        tzidparam?
    }?,

    value-date-time |
    value-date
}

# 3.8.2.3 Date/Time Due

property-due = element due {

    element parameters {
        tzidparam?
    }?,

    value-date-time |
    value-date
}

# 3.8.2.4 Date/Time Start

property-dtstart = element dtstart {

    element parameters {
        tzidparam?
    }?,

    value-date-time |
    value-date
}

# 3.8.2.5 Duration

property-duration = element duration {

    element parameters { empty }?,

    value-duration
}

# 3.8.2.6 Free/Busy Time

property-freebusy = element freebusy {

    element parameters {
        fbtypeparam?
    }?,


    value-period+
}

# 3.8.2.7 Time Transparency

property-transp = element transp {

    element parameters { empty }?,

    element text {
        "OPAQUE" |
        "TRANSPARENT"
    }
}

# 3.8.3 Time Zone Component Properties

# 3.8.3.1 Time Zone Identifier

property-tzid = element tzid {

    element parameters { empty }?,

    value-text
}

# 3.8.3.2 Time Zone Name

property-tzname = element tzname {

    element parameters {
        languageparam?
    }?,

    value-text
}

# 3.8.3.3 Time Zone Offset From

property-tzoffsetfrom = element tzoffsetfrom {

    element parameters { empty }?,

    value-utc-offset
}

# 3.8.3.4 Time Zone Offset To

property-tzoffsetto = element tzoffsetto {

    element parameters { empty }?,

    value-utc-offset
}

# 3.8.3.5 Time Zone URL

property-tzurl = element tzurl {

    element parameters { empty }?,

    value-uri
}

# 3.8.4 Relationship Component Properties

# 3.8.4.1 Attendee

property-attendee = element attendee {

    element parameters {
        cutypeparam? &
        memberparam? &
        roleparam? &
        partstatparam? &
        rsvpparam? &
        deltoparam? &
        delfromparam? &
        sentbyparam? &
        cnparam? &
        dirparam? &
        languageparam?
    }?,

    value-cal-address
}

# 3.8.4.2 Contact

property-contact = element contact {

    element parameters {
        altrepparam? &
        languageparam?
    }?,

    value-text
}

# 3.8.4.3 Organizer

property-organizer = element organizer {

    element parameters {
        cnparam? &
        dirparam? &
        sentbyparam? &
        languageparam?
    }?,

    value-cal-address
}

# 3.8.4.4 Recurrence ID

property-recurid = element recurrence-id {

    element parameters {
        tzidparam? &
        rangeparam?
    }?,

    value-date-time |
    value-date
}

# 3.8.4.5 Related-To

property-related = element related-to {

    element parameters {
        reltypeparam?
    }?,

    value-text
}

# 3.8.4.6 Uniform Resource Locator

property-url = element url {

    element parameters { empty }?,

    value-uri
}

# 3.8.4.7 Unique Identifier

property-uid = element uid {

    element parameters { empty }?,

    value-text
}

# 3.8.5 Recurrence Component Properties

# 3.8.5.1 Exception Date/Times

property-exdate = element exdate {

    element parameters {
        tzidparam?
    }?,

    value-date-time+ |
    value-date+
}

# 3.8.5.2 Recurrence Date/Times

property-rdate = element rdate {

    element parameters {
        tzidparam?
    }?,

    value-date-time+ |
    value-date+ |
    value-period+
}

# 3.8.5.3 Recurrence Rule

property-rrule = element rrule {

    element parameters { empty }?,

    value-recur
}

# 3.8.6 Alarm Component Properties

# 3.8.6.1 Action

property-action = element action {

    element parameters { empty }?,

    element text {
        "AUDIO" |
        "DISPLAY" |
        "EMAIL"
    }
}

# 3.8.6.2 Repeat Count

property-repeat = element repeat {

    element parameters { empty }?,

    value-integer
}

# 3.8.6.3 Trigger

property-trigger = element trigger {

    (
        element parameters {
            trigrelparam?
        }?,

        value-duration
    ) |
    (
        element parameters { empty }?,

        value-date-time
    )
}

# 3.8.7 Change Management Component Properties

# 3.8.7.1 Date/Time Created

property-created = element created {

    element parameters { empty }?,

    value-date-time
}

# 3.8.7.2 Date/Time Stamp

property-dtstamp = element dtstamp {

    element parameters { empty }?,

    value-date-time
}

# 3.8.7.3 Last Modified

property-last-mod = element last-modified {

    element parameters { empty }?,

    value-date-time
}

# 3.8.7.4 Sequence Number

property-seq = element sequence {

    element parameters { empty }?,

    value-integer
}

# 3.8.8 Miscellaneous Component Properties

# 3.8.8.3 Request Status

property-rstatus = element request-status {

    element parameters {
        languageparam?
    }?,

    element code { xsd:string },
    element description { xsd:string },
    element data { xsd:string }?
}
# RELAX NG Schema for vCard in XML
# Extract from RFC6351.
# Erratum 2994 applied.
# Erratum 3047 applied.
# Erratum 3008 applied.
# Erratum 4247 applied.

default namespace = "urn:ietf:params:xml:ns:vcard-4.0"

### Section 3.3: vCard Format Specification
#
# 3.3
iana-token = xsd:string { pattern = "[a-zA-Z0-9\-]+" }
x-name = xsd:string { pattern = "x-[a-zA-Z0-9\-]+" }

### Section 4: Value types
#
# 4.1
value-text = element text { text }
value-text-list = value-text+

# 4.2
value-uri = element uri { xsd:anyURI }

# 4.3.1
value-date = element date {
    xsd:string { pattern = "\d{8}|\d{4}-\d\d|--\d\d(\d\d)?|---\d\d" }
  }

# 4.3.2
value-time = element time {
    xsd:string { pattern = "(\d\d(\d\d(\d\d)?)?|-\d\d(\d\d)?|--\d\d)"
                         ~ "(Z|[+\-]\d\d(\d\d)?)?" }
  }

# 4.3.3
value-date-time = element date-time {
    xsd:string { pattern = "(\d{8}|--\d{4}|---\d\d)T\d\d(\d\d(\d\d)?)?"
                         ~ "(Z|[+\-]\d\d(\d\d)?)?" }
  }

# 4.3.4
value-date-and-or-time = value-date | value-date-time | value-time

# 4.3.5
value-timestamp = element timestamp {
    xsd:string { pattern = "\d{8}T\d{6}(Z|[+\-]\d\d(\d\d)?)?" }
  }

# 4.4
value-boolean = element boolean { xsd:boolean }

# 4.5
value-integer = element integer { xsd:integer }

# 4.6
value-float = element float { xsd:float }

# 4.7
value-utc-offset = element utc-offset {
    xsd:string { pattern = "[+\-]\d\d(\d\d)?" }
  }

# 4.8
value-language-tag = element language-tag {
    xsd:string { pattern = "([a-z]{2,3}((-[a-z]{3}){0,3})?|[a-z]{4,8})"
                         ~ "(-[a-z]{4})?(-([a-z]{2}|\d{3}))?"
                         ~ "(-([0-9a-z]{5,8}|\d[0-9a-z]{3}))*"
                         ~ "(-[0-9a-wyz](-[0-9a-z]{2,8})+)*"
                         ~ "(-x(-[0-9a-z]{1,8})+)?|x(-[0-9a-z]{1,8})+|"
                         ~ "[a-z]{1,3}(-[0-9a-z]{2,8}){1,2}" }
  }

### Section 5: Parameters
#
# 5.1
param-language = element language { value-language-tag }?

# 5.2
param-pref = element pref {
    element integer {
      xsd:integer { minInclusive = "1" maxInclusive = "100" }
    }
  }?

# 5.4
param-altid = element altid { value-text }?

# 5.5
param-pid = element pid {
    element text { xsd:string { pattern = "\d+(\.\d+)?" } }+
  }?

# 5.6
param-type = element type { element text { "work" | "home" }+ }?

# 5.7
param-mediatype = element mediatype { value-text }?

# 5.8
param-calscale = element calscale { element text { "gregorian" } }?

# 5.9
param-sort-as = element sort-as { value-text+ }?

# 5.10
param-geo = element geo { value-uri }?

# 5.11
param-tz = element tz { value-text | value-uri }?

### Section 6: Properties
#
# 6.1.3
property-source = element source {
    element parameters { param-altid, param-pid, param-pref,
                         param-mediatype }?,
    value-uri
  }

# 6.1.4
property-kind = element kind {
    element text { "individual" | "group" | "org" | "location" |
                   x-name | iana-token }*
  }

# 6.2.1
property-fn = element fn {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type }?,
    value-text
  }

# 6.2.2
property-n = element n {
    element parameters { param-language, param-sort-as, param-altid }?,
    element surname { text }+,
    element given { text }+,
    element additional { text }+,
    element prefix { text }+,
    element suffix { text }+
  }

# 6.2.3
property-nickname = element nickname {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type }?,
    value-text-list
  }

# 6.2.4
property-photo = element photo {
    element parameters { param-altid, param-pid, param-pref, param-type,
                         param-mediatype }?,
    value-uri
  }

# 6.2.5
property-bday = element bday {
    element parameters { param-altid, param-calscale }?,
    (value-date-and-or-time | value-text)
  }

# 6.2.6
property-anniversary = element anniversary {
    element parameters { param-altid, param-calscale }?,
    (value-date-and-or-time | value-text)
  }

# 6.2.7
property-gender = element gender {
    element sex { "" | "M" | "F" | "O" | "N" | "U" },
    element identity { text }?
  }

# 6.3.1
param-label = element label { value-text }?
property-adr = element adr {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type, param-geo, param-tz,
                         param-label }?,
    element pobox { text }+,
    element ext { text }+,
    element street { text }+,
    element locality { text }+,
    element region { text }+,
    element code { text }+,
    element country { text }+
  }

# 6.4.1
property-tel = element tel {
    element parameters {
      param-altid,
      param-pid,
      param-pref,
      element type {
        element text { "work" | "home" | "text" | "voice"
                     | "fax" | "cell" | "video" | "pager"
                     | "textphone" | x-name | iana-token }+
      }?,
      param-mediatype
    }?,
    (value-text | value-uri)
  }

# 6.4.2
property-email = element email {
    element parameters { param-altid, param-pid, param-pref,
                         param-type }?,
    value-text
  }

# 6.4.3
property-impp = element impp {
    element parameters { param-altid, param-pid, param-pref,
                         param-type, param-mediatype }?,
    value-uri
  }

# 6.4.4
property-lang = element lang {
    element parameters { param-altid, param-pid, param-pref,
                         param-type }?,
    value-language-tag
  }

# 6.5.1
property-tz = element tz {
    element parameters { param-altid, param-pid, param-pref,
                         param-type, param-mediatype }?,
    (value-text | value-uri | value-utc-offset)
  }

# 6.5.2
property-geo = element geo {
    element parameters { param-altid, param-pid, param-pref,
                         param-type, param-mediatype }?,
    value-uri
  }

# 6.6.1
property-title = element title {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type }?,
    value-text
  }

# 6.6.2
property-role = element role {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type }?,
    value-text
  }

# 6.6.3
property-logo = element logo {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type, param-mediatype }?,
    value-uri
  }

# 6.6.4
property-org = element org {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type, param-sort-as }?,
    value-text-list
  }

# 6.6.5
property-member = element member {
    element parameters { param-altid, param-pid, param-pref,
                         param-mediatype }?,
    value-uri
  }

# 6.6.6
property-related = element related {
    element parameters {
      param-altid,
      param-pid,
      param-pref,
      element type {
        element text {
          "work" | "home" | "contact" | "acquaintance" |
          "friend" | "met" | "co-worker" | "colleague" | "co-resident" |
          "neighbor" | "child" | "parent" | "sibling" | "spouse" |
          "kin" | "muse" | "crush" | "date" | "sweetheart" | "me" |
          "agent" | "emergency"
        }+
      }?,
      param-mediatype
    }?,
    (value-uri | value-text)
  }

# 6.7.1
property-categories = element categories {
    element parameters { param-altid, param-pid, param-pref,
                         param-type }?,
    value-text-list
  }

# 6.7.2
property-note = element note {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type }?,
    value-text
  }

# 6.7.3
property-prodid = element prodid { value-text }

# 6.7.4
property-rev = element rev { value-timestamp }

# 6.7.5
property-sound = element sound {
    element parameters { param-language, param-altid, param-pid,
                         param-pref, param-type, param-mediatype }?,
    value-uri
  }

# 6.7.6
property-uid = element uid { value-uri }

# 6.7.7
property-clientpidmap = element clientpidmap {
    element sourceid { xsd:positiveInteger },
    value-uri
  }

# 6.7.8
property-url = element url {
    element parameters { param-altid, param-pid, param-pref,
                         param-type, param-mediatype }?,
    value-uri
  }

# 6.8.1
property-key = element key {
    element parameters { param-altid, param-pid, param-pref,
                         param-type, param-mediatype }?,
    (value-uri | value-text)
  }

# 6.9.1
property-fburl = element fburl {
    element parameters { param-altid, param-pid, param-pref,
                         param-type, param-mediatype }?,
    value-uri
  }

# 6.9.2
property-caladruri = element caladruri {
    element parameters { param-altid, param-pid, param-pref,
                         param-type, param-mediatype }?,
    value-uri
  }

# 6.9.3
property-caluri = element caluri {
    element parameters { param-altid, param-pid, param-pref,
                         param-type, param-mediatype }?,
    value-uri
  }

# Top-level grammar
property = property-adr | property-anniversary | property-bday
         | property-caladruri | property-caluri | property-categories
         | property-clientpidmap | property-email | property-fburl
         | property-fn | property-geo | property-impp | property-key
         | property-kind | property-lang | property-logo
         | property-member | property-n | property-nickname
         | property-note | property-org | property-photo
         | property-prodid | property-related | property-rev
         | property-role | property-gender | property-sound
         | property-source | property-tel | property-title
         | property-tz | property-uid | property-url
start = element vcards {
    element vcard {
      (property
       | element group {
           attribute name { text },
           property*
         })+
    }+
  }
vendor
composer.lock
tests/cov
.*.swp

# Composer binaries
bin/phpunit
bin/php-cs-fixer
bin/sabre-cs-fixer
language: php
php:
  - 5.5
  - 5.6
  - 7.0
  - 7.1

matrix:
  fast_finish: true

sudo: false

cache:
  directories:
    - $HOME/.composer/cache

before_install:
  - phpenv config-rm xdebug.ini; true

install:
  - composer install

script:
  - ./bin/phpunit --configuration tests/phpunit.xml.dist
  - ./bin/sabre-cs-fixer fix . --dry-run --diff

ChangeLog
=========

1.5.0 (2016-10-09)
------------------

* Now requires PHP 5.5.
* Using `finally` to always roll back the context stack when serializing.
* #94: Fixed an infinite loop condition when reading some invalid XML
  documents.


1.4.2 (2016-05-19)
------------------

* The `contextStack` in the Reader object is now correctly rolled back in
  error conditions (@staabm).
* repeatingElements deserializer now still parses if a bare element name
  without clark notation was given.
* `$elementMap` in the Reader now also supports bare element names.
* `Service::expect()` can now also work with bare element names.


1.4.1 (2016-03-12)
-----------------

* Parsing clark-notation is now cached. This can speed up parsing large
  documents with lots of repeating elements a fair bit. (@icewind1991).


1.4.0 (2016-02-14)
------------------

* Any array thrown into the serializer with numeric keys is now simply
  traversed and each individual item is serialized. This fixes an issue
  related to serializing value objects with array children.
* When serializing value objects, properties that have a null value or an
  empty array are now skipped. We believe this to be the saner default, but
  does constitute a BC break for those depending on this.
* Serializing array properties in value objects was broken.


1.3.0 (2015-12-29)
------------------

* The `Service` class adds a new `mapValueObject` method which provides basic
  capabilities to map between ValueObjects and XML.
* #61: You can now specify serializers for specific classes, allowing you
  separate the object you want to serialize from the serializer. This uses the
  `$classMap` property which is defined on both the `Service` and `Writer`.
* It's now possible to pass an array of possible root elements to
  `Sabre\Xml\Service::expect()`.
* Moved some parsing logic to `Reader::getDeserializerForElementName()`,
  so people with more advanced use-cases can implement their own logic there.
* #63: When serializing elements using arrays, the `value` key in the array is
  now optional.
* #62: Added a `keyValue` deserializer function. This can be used instead of
  the `Element\KeyValue` class and is a lot more flexible. (@staabm)
* Also added an `enum` deserializer function to replace
  `Element\Elements`.
* Using an empty string for a namespace prefix now has the same effect as
  `null`.


1.2.0 (2015-08-30)
------------------

* #53: Added `parseGetElements`, a function like `parseInnerTree`, except
  that it always returns an array of elements, or an empty array.


1.1.0 (2015-06-29)
------------------

* #44, #45: Catching broken and invalid XML better and throwing
  `Sabre\Xml\LibXMLException` whenever we encounter errors. (@stefanmajoor,
   @DaanBiesterbos)


1.0.0 (2015-05-25)
------------------

* No functional changes since 0.4.3. Marking it as 1.0.0 as a promise for
  API stability.
* Using php-cs-fixer for automated CS enforcement.


0.4.3 (2015-04-01)
-----------------

* Minor tweaks for the public release.


0.4.2 (2015-03-20)
------------------

* Removed `constants.php` again. They messed with PHPUnit and don't really
  provide a great benefit.
* #41: Correctly handle self-closing xml elements.


0.4.1 (2015-03-19)
------------------

* #40: An element with an empty namespace (xmlns="") is not allowed to have a
  prefix. This is now fixed.


0.4.0 (2015-03-18)
------------------

* Added `Sabre\Xml\Service`. This is intended as a simple way to centrally
  configure xml applications and easily parse/write things from there. #35, #38.
* Renamed 'baseUri' to 'contextUri' everywhere.
* #36: Added a few convenience constants to `lib/constants.php`.
* `Sabre\Xml\Util::parseClarkNotation` is now in the `Sabre\Xml\Service` class.


0.3.1 (2015-02-08)
------------------

* Added `XmlDeserializable` to match `XmlSerializable`.


0.3.0 (2015-02-06)
------------------

* Added `$elementMap` argument to parseInnerTree, for quickly overriding
  parsing rules within an element.


0.2.2 (2015-02-05)
------------------

* Now depends on sabre/uri 1.0.


0.2.1 (2014-12-17)
------------------

* LibXMLException now inherits from ParseException, so it's easy for users to
  catch any exception thrown by the parser.


0.2.0 (2014-12-05)
------------------

* Major BC Break: method names for the Element interface have been renamed
  from `serializeXml` and `deserializeXml` to `xmlSerialize` and
  `xmlDeserialize`. This is so that it matches PHP's `JsonSerializable`
  interface.
* #25: Added `XmlSerializable` to allow people to write serializers without
  having to implement a deserializer in the same class.
* #26: Renamed the `Sabre\XML` namespace to `Sabre\Xml`. Due to composer magic
  and the fact that PHP namespace are case-insensitive, this should not affect
  anyone, unless you are doing exact string matches on class names.
* #23: It's not possible to automatically extract or serialize Xml fragments
  from documents using `Sabre\Xml\Element\XmlFragment`.


0.1.0 (2014-11-24)
------------------

* #16: Added ability to override `elementMap`, `namespaceMap` and `baseUri` for
  a fragment of a document during reading an writing using `pushContext` and
  `popContext`.
* Removed: `Writer::$context` and `Reader::$context`.
* #15: Added `Reader::$baseUri` to match `Writer::$baseUri`.
* #20: Allow callbacks to be used instead of `Element` classes in the `Reader`.
* #25: Added `readText` to quickly grab all text from a node and advance the
  reader to the next node.
* #15: Added `Sabre\XML\Element\Uri`.


0.0.6 (2014-09-26)
------------------

* Added: `CData` element.
* #13: Better support for xml with no namespaces. (@kalmas)
* Switched to PSR-4 directory structure.


0.0.5 (2013-03-27)
------------------

* Added: baseUri property to the Writer class.
* Added: The writeElement method can now write complex elements.
* Added: Throwing exception when invalid objects are written.


0.0.4 (2013-03-14)
------------------

* Fixed: The KeyValue parser was skipping over elements when there was no
  whitespace between them.
* Fixed: Clearing libxml errors after parsing.
* Added: Support for CDATA.
* Added: Context properties.


0.0.3 (2013-02-22)
------------------

* Changed: Reader::parse returns an array with 1 level less depth.
* Added: A LibXMLException is now thrown if the XMLReader comes across an error.
* Fixed: Both the Elements and KeyValue parsers had severe issues with
  nesting.
* Fixed: The reader now detects when the end of the document is hit before it
  should (because we're still parsing an element).


0.0.2 (2013-02-17)
------------------

* Added: Elements parser.
* Added: KeyValue parser.
* Change: Reader::parseSubTree is now named parseInnerTree, and returns either
  a string (in case of a text-node), or an array (in case there were child
  elements).
* Added: Reader::parseCurrentElement is now public.


0.0.1 (2013-02-07)
------------------

* First alpha release

Project started: 2012-11-13. First experiments in June 2009.
{
    "name": "sabre/xml",
    "description" : "sabre/xml is an XML library that you may not hate.",
    "keywords" : [ "XML", "XMLReader", "XMLWriter", "DOM" ],
    "homepage" : "https://sabre.io/xml/",
    "license" : "BSD-3-Clause",
    "require" : {
        "php" : ">=5.5.5",
        "ext-xmlwriter" : "*",
        "ext-xmlreader" : "*",
        "ext-dom" : "*",
        "lib-libxml" : ">=2.6.20",
        "sabre/uri" : ">=1.0,<3.0.0"
    },
    "authors" : [
        {
            "name" : "Evert Pot",
            "email" : "me@evertpot.com",
            "homepage" : "http://evertpot.com/",
            "role" : "Developer"
        },
        {
            "name": "Markus Staab",
            "email": "markus.staab@redaxo.de",
            "role" : "Developer"
        }
    ],
    "support" : {
        "forum" : "https://groups.google.com/group/sabredav-discuss",
        "source" : "https://github.com/fruux/sabre-xml"
    },
    "autoload" : {
        "psr-4" : {
            "Sabre\\Xml\\" : "lib/"
        },
        "files": [
            "lib/Deserializer/functions.php",
            "lib/Serializer/functions.php"
        ]
    },
    "autoload-dev" : {
        "psr-4" : {
            "Sabre\\Xml\\" : "tests/Sabre/Xml/"
        }
    },
    "require-dev": {
        "sabre/cs": "~1.0.0",
        "phpunit/phpunit" : "*"
    },
    "config" : {
        "bin-dir" : "bin/"
    }
}
<?php

namespace Sabre\Xml;

/**
 * Context Stack
 *
 * The Context maintains information about a document during either reading or
 * writing.
 *
 * During this process, it may be neccesary to override this context
 * information.
 *
 * This trait allows easy access to the context, and allows the end-user to
 * override its settings for document fragments, and easily restore it again
 * later.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
trait ContextStackTrait {

    /**
     * This is the element map. It contains a list of XML elements (in clark
     * notation) as keys and PHP class names as values.
     *
     * The PHP class names must implement Sabre\Xml\Element.
     *
     * Values may also be a callable. In that case the function will be called
     * directly.
     *
     * @var array
     */
    public $elementMap = [];

    /**
     * A contextUri pointing to the document being parsed / written.
     * This uri may be used to resolve relative urls that may appear in the
     * document.
     *
     * The reader and writer don't use this property, but as it's an extremely
     * common use-case for parsing XML documents, it's added here as a
     * convenience.
     *
     * @var string
     */
    public $contextUri;

    /**
     * This is a list of namespaces that you want to give default prefixes.
     *
     * You must make sure you create this entire list before starting to write.
     * They should be registered on the root element.
     *
     * @var array
     */
    public $namespaceMap = [];

    /**
     * This is a list of custom serializers for specific classes.
     *
     * The writer may use this if you attempt to serialize an object with a
     * class that does not implement XmlSerializable.
     *
     * Instead it will look at this classmap to see if there is a custom
     * serializer here. This is useful if you don't want your value objects
     * to be responsible for serializing themselves.
     *
     * The keys in this classmap need to be fully qualified PHP class names,
     * the values must be callbacks. The callbacks take two arguments. The
     * writer class, and the value that must be written.
     *
     * function (Writer $writer, object $value)
     *
     * @var array
     */
    public $classMap = [];

    /**
     * Backups of previous contexts.
     *
     * @var array
     */
    protected $contextStack = [];

    /**
     * Create a new "context".
     *
     * This allows you to safely modify the elementMap, contextUri or
     * namespaceMap. After you're done, you can restore the old data again
     * with popContext.
     *
     * @return null
     */
    function pushContext() {

        $this->contextStack[] = [
            $this->elementMap,
            $this->contextUri,
            $this->namespaceMap,
            $this->classMap
        ];

    }

    /**
     * Restore the previous "context".
     *
     * @return null
     */
    function popContext() {

        list(
            $this->elementMap,
            $this->contextUri,
            $this->namespaceMap,
            $this->classMap
        ) = array_pop($this->contextStack);

    }

}
<?php

namespace Sabre\Xml\Deserializer;

use Sabre\Xml\Reader;

/**
 * This class provides a number of 'deserializer' helper functions.
 * These can be used to easily specify custom deserializers for specific
 * XML elements.
 *
 * You can either use these functions from within the $elementMap in the
 * Service or Reader class, or you can call them from within your own
 * deserializer functions.
 */

/**
 * The 'keyValue' deserializer parses all child elements, and outputs them as
 * a "key=>value" array.
 *
 * For example, keyvalue will parse:
 *
 * <?xml version="1.0"?>
 * <s:root xmlns:s="http://sabredav.org/ns">
 *   <s:elem1>value1</s:elem1>
 *   <s:elem2>value2</s:elem2>
 *   <s:elem3 />
 * </s:root>
 *
 * Into:
 *
 * [
 *   "{http://sabredav.org/ns}elem1" => "value1",
 *   "{http://sabredav.org/ns}elem2" => "value2",
 *   "{http://sabredav.org/ns}elem3" => null,
 * ];
 *
 * If you specify the 'namespace' argument, the deserializer will remove
 * the namespaces of the keys that match that namespace.
 *
 * For example, if you call keyValue like this:
 *
 * keyValue($reader, 'http://sabredav.org/ns')
 *
 * it's output will instead be:
 *
 * [
 *   "elem1" => "value1",
 *   "elem2" => "value2",
 *   "elem3" => null,
 * ];
 *
 * Attributes will be removed from the top-level elements. If elements with
 * the same name appear twice in the list, only the last one will be kept.
 *
 *
 * @param Reader $reader
 * @param string $namespace
 * @return array
 */
function keyValue(Reader $reader, $namespace = null) {

    // If there's no children, we don't do anything.
    if ($reader->isEmptyElement) {
        $reader->next();
        return [];
    }

    $values = [];

    $reader->read();
    do {

        if ($reader->nodeType === Reader::ELEMENT) {
            if ($namespace !== null && $reader->namespaceURI === $namespace) {
                $values[$reader->localName] = $reader->parseCurrentElement()['value'];
            } else {
                $clark = $reader->getClark();
                $values[$clark] = $reader->parseCurrentElement()['value'];
            }
        } else {
            $reader->read();
        }
    } while ($reader->nodeType !== Reader::END_ELEMENT);

    $reader->read();

    return $values;

}

/**
 * The 'enum' deserializer parses elements into a simple list
 * without values or attributes.
 *
 * For example, Elements will parse:
 *
 * <?xml version="1.0"? >
 * <s:root xmlns:s="http://sabredav.org/ns">
 *   <s:elem1 />
 *   <s:elem2 />
 *   <s:elem3 />
 *   <s:elem4>content</s:elem4>
 *   <s:elem5 attr="val" />
 * </s:root>
 *
 * Into:
 *
 * [
 *   "{http://sabredav.org/ns}elem1",
 *   "{http://sabredav.org/ns}elem2",
 *   "{http://sabredav.org/ns}elem3",
 *   "{http://sabredav.org/ns}elem4",
 *   "{http://sabredav.org/ns}elem5",
 * ];
 *
 * This is useful for 'enum'-like structures.
 *
 * If the $namespace argument is specified, it will strip the namespace
 * for all elements that match that.
 *
 * For example,
 *
 * enum($reader, 'http://sabredav.org/ns')
 *
 * would return:
 *
 * [
 *   "elem1",
 *   "elem2",
 *   "elem3",
 *   "elem4",
 *   "elem5",
 * ];
 *
 * @param Reader $reader
 * @param string $namespace
 * @return string[]
 */
function enum(Reader $reader, $namespace = null) {

    // If there's no children, we don't do anything.
    if ($reader->isEmptyElement) {
        $reader->next();
        return [];
    }
    $reader->read();
    $currentDepth = $reader->depth;

    $values = [];
    do {

        if ($reader->nodeType !== Reader::ELEMENT) {
            continue;
        }
        if (!is_null($namespace) && $namespace === $reader->namespaceURI) {
            $values[] = $reader->localName;
        } else {
            $values[] = $reader->getClark();
        }

    } while ($reader->depth >= $currentDepth && $reader->next());

    $reader->next();
    return $values;

}

/**
 * The valueObject deserializer turns an xml element into a PHP object of
 * a specific class.
 *
 * This is primarily used by the mapValueObject function from the Service
 * class, but it can also easily be used for more specific situations.
 *
 * @param Reader $reader
 * @param string $className
 * @param string $namespace
 * @return object
 */
function valueObject(Reader $reader, $className, $namespace) {

    $valueObject = new $className();
    if ($reader->isEmptyElement) {
        $reader->next();
        return $valueObject;
    }

    $defaultProperties = get_class_vars($className);

    $reader->read();
    do {

        if ($reader->nodeType === Reader::ELEMENT && $reader->namespaceURI == $namespace) {

            if (property_exists($valueObject, $reader->localName)) {
                if (is_array($defaultProperties[$reader->localName])) {
                    $valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value'];
                } else {
                    $valueObject->{$reader->localName} = $reader->parseCurrentElement()['value'];
                }
            } else {
                // Ignore property
                $reader->next();
            }
        } else {
            $reader->read();
        }
    } while ($reader->nodeType !== Reader::END_ELEMENT);

    $reader->read();
    return $valueObject;

}

/**
 * This deserializer helps you deserialize xml structures that look like
 * this:
 *
 * <collection>
 *    <item>...</item>
 *    <item>...</item>
 *    <item>...</item>
 * </collection>
 *
 * Many XML documents use  patterns like that, and this deserializer
 * allow you to get all the 'items' as an array.
 *
 * In that previous example, you would register the deserializer as such:
 *
 * $reader->elementMap['{}collection'] = function($reader) {
 *     return repeatingElements($reader, '{}item');
 * }
 *
 * The repeatingElements deserializer simply returns everything as an array.
 *
 * @param Reader $reader
 * @param string $childElementName Element name in clark-notation
 * @return array
 */
function repeatingElements(Reader $reader, $childElementName) {

    if ($childElementName[0] !== '{') {
        $childElementName = '{}' . $childElementName;
    }
    $result = [];

    foreach ($reader->parseGetElements() as $element) {

        if ($element['name'] === $childElementName) {
            $result[] = $element['value'];
        }

    }

    return $result;

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml;

/**
 * The Base XML element is the standard parser & generator that's used by the
 * XML reader and writer.
 *
 * It spits out a simple PHP array structure during deserialization, that can
 * also be directly injected back into Writer::write.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Base implements Xml\Element {

    /**
     * PHP value to serialize.
     *
     * @var mixed
     */
    protected $value;

    /**
     * Constructor
     *
     * @param mixed $value
     */
    function __construct($value = null) {

        $this->value = $value;

    }

    /**
     * The xmlSerialize metod is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializable should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $writer->write($this->value);

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statictly, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Xml\Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Xml\Reader $reader) {

        $subTree = $reader->parseInnerTree();
        return $subTree;

    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml;

/**
 * CDATA element.
 *
 * This element allows you to easily inject CDATA.
 *
 * Note that we strongly recommend avoiding CDATA nodes, unless you definitely
 * know what you're doing, or you're working with unchangable systems that
 * require CDATA.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Cdata implements Xml\XmlSerializable
{
    /**
     * CDATA element value.
     *
     * @var string
     */
    protected $value;

    /**
     * Constructor
     *
     * @param string $value
     */
    function __construct($value)
    {
        $this->value = $value;
    }

    /**
     * The xmlSerialize metod is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializble should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $writer->writeCData($this->value);

    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml;
use Sabre\Xml\Deserializer;
use Sabre\Xml\Serializer;

/**
 * 'Elements' is a simple list of elements, without values or attributes.
 * For example, Elements will parse:
 *
 * <?xml version="1.0"?>
 * <s:root xmlns:s="http://sabredav.org/ns">
 *   <s:elem1 />
 *   <s:elem2 />
 *   <s:elem3 />
 *   <s:elem4>content</s:elem4>
 *   <s:elem5 attr="val" />
 * </s:root>
 *
 * Into:
 *
 * [
 *   "{http://sabredav.org/ns}elem1",
 *   "{http://sabredav.org/ns}elem2",
 *   "{http://sabredav.org/ns}elem3",
 *   "{http://sabredav.org/ns}elem4",
 *   "{http://sabredav.org/ns}elem5",
 * ];
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Elements implements Xml\Element {

    /**
     * Value to serialize
     *
     * @var array
     */
    protected $value;

    /**
     * Constructor
     *
     * @param array $value
     */
    function __construct(array $value = []) {

        $this->value = $value;

    }

    /**
     * The xmlSerialize metod is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializble should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        Serializer\enum($writer, $this->value);

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statictly, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseSubTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Xml\Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Xml\Reader $reader) {

        return Deserializer\enum($reader);

    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml;
use Sabre\Xml\Deserializer;

/**
 * 'KeyValue' parses out all child elements from a single node, and outputs a
 * key=>value struct.
 *
 * Attributes will be removed, and duplicate child elements are discarded.
 * Complex values within the elements will be parsed by the 'standard' parser.
 *
 * For example, KeyValue will parse:
 *
 * <?xml version="1.0"?>
 * <s:root xmlns:s="http://sabredav.org/ns">
 *   <s:elem1>value1</s:elem1>
 *   <s:elem2>value2</s:elem2>
 *   <s:elem3 />
 * </s:root>
 *
 * Into:
 *
 * [
 *   "{http://sabredav.org/ns}elem1" => "value1",
 *   "{http://sabredav.org/ns}elem2" => "value2",
 *   "{http://sabredav.org/ns}elem3" => null,
 * ];
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class KeyValue implements Xml\Element {

    /**
     * Value to serialize
     *
     * @var array
     */
    protected $value;

    /**
     * Constructor
     *
     * @param array $value
     */
    function __construct(array $value = []) {

        $this->value = $value;

    }

    /**
     * The xmlSerialize metod is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializble should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $writer->write($this->value);

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called staticly, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Xml\Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Xml\Reader $reader) {

        return Deserializer\keyValue($reader);

    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml;

/**
 * Uri element.
 *
 * This represents a single uri. An example of how this may be encoded:
 *
 *    <link>/foo/bar</link>
 *    <d:href xmlns:d="DAV:">http://example.org/hi</d:href>
 *
 * If the uri is relative, it will be automatically expanded to an absolute
 * url during writing and reading, if the contextUri property is set on the
 * reader and/or writer.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Uri implements Xml\Element {

    /**
     * Uri element value.
     *
     * @var string
     */
    protected $value;

    /**
     * Constructor
     *
     * @param string $value
     */
    function __construct($value)
    {
        $this->value = $value;
    }

    /**
     * The xmlSerialize metod is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializble should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $writer->text(
            \Sabre\Uri\resolve(
                $writer->contextUri,
                $this->value
            )
        );

    }

    /**
     * This method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseSubTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Xml\Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Xml\Reader $reader) {

        return new self(
            \Sabre\Uri\resolve(
                $reader->contextUri,
                $reader->readText()
            )
        );

    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml\Element;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

/**
 * The XmlFragment element allows you to extract a portion of your xml tree,
 * and get a well-formed xml string.
 *
 * This goes a bit beyond `innerXml` and friends, as we'll also match all the
 * correct namespaces.
 *
 * Please note that the XML fragment:
 *
 * 1. Will not have an <?xml declaration.
 * 2. Or a DTD
 * 3. It will have all the relevant xmlns attributes.
 * 4. It may not have a root element.
 */
class XmlFragment implements Element {

    protected $xml;

    function __construct($xml) {

        $this->xml = $xml;

    }

    function getXml() {

        return $this->xml;

    }

    /**
     * The xmlSerialize metod is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializble should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer) {

        $reader = new Reader();

        // Wrapping the xml in a container, so root-less values can still be
        // parsed.
        $xml = <<<XML
<?xml version="1.0"?>
<xml-fragment xmlns="http://sabre.io/ns">{$this->getXml()}</xml-fragment>
XML;

        $reader->xml($xml);

        while ($reader->read()) {

            if ($reader->depth < 1) {
                // Skipping the root node.
                continue;
            }

            switch ($reader->nodeType) {

                case Reader::ELEMENT :
                    $writer->startElement(
                        $reader->getClark()
                    );
                    $empty = $reader->isEmptyElement;
                    while ($reader->moveToNextAttribute()) {
                        switch ($reader->namespaceURI) {
                            case '' :
                                $writer->writeAttribute($reader->localName, $reader->value);
                                break;
                            case 'http://www.w3.org/2000/xmlns/' :
                                // Skip namespace declarations
                                break;
                            default :
                                $writer->writeAttribute($reader->getClark(), $reader->value);
                                break;
                        }
                    }
                    if ($empty) {
                        $writer->endElement();
                    }
                    break;
                case Reader::CDATA :
                case Reader::TEXT :
                    $writer->text(
                        $reader->value
                    );
                    break;
                case Reader::END_ELEMENT :
                    $writer->endElement();
                    break;

            }

        }

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statictly, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader) {

        $result = new self($reader->readInnerXml());
        $reader->next();
        return $result;

    }

}
<?php

namespace Sabre\Xml;

/**
 * This is the XML element interface.
 *
 * Elements are responsible for serializing and deserializing part of an XML
 * document into PHP values.
 *
 * It combines XmlSerializable and XmlDeserializable into one logical class
 * that does both.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface Element extends XmlSerializable, XmlDeserializable {

}
<?php

namespace Sabre\Xml;

use
    LibXMLError;

/**
 * This exception is thrown when the Readers runs into a parsing error.
 *
 * This exception effectively wraps 1 or more LibXMLError objects.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class LibXMLException extends ParseException {

    /**
     * The error list.
     *
     * @var LibXMLError[]
     */
    protected $errors;

    /**
     * Creates the exception.
     *
     * You should pass a list of LibXMLError objects in its constructor.
     *
     * @param LibXMLError[] $errors
     * @param int $code
     * @param Exception $previousException
     */
    function __construct(array $errors, $code = null, Exception $previousException = null) {

        $this->errors = $errors;
        parent::__construct($errors[0]->message . ' on line ' . $errors[0]->line . ', column ' . $errors[0]->column, $code, $previousException);

    }

    /**
     * Returns the LibXML errors
     *
     * @return void
     */
    function getErrors() {

        return $this->errors;

    }

}
<?php

namespace Sabre\Xml;

use
    Exception;

/**
 * This is a base exception for any exception related to parsing xml files.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ParseException extends Exception {

}
<?php

namespace Sabre\Xml;

use XMLReader;

/**
 * The Reader class expands upon PHP's built-in XMLReader.
 *
 * The intended usage, is to assign certain XML elements to PHP classes. These
 * need to be registered using the $elementMap public property.
 *
 * After this is done, a single call to parse() will parse the entire document,
 * and delegate sub-sections of the document to element classes.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Reader extends XMLReader {

    use ContextStackTrait;

    /**
     * Returns the current nodename in clark-notation.
     *
     * For example: "{http://www.w3.org/2005/Atom}feed".
     * Or if no namespace is defined: "{}feed".
     *
     * This method returns null if we're not currently on an element.
     *
     * @return string|null
     */
    function getClark() {

        if (! $this->localName) {
            return null;
        }

        return '{' . $this->namespaceURI . '}' . $this->localName;

    }

    /**
     * Reads the entire document.
     *
     * This function returns an array with the following three elements:
     *    * name - The root element name.
     *    * value - The value for the root element.
     *    * attributes - An array of attributes.
     *
     * This function will also disable the standard libxml error handler (which
     * usually just results in PHP errors), and throw exceptions instead.
     *
     * @return array
     */
    function parse() {

        $previousEntityState = libxml_disable_entity_loader(true);
        $previousSetting = libxml_use_internal_errors(true);

        try {

            // Really sorry about the silence operator, seems like I have no
            // choice. See:
            //
            // https://bugs.php.net/bug.php?id=64230
            while ($this->nodeType !== self::ELEMENT && @$this->read()) {
                // noop
            }
            $result = $this->parseCurrentElement();

            $errors = libxml_get_errors();
            libxml_clear_errors();
            if ($errors) {
                throw new LibXMLException($errors);
            }

        } finally {
            libxml_use_internal_errors($previousSetting);
            libxml_disable_entity_loader($previousEntityState);
        }

        return $result;
    }



    /**
     * parseGetElements parses everything in the current sub-tree,
     * and returns a an array of elements.
     *
     * Each element has a 'name', 'value' and 'attributes' key.
     *
     * If the the element didn't contain sub-elements, an empty array is always
     * returned. If there was any text inside the element, it will be
     * discarded.
     *
     * If the $elementMap argument is specified, the existing elementMap will
     * be overridden while parsing the tree, and restored after this process.
     *
     * @param array $elementMap
     * @return array
     */
    function parseGetElements(array $elementMap = null) {

        $result = $this->parseInnerTree($elementMap);
        if (!is_array($result)) {
            return [];
        }
        return $result;

    }

    /**
     * Parses all elements below the current element.
     *
     * This method will return a string if this was a text-node, or an array if
     * there were sub-elements.
     *
     * If there's both text and sub-elements, the text will be discarded.
     *
     * If the $elementMap argument is specified, the existing elementMap will
     * be overridden while parsing the tree, and restored after this process.
     *
     * @param array $elementMap
     * @return array|string
     */
    function parseInnerTree(array $elementMap = null) {

        $text = null;
        $elements = [];

        if ($this->nodeType === self::ELEMENT && $this->isEmptyElement) {
            // Easy!
            $this->next();
            return null;
        }

        if (!is_null($elementMap)) {
            $this->pushContext();
            $this->elementMap = $elementMap;
        }

        try {

            // Really sorry about the silence operator, seems like I have no
            // choice. See:
            //
            // https://bugs.php.net/bug.php?id=64230
            if (!@$this->read()) {
                $errors = libxml_get_errors();
                libxml_clear_errors();
                if ($errors) {
                    throw new LibXMLException($errors);
                }
                throw new ParseException('This should never happen (famous last words)');
            }

            while (true) {

                if (!$this->isValid()) {

                    $errors = libxml_get_errors();

                    if ($errors) {
                        libxml_clear_errors();
                        throw new LibXMLException($errors);
                    }
                }

                switch ($this->nodeType) {
                    case self::ELEMENT :
                        $elements[] = $this->parseCurrentElement();
                        break;
                    case self::TEXT :
                    case self::CDATA :
                        $text .= $this->value;
                        $this->read();
                        break;
                    case self::END_ELEMENT :
                        // Ensuring we are moving the cursor after the end element.
                        $this->read();
                        break 2;
                    case self::NONE :
                        throw new ParseException('We hit the end of the document prematurely. This likely means that some parser "eats" too many elements. Do not attempt to continue parsing.');
                    default :
                        // Advance to the next element
                        $this->read();
                        break;
                }

            }

        } finally {

            if (!is_null($elementMap)) {
                $this->popContext();
            }

        }
        return ($elements ? $elements : $text);

    }

    /**
     * Reads all text below the current element, and returns this as a string.
     *
     * @return string
     */
    function readText() {

        $result = '';
        $previousDepth = $this->depth;

        while ($this->read() && $this->depth != $previousDepth) {
            if (in_array($this->nodeType, [XMLReader::TEXT, XMLReader::CDATA, XMLReader::WHITESPACE])) {
                $result .= $this->value;
            }
        }
        return $result;

    }

    /**
     * Parses the current XML element.
     *
     * This method returns arn array with 3 properties:
     *   * name - A clark-notation XML element name.
     *   * value - The parsed value.
     *   * attributes - A key-value list of attributes.
     *
     * @return array
     */
    function parseCurrentElement() {

        $name = $this->getClark();

        $attributes = [];

        if ($this->hasAttributes) {
            $attributes = $this->parseAttributes();
        }

        $value = call_user_func(
            $this->getDeserializerForElementName($name),
            $this
        );

        return [
            'name'       => $name,
            'value'      => $value,
            'attributes' => $attributes,
        ];
    }


    /**
     * Grabs all the attributes from the current element, and returns them as a
     * key-value array.
     *
     * If the attributes are part of the same namespace, they will simply be
     * short keys. If they are defined on a different namespace, the attribute
     * name will be retured in clark-notation.
     *
     * @return array
     */
    function parseAttributes() {

        $attributes = [];

        while ($this->moveToNextAttribute()) {
            if ($this->namespaceURI) {

                // Ignoring 'xmlns', it doesn't make any sense.
                if ($this->namespaceURI === 'http://www.w3.org/2000/xmlns/') {
                    continue;
                }

                $name = $this->getClark();
                $attributes[$name] = $this->value;

            } else {
                $attributes[$this->localName] = $this->value;
            }
        }
        $this->moveToElement();

        return $attributes;

    }

    /**
     * Returns the function that should be used to parse the element identified
     * by it's clark-notation name.
     *
     * @param string $name
     * @return callable
     */
    function getDeserializerForElementName($name) {


        if (!array_key_exists($name, $this->elementMap)) {
            if (substr($name, 0, 2) == '{}' && array_key_exists(substr($name, 2), $this->elementMap)) {
                $name = substr($name, 2);
            } else {
                return ['Sabre\\Xml\\Element\\Base', 'xmlDeserialize'];
            }
        }

        $deserializer = $this->elementMap[$name];
        if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) {
            return [$deserializer, 'xmlDeserialize'];
        }

        if (is_callable($deserializer)) {
            return $deserializer;
        }

        $type = gettype($deserializer);
        if ($type === 'string') {
            $type .= ' (' . $deserializer . ')';
        } elseif ($type === 'object') {
            $type .= ' (' . get_class($deserializer) . ')';
        }
        throw new \LogicException('Could not use this type as a deserializer: ' . $type . ' for element: ' . $name);

    }

}
<?php

namespace Sabre\Xml\Serializer;

use InvalidArgumentException;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;

/**
 * This file provides a number of 'serializer' helper functions.
 *
 * These helper functions can be used to easily xml-encode common PHP
 * data structures, or can be placed in the $classMap.
 */

/**
 * The 'enum' serializer writes simple list of elements.
 *
 * For example, calling:
 *
 * enum($writer, [
 *   "{http://sabredav.org/ns}elem1",
 *   "{http://sabredav.org/ns}elem2",
 *   "{http://sabredav.org/ns}elem3",
 *   "{http://sabredav.org/ns}elem4",
 *   "{http://sabredav.org/ns}elem5",
 * ]);
 *
 * Will generate something like this (if the correct namespace is declared):
 *
 * <s:elem1 />
 * <s:elem2 />
 * <s:elem3 />
 * <s:elem4>content</s:elem4>
 * <s:elem5 attr="val" />
 *
 * @param Writer $writer
 * @param string[] $values
 * @return void
 */
function enum(Writer $writer, array $values) {

    foreach ($values as $value) {
        $writer->writeElement($value);
    }
}

/**
 * The valueObject serializer turns a simple PHP object into a classname.
 *
 * Every public property will be encoded as an xml element with the same
 * name, in the XML namespace as specified.
 *
 * Values that are set to null or an empty array are not serialized. To
 * serialize empty properties, you must specify them as an empty string.
 *
 * @param Writer $writer
 * @param object $valueObject
 * @param string $namespace
 */
function valueObject(Writer $writer, $valueObject, $namespace) {
    foreach (get_object_vars($valueObject) as $key => $val) {
        if (is_array($val)) {
            // If $val is an array, it has a special meaning. We need to
            // generate one child element for each item in $val
            foreach ($val as $child) {
                $writer->writeElement('{' . $namespace . '}' . $key, $child);
            }

        } elseif ($val !== null) {
            $writer->writeElement('{' . $namespace . '}' . $key, $val);
        }
    }
}


/**
 * This serializer helps you serialize xml structures that look like
 * this:
 *
 * <collection>
 *    <item>...</item>
 *    <item>...</item>
 *    <item>...</item>
 * </collection>
 *
 * In that previous example, this serializer just serializes the item element,
 * and this could be called like this:
 *
 * repeatingElements($writer, $items, '{}item');
 *
 * @param Writer $writer
 * @param array $items A list of items sabre/xml can serialize.
 * @param string $childElementName Element name in clark-notation
 * @return void
 */
function repeatingElements(Writer $writer, array $items, $childElementName) {

    foreach ($items as $item) {
        $writer->writeElement($childElementName, $item);
    }

}

/**
 * This function is the 'default' serializer that is able to serialize most
 * things, and delegates to other serializers if needed.
 *
 * The standardSerializer supports a wide-array of values.
 *
 * $value may be a string or integer, it will just write out the string as text.
 * $value may be an instance of XmlSerializable or Element, in which case it
 *    calls it's xmlSerialize() method.
 * $value may be a PHP callback/function/closure, in case we call the callback
 *    and give it the Writer as an argument.
 * $value may be a an object, and if it's in the classMap we automatically call
 *    the correct serializer for it.
 * $value may be null, in which case we do nothing.
 *
 * If $value is an array, the array must look like this:
 *
 * [
 *    [
 *       'name' => '{namespaceUri}element-name',
 *       'value' => '...',
 *       'attributes' => [ 'attName' => 'attValue' ]
 *    ]
 *    [,
 *       'name' => '{namespaceUri}element-name2',
 *       'value' => '...',
 *    ]
 * ]
 *
 * This would result in xml like:
 *
 * <element-name xmlns="namespaceUri" attName="attValue">
 *   ...
 * </element-name>
 * <element-name2>
 *   ...
 * </element-name2>
 *
 * The value property may be any value standardSerializer supports, so you can
 * nest data-structures this way. Both value and attributes are optional.
 *
 * Alternatively, you can also specify the array using this syntax:
 *
 * [
 *    [
 *       '{namespaceUri}element-name' => '...',
 *       '{namespaceUri}element-name2' => '...',
 *    ]
 * ]
 *
 * This is excellent for simple key->value structures, and here you can also
 * specify anything for the value.
 *
 * You can even mix the two array syntaxes.
 *
 * @param Writer $writer
 * @param string|int|float|bool|array|object
 * @return void
 */
function standardSerializer(Writer $writer, $value) {

    if (is_scalar($value)) {

        // String, integer, float, boolean
        $writer->text($value);

    } elseif ($value instanceof XmlSerializable) {

        // XmlSerializable classes or Element classes.
        $value->xmlSerialize($writer);

    } elseif (is_object($value) && isset($writer->classMap[get_class($value)])) {

        // It's an object which class appears in the classmap.
        $writer->classMap[get_class($value)]($writer, $value);

    } elseif (is_callable($value)) {

        // A callback
        $value($writer);

    } elseif (is_null($value)) {

        // nothing!

    } elseif (is_array($value) && array_key_exists('name', $value)) {

        // if the array had a 'name' element, we assume that this array
        // describes a 'name' and optionally 'attributes' and 'value'.

        $name = $value['name'];
        $attributes = isset($value['attributes']) ? $value['attributes'] : [];
        $value = isset($value['value']) ? $value['value'] : null;

        $writer->startElement($name);
        $writer->writeAttributes($attributes);
        $writer->write($value);
        $writer->endElement();

    } elseif (is_array($value)) {

        foreach ($value as $name => $item) {

            if (is_int($name)) {

                // This item has a numeric index. We just loop through the
                // array and throw it back in the writer.
                standardSerializer($writer, $item);

            } elseif (is_string($name) && is_array($item) && isset($item['attributes'])) {

                // The key is used for a name, but $item has 'attributes' and
                // possibly 'value'
                $writer->startElement($name);
                $writer->writeAttributes($item['attributes']);
                if (isset($item['value'])) {
                    $writer->write($item['value']);
                }
                $writer->endElement();

            } elseif (is_string($name)) {

                // This was a plain key-value array.
                $writer->startElement($name);
                $writer->write($item);
                $writer->endElement();

            } else {

                throw new InvalidArgumentException('The writer does not know how to serialize arrays with keys of type: ' . gettype($name));

            }
        }

    } elseif (is_object($value)) {

        throw new InvalidArgumentException('The writer cannot serialize objects of class: ' . get_class($value));

    } else {

        throw new InvalidArgumentException('The writer cannot serialize values of type: ' . gettype($value));

    }

}
<?php

namespace Sabre\Xml;

/**
 * XML parsing and writing service.
 *
 * You are encouraged to make a instance of this for your application and
 * potentially extend it, as a central API point for dealing with xml and
 * configuring the reader and writer.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Service {

    /**
     * This is the element map. It contains a list of XML elements (in clark
     * notation) as keys and PHP class names as values.
     *
     * The PHP class names must implement Sabre\Xml\Element.
     *
     * Values may also be a callable. In that case the function will be called
     * directly.
     *
     * @var array
     */
    public $elementMap = [];

    /**
     * This is a list of namespaces that you want to give default prefixes.
     *
     * You must make sure you create this entire list before starting to write.
     * They should be registered on the root element.
     *
     * @var array
     */
    public $namespaceMap = [];

    /**
     * This is a list of custom serializers for specific classes.
     *
     * The writer may use this if you attempt to serialize an object with a
     * class that does not implement XmlSerializable.
     *
     * Instead it will look at this classmap to see if there is a custom
     * serializer here. This is useful if you don't want your value objects
     * to be responsible for serializing themselves.
     *
     * The keys in this classmap need to be fully qualified PHP class names,
     * the values must be callbacks. The callbacks take two arguments. The
     * writer class, and the value that must be written.
     *
     * function (Writer $writer, object $value)
     *
     * @var array
     */
    public $classMap = [];

    /**
     * Returns a fresh XML Reader
     *
     * @return Reader
     */
    function getReader() {

        $r = new Reader();
        $r->elementMap = $this->elementMap;
        return $r;

    }

    /**
     * Returns a fresh xml writer
     *
     * @return Writer
     */
    function getWriter() {

        $w = new Writer();
        $w->namespaceMap = $this->namespaceMap;
        $w->classMap = $this->classMap;
        return $w;

    }

    /**
     * Parses a document in full.
     *
     * Input may be specified as a string or readable stream resource.
     * The returned value is the value of the root document.
     *
     * Specifying the $contextUri allows the parser to figure out what the URI
     * of the document was. This allows relative URIs within the document to be
     * expanded easily.
     *
     * The $rootElementName is specified by reference and will be populated
     * with the root element name of the document.
     *
     * @param string|resource $input
     * @param string|null $contextUri
     * @param string|null $rootElementName
     * @throws ParseException
     * @return array|object|string
     */
    function parse($input, $contextUri = null, &$rootElementName = null) {

        if (is_resource($input)) {
            // Unfortunately the XMLReader doesn't support streams. When it
            // does, we can optimize this.
            $input = stream_get_contents($input);
        }
        $r = $this->getReader();
        $r->contextUri = $contextUri;
        $r->xml($input);

        $result = $r->parse();
        $rootElementName = $result['name'];
        return $result['value'];

    }

    /**
     * Parses a document in full, and specify what the expected root element
     * name is.
     *
     * This function works similar to parse, but the difference is that the
     * user can specify what the expected name of the root element should be,
     * in clark notation.
     *
     * This is useful in cases where you expected a specific document to be
     * passed, and reduces the amount of if statements.
     *
     * It's also possible to pass an array of expected rootElements if your
     * code may expect more than one document type.
     *
     * @param string|string[] $rootElementName
     * @param string|resource $input
     * @param string|null $contextUri
     * @return void
     */
    function expect($rootElementName, $input, $contextUri = null) {

        if (is_resource($input)) {
            // Unfortunately the XMLReader doesn't support streams. When it
            // does, we can optimize this.
            $input = stream_get_contents($input);
        }
        $r = $this->getReader();
        $r->contextUri = $contextUri;
        $r->xml($input);

        $rootElementName = (array)$rootElementName;

        foreach ($rootElementName as &$rEl) {
            if ($rEl[0] !== '{') $rEl = '{}' . $rEl;
        }

        $result = $r->parse();
        if (!in_array($result['name'], $rootElementName, true)) {
            throw new ParseException('Expected ' . implode(' or ', (array)$rootElementName) . ' but received ' . $result['name'] . ' as the root element');
        }
        return $result['value'];

    }

    /**
     * Generates an XML document in one go.
     *
     * The $rootElement must be specified in clark notation.
     * The value must be a string, an array or an object implementing
     * XmlSerializable. Basically, anything that's supported by the Writer
     * object.
     *
     * $contextUri can be used to specify a sort of 'root' of the PHP application,
     * in case the xml document is used as a http response.
     *
     * This allows an implementor to easily create URI's relative to the root
     * of the domain.
     *
     * @param string $rootElementName
     * @param string|array|XmlSerializable $value
     * @param string|null $contextUri
     */
    function write($rootElementName, $value, $contextUri = null) {

        $w = $this->getWriter();
        $w->openMemory();
        $w->contextUri = $contextUri;
        $w->setIndent(true);
        $w->startDocument();
        $w->writeElement($rootElementName, $value);
        return $w->outputMemory();

    }

    /**
     * Map an xml element to a PHP class.
     *
     * Calling this function will automatically setup the Reader and Writer
     * classes to turn a specific XML element to a PHP class.
     *
     * For example, given a class such as :
     *
     * class Author {
     *   public $firstName;
     *   public $lastName;
     * }
     *
     * and an XML element such as:
     *
     * <author xmlns="http://example.org/ns">
     *   <firstName>...</firstName>
     *   <lastName>...</lastName>
     * </author>
     *
     * These can easily be mapped by calling:
     *
     * $service->mapValueObject('{http://example.org}author', 'Author');
     *
     * @param string $elementName
     * @param object $className
     * @return void
     */
    function mapValueObject($elementName, $className) {
        list($namespace) = self::parseClarkNotation($elementName);

        $this->elementMap[$elementName] = function(Reader $reader) use ($className, $namespace) {
            return \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace);
        };
        $this->classMap[$className] = function(Writer $writer, $valueObject) use ($namespace) {
            return \Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace);
        };
        $this->valueObjectMap[$className] = $elementName;
    }

    /**
     * Writes a value object.
     *
     * This function largely behaves similar to write(), except that it's
     * intended specifically to serialize a Value Object into an XML document.
     *
     * The ValueObject must have been previously registered using
     * mapValueObject().
     *
     * @param object $object
     * @param string $contextUri
     * @return void
     */
    function writeValueObject($object, $contextUri = null) {

        if (!isset($this->valueObjectMap[get_class($object)])) {
            throw new \InvalidArgumentException('"' . get_class($object) . '" is not a registered value object class. Register your class with mapValueObject.');
        }
        return $this->write(
            $this->valueObjectMap[get_class($object)],
            $object,
            $contextUri
        );

    }

    /**
     * Parses a clark-notation string, and returns the namespace and element
     * name components.
     *
     * If the string was invalid, it will throw an InvalidArgumentException.
     *
     * @param string $str
     * @throws InvalidArgumentException
     * @return array
     */
    static function parseClarkNotation($str) {
        static $cache = [];

        if (!isset($cache[$str])) {

            if (!preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) {
                throw new \InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string');
            }

            $cache[$str] = [
                $matches[1],
                $matches[2]
            ];
        }

        return $cache[$str];
    }

    /**
     * A list of classes and which XML elements they map to.
     */
    protected $valueObjectMap = [];

}
<?php

namespace Sabre\Xml;

/**
 * This class contains the version number for this package.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/
 */
class Version {

    /**
     * Full version number
     */
    const VERSION = '1.5.0';

}
<?php

namespace Sabre\Xml;

use XMLWriter;

/**
 * The XML Writer class.
 *
 * This class works exactly as PHP's built-in XMLWriter, with a few additions.
 *
 * Namespaces can be registered beforehand, globally. When the first element is
 * written, namespaces will automatically be declared.
 *
 * The writeAttribute, startElement and writeElement can now take a
 * clark-notation element name (example: {http://www.w3.org/2005/Atom}link).
 *
 * If, when writing the namespace is a known one a prefix will automatically be
 * selected, otherwise a random prefix will be generated.
 *
 * Instead of standard string values, the writer can take Element classes (as
 * defined by this library) to delegate the serialization.
 *
 * The write() method can take array structures to quickly write out simple xml
 * trees.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Writer extends XMLWriter {

    use ContextStackTrait;

    /**
     * Any namespace that the writer is asked to write, will be added here.
     *
     * Any of these elements will get a new namespace definition *every single
     * time* they are used, but this array allows the writer to make sure that
     * the prefixes are consistent anyway.
     *
     * @var array
     */
    protected $adhocNamespaces = [];

    /**
     * When the first element is written, this flag is set to true.
     *
     * This ensures that the namespaces in the namespaces map are only written
     * once.
     *
     * @var bool
     */
    protected $namespacesWritten = false;

    /**
     * Writes a value to the output stream.
     *
     * The following values are supported:
     *   1. Scalar values will be written as-is, as text.
     *   2. Null values will be skipped (resulting in a short xml tag).
     *   3. If a value is an instance of an Element class, writing will be
     *      delegated to the object.
     *   4. If a value is an array, two formats are supported.
     *
     *  Array format 1:
     *  [
     *    "{namespace}name1" => "..",
     *    "{namespace}name2" => "..",
     *  ]
     *
     *  One element will be created for each key in this array. The values of
     *  this array support any format this method supports (this method is
     *  called recursively).
     *
     *  Array format 2:
     *
     *  [
     *    [
     *      "name" => "{namespace}name1"
     *      "value" => "..",
     *      "attributes" => [
     *          "attr" => "attribute value",
     *      ]
     *    ],
     *    [
     *      "name" => "{namespace}name1"
     *      "value" => "..",
     *      "attributes" => [
     *          "attr" => "attribute value",
     *      ]
     *    ]
     * ]
     *
     * @param mixed $value
     * @return void
     */
    function write($value) {

        Serializer\standardSerializer($this, $value);

    }

    /**
     * Opens a new element.
     *
     * You can either just use a local elementname, or you can use clark-
     * notation to start a new element.
     *
     * Example:
     *
     *     $writer->startElement('{http://www.w3.org/2005/Atom}entry');
     *
     * Would result in something like:
     *
     *     <entry xmlns="http://w3.org/2005/Atom">
     *
     * @param string $name
     * @return bool
     */
    function startElement($name) {

        if ($name[0] === '{') {

            list($namespace, $localName) =
                Service::parseClarkNotation($name);

            if (array_key_exists($namespace, $this->namespaceMap)) {
                $result = $this->startElementNS(
                    $this->namespaceMap[$namespace] === '' ? null : $this->namespaceMap[$namespace],
                    $localName,
                    null
                );
            } else {

                // An empty namespace means it's the global namespace. This is
                // allowed, but it mustn't get a prefix.
                if ($namespace === "" || $namespace === null) {
                    $result = $this->startElement($localName);
                    $this->writeAttribute('xmlns', '');
                } else {
                    if (!isset($this->adhocNamespaces[$namespace])) {
                        $this->adhocNamespaces[$namespace] = 'x' . (count($this->adhocNamespaces) + 1);
                    }
                    $result = $this->startElementNS($this->adhocNamespaces[$namespace], $localName, $namespace);
                }
            }

        } else {
            $result = parent::startElement($name);
        }

        if (!$this->namespacesWritten) {

            foreach ($this->namespaceMap as $namespace => $prefix) {
                $this->writeAttribute(($prefix ? 'xmlns:' . $prefix : 'xmlns'), $namespace);
            }
            $this->namespacesWritten = true;

        }

        return $result;

    }

    /**
     * Write a full element tag and it's contents.
     *
     * This method automatically closes the element as well.
     *
     * The element name may be specified in clark-notation.
     *
     * Examples:
     *
     *    $writer->writeElement('{http://www.w3.org/2005/Atom}author',null);
     *    becomes:
     *    <author xmlns="http://www.w3.org/2005" />
     *
     *    $writer->writeElement('{http://www.w3.org/2005/Atom}author', [
     *       '{http://www.w3.org/2005/Atom}name' => 'Evert Pot',
     *    ]);
     *    becomes:
     *    <author xmlns="http://www.w3.org/2005" /><name>Evert Pot</name></author>
     *
     * @param string $name
     * @param string $content
     * @return bool
     */
    function writeElement($name, $content = null) {

        $this->startElement($name);
        if (!is_null($content)) {
            $this->write($content);
        }
        $this->endElement();

    }

    /**
     * Writes a list of attributes.
     *
     * Attributes are specified as a key->value array.
     *
     * The key is an attribute name. If the key is a 'localName', the current
     * xml namespace is assumed. If it's a 'clark notation key', this namespace
     * will be used instead.
     *
     * @param array $attributes
     * @return void
     */
    function writeAttributes(array $attributes) {

        foreach ($attributes as $name => $value) {
            $this->writeAttribute($name, $value);
        }

    }

    /**
     * Writes a new attribute.
     *
     * The name may be specified in clark-notation.
     *
     * Returns true when successful.
     *
     * @param string $name
     * @param string $value
     * @return bool
     */
    function writeAttribute($name, $value) {

        if ($name[0] === '{') {

            list(
                $namespace,
                $localName
            ) = Service::parseClarkNotation($name);

            if (array_key_exists($namespace, $this->namespaceMap)) {
                // It's an attribute with a namespace we know
                $this->writeAttribute(
                    $this->namespaceMap[$namespace] . ':' . $localName,
                    $value
                );
            } else {

                // We don't know the namespace, we must add it in-line
                if (!isset($this->adhocNamespaces[$namespace])) {
                    $this->adhocNamespaces[$namespace] = 'x' . (count($this->adhocNamespaces) + 1);
                }
                $this->writeAttributeNS(
                    $this->adhocNamespaces[$namespace],
                    $localName,
                    $namespace,
                    $value
                );

            }

        } else {
            return parent::writeAttribute($name, $value);
        }

    }

}
<?php

namespace Sabre\Xml;

/**
 * Implementing the XmlDeserializable interface allows you to use a class as a
 * deserializer for a specific element.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface XmlDeserializable {

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statically, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * You are responsible for advancing the reader to the next element. Not
     * doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Reader $reader);

}
<?php

namespace Sabre\Xml;

/**
 * Objects implementing XmlSerializable can control how they are represented in
 * Xml.
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
interface XmlSerializable {

    /**
     * The xmlSerialize method is called during xml writing.
     *
     * Use the $writer argument to write its own xml serialization.
     *
     * An important note: do _not_ create a parent element. Any element
     * implementing XmlSerializble should only ever write what's considered
     * its 'inner xml'.
     *
     * The parent of the current element is responsible for writing a
     * containing element.
     *
     * This allows serializers to be re-used for different element names.
     *
     * If you are opening new elements, you must also close them again.
     *
     * @param Writer $writer
     * @return void
     */
    function xmlSerialize(Writer $writer);

}
Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/)

All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * 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.
    * Neither the name Sabre 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
sabre/xml
=========

[![Build Status](https://secure.travis-ci.org/fruux/sabre-xml.svg?branch=master)](http://travis-ci.org/fruux/sabre-xml)

The sabre/xml library is a specialized XML reader and writer.

Documentation
-------------

* [Introduction](http://sabre.io/xml/).
* [Installation](http://sabre.io/xml/install/).
* [Reading XML](http://sabre.io/xml/reading/).
* [Writing XML](http://sabre.io/xml/writing/).


Support
-------

Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions.

Made at fruux
-------------

This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support.
<?xml version="1.0"?>
<ruleset name="sabre.php">
    <description>sabre.io codesniffer ruleset</description>

     <!-- Include the whole PSR-1 standard -->
     <rule ref="PSR1" />

     <!-- All PHP files MUST use the Unix LF (linefeed) line ending. -->
     <rule ref="Generic.Files.LineEndings">
      <properties>
       <property name="eolChar" value="\n"/>
      </properties>
     </rule>

     <!-- The closing ?> tag MUST be omitted from files containing only PHP. -->
     <rule ref="Zend.Files.ClosingTag"/>

     <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. -->
     <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
      <properties>
       <property name="ignoreBlankLines" value="true"/>
       </properties>
   </rule>

   <!-- There MUST NOT be more than one statement per line. -->
   <rule ref="Generic.Formatting.DisallowMultipleStatements"/>

   <rule ref="Generic.WhiteSpace.ScopeIndent">
      <properties>
       <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/>
      </properties>
   </rule>
   <rule ref="Generic.WhiteSpace.DisallowTabIndent"/>

   <!-- PHP keywords MUST be in lower case. -->
   <rule ref="Generic.PHP.LowerCaseKeyword"/>

   <!-- The PHP constants true, false, and null MUST be in lower case. -->
   <rule ref="Generic.PHP.LowerCaseConstant"/>

   <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/>

   <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. -->
   <!--
   <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing">
    <properties>
     <property name="equalsSpacing" value="1"/>
    </properties>
   </rule>
   <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint">
    <severity>0</severity>
   </rule>
    -->
   <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/>

</ruleset>
<phpunit
  colors="true"
  bootstrap="../vendor/autoload.php"
  convertErrorsToExceptions="true"
  convertNoticesToExceptions="true"
  convertWarningsToExceptions="true"
  >
  <testsuite name="Sabre_XML">
    <directory>Sabre/</directory>
  </testsuite>

  <filter>
    <whitelist addUncoveredFilesFromWhitelist="true">
       <directory suffix=".php">../lib/</directory>
    </whitelist>
  </filter>
</phpunit>
<?php

namespace Sabre\Xml;

/**
 * Test for the ContextStackTrait
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class ContextStackTest extends \PHPUnit_Framework_TestCase {

    function setUp() {

        $this->stack = $this->getMockForTrait('Sabre\\Xml\\ContextStackTrait');

    }

    function testPushAndPull() {

        $this->stack->contextUri = '/foo/bar';
        $this->stack->elementMap['{DAV:}foo'] = 'Bar';
        $this->stack->namespaceMap['DAV:'] = 'd';

        $this->stack->pushContext();

        $this->assertEquals('/foo/bar', $this->stack->contextUri);
        $this->assertEquals('Bar', $this->stack->elementMap['{DAV:}foo']);
        $this->assertEquals('d', $this->stack->namespaceMap['DAV:']);

        $this->stack->contextUri = '/gir/zim';
        $this->stack->elementMap['{DAV:}foo'] = 'newBar';
        $this->stack->namespaceMap['DAV:'] = 'dd';

        $this->stack->popContext();

        $this->assertEquals('/foo/bar', $this->stack->contextUri);
        $this->assertEquals('Bar', $this->stack->elementMap['{DAV:}foo']);
        $this->assertEquals('d', $this->stack->namespaceMap['DAV:']);

    }

}
<?php

namespace Sabre\Xml\Deserializer;

use Sabre\Xml\Service;

class EnumTest extends \PHPUnit_Framework_TestCase {

    function testDeserialize() {

        $service = new Service();
        $service->elementMap['{urn:test}root'] = 'Sabre\Xml\Deserializer\enum';

        $xml = <<<XML
<?xml version="1.0"?>
<root xmlns="urn:test">
   <foo1/>
   <foo2/>
</root>
XML;

        $result = $service->parse($xml);

        $expected = [
            '{urn:test}foo1',
            '{urn:test}foo2',
        ];


        $this->assertEquals($expected, $result);


    }

    function testDeserializeDefaultNamespace() {

        $service = new Service();
        $service->elementMap['{urn:test}root'] = function($reader) {
            return enum($reader, 'urn:test');
        };

        $xml = <<<XML
<?xml version="1.0"?>
<root xmlns="urn:test">
   <foo1/>
   <foo2/>
</root>
XML;

        $result = $service->parse($xml);

        $expected = [
            'foo1',
            'foo2',
        ];


        $this->assertEquals($expected, $result);

    }

}
<?php

namespace Sabre\Xml\Deserializer;

use
    Sabre\Xml\Reader;

class KeyValueTest extends \PHPUnit_Framework_TestCase {

    function testKeyValue() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <struct>
    <elem1 />
    <elem2>hi</elem2>
    <elem3 xmlns="http://sabredav.org/another-ns">
       <elem4>foo</elem4>
       <elem5>foo &amp; bar</elem5>
    </elem3>
  </struct>
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}struct' => function(Reader $reader) {
                return keyValue($reader, 'http://sabredav.org/ns');
            }
        ];
        $reader->xml($input);
        $output = $reader->parse();

        $this->assertEquals([
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'  => '{http://sabredav.org/ns}struct',
                    'value' => [
                        'elem1'                                 => null,
                        'elem2'                                 => 'hi',
                        '{http://sabredav.org/another-ns}elem3' => [
                            [
                                'name'       => '{http://sabredav.org/another-ns}elem4',
                                'value'      => 'foo',
                                'attributes' => [],
                            ],
                            [
                                'name'       => '{http://sabredav.org/another-ns}elem5',
                                'value'      => 'foo & bar',
                                'attributes' => [],
                            ],
                        ]
                    ],
                    'attributes' => [],
                ]
            ],
            'attributes' => [],
        ], $output);
    }

    /**
     * @expectedException \Sabre\Xml\LibXMLException
     */
    function testKeyValueLoop() {

        /**
         * This bug is a weird one, because it triggers an infinite loop, but
         * only if the XML document is a certain size (in bytes). Removing one
         * or two characters from the xml body here cause the infinite loop to
         * *not* get triggered, so to properly test this bug (Issue #94), don't
         * change the XML body.
         */
        $invalid_xml = '
        <foo ft="PRNTING" Ppt="YES" AutoClose="YES" SkipUnverified="NO" Test="NO">
            <Package ID="1">
                <MailClass>NONE</MailClass>
                <PackageType>ENVELOPE</PackageType>
                <WeightOz>1</WeightOz>
                <FleetType>DC</FleetType>
            <Package ID="2">
                <MailClass>NONE</MailClass>
                <PackageType>ENVELOPE</PackageType>
                <WeightOz>1</WeightOz>
                <FleetType>DC/FleetType>
            </Package>
        </foo>';
        $reader = new Reader();

        $reader->xml($invalid_xml);
        $reader->elementMap = [

            '{}Package' => function($reader) {
                $recipient = [];
                // Borrowing a parser from the KeyValue class.
                $keyValue = keyValue($reader);

                if (isset($keyValue['{}WeightOz'])){
                    $recipient['referenceId'] = $keyValue['{}WeightOz'];
                }

                return $recipient;
            },
        ];

        $reader->parse();


    }

}
<?php

namespace Sabre\Xml\Deserializer;

use Sabre\Xml\Service;

class RepeatingElementsTest extends \PHPUnit_Framework_TestCase {

    function testRead() {

        $service = new Service();
        $service->elementMap['{urn:test}collection'] = function($reader) {
            return repeatingElements($reader, '{urn:test}item');
        };

        $xml = <<<XML
<?xml version="1.0"?>
<collection xmlns="urn:test">
    <item>foo</item>
    <item>bar</item>
</collection>
XML;

        $result = $service->parse($xml);

        $expected = [
            'foo',
            'bar',
        ];

        $this->assertEquals($expected, $result);

    }

}
<?php

namespace Sabre\XML\Deserializer;

use
    Sabre\Xml\Reader;

class ValueObjectTest extends \PHPUnit_Framework_TestCase {

    function testDeserializeValueObject() {

        $input = <<<XML
<?xml version="1.0"?>
<foo xmlns="urn:foo">
   <firstName>Harry</firstName>
   <lastName>Turtle</lastName>
</foo>
XML;

        $reader = new Reader();
        $reader->xml($input);
        $reader->elementMap = [
            '{urn:foo}foo' => function(Reader $reader) {
                return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo');
            }
        ];

        $output = $reader->parse();

        $vo = new TestVo();
        $vo->firstName = 'Harry';
        $vo->lastName = 'Turtle';

        $expected = [
            'name'       => '{urn:foo}foo',
            'value'      => $vo,
            'attributes' => []
        ];

        $this->assertEquals(
            $expected,
            $output
        );

    }

    function testDeserializeValueObjectIgnoredElement() {

        $input = <<<XML
<?xml version="1.0"?>
<foo xmlns="urn:foo">
   <firstName>Harry</firstName>
   <lastName>Turtle</lastName>
   <email>harry@example.org</email>
</foo>
XML;

        $reader = new Reader();
        $reader->xml($input);
        $reader->elementMap = [
            '{urn:foo}foo' => function(Reader $reader) {
                return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo');
            }
        ];

        $output = $reader->parse();

        $vo = new TestVo();
        $vo->firstName = 'Harry';
        $vo->lastName = 'Turtle';

        $expected = [
            'name'       => '{urn:foo}foo',
            'value'      => $vo,
            'attributes' => []
        ];

        $this->assertEquals(
            $expected,
            $output
        );

    }

    function testDeserializeValueObjectAutoArray() {

        $input = <<<XML
<?xml version="1.0"?>
<foo xmlns="urn:foo">
   <firstName>Harry</firstName>
   <lastName>Turtle</lastName>
   <link>http://example.org/</link>
   <link>http://example.net/</link>
</foo>
XML;

        $reader = new Reader();
        $reader->xml($input);
        $reader->elementMap = [
            '{urn:foo}foo' => function(Reader $reader) {
                return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo');
            }
        ];

        $output = $reader->parse();

        $vo = new TestVo();
        $vo->firstName = 'Harry';
        $vo->lastName = 'Turtle';
        $vo->link = [
            'http://example.org/',
            'http://example.net/',
        ];


        $expected = [
            'name'       => '{urn:foo}foo',
            'value'      => $vo,
            'attributes' => []
        ];

        $this->assertEquals(
            $expected,
            $output
        );

    }
    function testDeserializeValueObjectEmpty() {

        $input = <<<XML
<?xml version="1.0"?>
<foo xmlns="urn:foo" />
XML;

        $reader = new Reader();
        $reader->xml($input);
        $reader->elementMap = [
            '{urn:foo}foo' => function(Reader $reader) {
                return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo');
            }
        ];

        $output = $reader->parse();

        $vo = new TestVo();

        $expected = [
            'name'       => '{urn:foo}foo',
            'value'      => $vo,
            'attributes' => []
        ];

        $this->assertEquals(
            $expected,
            $output
        );

    }

}

class TestVo {

    public $firstName;
    public $lastName;

    public $link = [];

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

class CDataTest extends \PHPUnit_Framework_TestCase {

    /**
     * @expectedException \LogicException
     */
    function testDeserialize() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
 <blabla />
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}blabla' => 'Sabre\\Xml\\Element\\Cdata',
        ];
        $reader->xml($input);

        $output = $reader->parse();

    }

    function testSerialize() {

        $writer = new Writer();
        $writer->namespaceMap = [
            'http://sabredav.org/ns' => null
        ];
        $writer->openMemory();
        $writer->startDocument('1.0');
        $writer->setIndent(true);
        $writer->write([
            '{http://sabredav.org/ns}root' => new Cdata('<foo&bar>'),
        ]);

        $output = $writer->outputMemory();

        $expected = <<<XML
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns"><![CDATA[<foo&bar>]]></root>

XML;

        $this->assertEquals($expected, $output);


    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml;

/**
 * The intention for this reader class, is to read past the end element. This
 * should trigger a ParseException
 *
 * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Eater implements Xml\Element {

    /**
     * The serialize method is called during xml writing.
     *
     * It should use the $writer argument to encode this object into Xml.
     *
     * Important note: it is not needed to create the parent element. The
     * parent element is already created, and we only have to worry about
     * attributes, child elements and text (if any).
     *
     * Important note 2: If you are writing any new elements, you are also
     * responsible for closing them.
     *
     * @param Xml\Writer $writer
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $writer->startElement('{http://sabredav.org/ns}elem1');
        $writer->write('hiiii!');
        $writer->endElement();

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statictly, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseSubTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Xml\Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Xml\Reader $reader) {

        $reader->next();

        $count = 1;
        while ($count) {

            $reader->read();
            if ($reader->nodeType === $reader::END_ELEMENT) {
                $count--;
            }

        }
        $reader->read();

    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

class ElementsTest extends \PHPUnit_Framework_TestCase {

    function testDeserialize() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <listThingy>
    <elem1 />
    <elem2 />
    <elem3 />
    <elem4 attr="val" />
    <elem5>content</elem5>
    <elem6><subnode /></elem6>
  </listThingy>
  <listThingy />
  <otherThing>
    <elem1 />
    <elem2 />
    <elem3 />
  </otherThing>
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}listThingy' => 'Sabre\\Xml\\Element\\Elements',
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $this->assertEquals([
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'  => '{http://sabredav.org/ns}listThingy',
                    'value' => [
                        '{http://sabredav.org/ns}elem1',
                        '{http://sabredav.org/ns}elem2',
                        '{http://sabredav.org/ns}elem3',
                        '{http://sabredav.org/ns}elem4',
                        '{http://sabredav.org/ns}elem5',
                        '{http://sabredav.org/ns}elem6',
                    ],
                    'attributes' => [],
                ],
                [
                    'name'       => '{http://sabredav.org/ns}listThingy',
                    'value'      => [],
                    'attributes' => [],
                ],
                [
                    'name'  => '{http://sabredav.org/ns}otherThing',
                    'value' => [
                        [
                            'name'       => '{http://sabredav.org/ns}elem1',
                            'value'      => null,
                            'attributes' => [],
                        ],
                        [
                            'name'       => '{http://sabredav.org/ns}elem2',
                            'value'      => null,
                            'attributes' => [],
                        ],
                        [
                            'name'       => '{http://sabredav.org/ns}elem3',
                            'value'      => null,
                            'attributes' => [],
                        ],
                    ],
                    'attributes' => [],
                ],
            ],
            'attributes' => [],
        ], $output);

    }

    function testSerialize() {

        $value = [
            '{http://sabredav.org/ns}elem1',
            '{http://sabredav.org/ns}elem2',
            '{http://sabredav.org/ns}elem3',
            '{http://sabredav.org/ns}elem4',
            '{http://sabredav.org/ns}elem5',
            '{http://sabredav.org/ns}elem6',
        ];

        $writer = new Writer();
        $writer->namespaceMap = [
            'http://sabredav.org/ns' => null
        ];
        $writer->openMemory();
        $writer->startDocument('1.0');
        $writer->setIndent(true);
        $writer->write([
            '{http://sabredav.org/ns}root' => new Elements($value),
        ]);

        $output = $writer->outputMemory();

        $expected = <<<XML
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
 <elem1/>
 <elem2/>
 <elem3/>
 <elem4/>
 <elem5/>
 <elem6/>
</root>

XML;

        $this->assertEquals($expected, $output);


    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

class KeyValueTest extends \PHPUnit_Framework_TestCase {

    function testDeserialize() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <struct>
    <elem1 />
    <elem2>hi</elem2>
    <elem3>
       <elem4>foo</elem4>
       <elem5>foo &amp; bar</elem5>
    </elem3>
    <elem6>Hi<!-- ignore me -->there</elem6>
  </struct>
  <struct />
  <otherThing>
    <elem1 />
  </otherThing>
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}struct' => 'Sabre\\Xml\\Element\\KeyValue',
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $this->assertEquals([
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'  => '{http://sabredav.org/ns}struct',
                    'value' => [
                        '{http://sabredav.org/ns}elem1' => null,
                        '{http://sabredav.org/ns}elem2' => 'hi',
                        '{http://sabredav.org/ns}elem3' => [
                            [
                                'name'       => '{http://sabredav.org/ns}elem4',
                                'value'      => 'foo',
                                'attributes' => [],
                            ],
                            [
                                'name'       => '{http://sabredav.org/ns}elem5',
                                'value'      => 'foo & bar',
                                'attributes' => [],
                            ],
                        ],
                        '{http://sabredav.org/ns}elem6' => 'Hithere',
                    ],
                    'attributes' => [],
                ],
                [
                    'name'       => '{http://sabredav.org/ns}struct',
                    'value'      => [],
                    'attributes' => [],
                ],
                [
                    'name'  => '{http://sabredav.org/ns}otherThing',
                    'value' => [
                        [
                            'name'       => '{http://sabredav.org/ns}elem1',
                            'value'      => null,
                            'attributes' => [],
                        ],
                    ],
                    'attributes' => [],
                ],
            ],
            'attributes' => [],
        ], $output);

    }

    /**
     * This test was added to find out why an element gets eaten by the
     * SabreDAV MKCOL parser.
     */
    function testElementEater() {

        $input = <<<BLA
<?xml version="1.0"?>
<mkcol xmlns="DAV:">
  <set>
    <prop>
        <resourcetype><collection /></resourcetype>
        <displayname>bla</displayname>
    </prop>
  </set>
</mkcol>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{DAV:}set'          => 'Sabre\\Xml\\Element\\KeyValue',
            '{DAV:}prop'         => 'Sabre\\Xml\\Element\\KeyValue',
            '{DAV:}resourcetype' => 'Sabre\\Xml\\Element\\Elements',
        ];
        $reader->xml($input);

        $expected = [
            'name'  => '{DAV:}mkcol',
            'value' => [
                [
                    'name'  => '{DAV:}set',
                    'value' => [
                        '{DAV:}prop' => [
                            '{DAV:}resourcetype' => [
                                '{DAV:}collection',
                            ],
                            '{DAV:}displayname' => 'bla',
                        ],
                    ],
                    'attributes' => [],
                ],
            ],
            'attributes' => [],
        ];

        $this->assertEquals($expected, $reader->parse());

    }


    function testSerialize() {

        $value = [
            '{http://sabredav.org/ns}elem1' => null,
            '{http://sabredav.org/ns}elem2' => 'textValue',
            '{http://sabredav.org/ns}elem3' => [
                '{http://sabredav.org/ns}elem4' => 'text2',
                '{http://sabredav.org/ns}elem5' => null,
            ],
            '{http://sabredav.org/ns}elem6' => 'text3',
        ];

        $writer = new Writer();
        $writer->namespaceMap = [
            'http://sabredav.org/ns' => null
        ];
        $writer->openMemory();
        $writer->startDocument('1.0');
        $writer->setIndent(true);
        $writer->write([
            '{http://sabredav.org/ns}root' => new KeyValue($value),
        ]);

        $output = $writer->outputMemory();

        $expected = <<<XML
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
 <elem1/>
 <elem2>textValue</elem2>
 <elem3>
  <elem4>text2</elem4>
  <elem5/>
 </elem3>
 <elem6>text3</elem6>
</root>

XML;

        $this->assertEquals($expected, $output);

    }

    /**
     * I discovered that when there's no whitespace between elements, elements
     * can get skipped.
     */
    function testElementSkipProblem() {

        $input = <<<BLA
<?xml version="1.0" encoding="utf-8"?>
<root xmlns="http://sabredav.org/ns">
<elem3>val3</elem3><elem4>val4</elem4><elem5>val5</elem5></root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}root' => 'Sabre\\Xml\\Element\\KeyValue',
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $this->assertEquals([
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                '{http://sabredav.org/ns}elem3' => 'val3',
                '{http://sabredav.org/ns}elem4' => 'val4',
                '{http://sabredav.org/ns}elem5' => 'val5',
            ],
            'attributes' => [],
        ], $output);

    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml;

class Mock implements Xml\Element {

    /**
     * The serialize method is called during xml writing.
     *
     * It should use the $writer argument to encode this object into XML.
     *
     * Important note: it is not needed to create the parent element. The
     * parent element is already created, and we only have to worry about
     * attributes, child elements and text (if any).
     *
     * Important note 2: If you are writing any new elements, you are also
     * responsible for closing them.
     *
     * @param Xml\Writer $writer
     * @return void
     */
    function xmlSerialize(Xml\Writer $writer) {

        $writer->startElement('{http://sabredav.org/ns}elem1');
        $writer->write('hiiii!');
        $writer->endElement();

    }

    /**
     * The deserialize method is called during xml parsing.
     *
     * This method is called statictly, this is because in theory this method
     * may be used as a type of constructor, or factory method.
     *
     * Often you want to return an instance of the current class, but you are
     * free to return other data as well.
     *
     * Important note 2: You are responsible for advancing the reader to the
     * next element. Not doing anything will result in a never-ending loop.
     *
     * If you just want to skip parsing for this element altogether, you can
     * just call $reader->next();
     *
     * $reader->parseSubTree() will parse the entire sub-tree, and advance to
     * the next element.
     *
     * @param Xml\Reader $reader
     * @return mixed
     */
    static function xmlDeserialize(Xml\Reader $reader) {

        $reader->next();
        return 'foobar';

    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

class UriTest extends \PHPUnit_Framework_TestCase {

    function testDeserialize() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <uri>/foo/bar</uri>
</root>
BLA;

        $reader = new Reader();
        $reader->contextUri = 'http://example.org/';
        $reader->elementMap = [
            '{http://sabredav.org/ns}uri' => 'Sabre\\Xml\\Element\\Uri',
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $this->assertEquals(
            [
                'name'  => '{http://sabredav.org/ns}root',
                'value' => [
                    [
                        'name'       => '{http://sabredav.org/ns}uri',
                        'value'      => new Uri('http://example.org/foo/bar'),
                        'attributes' => [],
                    ]
                ],
                'attributes' => [],
            ],
            $output
        );

    }

    function testSerialize() {

        $writer = new Writer();
        $writer->namespaceMap = [
            'http://sabredav.org/ns' => null
        ];
        $writer->openMemory();
        $writer->startDocument('1.0');
        $writer->setIndent(true);
        $writer->contextUri = 'http://example.org/';
        $writer->write([
            '{http://sabredav.org/ns}root' => [
                '{http://sabredav.org/ns}uri' => new Uri('/foo/bar'),
            ]
        ]);

        $output = $writer->outputMemory();

        $expected = <<<XML
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
 <uri>http://example.org/foo/bar</uri>
</root>

XML;

        $this->assertEquals($expected, $output);


    }

}
<?php

namespace Sabre\Xml\Element;

use Sabre\Xml\Reader;
use Sabre\Xml\Writer;

class XmlFragmentTest extends \PHPUnit_Framework_TestCase {

    /**
     * @dataProvider xmlProvider
     */
    function testDeserialize($input, $expected) {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
   <fragment>$input</fragment>
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}fragment' => 'Sabre\\Xml\\Element\\XmlFragment',
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $this->assertEquals([
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'       => '{http://sabredav.org/ns}fragment',
                    'value'      => new XmlFragment($expected),
                    'attributes' => [],
                ],
            ],
            'attributes' => [],
        ], $output);

    }

    /**
     * Data provider for serialize and deserialize tests.
     *
     * Returns three items per test:
     *
     * 1. Input data for the reader.
     * 2. Expected output for XmlFragment deserializer
     * 3. Expected output after serializing that value again.
     *
     * If 3 is not set, use 1 for 3.
     *
     * @return void
     */
    function xmlProvider() {

        return [
            [
                'hello',
                'hello',
            ],
            [
                '<element>hello</element>',
                '<element xmlns="http://sabredav.org/ns">hello</element>'
            ],
            [
                '<element foo="bar">hello</element>',
                '<element xmlns="http://sabredav.org/ns" foo="bar">hello</element>'
            ],
            [
                '<element x1:foo="bar" xmlns:x1="http://example.org/ns">hello</element>',
                '<element xmlns:x1="http://example.org/ns" xmlns="http://sabredav.org/ns" x1:foo="bar">hello</element>'
            ],
            [
                '<element xmlns="http://example.org/ns">hello</element>',
                '<element xmlns="http://example.org/ns">hello</element>',
                '<x1:element xmlns:x1="http://example.org/ns">hello</x1:element>',
            ],
            [
                '<element xmlns:foo="http://example.org/ns">hello</element>',
                '<element xmlns:foo="http://example.org/ns" xmlns="http://sabredav.org/ns">hello</element>',
                '<element>hello</element>',
            ],
            [
                '<foo:element xmlns:foo="http://example.org/ns">hello</foo:element>',
                '<foo:element xmlns:foo="http://example.org/ns">hello</foo:element>',
                '<x1:element xmlns:x1="http://example.org/ns">hello</x1:element>',
            ],
            [
                '<foo:element xmlns:foo="http://example.org/ns"><child>hello</child></foo:element>',
                '<foo:element xmlns:foo="http://example.org/ns" xmlns="http://sabredav.org/ns"><child>hello</child></foo:element>',
                '<x1:element xmlns:x1="http://example.org/ns"><child>hello</child></x1:element>',
            ],
            [
                '<foo:element xmlns:foo="http://example.org/ns"><child/></foo:element>',
                '<foo:element xmlns:foo="http://example.org/ns" xmlns="http://sabredav.org/ns"><child/></foo:element>',
                '<x1:element xmlns:x1="http://example.org/ns"><child/></x1:element>',
            ],
            [
                '<foo:element xmlns:foo="http://example.org/ns"><child a="b"/></foo:element>',
                '<foo:element xmlns:foo="http://example.org/ns" xmlns="http://sabredav.org/ns"><child a="b"/></foo:element>',
                '<x1:element xmlns:x1="http://example.org/ns"><child a="b"/></x1:element>',
            ],
        ];

    }

    /**
     * @dataProvider xmlProvider
     */
    function testSerialize($expectedFallback, $input, $expected = null) {

        if (is_null($expected)) {
            $expected = $expectedFallback;
        }

        $writer = new Writer();
        $writer->namespaceMap = [
            'http://sabredav.org/ns' => null
        ];
        $writer->openMemory();
        $writer->startDocument('1.0');
        //$writer->setIndent(true);
        $writer->write([
            '{http://sabredav.org/ns}root' => [
                '{http://sabredav.org/ns}fragment' => new XmlFragment($input),
            ],
        ]);

        $output = $writer->outputMemory();

        $expected = <<<XML
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns"><fragment>$expected</fragment></root>
XML;

        $this->assertEquals($expected, $output);

    }

}
<?php

namespace Sabre\Xml;

class InfiteLoopTest extends \PHPUnit_Framework_TestCase {

    /**
     * This particular xml body caused the parser to go into an infinite loop.
     * Need to know why.
     */
    function testDeserialize() {

        $body = '<?xml version="1.0"?>
<d:propertyupdate xmlns:d="DAV:" xmlns:s="http://sabredav.org/NS/test">
  <d:set><d:prop></d:prop></d:set>
  <d:set><d:prop></d:prop></d:set>
</d:propertyupdate>';

        $reader = new Reader();
        $reader->elementMap = [
            '{DAV:}set' => 'Sabre\\Xml\\Element\\KeyValue',
        ];
        $reader->xml($body);

        $output = $reader->parse();

        $this->assertEquals([
            'name'  => '{DAV:}propertyupdate',
            'value' => [
                [
                    'name'  => '{DAV:}set',
                    'value' => [
                        '{DAV:}prop' => null,
                    ],
                    'attributes' => [],
                ],
                [
                    'name'  => '{DAV:}set',
                    'value' => [
                        '{DAV:}prop' => null,
                    ],
                    'attributes' => [],
                ],
            ],
            'attributes' => [],
        ], $output);

    }

}
<?php

namespace Sabre\Xml;

class ReaderTest extends \PHPUnit_Framework_TestCase {

    function testGetClark() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns" />
BLA;
        $reader = new Reader();
        $reader->xml($input);

        $reader->next();

        $this->assertEquals('{http://sabredav.org/ns}root', $reader->getClark());

    }

    function testGetClarkNoNS() {

        $input = <<<BLA
<?xml version="1.0"?>
<root />
BLA;
        $reader = new Reader();
        $reader->xml($input);

        $reader->next();

        $this->assertEquals('{}root', $reader->getClark());

    }

    function testGetClarkNotOnAnElement() {

        $input = <<<BLA
<?xml version="1.0"?>
<root />
BLA;
        $reader = new Reader();
        $reader->xml($input);

        $this->assertNull($reader->getClark());
    }

    function testSimple() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <elem1 attr="val" />
  <elem2>
    <elem3>Hi!</elem3>
  </elem2>
</root>
BLA;

        $reader = new Reader();
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'value'      => null,
                    'attributes' => [
                        'attr' => 'val',
                    ],
                ],
                [
                    'name'  => '{http://sabredav.org/ns}elem2',
                    'value' => [
                        [
                            'name'       => '{http://sabredav.org/ns}elem3',
                            'value'      => 'Hi!',
                            'attributes' => [],
                        ],
                    ],
                    'attributes' => [],
                ],

            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }

    function testCDATA() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <foo><![CDATA[bar]]></foo>
</root>
BLA;

        $reader = new Reader();
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'       => '{http://sabredav.org/ns}foo',
                    'value'      => 'bar',
                    'attributes' => [],
                ],

            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }

    function testSimpleNamespacedAttribute() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns" xmlns:foo="urn:foo">
  <elem1 foo:attr="val" />
</root>
BLA;

        $reader = new Reader();
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'value'      => null,
                    'attributes' => [
                        '{urn:foo}attr' => 'val',
                    ],
                ],
            ],
            'attributes' => [],
        ];

        $this->assertEquals($expected, $output);

    }

    function testMappedElement() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <elem1 />
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Mock'
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'value'      => 'foobar',
                    'attributes' => [],
                ],
            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }

    /**
     * @expectedException \LogicException
     */
    function testMappedElementBadClass() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <elem1 />
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => new \StdClass()
        ];
        $reader->xml($input);

        $reader->parse();
    }

    /**
     * @depends testMappedElement
     */
    function testMappedElementCallBack() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <elem1 />
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => function(Reader $reader) {
                $reader->next();
                return 'foobar';
            }
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'value'      => 'foobar',
                    'attributes' => [],
                ],
            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }

    /**
     * @depends testMappedElementCallBack
     */
    function testMappedElementCallBackNoNamespace() {

        $input = <<<BLA
<?xml version="1.0"?>
<root>
  <elem1 />
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            'elem1' => function(Reader $reader) {
                $reader->next();
                return 'foobar';
            }
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{}root',
            'value' => [
                [
                    'name'       => '{}elem1',
                    'value'      => 'foobar',
                    'attributes' => [],
                ],
            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }

    /**
     * @depends testMappedElementCallBack
     */
    function testReadText() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <elem1>
    <elem2>hello </elem2>
    <elem2>world</elem2>
  </elem1>
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => function(Reader $reader) {
                return $reader->readText();
            }
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'value'      => 'hello world',
                    'attributes' => [],
                ],
            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }

    function testParseProblem() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Mock'
        ];
        $reader->xml($input);

        try {
            $output = $reader->parse();
            $this->fail('We expected a ParseException to be thrown');
        } catch (LibXMLException $e) {

            $this->assertInternalType('array', $e->getErrors());

        }

    }

    /**
     * @expectedException \Sabre\Xml\ParseException
     */
    function testBrokenParserClass() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
<elem1 />
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Eater'
        ];
        $reader->xml($input);
        $reader->parse();


    }

    /**
     * Test was added for Issue #10.
     *
     * @expectedException Sabre\Xml\LibXMLException
     */
    function testBrokenXml() {

        $input = <<<BLA
<test>
<hello>
</hello>
</sffsdf>
BLA;

        $reader = new Reader();
        $reader->xml($input);
        $reader->parse();

    }

    /**
     * Test was added for Issue #45.
     *
     * @expectedException Sabre\Xml\LibXMLException
     */
    function testBrokenXml2() {

        $input = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<definitions>
    <collaboration>
        <participant id="sid-A33D08EB-A2DE-448F-86FE-A2B62E98818" name="Company" processRef="sid-A0A6A196-3C9A-4C69-88F6-7ED7DDFDD264">
            <extensionElements>
                <signavio:signavioMetaData metaKey="bgcolor" />
                ""Administrative w">
                <extensionElements>
                    <signavio:signavioMetaData metaKey="bgcolor" metaValue=""/>
                </extensionElements>
                </lan
XML;
        $reader = new Reader();
        $reader->xml($input);
        $reader->parse();

    }


    /**
     * @depends testMappedElement
     */
    function testParseInnerTree() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <elem1>
     <elem1 />
  </elem1>
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => function(Reader $reader) {

                $innerTree = $reader->parseInnerTree(['{http://sabredav.org/ns}elem1' => function(Reader $reader) {
                    $reader->next();
                    return "foobar";
                }]);

                return $innerTree;
            }
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'  => '{http://sabredav.org/ns}elem1',
                    'value' => [
                        [
                            'name'       => '{http://sabredav.org/ns}elem1',
                            'value'      => 'foobar',
                            'attributes' => [],
                        ]
                    ],
                    'attributes' => [],
                ],
            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }

    /**
     * @depends testParseInnerTree
     */
    function testParseGetElements() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <elem1>
     <elem1 />
  </elem1>
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => function(Reader $reader) {

                $innerTree = $reader->parseGetElements(['{http://sabredav.org/ns}elem1' => function(Reader $reader) {
                    $reader->next();
                    return "foobar";
                }]);

                return $innerTree;
            }
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'  => '{http://sabredav.org/ns}elem1',
                    'value' => [
                        [
                            'name'       => '{http://sabredav.org/ns}elem1',
                            'value'      => 'foobar',
                            'attributes' => [],
                        ]
                    ],
                    'attributes' => [],
                ],
            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }

    /**
     * @depends testParseInnerTree
     */
    function testParseGetElementsNoElements() {

        $input = <<<BLA
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
  <elem1>
    hi
  </elem1>
</root>
BLA;

        $reader = new Reader();
        $reader->elementMap = [
            '{http://sabredav.org/ns}elem1' => function(Reader $reader) {

                $innerTree = $reader->parseGetElements(['{http://sabredav.org/ns}elem1' => function(Reader $reader) {
                    $reader->next();
                    return "foobar";
                }]);

                return $innerTree;
            }
        ];
        $reader->xml($input);

        $output = $reader->parse();

        $expected = [
            'name'  => '{http://sabredav.org/ns}root',
            'value' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'value'      => [],
                    'attributes' => [],
                ],
            ],
            'attributes' => [],

        ];

        $this->assertEquals($expected, $output);

    }


}
<?php

namespace Sabre\Xml\Serializer;

use Sabre\Xml\Service;

class EnumTest extends \PHPUnit_Framework_TestCase {

    function testSerialize() {

        $service = new Service();
        $service->namespaceMap['urn:test'] = null;

        $xml = $service->write('{urn:test}root', function($writer) {
            enum($writer, [
                '{urn:test}foo1',
                '{urn:test}foo2',
            ]);
        });

        $expected = <<<XML
<?xml version="1.0"?>
<root xmlns="urn:test">
   <foo1/>
   <foo2/>
</root>
XML;


        $this->assertXmlStringEqualsXmlString($expected, $xml);


    }


}
<?php

namespace Sabre\Xml\Serializer;

use Sabre\Xml\Service;

class RepeatingElementsTest extends \PHPUnit_Framework_TestCase {

    function testSerialize() {

        $service = new Service();
        $service->namespaceMap['urn:test'] = null;
        $xml = $service->write('{urn:test}collection', function($writer) {
            repeatingElements($writer, [
                'foo',
                'bar',
            ], '{urn:test}item');
        });

        $expected = <<<XML
<?xml version="1.0"?>
<collection xmlns="urn:test">
   <item>foo</item>
   <item>bar</item>
</collection>
XML;


        $this->assertXmlStringEqualsXmlString($expected, $xml);


    }


}
<?php

namespace Sabre\Xml;

class ServiceTest extends \PHPUnit_Framework_TestCase {

    function testGetReader() {

        $elems = [
            '{http://sabre.io/ns}test' => 'Test!',
        ];

        $util = new Service();
        $util->elementMap = $elems;

        $reader = $util->getReader();
        $this->assertInstanceOf('Sabre\\Xml\\Reader', $reader);
        $this->assertEquals($elems, $reader->elementMap);

    }

    function testGetWriter() {

        $ns = [
            'http://sabre.io/ns' => 's',
        ];

        $util = new Service();
        $util->namespaceMap = $ns;

        $writer = $util->getWriter();
        $this->assertInstanceOf('Sabre\\Xml\\Writer', $writer);
        $this->assertEquals($ns, $writer->namespaceMap);

    }

    /**
     * @depends testGetReader
     */
    function testParse() {

        $xml = <<<XML
<root xmlns="http://sabre.io/ns">
  <child>value</child>
</root>
XML;
        $util = new Service();
        $result = $util->parse($xml, null, $rootElement);
        $this->assertEquals('{http://sabre.io/ns}root', $rootElement);

        $expected = [
            [
                'name'       => '{http://sabre.io/ns}child',
                'value'      => 'value',
                'attributes' => [],
            ]
        ];

        $this->assertEquals(
            $expected,
            $result
        );

    }

    /**
     * @depends testGetReader
     */
    function testParseStream() {

        $xml = <<<XML
<root xmlns="http://sabre.io/ns">
  <child>value</child>
</root>
XML;
        $stream = fopen('php://memory', 'r+');
        fwrite($stream, $xml);
        rewind($stream);

        $util = new Service();
        $result = $util->parse($stream, null, $rootElement);
        $this->assertEquals('{http://sabre.io/ns}root', $rootElement);

        $expected = [
            [
                'name'       => '{http://sabre.io/ns}child',
                'value'      => 'value',
                'attributes' => [],
            ]
        ];

        $this->assertEquals(
            $expected,
            $result
        );

    }

    /**
     * @depends testGetReader
     */
    function testExpect() {

        $xml = <<<XML
<root xmlns="http://sabre.io/ns">
  <child>value</child>
</root>
XML;
        $util = new Service();
        $result = $util->expect('{http://sabre.io/ns}root', $xml);

        $expected = [
            [
                'name'       => '{http://sabre.io/ns}child',
                'value'      => 'value',
                'attributes' => [],
            ]
        ];

        $this->assertEquals(
            $expected,
            $result
        );
    }

    /**
     * @depends testGetReader
     */
    function testExpectStream() {

        $xml = <<<XML
<root xmlns="http://sabre.io/ns">
  <child>value</child>
</root>
XML;

        $stream = fopen('php://memory', 'r+');
        fwrite($stream, $xml);
        rewind($stream);

        $util = new Service();
        $result = $util->expect('{http://sabre.io/ns}root', $stream);

        $expected = [
            [
                'name'       => '{http://sabre.io/ns}child',
                'value'      => 'value',
                'attributes' => [],
            ]
        ];

        $this->assertEquals(
            $expected,
            $result
        );
    }

    /**
     * @depends testGetReader
     * @expectedException \Sabre\Xml\ParseException
     */
    function testExpectWrong() {

        $xml = <<<XML
<root xmlns="http://sabre.io/ns">
  <child>value</child>
</root>
XML;
        $util = new Service();
        $util->expect('{http://sabre.io/ns}error', $xml);

    }

    /**
     * @depends testGetWriter
     */
    function testWrite() {

        $util = new Service();
        $util->namespaceMap = [
            'http://sabre.io/ns' => 's',
        ];
        $result = $util->write('{http://sabre.io/ns}root', [
            '{http://sabre.io/ns}child' => 'value',
        ]);

        $expected = <<<XML
<?xml version="1.0"?>
<s:root xmlns:s="http://sabre.io/ns">
 <s:child>value</s:child>
</s:root>

XML;
        $this->assertEquals(
            $expected,
            $result
        );

    }

    function testMapValueObject() {

        $input = <<<XML
<?xml version="1.0"?>
<order xmlns="http://sabredav.org/ns">
 <id>1234</id>
 <amount>99.99</amount>
 <description>black friday deal</description>
 <status>
  <id>5</id>
  <label>processed</label>
 </status>
</order>

XML;

        $ns = 'http://sabredav.org/ns';
        $orderService = new \Sabre\Xml\Service();
        $orderService->mapValueObject('{' . $ns . '}order', 'Sabre\Xml\Order');
        $orderService->mapValueObject('{' . $ns . '}status', 'Sabre\Xml\OrderStatus');
        $orderService->namespaceMap[$ns] = null;

        $order = $orderService->parse($input);
        $expected = new Order();
        $expected->id = 1234;
        $expected->amount = 99.99;
        $expected->description = 'black friday deal';
        $expected->status = new OrderStatus();
        $expected->status->id = 5;
        $expected->status->label = 'processed';

        $this->assertEquals($expected, $order);

        $writtenXml = $orderService->writeValueObject($order);
        $this->assertEquals($input, $writtenXml);
    }

    function testMapValueObjectArrayProperty() {

        $input = <<<XML
<?xml version="1.0"?>
<order xmlns="http://sabredav.org/ns">
 <id>1234</id>
 <amount>99.99</amount>
 <description>black friday deal</description>
 <status>
  <id>5</id>
  <label>processed</label>
 </status>
 <link>http://example.org/</link>
 <link>http://example.com/</link>
</order>

XML;

        $ns = 'http://sabredav.org/ns';
        $orderService = new \Sabre\Xml\Service();
        $orderService->mapValueObject('{' . $ns . '}order', 'Sabre\Xml\Order');
        $orderService->mapValueObject('{' . $ns . '}status', 'Sabre\Xml\OrderStatus');
        $orderService->namespaceMap[$ns] = null;

        $order = $orderService->parse($input);
        $expected = new Order();
        $expected->id = 1234;
        $expected->amount = 99.99;
        $expected->description = 'black friday deal';
        $expected->status = new OrderStatus();
        $expected->status->id = 5;
        $expected->status->label = 'processed';
        $expected->link = ['http://example.org/', 'http://example.com/'];

        $this->assertEquals($expected, $order);

        $writtenXml = $orderService->writeValueObject($order);
        $this->assertEquals($input, $writtenXml);
    }

    /**
     * @expectedException \InvalidArgumentException
     */
    function testWriteVoNotFound() {

        $service = new Service();
        $service->writeValueObject(new \StdClass());

    }

    function testParseClarkNotation() {

        $this->assertEquals([
            'http://sabredav.org/ns',
            'elem',
        ], Service::parseClarkNotation('{http://sabredav.org/ns}elem'));

    }

    /**
     * @expectedException \InvalidArgumentException
     */
    function testParseClarkNotationFail() {

        Service::parseClarkNotation('http://sabredav.org/ns}elem');

    }

}

/**
 * asset for testMapValueObject()
 * @internal
 */
class Order {
    public $id;
    public $amount;
    public $description;
    public $status;
    public $empty;
    public $link = [];
}

/**
 * asset for testMapValueObject()
 * @internal
 */
class OrderStatus {
    public $id;
    public $label;
}
<?php

namespace Sabre\Xml;

class WriterTest extends \PHPUnit_Framework_TestCase {

    protected $writer;

    function setUp() {

        $this->writer = new Writer();
        $this->writer->namespaceMap = [
            'http://sabredav.org/ns' => 's',
        ];
        $this->writer->openMemory();
        $this->writer->setIndent(true);
        $this->writer->startDocument();

    }

    function compare($input, $output) {

        $this->writer->write($input);
        $this->assertEquals($output, $this->writer->outputMemory());

    }


    function testSimple() {

        $this->compare([
            '{http://sabredav.org/ns}root' => 'text',
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">text</s:root>

HI
        );

    }

    /**
     * @depends testSimple
     */
    function testSimpleQuotes() {

        $this->compare([
            '{http://sabredav.org/ns}root' => '"text"',
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">&quot;text&quot;</s:root>

HI
        );

    }

    function testSimpleAttributes() {

        $this->compare([
            '{http://sabredav.org/ns}root' => [
                'value'      => 'text',
                'attributes' => [
                    'attr1' => 'attribute value',
                ],
            ],
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns" attr1="attribute value">text</s:root>

HI
        );

    }
    function testMixedSyntax() {
        $this->compare([
            '{http://sabredav.org/ns}root' => [
                '{http://sabredav.org/ns}single'   => 'value',
                '{http://sabredav.org/ns}multiple' => [
                    [
                        'name'  => '{http://sabredav.org/ns}foo',
                        'value' => 'bar',
                    ],
                    [
                        'name'  => '{http://sabredav.org/ns}foo',
                        'value' => 'foobar',
                    ],
                ],
                [
                    'name'       => '{http://sabredav.org/ns}attributes',
                    'value'      => null,
                    'attributes' => [
                        'foo' => 'bar',
                    ],
                ],
                [
                    'name'       => '{http://sabredav.org/ns}verbose',
                    'value'      => 'syntax',
                    'attributes' => [
                        'foo' => 'bar',
                    ],
                ],
            ],
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <s:single>value</s:single>
 <s:multiple>
  <s:foo>bar</s:foo>
  <s:foo>foobar</s:foo>
 </s:multiple>
 <s:attributes foo="bar"/>
 <s:verbose foo="bar">syntax</s:verbose>
</s:root>

HI
        );
    }

    function testNull() {

        $this->compare([
            '{http://sabredav.org/ns}root' => null,
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns"/>

HI
        );

    }

    function testArrayFormat2() {

        $this->compare([
            '{http://sabredav.org/ns}root' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'value'      => 'text',
                    'attributes' => [
                        'attr1' => 'attribute value',
                    ],
                ],
            ],
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <s:elem1 attr1="attribute value">text</s:elem1>
</s:root>

HI
        );

    }

    function testArrayOfValues() {

        $this->compare([
            '{http://sabredav.org/ns}root' => [
                [
                    'name'  => '{http://sabredav.org/ns}elem1',
                    'value' => [
                        'foo',
                        'bar',
                        'baz',
                    ],
                ],
            ],
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <s:elem1>foobarbaz</s:elem1>
</s:root>

HI
        );

    }

    /**
     * @depends testArrayFormat2
     */
    function testArrayFormat2NoValue() {

        $this->compare([
            '{http://sabredav.org/ns}root' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'attributes' => [
                        'attr1' => 'attribute value',
                    ],
                ],
            ],
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <s:elem1 attr1="attribute value"/>
</s:root>

HI
        );

    }

    function testCustomNamespace() {

        $this->compare([
            '{http://sabredav.org/ns}root' => [
                '{urn:foo}elem1' => 'bar',
            ],
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <x1:elem1 xmlns:x1="urn:foo">bar</x1:elem1>
</s:root>

HI
        );

    }

    function testEmptyNamespace() {

        // Empty namespaces are allowed, so we should support this.
        $this->compare([
            '{http://sabredav.org/ns}root' => [
                '{}elem1' => 'bar',
            ],
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <elem1 xmlns="">bar</elem1>
</s:root>

HI
        );

    }

    function testAttributes() {

        $this->compare([
            '{http://sabredav.org/ns}root' => [
                [
                    'name'       => '{http://sabredav.org/ns}elem1',
                    'value'      => 'text',
                    'attributes' => [
                        'attr1'                         => 'val1',
                        '{http://sabredav.org/ns}attr2' => 'val2',
                        '{urn:foo}attr3'                => 'val3',
                    ],
                ],
            ],
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <s:elem1 attr1="val1" s:attr2="val2" x1:attr3="val3" xmlns:x1="urn:foo">text</s:elem1>
</s:root>

HI
        );

    }

    function testBaseElement() {

        $this->compare([
            '{http://sabredav.org/ns}root' => new Element\Base('hello')
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">hello</s:root>

HI
        );

    }

    function testElementObj() {

        $this->compare([
            '{http://sabredav.org/ns}root' => new Element\Mock()
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <s:elem1>hiiii!</s:elem1>
</s:root>

HI
        );

    }

    function testEmptyNamespacePrefix() {

        $this->writer->namespaceMap['http://sabredav.org/ns'] = null;
        $this->compare([
            '{http://sabredav.org/ns}root' => new Element\Mock()
        ], <<<HI
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
 <elem1>hiiii!</elem1>
</root>

HI
        );

    }

    function testEmptyNamespacePrefixEmptyString() {

        $this->writer->namespaceMap['http://sabredav.org/ns'] = '';
        $this->compare([
            '{http://sabredav.org/ns}root' => new Element\Mock()
        ], <<<HI
<?xml version="1.0"?>
<root xmlns="http://sabredav.org/ns">
 <elem1>hiiii!</elem1>
</root>

HI
        );

    }

    function testWriteElement() {

        $this->writer->writeElement("{http://sabredav.org/ns}foo", 'content');

        $output = <<<HI
<?xml version="1.0"?>
<s:foo xmlns:s="http://sabredav.org/ns">content</s:foo>

HI;

        $this->assertEquals($output, $this->writer->outputMemory());


    }

    function testWriteElementComplex() {

        $this->writer->writeElement("{http://sabredav.org/ns}foo", new Element\KeyValue(['{http://sabredav.org/ns}bar' => 'test']));

        $output = <<<HI
<?xml version="1.0"?>
<s:foo xmlns:s="http://sabredav.org/ns">
 <s:bar>test</s:bar>
</s:foo>

HI;

        $this->assertEquals($output, $this->writer->outputMemory());

    }

    /**
     * @expectedException \InvalidArgumentException
     */
    function testWriteBadObject() {

        $this->writer->write(new \StdClass());

    }

    function testStartElementSimple() {

        $this->writer->startElement("foo");
        $this->writer->endElement();

        $output = <<<HI
<?xml version="1.0"?>
<foo xmlns:s="http://sabredav.org/ns"/>

HI;

        $this->assertEquals($output, $this->writer->outputMemory());

    }

    function testCallback() {

        $this->compare([
            '{http://sabredav.org/ns}root' => function(Writer $writer) {
                $writer->text('deferred writer');
            },
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">deferred writer</s:root>

HI
        );

    }

    /**
     * @expectedException \InvalidArgumentException
     */
    function testResource() {

        $this->compare([
            '{http://sabredav.org/ns}root' => fopen('php://memory', 'r'),
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">deferred writer</s:root>

HI
        );

    }

    function testClassMap() {

        $obj = (object)[
            'key1' => 'value1',
            'key2' => 'value2',
        ];

        $this->writer->classMap['stdClass'] = function(Writer $writer, $value) {

            foreach (get_object_vars($value) as $key => $val) {
                $writer->writeElement('{http://sabredav.org/ns}' . $key, $val);
            }

        };

        $this->compare([
            '{http://sabredav.org/ns}root' => $obj
        ], <<<HI
<?xml version="1.0"?>
<s:root xmlns:s="http://sabredav.org/ns">
 <s:key1>value1</s:key1>
 <s:key2>value2</s:key2>
</s:root>

HI
        );

    }
}
   Bud1                                                                       slg1Scomp                                             t e s t slg1Scomp           t e s t smoDDdutc        t e s t smodDdutc        t e s t sph1Scomp                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                @                                              @                                                @                                                @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   E                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          DSDB                                 `                                                   @                                                @                                                @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              *.iml
.idea/
composer.phar
vendor/
composer.lock
apigen.phar
docs/

language: php
sudo: false
php:
- 5.4
- 5.5
- 5.6
- nightly
- hhvm
before_script:
- composer self-update
- composer install --prefer-source --no-interaction --dev
script: phpunit
after_success:
- sh generate-api.sh
env:
  global:
    secure: ctCQVPQgQziwIZf5QGHcnhHlXsyauG0W3AWF/6R8cTP+in2S/RygunPp7CkXiqA1YMluGr2Lo9h4DTVg7oqeXl79FXFXedijQmQEu3g3f4iDWtxbQhGf4bJQk57jXFldge4rQedlOJDzwGzJ1abrimJQlu090BZNeonzWL5cRK4=
tree: Yes
deprecated: Yes
accessLevels: [public]
todo: Yes
{
    "name": "splitbrain/php-archive",
    "description": "Pure-PHP implementation to read and write TAR and ZIP archives",
    "keywords": ["zip", "tar", "archive", "unpack", "extract", "unzip"],
    "authors": [
        {
            "name": "Andreas Gohr",
            "email": "andi@splitbrain.org"
        }
    ],
    "license": "MIT",

    "require": {
        "php": ">=5.3.0"
    },

    "require-dev": {
        "phpunit/phpunit": "4.5.*"
    },

    "autoload": {
        "psr-4": {
            "splitbrain\\PHPArchive\\": "src"
        }
    }
}
#!/bin/sh

# where's the source files?
SRC='src'

# for what branch to trigger
BRANCH='master'

# github repo
REPO='splitbrain/php-archive'

# ---- About -------------------------------------------------------
#
# This script use apigen to generate the documentation for the
# repository configured above. When run locally, the documentation
# will be placed in the 'docs' folder.
# However this script can also be run from travis. This requires
# the setup of a secret token as described at http://bit.ly/1MNbPn0
#
# Additional configuration can be done within an apigen.neon file
#
# ---- no modifications below ---------------------------------------

# when on travis, build outside of repository, otherwise locally
if [ -z "$TRAVIS" ]; then
    DST='docs'
else
    DST='../gh-pages'
    if [ "$TRAVIS_PHP_VERSION"  != '5.6'     ]; then exit; fi
    if [ "$TRAVIS_BRANCH"       != "$BRANCH" ]; then exit; fi
    if [ "$TRAVIS_PULL_REQUEST" != 'false'   ]; then exit; fi
    if [ -z "$GH_TOKEN"                      ]; then
        echo "GH_TOKEN not set! See: http://bit.ly/1MNbPn0"
        exit
    fi
fi

# Get ApiGen.phar
wget http://www.apigen.org/apigen.phar -O apigen.phar

# Generate SDK Docs
php apigen.phar generate --template-theme="bootstrap" -s $SRC -d $DST


### if we're not on travis, we're done
if [ -z "$TRAVIS" ]; then exit; fi

# go to the generated docs
cd $DST || exit

# Set identity
git config --global user.email "travis@travis-ci.org"
git config --global user.name "Travis"

# Add branch
git init
git remote add origin https://${GH_TOKEN}@github.com/${REPO}.git > /dev/null
git checkout -B gh-pages

# Push generated files
git add .
git commit -m "Docs updated by Travis"
git push origin gh-pages -fq > /dev/null
Copyright (c) 2015 Andreas Gohr <gohr@cosmocode.de>

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.<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false"
         syntaxCheck="false">
    <testsuites>
        <testsuite name="Test Suite">
            <directory suffix=".php">./tests/</directory>
        </testsuite>
    </testsuites>
</phpunit>PHPArchive - Pure PHP ZIP and TAR handling
==========================================

This library allows to handle new ZIP and TAR archives without the need for any special PHP extensions (gz and bzip are
needed for compression). It can create new files or extract existing ones.

To keep things simple, the modification (adding or removing files) of existing archives is not supported.

[![Build Status](https://travis-ci.org/splitbrain/php-archive.svg)](https://travis-ci.org/splitbrain/php-archive)

Install
-------

Use composer:

```php composer.phar require splitbrain/php-archive```

Usage
-----

The usage for the Zip and Tar classes are basically the same. Here are some
examples for working with TARs to get you started.

Check the [API docs](https://splitbrain.github.io/php-archive/) for more
info.


```php
require_once 'vendor/autoload.php';
use splitbrain\PHPArchive\Tar;

// To list the contents of an existing TAR archive, open() it and use
// contents() on it:
$tar = new Tar();
$tar->open('myfile.tgz');
$toc = $tar->contents();
print_r($toc); // array of FileInfo objects

// To extract the contents of an existing TAR archive, open() it and use
// extract() on it:
$tar = new Tar();
$tar->open('myfile.tgz');
$tar->extract('/tmp');

// To create a new TAR archive directly on the filesystem (low memory
// requirements), create() it:
$tar = new Tar();
$tar->create('myfile.tgz');
$tar->addFile(...);
$tar->addData(...);
...
$tar->close();

// To create a TAR archive directly in memory, create() it, add*()
// files and then either save() or getArchive() it:
$tar = new Tar();
$tar->setCompression(9, Archive::COMPRESS_BZIP);
$tar->create();
$tar->addFile(...);
$tar->addData(...);
...
$tar->save('myfile.tbz'); // compresses and saves it
echo $tar->getArchive(); // compresses and returns it
```

Differences between Tar and Zip: Tars are compressed as a whole, while Zips compress each file individually. Therefore
you can call ```setCompression``` before each ```addFile()``` and ```addData()``` function call.

The FileInfo class can be used to specify additional info like ownership or permissions when adding a file to
an archive. 
<?php

namespace splitbrain\PHPArchive;

abstract class Archive
{

    const COMPRESS_AUTO = -1;
    const COMPRESS_NONE = 0;
    const COMPRESS_GZIP = 1;
    const COMPRESS_BZIP = 2;

    /**
     * Set the compression level and type
     *
     * @param int $level Compression level (0 to 9)
     * @param int $type  Type of compression to use (use COMPRESS_* constants)
     * @return mixed
     */
    abstract public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO);

    /**
     * Open an existing archive file for reading
     *
     * @param string $file
     * @throws ArchiveIOException
     */
    abstract public function open($file);

    /**
     * Read the contents of an archive
     *
     * This function lists the files stored in the archive, and returns an indexed array of FileInfo objects
     *
     * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
     * Reopen the file with open() again if you want to do additional operations
     *
     * @return FileInfo[]
     */
    abstract public function contents();

    /**
     * Extract an existing archive
     *
     * The $strip parameter allows you to strip a certain number of path components from the filenames
     * found in the archive file, similar to the --strip-components feature of GNU tar. This is triggered when
     * an integer is passed as $strip.
     * Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix,
     * the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
     *
     * By default this will extract all files found in the archive. You can restrict the output using the $include
     * and $exclude parameter. Both expect a full regular expression (including delimiters and modifiers). If
     * $include is set, only files that match this expression will be extracted. Files that match the $exclude
     * expression will never be extracted. Both parameters can be used in combination. Expressions are matched against
     * stripped filenames as described above.
     *
     * The archive is closed afterwards. Reopen the file with open() again if you want to do additional operations
     *
     * @param string     $outdir  the target directory for extracting
     * @param int|string $strip   either the number of path components or a fixed prefix to strip
     * @param string     $exclude a regular expression of files to exclude
     * @param string     $include a regular expression of files to include
     * @throws ArchiveIOException
     * @return array
     */
    abstract public function extract($outdir, $strip = '', $exclude = '', $include = '');

    /**
     * Create a new archive file
     *
     * If $file is empty, the archive file will be created in memory
     *
     * @param string $file
     */
    abstract public function create($file = '');

    /**
     * Add a file to the current archive using an existing file in the filesystem
     *
     * @param string          $file     path to the original file
     * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data, empty to take from original
     * @throws ArchiveIOException
     */
    abstract public function addFile($file, $fileinfo = '');

    /**
     * Add a file to the current archive using the given $data as content
     *
     * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data
     * @param string          $data     binary content of the file to add
     * @throws ArchiveIOException
     */
    abstract public function addData($fileinfo, $data);

    /**
     * Close the archive, close all file handles
     *
     * After a call to this function no more data can be added to the archive, for
     * read access no reading is allowed anymore
     */
    abstract public function close();

    /**
     * Returns the created in-memory archive data
     *
     * This implicitly calls close() on the Archive
     */
    abstract public function getArchive();

    /**
     * Save the created in-memory archive data
     *
     * Note: It is more memory effective to specify the filename in the create() function and
     * let the library work on the new file directly.
     *
     * @param string $file
     */
    abstract public function save($file);

}

class ArchiveIOException extends \Exception
{
}

class ArchiveIllegalCompressionException extends \Exception
{
}

class ArchiveCorruptedException extends \Exception
{
}
<?php

namespace splitbrain\PHPArchive;

/**
 * Class FileInfo
 *
 * stores meta data about a file in an Archive
 *
 * @author  Andreas Gohr <andi@splitbrain.org>
 * @package splitbrain\PHPArchive
 * @license MIT
 */
class FileInfo
{

    protected $isdir = false;
    protected $path = '';
    protected $size = 0;
    protected $csize = 0;
    protected $mtime = 0;
    protected $mode = 0664;
    protected $owner = '';
    protected $group = '';
    protected $uid = 0;
    protected $gid = 0;
    protected $comment = '';

    /**
     * initialize dynamic defaults
     *
     * @param string $path The path of the file, can also be set later through setPath()
     */
    public function __construct($path = '')
    {
        $this->mtime = time();
        $this->setPath($path);
    }

    /**
     * Factory to build FileInfo from existing file or directory
     *
     * @param string $path path to a file on the local file system
     * @param string $as   optional path to use inside the archive
     * @throws FileInfoException
     * @return FileInfo
     */
    public static function fromPath($path, $as = '')
    {
        clearstatcache(false, $path);

        if (!file_exists($path)) {
            throw new FileInfoException("$path does not exist");
        }

        $stat = stat($path);
        $file = new FileInfo();
        
        if(is_dir($path))
			$size = 0;
		else
			$size = filesize($path);

        $file->setPath($path);
        $file->setIsdir(is_dir($path));
        $file->setMode(fileperms($path));
        $file->setOwner(fileowner($path));
        $file->setGroup(filegroup($path));
        $file->setSize($size);
        $file->setUid($stat['uid']);
        $file->setGid($stat['gid']);
        $file->setMtime($stat['mtime']);

        if ($as) {
            $file->setPath($as);
        }

        return $file;
    }

    /**
     * @return int
     */
    public function getSize()
    {
		if($this->isdir) 
			return 0;
			
        return $this->size;
    }

    /**
     * @param int $size
     */
    public function setSize($size)
    {
        $this->size = $size;
    }

    /**
     * @return int
     */
    public function getCompressedSize()
    {
        return $this->csize;
    }

    /**
     * @param int $csize
     */
    public function setCompressedSize($csize)
    {
        $this->csize = $csize;
    }

    /**
     * @return int
     */
    public function getMtime()
    {
        return $this->mtime;
    }

    /**
     * @param int $mtime
     */
    public function setMtime($mtime)
    {
        $this->mtime = $mtime;
    }

    /**
     * @return int
     */
    public function getGid()
    {
        return $this->gid;
    }

    /**
     * @param int $gid
     */
    public function setGid($gid)
    {
        $this->gid = $gid;
    }

    /**
     * @return int
     */
    public function getUid()
    {
        return $this->uid;
    }

    /**
     * @param int $uid
     */
    public function setUid($uid)
    {
        $this->uid = $uid;
    }

    /**
     * @return string
     */
    public function getComment()
    {
        return $this->comment;
    }

    /**
     * @param string $comment
     */
    public function setComment($comment)
    {
        $this->comment = $comment;
    }

    /**
     * @return string
     */
    public function getGroup()
    {
        return $this->group;
    }

    /**
     * @param string $group
     */
    public function setGroup($group)
    {
        $this->group = $group;
    }

    /**
     * @return boolean
     */
    public function getIsdir()
    {
        return $this->isdir;
    }

    /**
     * @param boolean $isdir
     */
    public function setIsdir($isdir)
    {
        // default mode for directories
        if ($isdir && $this->mode === 0664) {
            $this->mode = 0775;
        }
        $this->isdir = $isdir;
    }

    /**
     * @return int
     */
    public function getMode()
    {
        return $this->mode;
    }

    /**
     * @param int $mode
     */
    public function setMode($mode)
    {
        $this->mode = $mode;
    }

    /**
     * @return string
     */
    public function getOwner()
    {
        return $this->owner;
    }

    /**
     * @param string $owner
     */
    public function setOwner($owner)
    {
        $this->owner = $owner;
    }

    /**
     * @return string
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * @param string $path
     */
    public function setPath($path)
    {
        $this->path = $this->cleanPath($path);
    }

    /**
     * Cleans up a path and removes relative parts, also strips leading slashes
     *
     * @param string $path
     * @return string
     */
    protected function cleanPath($path)
    {
        $path    = str_replace('\\', '/', $path);
        $path    = explode('/', $path);
        $newpath = array();
        foreach ($path as $p) {
            if ($p === '' || $p === '.') {
                continue;
            }
            if ($p === '..') {
                array_pop($newpath);
                continue;
            }
            array_push($newpath, $p);
        }
        return trim(implode('/', $newpath), '/');
    }

    /**
     * Strip given prefix or number of path segments from the filename
     *
     * The $strip parameter allows you to strip a certain number of path components from the filenames
     * found in the tar file, similar to the --strip-components feature of GNU tar. This is triggered when
     * an integer is passed as $strip.
     * Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix,
     * the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
     *
     * @param  int|string $strip
     * @return FileInfo
     */
    public function strip($strip)
    {
        $filename = $this->getPath();
        $striplen = strlen($strip);
        if (is_int($strip)) {
            // if $strip is an integer we strip this many path components
            $parts = explode('/', $filename);
            if (!$this->getIsdir()) {
                $base = array_pop($parts); // keep filename itself
            } else {
                $base = '';
            }
            $filename = join('/', array_slice($parts, $strip));
            if ($base) {
                $filename .= "/$base";
            }
        } else {
            // if strip is a string, we strip a prefix here
            if (substr($filename, 0, $striplen) == $strip) {
                $filename = substr($filename, $striplen);
            }
        }

        $this->setPath($filename);
    }

    /**
     * Does the file match the given include and exclude expressions?
     *
     * Exclude rules take precedence over include rules
     *
     * @param string $include Regular expression of files to include
     * @param string $exclude Regular expression of files to exclude
     * @return bool
     */
    public function match($include = '', $exclude = '')
    {
        $extract = true;
        if ($include && !preg_match($include, $this->getPath())) {
            $extract = false;
        }
        if ($exclude && preg_match($exclude, $this->getPath())) {
            $extract = false;
        }

        return $extract;
    }
}

class FileInfoException extends \Exception
{
}
<?php

namespace splitbrain\PHPArchive;

/**
 * Class Tar
 *
 * Creates or extracts Tar archives. Supports gz and bzip compression
 *
 * Long pathnames (>100 chars) are supported in POSIX ustar and GNU longlink formats.
 *
 * @author  Andreas Gohr <andi@splitbrain.org>
 * @package splitbrain\PHPArchive
 * @license MIT
 */
class Tar extends Archive
{

    protected $file = '';
    protected $comptype = Archive::COMPRESS_AUTO;
    protected $complevel = 9;
    protected $fh;
    protected $memory = '';
    protected $closed = true;
    protected $writeaccess = false;

    /**
     * Sets the compression to use
     *
     * @param int $level Compression level (0 to 9)
     * @param int $type  Type of compression to use (use COMPRESS_* constants)
     * @return mixed
     */
    public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO)
    {
        $this->compressioncheck($type);
        $this->comptype  = $type;
        $this->complevel = $level;
        if($level == 0) $this->comptype = Archive::COMPRESS_NONE;
        if($type == Archive::COMPRESS_NONE) $this->complevel = 0;
    }

    /**
     * Open an existing TAR file for reading
     *
     * @param string $file
     * @throws ArchiveIOException
     */
    public function open($file, $start_byte = 0)
    {
        $this->file = $file;

        // update compression to mach file
        if ($this->comptype == Tar::COMPRESS_AUTO) {
            $this->setCompression($this->complevel, $this->filetype($file));
        }

        // open file handles
        if ($this->comptype === Archive::COMPRESS_GZIP) {
            $this->fh = @gzopen($this->file, 'rb');
        } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
            $this->fh = @bzopen($this->file, 'r');
        } else {
            $this->fh = @fopen($this->file, 'rb');
        }

        if (!$this->fh) {
            throw new ArchiveIOException('Could not open file for reading: '.$this->file);
        }
        $this->closed = false;
        
        if($start_byte)
			fseek($this->fh, $start_byte);
    }

    /**
     * Read the contents of a TAR archive
     *
     * This function lists the files stored in the archive
     *
     * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
     * Reopen the file with open() again if you want to do additional operations
     *
     * @throws ArchiveIOException
     * @returns FileInfo[]
     */
    public function contents($files_limit = 0)
    {
        if ($this->closed || !$this->file) {
            throw new ArchiveIOException('Can not read from a closed archive');
        }
		
		$files_counter = 0;
        $result = array();
        
        while ($read = $this->readbytes(512)) {
            $header = $this->parseHeader($read);
            if (!is_array($header)) {
                continue;
            }
            
            if($files_limit)
            {
				if(++$files_counter > $files_limit)
				{
					$return['extracted_files'] = $result;
					$return['start'] = ftell($this->fh)-512;
					return $return;
				}
			}
			
			if($header['typeflag'] == 5)
				$header['size'] = 0;
				
            $this->skipbytes(ceil($header['size'] / 512) * 512);
            $result[] = $this->header2fileinfo($header);
        }
		
		$return['extracted_files'] = $result;
		
        $this->close();
        return $return;
    }

    /**
     * Extract an existing TAR archive
     *
     * The $strip parameter allows you to strip a certain number of path components from the filenames
     * found in the tar file, similar to the --strip-components feature of GNU tar. This is triggered when
     * an integer is passed as $strip.
     * Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix,
     * the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
     *
     * By default this will extract all files found in the archive. You can restrict the output using the $include
     * and $exclude parameter. Both expect a full regular expression (including delimiters and modifiers). If
     * $include is set only files that match this expression will be extracted. Files that match the $exclude
     * expression will never be extracted. Both parameters can be used in combination. Expressions are matched against
     * stripped filenames as described above.
     *
     * The archive is closed afer reading the contents, because rewinding is not possible in bzip2 streams.
     * Reopen the file with open() again if you want to do additional operations
     *
     * @param string     $outdir  the target directory for extracting
     * @param int|string $strip   either the number of path components or a fixed prefix to strip
     * @param string     $exclude a regular expression of files to exclude
     * @param string     $include a regular expression of files to include
     * @throws ArchiveIOException
     * @return FileInfo[]
     */
    public function extract($outdir, $strip = '', $exclude = '', $include = '', $files_limit = 0)
    {
        if ($this->closed || !$this->file) {
            throw new ArchiveIOException('Can not read from a closed archive');
        }

        $outdir = rtrim($outdir, '/');
        if(!is_dir($outdir))
				@mkdir($outdir, 0755, true);
			else
				@chmod($outdir, 0755);
        
        //@mkdir($outdir, 0777, true);
        
        if (!is_dir($outdir)) {
            throw new ArchiveIOException("Could not create directory '$outdir'");
        }
		
		$files_counter = 0;
		$return = array();
		
        $extracted = array();
        while ($dat = $this->readbytes(512)) {
            // read the file header
            $header = $this->parseHeader($dat);
            if (!is_array($header)) {
                continue;
            }
            
            if($files_limit)
            {
				if(++$files_counter > $files_limit)
				{
					$return['extracted_files'] = $extracted;
					$return['start'] = ftell($this->fh)-512;
					return $return;
				}
			}
            
            $fileinfo = $this->header2fileinfo($header);

            // apply strip rules
            $fileinfo->strip($strip);

            // skip unwanted files
            if (!strlen($fileinfo->getPath()) || !$fileinfo->match($include, $exclude)) {
                $this->skipbytes(ceil($header['size'] / 512) * 512);
                continue;
            }

            // create output directory
            $output    = $outdir.'/'.$fileinfo->getPath();
            $directory = ($fileinfo->getIsdir()) ? $output : dirname($output);
            if(!is_dir($directory))
				@mkdir($directory, 0755, true);
			else
				@chmod($directory, 0755);

            // extract data
            if (!$fileinfo->getIsdir()) {
				if(file_exists($output))
					unlink($output);
					
                $fp = fopen($output, "wb");
                if (!$fp) {
                    throw new ArchiveIOException('Could not open file for writing: '.$output);
                }

                $size = floor($header['size'] / 512);
                for ($i = 0; $i < $size; $i++) {
                    fwrite($fp, $this->readbytes(512), 512);
                }
                if (($header['size'] % 512) != 0) {
                    fwrite($fp, $this->readbytes(512), $header['size'] % 512);
                }

                fclose($fp);
                touch($output, $fileinfo->getMtime());
                chmod($output, $fileinfo->getMode());
            } else {
                //$this->skipbytes(ceil($header['size'] / 512) * 512); // the size is usually 0 for directories
                $this->skipbytes(ceil(0 / 512) * 512); // the size is usually 0 for directories
            }

            $extracted[] = $fileinfo;
        }

        $this->close();
        
        $return['extracted_files'] = $extracted;
        
        return $return;
    }

    /**
     * Create a new TAR file
     *
     * If $file is empty, the tar file will be created in memory
     *
     * @param string $file
     * @throws ArchiveIOException
     */
    public function create($file = '')
    {
        $this->file   = $file;
        $this->memory = '';
        $this->fh     = 0;

        if ($this->file) {
            // determine compression
            if ($this->comptype == Archive::COMPRESS_AUTO) {
                $this->setCompression($this->complevel, $this->filetype($file));
            }

            if ($this->comptype === Archive::COMPRESS_GZIP) {
                $this->fh = @gzopen($this->file, 'wb'.$this->complevel);
            } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
                $this->fh = @bzopen($this->file, 'w');
            } else {
                $this->fh = @fopen($this->file, 'wb');
            }

            if (!$this->fh) {
                throw new ArchiveIOException('Could not open file for writing: '.$this->file);
            }
        }
        $this->writeaccess = true;
        $this->closed      = false;
    }

    /**
     * Add a file to the current TAR archive using an existing file in the filesystem
     *
     * @param string          $file     path to the original file
     * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data, empty to take from original
     * @throws ArchiveIOException
     */
    public function addFile($file, $fileinfo = '')
    {
        if (is_string($fileinfo)) {
            $fileinfo = FileInfo::fromPath($file, $fileinfo);
        }

        if ($this->closed) {
            throw new ArchiveIOException('Archive has been closed, files can no longer be added');
        }
		
		if(is_dir($file))
		{
			$this->writeFileHeader($fileinfo);
			return;
		}
		
        $fp = fopen($file, 'rb');
        if (!$fp) {
            throw new ArchiveIOException('Could not open file for reading: '.$file);
        }
		
        // create file header
        if(is_resource($this->fh))
        {
			$archive_header_position = ftell($this->fh);
		}
        $this->writeFileHeader($fileinfo);
			
        // write data
        while (!feof($fp)) {
            $data = fread($fp, 512);
            if ($data === false) {
                break;
            }
            if ($data === '') {
                break;
            }
            $packed = pack("a512", $data);
            $this->writebytes($packed);
        }
        
        $file_offset = ftell($fp);
        
        //rewrite header with new size if file size changed while reading
        if(is_resource($this->fh) && $file_offset && $file_offset != $fileinfo->getSize())
        {
			$archive_current_position = ftell($this->fh);
			fseek($this->fh, $archive_header_position);
			
			$fileinfo->setSize(ftell($fp));
			$this->writeFileHeader($fileinfo);

			fseek($this->fh, $archive_current_position);

		}

        fclose($fp);
    }

    /**
     * Add a file to the current TAR archive using the given $data as content
     *
     * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data
     * @param string          $data     binary content of the file to add
     * @throws ArchiveIOException
     */
    public function addData($fileinfo, $data)
    {
        if (is_string($fileinfo)) {
            $fileinfo = new FileInfo($fileinfo);
        }

        if ($this->closed) {
            throw new ArchiveIOException('Archive has been closed, files can no longer be added');
        }

        $len = strlen($data);
        $fileinfo->setSize($len);
        $this->writeFileHeader($fileinfo);

        for ($s = 0; $s < $len; $s += 512) {
            $this->writebytes(pack("a512", substr($data, $s, 512)));
        }
    }

    /**
     * Add the closing footer to the archive if in write mode, close all file handles
     *
     * After a call to this function no more data can be added to the archive, for
     * read access no reading is allowed anymore
     *
     * "Physically, an archive consists of a series of file entries terminated by an end-of-archive entry, which
     * consists of two 512 blocks of zero bytes"
     *
     * @link http://www.gnu.org/software/tar/manual/html_chapter/tar_8.html#SEC134
     */
    public function close()
    {
        if ($this->closed) {
            return;
        } // we did this already

        // write footer
        if ($this->writeaccess) {
            $this->writebytes(pack("a512", ""));
            $this->writebytes(pack("a512", ""));
        }

        // close file handles
        if ($this->file) {
            if ($this->comptype === Archive::COMPRESS_GZIP) {
                gzclose($this->fh);
            } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
                bzclose($this->fh);
            } else {
                fclose($this->fh);
            }

            $this->file = '';
            $this->fh   = 0;
        }

        $this->writeaccess = false;
        $this->closed      = true;
    }

    /**
     * Returns the created in-memory archive data
     *
     * This implicitly calls close() on the Archive
     */
    public function getArchive()
    {
        $this->close();

        if ($this->comptype === Archive::COMPRESS_AUTO) {
            $this->comptype = Archive::COMPRESS_NONE;
        }

        if ($this->comptype === Archive::COMPRESS_GZIP) {
            return gzcompress($this->memory, $this->complevel);
        }
        if ($this->comptype === Archive::COMPRESS_BZIP) {
            return bzcompress($this->memory);
        }
        return $this->memory;
    }

    /**
     * Save the created in-memory archive data
     *
     * Note: It more memory effective to specify the filename in the create() function and
     * let the library work on the new file directly.
     *
     * @param string $file
     * @throws ArchiveIOException
     */
    public function save($file)
    {
        if ($this->comptype === Archive::COMPRESS_AUTO) {
            $this->setCompression($this->complevel, $this->filetype($file));
        }

        if (!file_put_contents($file, $this->getArchive())) {
            throw new ArchiveIOException('Could not write to file: '.$file);
        }
    }

    /**
     * Read from the open file pointer
     *
     * @param int $length bytes to read
     * @return string
     */
    protected function readbytes($length)
    {
        if ($this->comptype === Archive::COMPRESS_GZIP) {
            return @gzread($this->fh, $length);
        } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
            return @bzread($this->fh, $length);
        } else {
            return @fread($this->fh, $length);
        }
    }

    /**
     * Write to the open filepointer or memory
     *
     * @param string $data
     * @throws ArchiveIOException
     * @return int number of bytes written
     */
    protected function writebytes($data)
    {
        if (!$this->file) {
            $this->memory .= $data;
            $written = strlen($data);
        } elseif ($this->comptype === Archive::COMPRESS_GZIP) {
            $written = @gzwrite($this->fh, $data);
        } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
            $written = @bzwrite($this->fh, $data);
        } else {
            $written = @fwrite($this->fh, $data);
        }
        if ($written === false) {
            throw new ArchiveIOException('Failed to write to archive stream');
        }

        return $written;
    }
	
	public function appendFileData($file, $fileinfo = '', $start = 0, $limit = 0)
    {
		$end = $start+($limit*512);
		
		//check to see if we are at the begining of writing the file
		if(!$start)
		{
	        if (is_string($fileinfo)) {
				$fileinfo = FileInfo::fromPath($file, $fileinfo);
	        }
		}
		
        if ($this->closed) {
            throw new ArchiveIOException('Archive has been closed, files can no longer be added');
        }
        
        if(is_dir($file))
		{
			$this->writeFileHeader($fileinfo);
			return;
		}

        $fp = fopen($file, 'rb');
        
        fseek($fp, $start);
        
        if (!$fp) {
            throw new ArchiveIOException('Could not open file for reading: '.$file);
        }

        // create file header
		if(!$start)
		{
			$this->writeFileHeader($fileinfo);
		}
		
		$bytes = 0;
        // write data
        while ($end >=ftell($fp) and !feof($fp) ) {
            $data = fread($fp, 512);
            if ($data === false) {
                break;
            }
            if ($data === '') {
                break;
            }
            $packed = pack("a512", $data);
            $bytes += $this->writebytes($packed);
        }
        
        
        
        //if we are not at the end of file, we return the current position for incremental writing
        if(!feof($fp))
			$last_position = ftell($fp);
		else
			$last_position = -1;
	
        fclose($fp);
        
        return $last_position;
    }
    
	public function openForAppend($file = '')
    {

        $this->file   = $file;
        $this->memory = '';
        $this->fh     = 0;

        if ($this->file) {
            // determine compression
            if ($this->comptype == Archive::COMPRESS_AUTO) {
                $this->setCompression($this->complevel, $this->filetype($file));
            }

            if ($this->comptype === Archive::COMPRESS_GZIP) {
                $this->fh = @gzopen($this->file, 'ab'.$this->complevel);
            } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
                $this->fh = @bzopen($this->file, 'a');
            } else {
                $this->fh = @fopen($this->file, 'ab');
            }

            if (!$this->fh) {
                throw new ArchiveIOException('Could not open file for writing: '.$this->file);
            }
        }
        $this->writeaccess = true;
        $this->closed      = false;
        
    }
    
    /**
     * Skip forward in the open file pointer
     *
     * This is basically a wrapper around seek() (and a workaround for bzip2)
     *
     * @param int $bytes seek to this position
     */
    function skipbytes($bytes)
    {
        if ($this->comptype === Archive::COMPRESS_GZIP) {
            @gzseek($this->fh, $bytes, SEEK_CUR);
        } elseif ($this->comptype === Archive::COMPRESS_BZIP) {
            // there is no seek in bzip2, we simply read on
            // bzread allows to read a max of 8kb at once
            while($bytes) {
                $toread = min(8192, $bytes);
                @bzread($this->fh, $toread);
                $bytes -= $toread;
            }
        } else {
            @fseek($this->fh, $bytes, SEEK_CUR);
        }
    }

    /**
     * Write the given file metat data as header
     *
     * @param FileInfo $fileinfo
     */
    protected function writeFileHeader(FileInfo $fileinfo)
    {
        $this->writeRawFileHeader(
            $fileinfo->getPath(),
            $fileinfo->getUid(),
            $fileinfo->getGid(),
            $fileinfo->getMode(),
            $fileinfo->getSize(),
            $fileinfo->getMtime(),
            $fileinfo->getIsdir() ? '5' : '0'
        );
    }

    /**
     * Write a file header to the stream
     *
     * @param string $name
     * @param int    $uid
     * @param int    $gid
     * @param int    $perm
     * @param int    $size
     * @param int    $mtime
     * @param string $typeflag Set to '5' for directories
     */
    protected function writeRawFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '')
    {
        // handle filename length restrictions
        $prefix  = '';
        $namelen = strlen($name);
        if ($namelen > 100) {
            $file = basename($name);
            $dir  = dirname($name);
            if (strlen($file) > 100 || strlen($dir) > 155) {
                // we're still too large, let's use GNU longlink
                $this->writeRawFileHeader('././@LongLink', 0, 0, 0, $namelen, 0, 'L');
                for ($s = 0; $s < $namelen; $s += 512) {
                    $this->writebytes(pack("a512", substr($name, $s, 512)));
                }
                $name = substr($name, 0, 100); // cut off name
            } else {
                // we're fine when splitting, use POSIX ustar
                $prefix = $dir;
                $name   = $file;
            }
        }

        // values are needed in octal
        $uid   = sprintf("%6s ", decoct($uid));
        $gid   = sprintf("%6s ", decoct($gid));
        $perm  = sprintf("%6s ", decoct($perm));
        $size  = sprintf("%11s ", decoct($size));
        $mtime = sprintf("%11s", decoct($mtime));

        $data_first = pack("a100a8a8a8a12A12", $name, $perm, $uid, $gid, $size, $mtime);
        $data_last  = pack("a1a100a6a2a32a32a8a8a155a12", $typeflag, '', 'ustar', '', '', '', '', '', $prefix, "");

        for ($i = 0, $chks = 0; $i < 148; $i++) {
            $chks += ord($data_first[$i]);
        }

        for ($i = 156, $chks += 256, $j = 0; $i < 512; $i++, $j++) {
            $chks += ord($data_last[$j]);
        }

        $this->writebytes($data_first);

        $chks = pack("a8", sprintf("%6s ", decoct($chks)));
        $this->writebytes($chks.$data_last);
    }

    /**
     * Decode the given tar file header
     *
     * @param string $block a 512 byte block containing the header data
     * @return array|false returns false when this was a null block
     * @throws ArchiveCorruptedException
     */
    protected function parseHeader($block)
    {
        if (!$block || strlen($block) != 512) {
            throw new ArchiveCorruptedException('Unexpected length of header');
        }

        // null byte blocks are ignored
        if(trim($block) === '') return false;

        for ($i = 0, $chks = 0; $i < 148; $i++) {
            $chks += ord($block[$i]);
        }

        for ($i = 156, $chks += 256; $i < 512; $i++) {
            $chks += ord($block[$i]);
        }

        $header = @unpack(
            "a100filename/a8perm/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix",
            $block
        );
        if (!$header) {
            throw new ArchiveCorruptedException('Failed to parse header');
        }
        $return['checksum'] = OctDec(trim($header['checksum']));
        
        if ($return['checksum'] != $chks) {
            throw new ArchiveCorruptedException('Header does not match it\'s checksum for ');
        }

        $return['filename'] = trim($header['filename']);
        $return['perm']     = OctDec(trim($header['perm']));
        $return['uid']      = OctDec(trim($header['uid']));
        $return['gid']      = OctDec(trim($header['gid']));
        $return['size']     = OctDec(trim($header['size']));
        $return['mtime']    = OctDec(trim($header['mtime']));
        $return['typeflag'] = $header['typeflag'];
        $return['link']     = trim($header['link']);
        $return['uname']    = trim($header['uname']);
        $return['gname']    = trim($header['gname']);

        // Handle ustar Posix compliant path prefixes
        if (trim($header['prefix'])) {
            $return['filename'] = trim($header['prefix']).'/'.$return['filename'];
        }

        // Handle Long-Link and PAX entries from GNU Tar
        if ($return['typeflag'] == 'L') {
            // following data block(s) is the filename
            $filename = trim($this->readbytes(ceil($return['size'] / 512) * 512));
            // next block is the real header
            $block  = $this->readbytes(512);
            $return = $this->parseHeader($block);

            // overwrite the filename
			$return['filename'] = $filename;
		}elseif ($return['typeflag'] == 'x') {
            // following data block(s) is the filename
            $filename = trim($this->readbytes(ceil($return['size'] / 512) * 512));
            // next block is the real header
            $block  = $this->readbytes(512);
            $return = $this->parseHeader($block);
        }

        return $return;
    }

    /**
     * Creates a FileInfo object from the given parsed header
     *
     * @param $header
     * @return FileInfo
     */
    protected function header2fileinfo($header)
    {
        $fileinfo = new FileInfo();
        $fileinfo->setPath($header['filename']);
        $fileinfo->setMode($header['perm']);
        $fileinfo->setUid($header['uid']);
        $fileinfo->setGid($header['gid']);
        $fileinfo->setSize($header['size']);
        $fileinfo->setMtime($header['mtime']);
        $fileinfo->setOwner($header['uname']);
        $fileinfo->setGroup($header['gname']);
        $fileinfo->setIsdir((bool) $header['typeflag']);

        return $fileinfo;
    }

    /**
     * Checks if the given compression type is available and throws an exception if not
     *
     * @param $comptype
     * @throws ArchiveIllegalCompressionException
     */
    protected function compressioncheck($comptype)
    {
        if ($comptype === Archive::COMPRESS_GZIP && !function_exists('gzopen')) {
            throw new ArchiveIllegalCompressionException('No gzip support available');
        }

        if ($comptype === Archive::COMPRESS_BZIP && !function_exists('bzopen')) {
            throw new ArchiveIllegalCompressionException('No bzip2 support available');
        }
    }

    /**
     * Guesses the wanted compression from the given file
     *
     * Uses magic bytes for existing files, the file extension otherwise
     *
     * You don't need to call this yourself. It's used when you pass Archive::COMPRESS_AUTO somewhere
     *
     * @param string $file
     * @return int
     */
    public function filetype($file)
    {
        // for existing files, try to read the magic bytes
        if(file_exists($file) && is_readable($file) && filesize($file) > 5) {
            $fh = fopen($file, 'rb');
            if(!$fh) return false;
            $magic = fread($fh, 5);
            fclose($fh);

            if(strpos($magic, "\x42\x5a") === 0) return Archive::COMPRESS_BZIP;
            if(strpos($magic, "\x1f\x8b") === 0) return Archive::COMPRESS_GZIP;
        }

        // otherwise rely on file name
        $file = strtolower($file);
        if (substr($file, -3) == '.gz' || substr($file, -4) == '.tgz') {
            return Archive::COMPRESS_GZIP;
        } elseif (substr($file, -4) == '.bz2' || substr($file, -4) == '.tbz') {
            return Archive::COMPRESS_BZIP;
        }

        return Archive::COMPRESS_NONE;
    }
}
<?php

namespace splitbrain\PHPArchive;

/**
 * Class Zip
 *
 * Creates or extracts Zip archives
 *
 * for specs see http://www.pkware.com/appnote
 *
 * @author  Andreas Gohr <andi@splitbrain.org>
 * @package splitbrain\PHPArchive
 * @license MIT
 */
class Zip extends Archive
{

    protected $file = '';
    protected $fh;
    protected $memory = '';
    protected $closed = true;
    protected $writeaccess = false;
    protected $ctrl_dir;
    protected $complevel = 9;

    /**
     * Set the compression level.
     *
     * Compression Type is ignored for ZIP
     *
     * You can call this function before adding each file to set differen compression levels
     * for each file.
     *
     * @param int $level Compression level (0 to 9)
     * @param int $type  Type of compression to use ignored for ZIP
     * @return mixed
     */
    public function setCompression($level = 9, $type = Archive::COMPRESS_AUTO)
    {
        $this->complevel = $level;
    }

    /**
     * Open an existing ZIP file for reading
     *
     * @param string $file
     * @throws ArchiveIOException
     */
    public function open($file)
    {
        $this->file = $file;
        $this->fh   = @fopen($this->file, 'rb');
        if (!$this->fh) {
            throw new ArchiveIOException('Could not open file for reading: '.$this->file);
        }
        $this->closed = false;
    }

    /**
     * Read the contents of a ZIP archive
     *
     * This function lists the files stored in the archive, and returns an indexed array of FileInfo objects
     *
     * The archive is closed afer reading the contents, for API compatibility with TAR files
     * Reopen the file with open() again if you want to do additional operations
     *
     * @throws ArchiveIOException
     * @return FileInfo[]
     */
    public function contents()
    {
        if ($this->closed || !$this->file) {
            throw new ArchiveIOException('Can not read from a closed archive');
        }

        $result = array();

        $centd = $this->readCentralDir();

        @rewind($this->fh);
        @fseek($this->fh, $centd['offset']);

        for ($i = 0; $i < $centd['entries']; $i++) {
            $result[] = $this->header2fileinfo($this->readCentralFileHeader());
        }

        $this->close();
        return $result;
    }

    /**
     * Extract an existing ZIP archive
     *
     * The $strip parameter allows you to strip a certain number of path components from the filenames
     * found in the tar file, similar to the --strip-components feature of GNU tar. This is triggered when
     * an integer is passed as $strip.
     * Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix,
     * the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
     *
     * By default this will extract all files found in the archive. You can restrict the output using the $include
     * and $exclude parameter. Both expect a full regular expression (including delimiters and modifiers). If
     * $include is set only files that match this expression will be extracted. Files that match the $exclude
     * expression will never be extracted. Both parameters can be used in combination. Expressions are matched against
     * stripped filenames as described above.
     *
     * @param string     $outdir  the target directory for extracting
     * @param int|string $strip   either the number of path components or a fixed prefix to strip
     * @param string     $exclude a regular expression of files to exclude
     * @param string     $include a regular expression of files to include
     * @throws ArchiveIOException
     * @return FileInfo[]
     */
    function extract($outdir, $strip = '', $exclude = '', $include = '')
    {
        if ($this->closed || !$this->file) {
            throw new ArchiveIOException('Can not read from a closed archive');
        }

        $outdir = rtrim($outdir, '/');
        @mkdir($outdir, 0777, true);

        $extracted = array();

        $cdir      = $this->readCentralDir();
        $pos_entry = $cdir['offset']; // begin of the central file directory

        for ($i = 0; $i < $cdir['entries']; $i++) {
            // read file header
            @fseek($this->fh, $pos_entry);
            $header          = $this->readCentralFileHeader();
            $header['index'] = $i;
            $pos_entry       = ftell($this->fh); // position of the next file in central file directory
            fseek($this->fh, $header['offset']); // seek to beginning of file header
            $header   = $this->readFileHeader($header);
            $fileinfo = $this->header2fileinfo($header);

            // apply strip rules
            $fileinfo->strip($strip);

            // skip unwanted files
            if (!strlen($fileinfo->getPath()) || !$fileinfo->match($include, $exclude)) {
                continue;
            }

            $extracted[] = $fileinfo;

            // create output directory
            $output    = $outdir.'/'.$fileinfo->getPath();
            $directory = ($header['folder']) ? $output : dirname($output);
            @mkdir($directory, 0777, true);

            // nothing more to do for directories
            if ($fileinfo->getIsdir()) {
                continue;
            }

            // compressed files are written to temporary .gz file first
            if ($header['compression'] == 0) {
                $extractto = $output;
            } else {
                $extractto = $output.'.gz';
            }

            // open file for writing
            $fp = fopen($extractto, "wb");
            if (!$fp) {
                throw new ArchiveIOException('Could not open file for writing: '.$extractto);
            }

            // prepend compression header
            if ($header['compression'] != 0) {
                $binary_data = pack(
                    'va1a1Va1a1',
                    0x8b1f,
                    chr($header['compression']),
                    chr(0x00),
                    time(),
                    chr(0x00),
                    chr(3)
                );
                fwrite($fp, $binary_data, 10);
            }

            // read the file and store it on disk
            $size = $header['compressed_size'];
            while ($size != 0) {
                $read_size   = ($size < 2048 ? $size : 2048);
                $buffer      = fread($this->fh, $read_size);
                $binary_data = pack('a'.$read_size, $buffer);
                fwrite($fp, $binary_data, $read_size);
                $size -= $read_size;
            }

            // finalize compressed file
            if ($header['compression'] != 0) {
                $binary_data = pack('VV', $header['crc'], $header['size']);
                fwrite($fp, $binary_data, 8);
            }

            // close file
            fclose($fp);

            // unpack compressed file
            if ($header['compression'] != 0) {
                $gzp = @gzopen($extractto, 'rb');
                if (!$gzp) {
                    @unlink($extractto);
                    throw new ArchiveIOException('Failed file extracting. gzip support missing?');
                }
                $fp = @fopen($output, 'wb');
                if (!$fp) {
                    throw new ArchiveIOException('Could not open file for writing: '.$extractto);
                }

                $size = $header['size'];
                while ($size != 0) {
                    $read_size   = ($size < 2048 ? $size : 2048);
                    $buffer      = gzread($gzp, $read_size);
                    $binary_data = pack('a'.$read_size, $buffer);
                    @fwrite($fp, $binary_data, $read_size);
                    $size -= $read_size;
                }
                fclose($fp);
                gzclose($gzp);
                unlink($extractto); // remove temporary gz file
            }

            touch($output, $fileinfo->getMtime());
            //FIXME what about permissions?
        }

        $this->close();
        return $extracted;
    }

    /**
     * Create a new ZIP file
     *
     * If $file is empty, the zip file will be created in memory
     *
     * @param string $file
     * @throws ArchiveIOException
     */
    public function create($file = '')
    {
        $this->file   = $file;
        $this->memory = '';
        $this->fh     = 0;

        if ($this->file) {
            $this->fh = @fopen($this->file, 'wb');

            if (!$this->fh) {
                throw new ArchiveIOException('Could not open file for writing: '.$this->file);
            }
        }
        $this->writeaccess = true;
        $this->closed      = false;
        $this->ctrl_dir    = array();
    }

    /**
     * Add a file to the current ZIP archive using an existing file in the filesystem
     *
     * @param string          $file     path to the original file
     * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data, empty to take from original
     * @throws ArchiveIOException
     */

    /**
     * Add a file to the current archive using an existing file in the filesystem
     *
     * @param string          $file     path to the original file
     * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data, empty to take from original
     * @throws ArchiveIOException
     */
    public function addFile($file, $fileinfo = '')
    {
        if (is_string($fileinfo)) {
            $fileinfo = FileInfo::fromPath($file, $fileinfo);
        }

        if ($this->closed) {
            throw new ArchiveIOException('Archive has been closed, files can no longer be added');
        }

        $data = @file_get_contents($file);
        if ($data === false) {
            throw new ArchiveIOException('Could not open file for reading: '.$file);
        }

        // FIXME could we stream writing compressed data? gzwrite on a fopen handle?
        $this->addData($fileinfo, $data);
    }

    /**
     * Add a file to the current TAR archive using the given $data as content
     *
     * @param string|FileInfo $fileinfo either the name to us in archive (string) or a FileInfo oject with all meta data
     * @param string          $data     binary content of the file to add
     * @throws ArchiveIOException
     */
    public function addData($fileinfo, $data)
    {
        if (is_string($fileinfo)) {
            $fileinfo = new FileInfo($fileinfo);
        }

        if ($this->closed) {
            throw new ArchiveIOException('Archive has been closed, files can no longer be added');
        }

        // prepare info and compress data
        $size     = strlen($data);
        $crc      = crc32($data);
        if ($this->complevel) {
            $data = gzcompress($data, $this->complevel);
            $data = substr($data, 2, -4); // strip compression headers
        }
        $csize  = strlen($data);
        $offset = $this->dataOffset();
        $name   = $fileinfo->getPath();
        $time   = $fileinfo->getMtime();

        // write local file header
        $this->writebytes($this->makeLocalFileHeader(
            $time,
            $crc,
            $size,
            $csize,
            $name,
            (bool) $this->complevel
        ));

        // we store no encryption header

        // write data
        $this->writebytes($data);

        // we store no data descriptor

        // add info to central file directory
        $this->ctrl_dir[] = $this->makeCentralFileRecord(
            $offset,
            $time,
            $crc,
            $size,
            $csize,
            $name,
            (bool) $this->complevel
        );
    }

    /**
     * Add the closing footer to the archive if in write mode, close all file handles
     *
     * After a call to this function no more data can be added to the archive, for
     * read access no reading is allowed anymore
     */
    public function close()
    {
        if ($this->closed) {
            return;
        } // we did this already

        if ($this->writeaccess) {
            // write central directory
            $offset = $this->dataOffset();
            $ctrldir = join('', $this->ctrl_dir);
            $this->writebytes($ctrldir);

            // write end of central directory record
            $this->writebytes("\x50\x4b\x05\x06"); // end of central dir signature
            $this->writebytes(pack('v', 0)); // number of this disk
            $this->writebytes(pack('v', 0)); // number of the disk with the start of the central directory
            $this->writebytes(pack('v',
                count($this->ctrl_dir))); // total number of entries in the central directory on this disk
            $this->writebytes(pack('v', count($this->ctrl_dir))); // total number of entries in the central directory
            $this->writebytes(pack('V', strlen($ctrldir))); // size of the central directory
            $this->writebytes(pack('V',
                $offset)); // offset of start of central directory with respect to the starting disk number
            $this->writebytes(pack('v', 0)); // .ZIP file comment length

            $this->ctrl_dir = array();
        }

        // close file handles
        if ($this->file) {
            fclose($this->fh);
            $this->file = '';
            $this->fh   = 0;
        }

        $this->writeaccess = false;
        $this->closed      = true;
    }

    /**
     * Returns the created in-memory archive data
     *
     * This implicitly calls close() on the Archive
     */
    public function getArchive()
    {
        $this->close();

        return $this->memory;
    }

    /**
     * Save the created in-memory archive data
     *
     * Note: It's more memory effective to specify the filename in the create() function and
     * let the library work on the new file directly.
     *
     * @param     $file
     * @throws ArchiveIOException
     */
    public function save($file)
    {
        if (!file_put_contents($file, $this->getArchive())) {
            throw new ArchiveIOException('Could not write to file: '.$file);
        }
    }

    /**
     * Read the central directory
     *
     * This key-value list contains general information about the ZIP file
     *
     * @return array
     */
    protected function readCentralDir()
    {
        $size = filesize($this->file);
        if ($size < 277) {
            $maximum_size = $size;
        } else {
            $maximum_size = 277;
        }

        @fseek($this->fh, $size - $maximum_size);
        $pos   = ftell($this->fh);
        $bytes = 0x00000000;

        while ($pos < $size) {
            $byte  = @fread($this->fh, 1);
            $bytes = (($bytes << 8) & 0xFFFFFFFF) | ord($byte);
            if ($bytes == 0x504b0506) {
                break;
            }
            $pos++;
        }

        $data = unpack(
            'vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size',
            fread($this->fh, 18)
        );

        if ($data['comment_size'] != 0) {
            $centd['comment'] = fread($this->fh, $data['comment_size']);
        } else {
            $centd['comment'] = '';
        }
        $centd['entries']      = $data['entries'];
        $centd['disk_entries'] = $data['disk_entries'];
        $centd['offset']       = $data['offset'];
        $centd['disk_start']   = $data['disk_start'];
        $centd['size']         = $data['size'];
        $centd['disk']         = $data['disk'];
        return $centd;
    }

    /**
     * Read the next central file header
     *
     * Assumes the current file pointer is pointing at the right position
     *
     * @return array
     */
    protected function readCentralFileHeader()
    {
        $binary_data = fread($this->fh, 46);
        $header      = unpack(
            'vchkid/vid/vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset',
            $binary_data
        );

        if ($header['filename_len'] != 0) {
            $header['filename'] = fread($this->fh, $header['filename_len']);
        } else {
            $header['filename'] = '';
        }

        if ($header['extra_len'] != 0) {
            $header['extra'] = fread($this->fh, $header['extra_len']);
        } else {
            $header['extra'] = '';
        }

        if ($header['comment_len'] != 0) {
            $header['comment'] = fread($this->fh, $header['comment_len']);
        } else {
            $header['comment'] = '';
        }

        $header['mtime']           = $this->makeUnixTime($header['mdate'], $header['mtime']);
        $header['stored_filename'] = $header['filename'];
        $header['status']          = 'ok';
        if (substr($header['filename'], -1) == '/') {
            $header['external'] = 0x41FF0010;
        }
        $header['folder'] = ($header['external'] == 0x41FF0010 || $header['external'] == 16) ? 1 : 0;

        return $header;
    }

    /**
     * Reads the local file header
     *
     * This header precedes each individual file inside the zip file. Assumes the current file pointer is pointing at
     * the right position already. Enhances the given central header with the data found at the local header.
     *
     * @param array $header the central file header read previously (see above)
     * @return array
     */
    protected function readFileHeader($header)
    {
        $binary_data = fread($this->fh, 30);
        $data        = unpack(
            'vchk/vid/vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len',
            $binary_data
        );

        $header['filename'] = fread($this->fh, $data['filename_len']);
        if ($data['extra_len'] != 0) {
            $header['extra'] = fread($this->fh, $data['extra_len']);
        } else {
            $header['extra'] = '';
        }

        $header['compression'] = $data['compression'];
        foreach (array(
                     'size',
                     'compressed_size',
                     'crc'
                 ) as $hd) { // On ODT files, these headers are 0. Keep the previous value.
            if ($data[$hd] != 0) {
                $header[$hd] = $data[$hd];
            }
        }
        $header['flag']  = $data['flag'];
        $header['mtime'] = $this->makeUnixTime($data['mdate'], $data['mtime']);

        $header['stored_filename'] = $header['filename'];
        $header['status']          = "ok";
        $header['folder']          = ($header['external'] == 0x41FF0010 || $header['external'] == 16) ? 1 : 0;
        return $header;
    }

    /**
     * Create fileinfo object from header data
     *
     * @param $header
     * @return FileInfo
     */
    protected function header2fileinfo($header)
    {
        $fileinfo = new FileInfo();
        $fileinfo->setPath($header['filename']);
        $fileinfo->setSize($header['size']);
        $fileinfo->setCompressedSize($header['compressed_size']);
        $fileinfo->setMtime($header['mtime']);
        $fileinfo->setComment($header['comment']);
        $fileinfo->setIsdir($header['external'] == 0x41FF0010 || $header['external'] == 16);
        return $fileinfo;
    }

    /**
     * Write to the open filepointer or memory
     *
     * @param string $data
     * @throws ArchiveIOException
     * @return int number of bytes written
     */
    protected function writebytes($data)
    {
        if (!$this->file) {
            $this->memory .= $data;
            $written = strlen($data);
        } else {
            $written = @fwrite($this->fh, $data);
        }
        if ($written === false) {
            throw new ArchiveIOException('Failed to write to archive stream');
        }
        return $written;
    }

    /**
     * Current data pointer position
     *
     * @fixme might need a -1
     * @return int
     */
    protected function dataOffset()
    {
        if ($this->file) {
            return ftell($this->fh);
        } else {
            return strlen($this->memory);
        }
    }

    /**
     * Create a DOS timestamp from a UNIX timestamp
     *
     * DOS timestamps start at 1980-01-01, earlier UNIX stamps will be set to this date
     *
     * @param $time
     * @return int
     */
    protected function makeDosTime($time)
    {
        $timearray = getdate($time);
        if ($timearray['year'] < 1980) {
            $timearray['year']    = 1980;
            $timearray['mon']     = 1;
            $timearray['mday']    = 1;
            $timearray['hours']   = 0;
            $timearray['minutes'] = 0;
            $timearray['seconds'] = 0;
        }
        return (($timearray['year'] - 1980) << 25) |
        ($timearray['mon'] << 21) |
        ($timearray['mday'] << 16) |
        ($timearray['hours'] << 11) |
        ($timearray['minutes'] << 5) |
        ($timearray['seconds'] >> 1);
    }

    /**
     * Create a UNIX timestamp from a DOS timestamp
     *
     * @param $mdate
     * @param $mtime
     * @return int
     */
    protected function makeUnixTime($mdate = null, $mtime = null)
    {
        if ($mdate && $mtime) {
            $year = (($mdate & 0xFE00) >> 9) + 1980;
            $month = ($mdate & 0x01E0) >> 5;
            $day = $mdate & 0x001F;

            $hour = ($mtime & 0xF800) >> 11;
            $minute = ($mtime & 0x07E0) >> 5;
            $seconde = ($mtime & 0x001F) << 1;

            $mtime = mktime($hour, $minute, $seconde, $month, $day, $year);
        } else {
            $mtime = time();
        }

        return $mtime;
    }

    /**
     * Returns a local file header for the given data
     *
     * @param int $offset location of the local header
     * @param int $ts unix timestamp
     * @param int $crc CRC32 checksum of the uncompressed data
     * @param int $len length of the uncompressed data
     * @param int $clen length of the compressed data
     * @param string $name file name
     * @param boolean|null $comp if compression is used, if null it's determined from $len != $clen
     * @return string
     */
    protected function makeCentralFileRecord($offset, $ts, $crc, $len, $clen, $name, $comp = null)
    {
        if(is_null($comp)) $comp = $len != $clen;
        $comp = $comp ? 8 : 0;
        $dtime = dechex($this->makeDosTime($ts));

        $header = "\x50\x4b\x01\x02"; // central file header signature
        $header .= pack('v', 14); // version made by - VFAT
        $header .= pack('v', 20); // version needed to extract - 2.0
        $header .= pack('v', 0); // general purpose flag - no flags set
        $header .= pack('v', $comp); // compression method - deflate|none
        $header .= pack(
            'H*',
            $dtime[6] . $dtime[7] .
            $dtime[4] . $dtime[5] .
            $dtime[2] . $dtime[3] .
            $dtime[0] . $dtime[1]
        ); //  last mod file time and date
        $header .= pack('V', $crc); // crc-32
        $header .= pack('V', $clen); // compressed size
        $header .= pack('V', $len); // uncompressed size
        $header .= pack('v', strlen($name)); // file name length
        $header .= pack('v', 0); // extra field length
        $header .= pack('v', 0); // file comment length
        $header .= pack('v', 0); // disk number start
        $header .= pack('v', 0); // internal file attributes
        $header .= pack('V', 0); // external file attributes  @todo was 0x32!?
        $header .= pack('V', $offset); // relative offset of local header
        $header .= $name; // file name

        return $header;
    }

    /**
     * Returns a local file header for the given data
     *
     * @param int $ts unix timestamp
     * @param int $crc CRC32 checksum of the uncompressed data
     * @param int $len length of the uncompressed data
     * @param int $clen length of the compressed data
     * @param string $name file name
     * @param boolean|null $comp if compression is used, if null it's determined from $len != $clen
     * @return string
     */
    protected function makeLocalFileHeader($ts, $crc, $len, $clen, $name, $comp = null)
    {
        if(is_null($comp)) $comp = $len != $clen;
        $comp = $comp ? 8 : 0;
        $dtime = dechex($this->makeDosTime($ts));

        $header = "\x50\x4b\x03\x04"; //  local file header signature
        $header .= pack('v', 20); // version needed to extract - 2.0
        $header .= pack('v', 0); // general purpose flag - no flags set
        $header .= pack('v', $comp); // compression method - deflate|none
        $header .= pack(
            'H*',
            $dtime[6] . $dtime[7] .
            $dtime[4] . $dtime[5] .
            $dtime[2] . $dtime[3] .
            $dtime[0] . $dtime[1]
        ); //  last mod file time and date
        $header .= pack('V', $crc); // crc-32
        $header .= pack('V', $clen); // compressed size
        $header .= pack('V', $len); // uncompressed size
        $header .= pack('v', strlen($name)); // file name length
        $header .= pack('v', 0); // extra field length
        $header .= $name;
        return $header;
    }
}
   Bud1                                                                     1Scomp                                                                                                                                                                                 t a rlg1Scomp      Z    t a rmoDDdutc        t a rmodDdutc        t a rph1Scomp           z i plg1Scomp      z    z i pmoDDdutc        z i pmodDdutc        z i pph1Scomp      @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   @                                              @                                                @                                                @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   E                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         DSDB                                 `                                                   @                                                @                                                @                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              /debug
/jstree.sublime-project
/jstree.sublime-workspace
/bower_components
/node_modules
/site
/nuget
/demo/filebrowser/data/root
/npm.txt
/libs
/docs
/dist/libs
/.vscode{
	"name": "jstree",
	"version": "3.3.3",
	"main" : [
		"./dist/jstree.js",
		"./dist/themes/default/style.css"
	],
	"ignore": [
		"**/.*",
		"docs",
		"demo",
		"libs",
		"node_modules",
		"test",
		"libs",
		"jstree.jquery.json",
		"gruntfile.js",
		"package.json",
		"bower.json",
		"component.json",
		"LICENCE-MIT",
		"README.md"
	],
	"dependencies": {
		"jquery": ">=1.9.1"
	},
	"keywords": [
		"ui",
		"tree",
		"jstree"
	]
}
{
	"name": "jstree",
	"repo": "vakata/jstree",
	"description": "jsTree is jquery plugin, that provides interactive trees.",
	"version": "3.3.3",
	"license": "MIT",
	"keywords": [
		"ui",
		"tree",
		"jstree"
	],
	"scripts": [
		"dist/jstree.js",
		"dist/jstree.min.js"
	],
	"images": [
		"dist/themes/default/32px.png",
		"dist/themes/default/40px.png",
		"dist/themes/default/throbber.gif"
	],
	"styles": [
		"dist/themes/default/style.css",
		"dist/themes/default/style.min.css"
	],
	"dependencies": {
		"components/jquery": ">=1.9.1"
	}
}
{
	"name": "vakata/jstree",
	"description": "jsTree is jquery plugin, that provides interactive trees.",
	"type": "component",
	"homepage": "http://jstree.com",
	"license": "MIT",
	"support": {
		"issues": "https://github.com/vakata/jstree/issues",
		"forum": "https://groups.google.com/forum/#!forum/jstree",
		"source": "https://github.com/vakata/jstree"
	},
	"authors": [
		{
			"name": "Ivan Bozhanov",
			"email": "jstree@jstree.com"
		}
	],
	"require": {
		"components/jquery": ">=1.9.1"
	},
	"suggest": {
		"robloach/component-installer": "Allows installation of Components via Composer"
	},
	"extra": {
		"component": {
			"scripts": [
				"dist/jstree.js"
			],
			"styles": [
				"dist/themes/default/style.css"
			],
			"images": [
				"dist/themes/default/32px.png",
				"dist/themes/default/40px.png",
				"dist/themes/default/throbber.gif"
			],
			"files": [
				"dist/jstree.min.js",
				"dist/themes/default/style.min.css",
				"dist/themes/default/32px.png",
				"dist/themes/default/40px.png",
				"dist/themes/default/throbber.gif"
			]
		}
	}
}/*globals jQuery, define, module, exports, require, window, document, postMessage */
(function (factory) {
	"use strict";
	if (typeof define === 'function' && define.amd) {
		define(['jquery'], factory);
	}
	else if(typeof module !== 'undefined' && module.exports) {
		module.exports = factory(require('jquery'));
	}
	else {
		factory(jQuery);
	}
}(function ($, undefined) {
	"use strict";
/*!
 * jsTree 3.3.3
 * http://jstree.com/
 *
 * Copyright (c) 2014 Ivan Bozhanov (http://vakata.com)
 *
 * Licensed same as jquery - under the terms of the MIT License
 *   http://www.opensource.org/licenses/mit-license.php
 */
/*!
 * if using jslint please allow for the jQuery global and use following options:
 * jslint: loopfunc: true, browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true
 */
/*jshint -W083 */

	// prevent another load? maybe there is a better way?
	if($.jstree) {
		return;
	}

	/**
	 * ### jsTree core functionality
	 */

	// internal variables
	var instance_counter = 0,
		ccp_node = false,
		ccp_mode = false,
		ccp_inst = false,
		themes_loaded = [],
		src = $('script:last').attr('src'),
		document = window.document; // local variable is always faster to access then a global

	/**
	 * holds all jstree related functions and variables, including the actual class and methods to create, access and manipulate instances.
	 * @name $.jstree
	 */
	$.jstree = {
		/**
		 * specifies the jstree version in use
		 * @name $.jstree.version
		 */
		version : '3.3.3',
		/**
		 * holds all the default options used when creating new instances
		 * @name $.jstree.defaults
		 */
		defaults : {
			/**
			 * configure which plugins will be active on an instance. Should be an array of strings, where each element is a plugin name. The default is `[]`
			 * @name $.jstree.defaults.plugins
			 */
			plugins : []
		},
		/**
		 * stores all loaded jstree plugins (used internally)
		 * @name $.jstree.plugins
		 */
		plugins : {},
		path : src && src.indexOf('/') !== -1 ? src.replace(/\/[^\/]+$/,'') : '',
		idregex : /[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g,
		root : '#'
	};
	
	/**
	 * creates a jstree instance
	 * @name $.jstree.create(el [, options])
	 * @param {DOMElement|jQuery|String} el the element to create the instance on, can be jQuery extended or a selector
	 * @param {Object} options options for this instance (extends `$.jstree.defaults`)
	 * @return {jsTree} the new instance
	 */
	$.jstree.create = function (el, options) {
		var tmp = new $.jstree.core(++instance_counter),
			opt = options;
		options = $.extend(true, {}, $.jstree.defaults, options);
		if(opt && opt.plugins) {
			options.plugins = opt.plugins;
		}
		$.each(options.plugins, function (i, k) {
			if(i !== 'core') {
				tmp = tmp.plugin(k, options[k]);
			}
		});
		$(el).data('jstree', tmp);
		tmp.init(el, options);
		return tmp;
	};
	/**
	 * remove all traces of jstree from the DOM and destroy all instances
	 * @name $.jstree.destroy()
	 */
	$.jstree.destroy = function () {
		$('.jstree:jstree').jstree('destroy');
		$(document).off('.jstree');
	};
	/**
	 * the jstree class constructor, used only internally
	 * @private
	 * @name $.jstree.core(id)
	 * @param {Number} id this instance's index
	 */
	$.jstree.core = function (id) {
		this._id = id;
		this._cnt = 0;
		this._wrk = null;
		this._data = {
			core : {
				themes : {
					name : false,
					dots : false,
					icons : false,
					ellipsis : false
				},
				selected : [],
				last_error : {},
				working : false,
				worker_queue : [],
				focused : null
			}
		};
	};
	/**
	 * get a reference to an existing instance
	 *
	 * __Examples__
	 *
	 *	// provided a container with an ID of "tree", and a nested node with an ID of "branch"
	 *	// all of there will return the same instance
	 *	$.jstree.reference('tree');
	 *	$.jstree.reference('#tree');
	 *	$.jstree.reference($('#tree'));
	 *	$.jstree.reference(document.getElementByID('tree'));
	 *	$.jstree.reference('branch');
	 *	$.jstree.reference('#branch');
	 *	$.jstree.reference($('#branch'));
	 *	$.jstree.reference(document.getElementByID('branch'));
	 *
	 * @name $.jstree.reference(needle)
	 * @param {DOMElement|jQuery|String} needle
	 * @return {jsTree|null} the instance or `null` if not found
	 */
	$.jstree.reference = function (needle) {
		var tmp = null,
			obj = null;
		if(needle && needle.id && (!needle.tagName || !needle.nodeType)) { needle = needle.id; }

		if(!obj || !obj.length) {
			try { obj = $(needle); } catch (ignore) { }
		}
		if(!obj || !obj.length) {
			try { obj = $('#' + needle.replace($.jstree.idregex,'\\$&')); } catch (ignore) { }
		}
		if(obj && obj.length && (obj = obj.closest('.jstree')).length && (obj = obj.data('jstree'))) {
			tmp = obj;
		}
		else {
			$('.jstree').each(function () {
				var inst = $(this).data('jstree');
				if(inst && inst._model.data[needle]) {
					tmp = inst;
					return false;
				}
			});
		}
		return tmp;
	};
	/**
	 * Create an instance, get an instance or invoke a command on a instance.
	 *
	 * If there is no instance associated with the current node a new one is created and `arg` is used to extend `$.jstree.defaults` for this new instance. There would be no return value (chaining is not broken).
	 *
	 * If there is an existing instance and `arg` is a string the command specified by `arg` is executed on the instance, with any additional arguments passed to the function. If the function returns a value it will be returned (chaining could break depending on function).
	 *
	 * If there is an existing instance and `arg` is not a string the instance itself is returned (similar to `$.jstree.reference`).
	 *
	 * In any other case - nothing is returned and chaining is not broken.
	 *
	 * __Examples__
	 *
	 *	$('#tree1').jstree(); // creates an instance
	 *	$('#tree2').jstree({ plugins : [] }); // create an instance with some options
	 *	$('#tree1').jstree('open_node', '#branch_1'); // call a method on an existing instance, passing additional arguments
	 *	$('#tree2').jstree(); // get an existing instance (or create an instance)
	 *	$('#tree2').jstree(true); // get an existing instance (will not create new instance)
	 *	$('#branch_1').jstree().select_node('#branch_1'); // get an instance (using a nested element and call a method)
	 *
	 * @name $().jstree([arg])
	 * @param {String|Object} arg
	 * @return {Mixed}
	 */
	$.fn.jstree = function (arg) {
		// check for string argument
		var is_method	= (typeof arg === 'string'),
			args		= Array.prototype.slice.call(arguments, 1),
			result		= null;
		if(arg === true && !this.length) { return false; }
		this.each(function () {
			// get the instance (if there is one) and method (if it exists)
			var instance = $.jstree.reference(this),
				method = is_method && instance ? instance[arg] : null;
			// if calling a method, and method is available - execute on the instance
			result = is_method && method ?
				method.apply(instance, args) :
				null;
			// if there is no instance and no method is being called - create one
			if(!instance && !is_method && (arg === undefined || $.isPlainObject(arg))) {
				$.jstree.create(this, arg);
			}
			// if there is an instance and no method is called - return the instance
			if( (instance && !is_method) || arg === true ) {
				result = instance || false;
			}
			// if there was a method call which returned a result - break and return the value
			if(result !== null && result !== undefined) {
				return false;
			}
		});
		// if there was a method call with a valid return value - return that, otherwise continue the chain
		return result !== null && result !== undefined ?
			result : this;
	};
	/**
	 * used to find elements containing an instance
	 *
	 * __Examples__
	 *
	 *	$('div:jstree').each(function () {
	 *		$(this).jstree('destroy');
	 *	});
	 *
	 * @name $(':jstree')
	 * @return {jQuery}
	 */
	$.expr.pseudos.jstree = $.expr.createPseudo(function(search) {
		return function(a) {
			return $(a).hasClass('jstree') &&
				$(a).data('jstree') !== undefined;
		};
	});

	/**
	 * stores all defaults for the core
	 * @name $.jstree.defaults.core
	 */
	$.jstree.defaults.core = {
		/**
		 * data configuration
		 *
		 * If left as `false` the HTML inside the jstree container element is used to populate the tree (that should be an unordered list with list items).
		 *
		 * You can also pass in a HTML string or a JSON array here.
		 *
		 * It is possible to pass in a standard jQuery-like AJAX config and jstree will automatically determine if the response is JSON or HTML and use that to populate the tree.
		 * In addition to the standard jQuery ajax options here you can suppy functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node is being loaded, the return value of those functions will be used.
		 *
		 * The last option is to specify a function, that function will receive the node being loaded as argument and a second param which is a function which should be called with the result.
		 *
		 * __Examples__
		 *
		 *	// AJAX
		 *	$('#tree').jstree({
		 *		'core' : {
		 *			'data' : {
		 *				'url' : '/get/children/',
		 *				'data' : function (node) {
		 *					return { 'id' : node.id };
		 *				}
		 *			}
		 *		});
		 *
		 *	// direct data
		 *	$('#tree').jstree({
		 *		'core' : {
		 *			'data' : [
		 *				'Simple root node',
		 *				{
		 *					'id' : 'node_2',
		 *					'text' : 'Root node with options',
		 *					'state' : { 'opened' : true, 'selected' : true },
		 *					'children' : [ { 'text' : 'Child 1' }, 'Child 2']
		 *				}
		 *			]
		 *		}
		 *	});
		 *
		 *	// function
		 *	$('#tree').jstree({
		 *		'core' : {
		 *			'data' : function (obj, callback) {
		 *				callback.call(this, ['Root 1', 'Root 2']);
		 *			}
		 *		});
		 *
		 * @name $.jstree.defaults.core.data
		 */
		data			: false,
		/**
		 * configure the various strings used throughout the tree
		 *
		 * You can use an object where the key is the string you need to replace and the value is your replacement.
		 * Another option is to specify a function which will be called with an argument of the needed string and should return the replacement.
		 * If left as `false` no replacement is made.
		 *
		 * __Examples__
		 *
		 *	$('#tree').jstree({
		 *		'core' : {
		 *			'strings' : {
		 *				'Loading ...' : 'Please wait ...'
		 *			}
		 *		}
		 *	});
		 *
		 * @name $.jstree.defaults.core.strings
		 */
		strings			: false,
		/**
		 * determines what happens when a user tries to modify the structure of the tree
		 * If left as `false` all operations like create, rename, delete, move or copy are prevented.
		 * You can set this to `true` to allow all interactions or use a function to have better control.
		 *
		 * __Examples__
		 *
		 *	$('#tree').jstree({
		 *		'core' : {
		 *			'check_callback' : function (operation, node, node_parent, node_position, more) {
		 *				// operation can be 'create_node', 'rename_node', 'delete_node', 'move_node' or 'copy_node'
		 *				// in case of 'rename_node' node_position is filled with the new node name
		 *				return operation === 'rename_node' ? true : false;
		 *			}
		 *		}
		 *	});
		 *
		 * @name $.jstree.defaults.core.check_callback
		 */
		check_callback	: false,
		/**
		 * a callback called with a single object parameter in the instance's scope when something goes wrong (operation prevented, ajax failed, etc)
		 * @name $.jstree.defaults.core.error
		 */
		error			: $.noop,
		/**
		 * the open / close animation duration in milliseconds - set this to `false` to disable the animation (default is `200`)
		 * @name $.jstree.defaults.core.animation
		 */
		animation		: 200,
		/**
		 * a boolean indicating if multiple nodes can be selected
		 * @name $.jstree.defaults.core.multiple
		 */
		multiple		: true,
		/**
		 * theme configuration object
		 * @name $.jstree.defaults.core.themes
		 */
		themes			: {
			/**
			 * the name of the theme to use (if left as `false` the default theme is used)
			 * @name $.jstree.defaults.core.themes.name
			 */
			name			: false,
			/**
			 * the URL of the theme's CSS file, leave this as `false` if you have manually included the theme CSS (recommended). You can set this to `true` too which will try to autoload the theme.
			 * @name $.jstree.defaults.core.themes.url
			 */
			url				: false,
			/**
			 * the location of all jstree themes - only used if `url` is set to `true`
			 * @name $.jstree.defaults.core.themes.dir
			 */
			dir				: false,
			/**
			 * a boolean indicating if connecting dots are shown
			 * @name $.jstree.defaults.core.themes.dots
			 */
			dots			: true,
			/**
			 * a boolean indicating if node icons are shown
			 * @name $.jstree.defaults.core.themes.icons
			 */
			icons			: true,
			/**
			 * a boolean indicating if node ellipsis should be shown - this only works with a fixed with on the container
			 * @name $.jstree.defaults.core.themes.ellipsis
			 */
			ellipsis		: false,
			/**
			 * a boolean indicating if the tree background is striped
			 * @name $.jstree.defaults.core.themes.stripes
			 */
			stripes			: false,
			/**
			 * a string (or boolean `false`) specifying the theme variant to use (if the theme supports variants)
			 * @name $.jstree.defaults.core.themes.variant
			 */
			variant			: false,
			/**
			 * a boolean specifying if a reponsive version of the theme should kick in on smaller screens (if the theme supports it). Defaults to `false`.
			 * @name $.jstree.defaults.core.themes.responsive
			 */
			responsive		: false
		},
		/**
		 * if left as `true` all parents of all selected nodes will be opened once the tree loads (so that all selected nodes are visible to the user)
		 * @name $.jstree.defaults.core.expand_selected_onload
		 */
		expand_selected_onload : true,
		/**
		 * if left as `true` web workers will be used to parse incoming JSON data where possible, so that the UI will not be blocked by large requests. Workers are however about 30% slower. Defaults to `true`
		 * @name $.jstree.defaults.core.worker
		 */
		worker : true,
		/**
		 * Force node text to plain text (and escape HTML). Defaults to `false`
		 * @name $.jstree.defaults.core.force_text
		 */
		force_text : false,
		/**
		 * Should the node should be toggled if the text is double clicked . Defaults to `true`
		 * @name $.jstree.defaults.core.dblclick_toggle
		 */
		dblclick_toggle : true
	};
	$.jstree.core.prototype = {
		/**
		 * used to decorate an instance with a plugin. Used internally.
		 * @private
		 * @name plugin(deco [, opts])
		 * @param  {String} deco the plugin to decorate with
		 * @param  {Object} opts options for the plugin
		 * @return {jsTree}
		 */
		plugin : function (deco, opts) {
			var Child = $.jstree.plugins[deco];
			if(Child) {
				this._data[deco] = {};
				Child.prototype = this;
				return new Child(opts, this);
			}
			return this;
		},
		/**
		 * initialize the instance. Used internally.
		 * @private
		 * @name init(el, optons)
		 * @param {DOMElement|jQuery|String} el the element we are transforming
		 * @param {Object} options options for this instance
		 * @trigger init.jstree, loading.jstree, loaded.jstree, ready.jstree, changed.jstree
		 */
		init : function (el, options) {
			this._model = {
				data : {},
				changed : [],
				force_full_redraw : false,
				redraw_timeout : false,
				default_state : {
					loaded : true,
					opened : false,
					selected : false,
					disabled : false
				}
			};
			this._model.data[$.jstree.root] = {
				id : $.jstree.root,
				parent : null,
				parents : [],
				children : [],
				children_d : [],
				state : { loaded : false }
			};

			this.element = $(el).addClass('jstree jstree-' + this._id);
			this.settings = options;

			this._data.core.ready = false;
			this._data.core.loaded = false;
			this._data.core.rtl = (this.element.css("direction") === "rtl");
			this.element[this._data.core.rtl ? 'addClass' : 'removeClass']("jstree-rtl");
			this.element.attr('role','tree');
			if(this.settings.core.multiple) {
				this.element.attr('aria-multiselectable', true);
			}
			if(!this.element.attr('tabindex')) {
				this.element.attr('tabindex','0');
			}

			this.bind();
			/**
			 * triggered after all events are bound
			 * @event
			 * @name init.jstree
			 */
			this.trigger("init");

			this._data.core.original_container_html = this.element.find(" > ul > li").clone(true);
			this._data.core.original_container_html
				.find("li").addBack()
				.contents().filter(function() {
					return this.nodeType === 3 && (!this.nodeValue || /^\s+$/.test(this.nodeValue));
				})
				.remove();
			this.element.html("<"+"ul class='jstree-container-ul jstree-children' role='group'><"+"li id='j"+this._id+"_loading' class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='tree-item'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
			this.element.attr('aria-activedescendant','j' + this._id + '_loading');
			this._data.core.li_height = this.get_container_ul().children("li").first().height() || 24;
			this._data.core.node = this._create_prototype_node();
			/**
			 * triggered after the loading text is shown and before loading starts
			 * @event
			 * @name loading.jstree
			 */
			this.trigger("loading");
			this.load_node($.jstree.root);
		},
		/**
		 * destroy an instance
		 * @name destroy()
		 * @param  {Boolean} keep_html if not set to `true` the container will be emptied, otherwise the current DOM elements will be kept intact
		 */
		destroy : function (keep_html) {
			if(this._wrk) {
				try {
					window.URL.revokeObjectURL(this._wrk);
					this._wrk = null;
				}
				catch (ignore) { }
			}
			if(!keep_html) { this.element.empty(); }
			this.teardown();
		},
		/**
		 * Create prototype node
		 */
		_create_prototype_node : function () {
			var _node = document.createElement('LI'), _temp1, _temp2;
			_node.setAttribute('role', 'treeitem');
			_temp1 = document.createElement('I');
			_temp1.className = 'jstree-icon jstree-ocl';
			_temp1.setAttribute('role', 'presentation');
			_node.appendChild(_temp1);
			_temp1 = document.createElement('A');
			_temp1.className = 'jstree-anchor';
			_temp1.setAttribute('href','#');
			_temp1.setAttribute('tabindex','-1');
			_temp2 = document.createElement('I');
			_temp2.className = 'jstree-icon jstree-themeicon';
			_temp2.setAttribute('role', 'presentation');
			_temp1.appendChild(_temp2);
			_node.appendChild(_temp1);
			_temp1 = _temp2 = null;

			return _node;
		},
		/**
		 * part of the destroying of an instance. Used internally.
		 * @private
		 * @name teardown()
		 */
		teardown : function () {
			this.unbind();
			this.element
				.removeClass('jstree')
				.removeData('jstree')
				.find("[class^='jstree']")
					.addBack()
					.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
			this.element = null;
		},
		/**
		 * bind all events. Used internally.
		 * @private
		 * @name bind()
		 */
		bind : function () {
			var word = '',
				tout = null,
				was_click = 0;
			this.element
				.on("dblclick.jstree", function (e) {
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
						if(document.selection && document.selection.empty) {
							document.selection.empty();
						}
						else {
							if(window.getSelection) {
								var sel = window.getSelection();
								try {
									sel.removeAllRanges();
									sel.collapse();
								} catch (ignore) { }
							}
						}
					})
				.on("mousedown.jstree", $.proxy(function (e) {
						if(e.target === this.element[0]) {
							e.preventDefault(); // prevent losing focus when clicking scroll arrows (FF, Chrome)
							was_click = +(new Date()); // ie does not allow to prevent losing focus
						}
					}, this))
				.on("mousedown.jstree", ".jstree-ocl", function (e) {
						e.preventDefault(); // prevent any node inside from losing focus when clicking the open/close icon
					})
				.on("click.jstree", ".jstree-ocl", $.proxy(function (e) {
						this.toggle_node(e.target);
					}, this))
				.on("dblclick.jstree", ".jstree-anchor", $.proxy(function (e) {
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
						if(this.settings.core.dblclick_toggle) {
							this.toggle_node(e.target);
						}
					}, this))
				.on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
						e.preventDefault();
						if(e.currentTarget !== document.activeElement) { $(e.currentTarget).focus(); }
						this.activate_node(e.currentTarget, e);
					}, this))
				.on('keydown.jstree', '.jstree-anchor', $.proxy(function (e) {
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
						if(e.which !== 32 && e.which !== 13 && (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)) { return true; }
						var o = null;
						if(this._data.core.rtl) {
							if(e.which === 37) { e.which = 39; }
							else if(e.which === 39) { e.which = 37; }
						}
						switch(e.which) {
							case 32: // aria defines space only with Ctrl
								if(e.ctrlKey) {
									e.type = "click";
									$(e.currentTarget).trigger(e);
								}
								break;
							case 13: // enter
								e.type = "click";
								$(e.currentTarget).trigger(e);
								break;
							case 37: // left
								e.preventDefault();
								if(this.is_open(e.currentTarget)) {
									this.close_node(e.currentTarget);
								}
								else {
									o = this.get_parent(e.currentTarget);
									if(o && o.id !== $.jstree.root) { this.get_node(o, true).children('.jstree-anchor').focus(); }
								}
								break;
							case 38: // up
								e.preventDefault();
								o = this.get_prev_dom(e.currentTarget);
								if(o && o.length) { o.children('.jstree-anchor').focus(); }
								break;
							case 39: // right
								e.preventDefault();
								if(this.is_closed(e.currentTarget)) {
									this.open_node(e.currentTarget, function (o) { this.get_node(o, true).children('.jstree-anchor').focus(); });
								}
								else if (this.is_open(e.currentTarget)) {
									o = this.get_node(e.currentTarget, true).children('.jstree-children')[0];
									if(o) { $(this._firstChild(o)).children('.jstree-anchor').focus(); }
								}
								break;
							case 40: // down
								e.preventDefault();
								o = this.get_next_dom(e.currentTarget);
								if(o && o.length) { o.children('.jstree-anchor').focus(); }
								break;
							case 106: // aria defines * on numpad as open_all - not very common
								this.open_all();
								break;
							case 36: // home
								e.preventDefault();
								o = this._firstChild(this.get_container_ul()[0]);
								if(o) { $(o).children('.jstree-anchor').filter(':visible').focus(); }
								break;
							case 35: // end
								e.preventDefault();
								this.element.find('.jstree-anchor').filter(':visible').last().focus();
								break;
							case 113: // f2 - safe to include - if check_callback is false it will fail
								e.preventDefault();
								this.edit(e.currentTarget);
								break;
							default:
								break;
							/*!
							// delete
							case 46:
								e.preventDefault();
								o = this.get_node(e.currentTarget);
								if(o && o.id && o.id !== $.jstree.root) {
									o = this.is_selected(o) ? this.get_selected() : o;
									this.delete_node(o);
								}
								break;

							*/
						}
					}, this))
				.on("load_node.jstree", $.proxy(function (e, data) {
						if(data.status) {
							if(data.node.id === $.jstree.root && !this._data.core.loaded) {
								this._data.core.loaded = true;
								if(this._firstChild(this.get_container_ul()[0])) {
									this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
								}
								/**
								 * triggered after the root node is loaded for the first time
								 * @event
								 * @name loaded.jstree
								 */
								this.trigger("loaded");
							}
							if(!this._data.core.ready) {
								setTimeout($.proxy(function() {
									if(this.element && !this.get_container_ul().find('.jstree-loading').length) {
										this._data.core.ready = true;
										if(this._data.core.selected.length) {
											if(this.settings.core.expand_selected_onload) {
												var tmp = [], i, j;
												for(i = 0, j = this._data.core.selected.length; i < j; i++) {
													tmp = tmp.concat(this._model.data[this._data.core.selected[i]].parents);
												}
												tmp = $.vakata.array_unique(tmp);
												for(i = 0, j = tmp.length; i < j; i++) {
													this.open_node(tmp[i], false, 0);
												}
											}
											this.trigger('changed', { 'action' : 'ready', 'selected' : this._data.core.selected });
										}
										/**
										 * triggered after all nodes are finished loading
										 * @event
										 * @name ready.jstree
										 */
										this.trigger("ready");
									}
								}, this), 0);
							}
						}
					}, this))
				// quick searching when the tree is focused
				.on('keypress.jstree', $.proxy(function (e) {
						if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
						if(tout) { clearTimeout(tout); }
						tout = setTimeout(function () {
							word = '';
						}, 500);

						var chr = String.fromCharCode(e.which).toLowerCase(),
							col = this.element.find('.jstree-anchor').filter(':visible'),
							ind = col.index(document.activeElement) || 0,
							end = false;
						word += chr;

						// match for whole word from current node down (including the current node)
						if(word.length > 1) {
							col.slice(ind).each($.proxy(function (i, v) {
								if($(v).text().toLowerCase().indexOf(word) === 0) {
									$(v).focus();
									end = true;
									return false;
								}
							}, this));
							if(end) { return; }

							// match for whole word from the beginning of the tree
							col.slice(0, ind).each($.proxy(function (i, v) {
								if($(v).text().toLowerCase().indexOf(word) === 0) {
									$(v).focus();
									end = true;
									return false;
								}
							}, this));
							if(end) { return; }
						}
						// list nodes that start with that letter (only if word consists of a single char)
						if(new RegExp('^' + chr.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '+$').test(word)) {
							// search for the next node starting with that letter
							col.slice(ind + 1).each($.proxy(function (i, v) {
								if($(v).text().toLowerCase().charAt(0) === chr) {
									$(v).focus();
									end = true;
									return false;
								}
							}, this));
							if(end) { return; }

							// search from the beginning
							col.slice(0, ind + 1).each($.proxy(function (i, v) {
								if($(v).text().toLowerCase().charAt(0) === chr) {
									$(v).focus();
									end = true;
									return false;
								}
							}, this));
							if(end) { return; }
						}
					}, this))
				// THEME RELATED
				.on("init.jstree", $.proxy(function () {
						var s = this.settings.core.themes;
						this._data.core.themes.dots			= s.dots;
						this._data.core.themes.stripes		= s.stripes;
						this._data.core.themes.icons		= s.icons;
						this._data.core.themes.ellipsis		= s.ellipsis;
						this.set_theme(s.name || "default", s.url);
						this.set_theme_variant(s.variant);
					}, this))
				.on("loading.jstree", $.proxy(function () {
						this[ this._data.core.themes.dots ? "show_dots" : "hide_dots" ]();
						this[ this._data.core.themes.icons ? "show_icons" : "hide_icons" ]();
						this[ this._data.core.themes.stripes ? "show_stripes" : "hide_stripes" ]();
						this[ this._data.core.themes.ellipsis ? "show_ellipsis" : "hide_ellipsis" ]();
					}, this))
				.on('blur.jstree', '.jstree-anchor', $.proxy(function (e) {
						this._data.core.focused = null;
						$(e.currentTarget).filter('.jstree-hovered').mouseleave();
						this.element.attr('tabindex', '0');
					}, this))
				.on('focus.jstree', '.jstree-anchor', $.proxy(function (e) {
						var tmp = this.get_node(e.currentTarget);
						if(tmp && tmp.id) {
							this._data.core.focused = tmp.id;
						}
						this.element.find('.jstree-hovered').not(e.currentTarget).mouseleave();
						$(e.currentTarget).mouseenter();
						this.element.attr('tabindex', '-1');
					}, this))
				.on('focus.jstree', $.proxy(function () {
						if(+(new Date()) - was_click > 500 && !this._data.core.focused) {
							was_click = 0;
							var act = this.get_node(this.element.attr('aria-activedescendant'), true);
							if(act) {
								act.find('> .jstree-anchor').focus();
							}
						}
					}, this))
				.on('mouseenter.jstree', '.jstree-anchor', $.proxy(function (e) {
						this.hover_node(e.currentTarget);
					}, this))
				.on('mouseleave.jstree', '.jstree-anchor', $.proxy(function (e) {
						this.dehover_node(e.currentTarget);
					}, this));
		},
		/**
		 * part of the destroying of an instance. Used internally.
		 * @private
		 * @name unbind()
		 */
		unbind : function () {
			this.element.off('.jstree');
			$(document).off('.jstree-' + this._id);
		},
		/**
		 * trigger an event. Used internally.
		 * @private
		 * @name trigger(ev [, data])
		 * @param  {String} ev the name of the event to trigger
		 * @param  {Object} data additional data to pass with the event
		 */
		trigger : function (ev, data) {
			if(!data) {
				data = {};
			}
			data.instance = this;
			this.element.triggerHandler(ev.replace('.jstree','') + '.jstree', data);
		},
		/**
		 * returns the jQuery extended instance container
		 * @name get_container()
		 * @return {jQuery}
		 */
		get_container : function () {
			return this.element;
		},
		/**
		 * returns the jQuery extended main UL node inside the instance container. Used internally.
		 * @private
		 * @name get_container_ul()
		 * @return {jQuery}
		 */
		get_container_ul : function () {
			return this.element.children(".jstree-children").first();
		},
		/**
		 * gets string replacements (localization). Used internally.
		 * @private
		 * @name get_string(key)
		 * @param  {String} key
		 * @return {String}
		 */
		get_string : function (key) {
			var a = this.settings.core.strings;
			if($.isFunction(a)) { return a.call(this, key); }
			if(a && a[key]) { return a[key]; }
			return key;
		},
		/**
		 * gets the first child of a DOM node. Used internally.
		 * @private
		 * @name _firstChild(dom)
		 * @param  {DOMElement} dom
		 * @return {DOMElement}
		 */
		_firstChild : function (dom) {
			dom = dom ? dom.firstChild : null;
			while(dom !== null && dom.nodeType !== 1) {
				dom = dom.nextSibling;
			}
			return dom;
		},
		/**
		 * gets the next sibling of a DOM node. Used internally.
		 * @private
		 * @name _nextSibling(dom)
		 * @param  {DOMElement} dom
		 * @return {DOMElement}
		 */
		_nextSibling : function (dom) {
			dom = dom ? dom.nextSibling : null;
			while(dom !== null && dom.nodeType !== 1) {
				dom = dom.nextSibling;
			}
			return dom;
		},
		/**
		 * gets the previous sibling of a DOM node. Used internally.
		 * @private
		 * @name _previousSibling(dom)
		 * @param  {DOMElement} dom
		 * @return {DOMElement}
		 */
		_previousSibling : function (dom) {
			dom = dom ? dom.previousSibling : null;
			while(dom !== null && dom.nodeType !== 1) {
				dom = dom.previousSibling;
			}
			return dom;
		},
		/**
		 * get the JSON representation of a node (or the actual jQuery extended DOM node) by using any input (child DOM element, ID string, selector, etc)
		 * @name get_node(obj [, as_dom])
		 * @param  {mixed} obj
		 * @param  {Boolean} as_dom
		 * @return {Object|jQuery}
		 */
		get_node : function (obj, as_dom) {
			if(obj && obj.id) {
				obj = obj.id;
			}
			var dom;
			try {
				if(this._model.data[obj]) {
					obj = this._model.data[obj];
				}
				else if(typeof obj === "string" && this._model.data[obj.replace(/^#/, '')]) {
					obj = this._model.data[obj.replace(/^#/, '')];
				}
				else if(typeof obj === "string" && (dom = $('#' + obj.replace($.jstree.idregex,'\\$&'), this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
					obj = this._model.data[dom.closest('.jstree-node').attr('id')];
				}
				else if((dom = $(obj, this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
					obj = this._model.data[dom.closest('.jstree-node').attr('id')];
				}
				else if((dom = $(obj, this.element)).length && dom.hasClass('jstree')) {
					obj = this._model.data[$.jstree.root];
				}
				else {
					return false;
				}

				if(as_dom) {
					obj = obj.id === $.jstree.root ? this.element : $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
				}
				return obj;
			} catch (ex) { return false; }
		},
		/**
		 * get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array)
		 * @name get_path(obj [, glue, ids])
		 * @param  {mixed} obj the node
		 * @param  {String} glue if you want the path as a string - pass the glue here (for example '/'), if a falsy value is supplied here, an array is returned
		 * @param  {Boolean} ids if set to true build the path using ID, otherwise node text is used
		 * @return {mixed}
		 */
		get_path : function (obj, glue, ids) {
			obj = obj.parents ? obj : this.get_node(obj);
			if(!obj || obj.id === $.jstree.root || !obj.parents) {
				return false;
			}
			var i, j, p = [];
			p.push(ids ? obj.id : obj.text);
			for(i = 0, j = obj.parents.length; i < j; i++) {
				p.push(ids ? obj.parents[i] : this.get_text(obj.parents[i]));
			}
			p = p.reverse().slice(1);
			return glue ? p.join(glue) : p;
		},
		/**
		 * get the next visible node that is below the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
		 * @name get_next_dom(obj [, strict])
		 * @param  {mixed} obj
		 * @param  {Boolean} strict
		 * @return {jQuery}
		 */
		get_next_dom : function (obj, strict) {
			var tmp;
			obj = this.get_node(obj, true);
			if(obj[0] === this.element[0]) {
				tmp = this._firstChild(this.get_container_ul()[0]);
				while (tmp && tmp.offsetHeight === 0) {
					tmp = this._nextSibling(tmp);
				}
				return tmp ? $(tmp) : false;
			}
			if(!obj || !obj.length) {
				return false;
			}
			if(strict) {
				tmp = obj[0];
				do {
					tmp = this._nextSibling(tmp);
				} while (tmp && tmp.offsetHeight === 0);
				return tmp ? $(tmp) : false;
			}
			if(obj.hasClass("jstree-open")) {
				tmp = this._firstChild(obj.children('.jstree-children')[0]);
				while (tmp && tmp.offsetHeight === 0) {
					tmp = this._nextSibling(tmp);
				}
				if(tmp !== null) {
					return $(tmp);
				}
			}
			tmp = obj[0];
			do {
				tmp = this._nextSibling(tmp);
			} while (tmp && tmp.offsetHeight === 0);
			if(tmp !== null) {
				return $(tmp);
			}
			return obj.parentsUntil(".jstree",".jstree-node").nextAll(".jstree-node:visible").first();
		},
		/**
		 * get the previous visible node that is above the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
		 * @name get_prev_dom(obj [, strict])
		 * @param  {mixed} obj
		 * @param  {Boolean} strict
		 * @return {jQuery}
		 */
		get_prev_dom : function (obj, strict) {
			var tmp;
			obj = this.get_node(obj, true);
			if(obj[0] === this.element[0]) {
				tmp = this.get_container_ul()[0].lastChild;
				while (tmp && tmp.offsetHeight === 0) {
					tmp = this._previousSibling(tmp);
				}
				return tmp ? $(tmp) : false;
			}
			if(!obj || !obj.length) {
				return false;
			}
			if(strict) {
				tmp = obj[0];
				do {
					tmp = this._previousSibling(tmp);
				} while (tmp && tmp.offsetHeight === 0);
				return tmp ? $(tmp) : false;
			}
			tmp = obj[0];
			do {
				tmp = this._previousSibling(tmp);
			} while (tmp && tmp.offsetHeight === 0);
			if(tmp !== null) {
				obj = $(tmp);
				while(obj.hasClass("jstree-open")) {
					obj = obj.children(".jstree-children").first().children(".jstree-node:visible:last");
				}
				return obj;
			}
			tmp = obj[0].parentNode.parentNode;
			return tmp && tmp.className && tmp.className.indexOf('jstree-node') !== -1 ? $(tmp) : false;
		},
		/**
		 * get the parent ID of a node
		 * @name get_parent(obj)
		 * @param  {mixed} obj
		 * @return {String}
		 */
		get_parent : function (obj) {
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			return obj.parent;
		},
		/**
		 * get a jQuery collection of all the children of a node (node must be rendered)
		 * @name get_children_dom(obj)
		 * @param  {mixed} obj
		 * @return {jQuery}
		 */
		get_children_dom : function (obj) {
			obj = this.get_node(obj, true);
			if(obj[0] === this.element[0]) {
				return this.get_container_ul().children(".jstree-node");
			}
			if(!obj || !obj.length) {
				return false;
			}
			return obj.children(".jstree-children").children(".jstree-node");
		},
		/**
		 * checks if a node has children
		 * @name is_parent(obj)
		 * @param  {mixed} obj
		 * @return {Boolean}
		 */
		is_parent : function (obj) {
			obj = this.get_node(obj);
			return obj && (obj.state.loaded === false || obj.children.length > 0);
		},
		/**
		 * checks if a node is loaded (its children are available)
		 * @name is_loaded(obj)
		 * @param  {mixed} obj
		 * @return {Boolean}
		 */
		is_loaded : function (obj) {
			obj = this.get_node(obj);
			return obj && obj.state.loaded;
		},
		/**
		 * check if a node is currently loading (fetching children)
		 * @name is_loading(obj)
		 * @param  {mixed} obj
		 * @return {Boolean}
		 */
		is_loading : function (obj) {
			obj = this.get_node(obj);
			return obj && obj.state && obj.state.loading;
		},
		/**
		 * check if a node is opened
		 * @name is_open(obj)
		 * @param  {mixed} obj
		 * @return {Boolean}
		 */
		is_open : function (obj) {
			obj = this.get_node(obj);
			return obj && obj.state.opened;
		},
		/**
		 * check if a node is in a closed state
		 * @name is_closed(obj)
		 * @param  {mixed} obj
		 * @return {Boolean}
		 */
		is_closed : function (obj) {
			obj = this.get_node(obj);
			return obj && this.is_parent(obj) && !obj.state.opened;
		},
		/**
		 * check if a node has no children
		 * @name is_leaf(obj)
		 * @param  {mixed} obj
		 * @return {Boolean}
		 */
		is_leaf : function (obj) {
			return !this.is_parent(obj);
		},
		/**
		 * loads a node (fetches its children using the `core.data` setting). Multiple nodes can be passed to by using an array.
		 * @name load_node(obj [, callback])
		 * @param  {mixed} obj
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives two arguments - the node and a boolean status
		 * @return {Boolean}
		 * @trigger load_node.jstree
		 */
		load_node : function (obj, callback) {
			var k, l, i, j, c;
			if($.isArray(obj)) {
				this._load_nodes(obj.slice(), callback);
				return true;
			}
			obj = this.get_node(obj);
			if(!obj) {
				if(callback) { callback.call(this, obj, false); }
				return false;
			}
			// if(obj.state.loading) { } // the node is already loading - just wait for it to load and invoke callback? but if called implicitly it should be loaded again?
			if(obj.state.loaded) {
				obj.state.loaded = false;
				for(i = 0, j = obj.parents.length; i < j; i++) {
					this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) {
						return $.inArray(v, obj.children_d) === -1;
					});
				}
				for(k = 0, l = obj.children_d.length; k < l; k++) {
					if(this._model.data[obj.children_d[k]].state.selected) {
						c = true;
					}
					delete this._model.data[obj.children_d[k]];
				}
				if (c) {
					this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) {
						return $.inArray(v, obj.children_d) === -1;
					});
				}
				obj.children = [];
				obj.children_d = [];
				if(c) {
					this.trigger('changed', { 'action' : 'load_node', 'node' : obj, 'selected' : this._data.core.selected });
				}
			}
			obj.state.failed = false;
			obj.state.loading = true;
			this.get_node(obj, true).addClass("jstree-loading").attr('aria-busy',true);
			this._load_node(obj, $.proxy(function (status) {
				obj = this._model.data[obj.id];
				obj.state.loading = false;
				obj.state.loaded = status;
				obj.state.failed = !obj.state.loaded;
				var dom = this.get_node(obj, true), i = 0, j = 0, m = this._model.data, has_children = false;
				for(i = 0, j = obj.children.length; i < j; i++) {
					if(m[obj.children[i]] && !m[obj.children[i]].state.hidden) {
						has_children = true;
						break;
					}
				}
				if(obj.state.loaded && dom && dom.length) {
					dom.removeClass('jstree-closed jstree-open jstree-leaf');
					if (!has_children) {
						dom.addClass('jstree-leaf');
					}
					else {
						if (obj.id !== '#') {
							dom.addClass(obj.state.opened ? 'jstree-open' : 'jstree-closed');
						}
					}
				}
				dom.removeClass("jstree-loading").attr('aria-busy',false);
				/**
				 * triggered after a node is loaded
				 * @event
				 * @name load_node.jstree
				 * @param {Object} node the node that was loading
				 * @param {Boolean} status was the node loaded successfully
				 */
				this.trigger('load_node', { "node" : obj, "status" : status });
				if(callback) {
					callback.call(this, obj, status);
				}
			}, this));
			return true;
		},
		/**
		 * load an array of nodes (will also load unavailable nodes as soon as the appear in the structure). Used internally.
		 * @private
		 * @name _load_nodes(nodes [, callback])
		 * @param  {array} nodes
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - the array passed to _load_nodes
		 */
		_load_nodes : function (nodes, callback, is_callback, force_reload) {
			var r = true,
				c = function () { this._load_nodes(nodes, callback, true); },
				m = this._model.data, i, j, tmp = [];
			for(i = 0, j = nodes.length; i < j; i++) {
				if(m[nodes[i]] && ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || (!is_callback && force_reload) )) {
					if(!this.is_loading(nodes[i])) {
						this.load_node(nodes[i], c);
					}
					r = false;
				}
			}
			if(r) {
				for(i = 0, j = nodes.length; i < j; i++) {
					if(m[nodes[i]] && m[nodes[i]].state.loaded) {
						tmp.push(nodes[i]);
					}
				}
				if(callback && !callback.done) {
					callback.call(this, tmp);
					callback.done = true;
				}
			}
		},
		/**
		 * loads all unloaded nodes
		 * @name load_all([obj, callback])
		 * @param {mixed} obj the node to load recursively, omit to load all nodes in the tree
		 * @param {function} callback a function to be executed once loading all the nodes is complete,
		 * @trigger load_all.jstree
		 */
		load_all : function (obj, callback) {
			if(!obj) { obj = $.jstree.root; }
			obj = this.get_node(obj);
			if(!obj) { return false; }
			var to_load = [],
				m = this._model.data,
				c = m[obj.id].children_d,
				i, j;
			if(obj.state && !obj.state.loaded) {
				to_load.push(obj.id);
			}
			for(i = 0, j = c.length; i < j; i++) {
				if(m[c[i]] && m[c[i]].state && !m[c[i]].state.loaded) {
					to_load.push(c[i]);
				}
			}
			if(to_load.length) {
				this._load_nodes(to_load, function () {
					this.load_all(obj, callback);
				});
			}
			else {
				/**
				 * triggered after a load_all call completes
				 * @event
				 * @name load_all.jstree
				 * @param {Object} node the recursively loaded node
				 */
				if(callback) { callback.call(this, obj); }
				this.trigger('load_all', { "node" : obj });
			}
		},
		/**
		 * handles the actual loading of a node. Used only internally.
		 * @private
		 * @name _load_node(obj [, callback])
		 * @param  {mixed} obj
		 * @param  {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - a boolean status
		 * @return {Boolean}
		 */
		_load_node : function (obj, callback) {
			var s = this.settings.core.data, t;
			var notTextOrCommentNode = function notTextOrCommentNode () {
				return this.nodeType !== 3 && this.nodeType !== 8;
			};
			// use original HTML
			if(!s) {
				if(obj.id === $.jstree.root) {
					return this._append_html_data(obj, this._data.core.original_container_html.clone(true), function (status) {
						callback.call(this, status);
					});
				}
				else {
					return callback.call(this, false);
				}
				// return callback.call(this, obj.id === $.jstree.root ? this._append_html_data(obj, this._data.core.original_container_html.clone(true)) : false);
			}
			if($.isFunction(s)) {
				return s.call(this, obj, $.proxy(function (d) {
					if(d === false) {
						callback.call(this, false);
					}
					else {
						this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $($.parseHTML(d)).filter(notTextOrCommentNode) : d, function (status) {
							callback.call(this, status);
						});
					}
					// return d === false ? callback.call(this, false) : callback.call(this, this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $(d) : d));
				}, this));
			}
			if(typeof s === 'object') {
				if(s.url) {
					s = $.extend(true, {}, s);
					if($.isFunction(s.url)) {
						s.url = s.url.call(this, obj);
					}
					if($.isFunction(s.data)) {
						s.data = s.data.call(this, obj);
					}
					return $.ajax(s)
						.done($.proxy(function (d,t,x) {
								var type = x.getResponseHeader('Content-Type');
								if((type && type.indexOf('json') !== -1) || typeof d === "object") {
									return this._append_json_data(obj, d, function (status) { callback.call(this, status); });
									//return callback.call(this, this._append_json_data(obj, d));
								}
								if((type && type.indexOf('html') !== -1) || typeof d === "string") {
									return this._append_html_data(obj, $($.parseHTML(d)).filter(notTextOrCommentNode), function (status) { callback.call(this, status); });
									// return callback.call(this, this._append_html_data(obj, $(d)));
								}
								this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : x }) };
								this.settings.core.error.call(this, this._data.core.last_error);
								return callback.call(this, false);
							}, this))
						.fail($.proxy(function (f) {
								callback.call(this, false);
								this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : f }) };
								this.settings.core.error.call(this, this._data.core.last_error);
							}, this));
				}
				t = ($.isArray(s) || $.isPlainObject(s)) ? JSON.parse(JSON.stringify(s)) : s;
				if(obj.id === $.jstree.root) {
					return this._append_json_data(obj, t, function (status) {
						callback.call(this, status);
					});
				}
				else {
					this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_05', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
					this.settings.core.error.call(this, this._data.core.last_error);
					return callback.call(this, false);
				}
				//return callback.call(this, (obj.id === $.jstree.root ? this._append_json_data(obj, t) : false) );
			}
			if(typeof s === 'string') {
				if(obj.id === $.jstree.root) {
					return this._append_html_data(obj, $($.parseHTML(s)).filter(notTextOrCommentNode), function (status) {
						callback.call(this, status);
					});
				}
				else {
					this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_06', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
					this.settings.core.error.call(this, this._data.core.last_error);
					return callback.call(this, false);
				}
				//return callback.call(this, (obj.id === $.jstree.root ? this._append_html_data(obj, $(s)) : false) );
			}
			return callback.call(this, false);
		},
		/**
		 * adds a node to the list of nodes to redraw. Used only internally.
		 * @private
		 * @name _node_changed(obj [, callback])
		 * @param  {mixed} obj
		 */
		_node_changed : function (obj) {
			obj = this.get_node(obj);
			if(obj) {
				this._model.changed.push(obj.id);
			}
		},
		/**
		 * appends HTML content to the tree. Used internally.
		 * @private
		 * @name _append_html_data(obj, data)
		 * @param  {mixed} obj the node to append to
		 * @param  {String} data the HTML string to parse and append
		 * @trigger model.jstree, changed.jstree
		 */
		_append_html_data : function (dom, data, cb) {
			dom = this.get_node(dom);
			dom.children = [];
			dom.children_d = [];
			var dat = data.is('ul') ? data.children() : data,
				par = dom.id,
				chd = [],
				dpc = [],
				m = this._model.data,
				p = m[par],
				s = this._data.core.selected.length,
				tmp, i, j;
			dat.each($.proxy(function (i, v) {
				tmp = this._parse_model_from_html($(v), par, p.parents.concat());
				if(tmp) {
					chd.push(tmp);
					dpc.push(tmp);
					if(m[tmp].children_d.length) {
						dpc = dpc.concat(m[tmp].children_d);
					}
				}
			}, this));
			p.children = chd;
			p.children_d = dpc;
			for(i = 0, j = p.parents.length; i < j; i++) {
				m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
			}
			/**
			 * triggered when new data is inserted to the tree model
			 * @event
			 * @name model.jstree
			 * @param {Array} nodes an array of node IDs
			 * @param {String} parent the parent ID of the nodes
			 */
			this.trigger('model', { "nodes" : dpc, 'parent' : par });
			if(par !== $.jstree.root) {
				this._node_changed(par);
				this.redraw();
			}
			else {
				this.get_container_ul().children('.jstree-initial-node').remove();
				this.redraw(true);
			}
			if(this._data.core.selected.length !== s) {
				this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
			}
			cb.call(this, true);
		},
		/**
		 * appends JSON content to the tree. Used internally.
		 * @private
		 * @name _append_json_data(obj, data)
		 * @param  {mixed} obj the node to append to
		 * @param  {String} data the JSON object to parse and append
		 * @param  {Boolean} force_processing internal param - do not set
		 * @trigger model.jstree, changed.jstree
		 */
		_append_json_data : function (dom, data, cb, force_processing) {
			if(this.element === null) { return; }
			dom = this.get_node(dom);
			dom.children = [];
			dom.children_d = [];
			// *%$@!!!
			if(data.d) {
				data = data.d;
				if(typeof data === "string") {
					data = JSON.parse(data);
				}
			}
			if(!$.isArray(data)) { data = [data]; }
			var w = null,
				args = {
					'df'	: this._model.default_state,
					'dat'	: data,
					'par'	: dom.id,
					'm'		: this._model.data,
					't_id'	: this._id,
					't_cnt'	: this._cnt,
					'sel'	: this._data.core.selected
				},
				func = function (data, undefined) {
					if(data.data) { data = data.data; }
					var dat = data.dat,
						par = data.par,
						chd = [],
						dpc = [],
						add = [],
						df = data.df,
						t_id = data.t_id,
						t_cnt = data.t_cnt,
						m = data.m,
						p = m[par],
						sel = data.sel,
						tmp, i, j, rslt,
						parse_flat = function (d, p, ps) {
							if(!ps) { ps = []; }
							else { ps = ps.concat(); }
							if(p) { ps.unshift(p); }
							var tid = d.id.toString(),
								i, j, c, e,
								tmp = {
									id			: tid,
									text		: d.text || '',
									icon		: d.icon !== undefined ? d.icon : true,
									parent		: p,
									parents		: ps,
									children	: d.children || [],
									children_d	: d.children_d || [],
									data		: d.data,
									state		: { },
									li_attr		: { id : false },
									a_attr		: { href : '#' },
									original	: false
								};
							for(i in df) {
								if(df.hasOwnProperty(i)) {
									tmp.state[i] = df[i];
								}
							}
							if(d && d.data && d.data.jstree && d.data.jstree.icon) {
								tmp.icon = d.data.jstree.icon;
							}
							if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
								tmp.icon = true;
							}
							if(d && d.data) {
								tmp.data = d.data;
								if(d.data.jstree) {
									for(i in d.data.jstree) {
										if(d.data.jstree.hasOwnProperty(i)) {
											tmp.state[i] = d.data.jstree[i];
										}
									}
								}
							}
							if(d && typeof d.state === 'object') {
								for (i in d.state) {
									if(d.state.hasOwnProperty(i)) {
										tmp.state[i] = d.state[i];
									}
								}
							}
							if(d && typeof d.li_attr === 'object') {
								for (i in d.li_attr) {
									if(d.li_attr.hasOwnProperty(i)) {
										tmp.li_attr[i] = d.li_attr[i];
									}
								}
							}
							if(!tmp.li_attr.id) {
								tmp.li_attr.id = tid;
							}
							if(d && typeof d.a_attr === 'object') {
								for (i in d.a_attr) {
									if(d.a_attr.hasOwnProperty(i)) {
										tmp.a_attr[i] = d.a_attr[i];
									}
								}
							}
							if(d && d.children && d.children === true) {
								tmp.state.loaded = false;
								tmp.children = [];
								tmp.children_d = [];
							}
							m[tmp.id] = tmp;
							for(i = 0, j = tmp.children.length; i < j; i++) {
								c = parse_flat(m[tmp.children[i]], tmp.id, ps);
								e = m[c];
								tmp.children_d.push(c);
								if(e.children_d.length) {
									tmp.children_d = tmp.children_d.concat(e.children_d);
								}
							}
							delete d.data;
							delete d.children;
							m[tmp.id].original = d;
							if(tmp.state.selected) {
								add.push(tmp.id);
							}
							return tmp.id;
						},
						parse_nest = function (d, p, ps) {
							if(!ps) { ps = []; }
							else { ps = ps.concat(); }
							if(p) { ps.unshift(p); }
							var tid = false, i, j, c, e, tmp;
							do {
								tid = 'j' + t_id + '_' + (++t_cnt);
							} while(m[tid]);

							tmp = {
								id			: false,
								text		: typeof d === 'string' ? d : '',
								icon		: typeof d === 'object' && d.icon !== undefined ? d.icon : true,
								parent		: p,
								parents		: ps,
								children	: [],
								children_d	: [],
								data		: null,
								state		: { },
								li_attr		: { id : false },
								a_attr		: { href : '#' },
								original	: false
							};
							for(i in df) {
								if(df.hasOwnProperty(i)) {
									tmp.state[i] = df[i];
								}
							}
							if(d && d.id) { tmp.id = d.id.toString(); }
							if(d && d.text) { tmp.text = d.text; }
							if(d && d.data && d.data.jstree && d.data.jstree.icon) {
								tmp.icon = d.data.jstree.icon;
							}
							if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
								tmp.icon = true;
							}
							if(d && d.data) {
								tmp.data = d.data;
								if(d.data.jstree) {
									for(i in d.data.jstree) {
										if(d.data.jstree.hasOwnProperty(i)) {
											tmp.state[i] = d.data.jstree[i];
										}
									}
								}
							}
							if(d && typeof d.state === 'object') {
								for (i in d.state) {
									if(d.state.hasOwnProperty(i)) {
										tmp.state[i] = d.state[i];
									}
								}
							}
							if(d && typeof d.li_attr === 'object') {
								for (i in d.li_attr) {
									if(d.li_attr.hasOwnProperty(i)) {
										tmp.li_attr[i] = d.li_attr[i];
									}
								}
							}
							if(tmp.li_attr.id && !tmp.id) {
								tmp.id = tmp.li_attr.id.toString();
							}
							if(!tmp.id) {
								tmp.id = tid;
							}
							if(!tmp.li_attr.id) {
								tmp.li_attr.id = tmp.id;
							}
							if(d && typeof d.a_attr === 'object') {
								for (i in d.a_attr) {
									if(d.a_attr.hasOwnProperty(i)) {
										tmp.a_attr[i] = d.a_attr[i];
									}
								}
							}
							if(d && d.children && d.children.length) {
								for(i = 0, j = d.children.length; i < j; i++) {
									c = parse_nest(d.children[i], tmp.id, ps);
									e = m[c];
									tmp.children.push(c);
									if(e.children_d.length) {
										tmp.children_d = tmp.children_d.concat(e.children_d);
									}
								}
								tmp.children_d = tmp.children_d.concat(tmp.children);
							}
							if(d && d.children && d.children === true) {
								tmp.state.loaded = false;
								tmp.children = [];
								tmp.children_d = [];
							}
							delete d.data;
							delete d.children;
							tmp.original = d;
							m[tmp.id] = tmp;
							if(tmp.state.selected) {
								add.push(tmp.id);
							}
							return tmp.id;
						};

					if(dat.length && dat[0].id !== undefined && dat[0].parent !== undefined) {
						// Flat JSON support (for easy import from DB):
						// 1) convert to object (foreach)
						for(i = 0, j = dat.length; i < j; i++) {
							if(!dat[i].children) {
								dat[i].children = [];
							}
							m[dat[i].id.toString()] = dat[i];
						}
						// 2) populate children (foreach)
						for(i = 0, j = dat.length; i < j; i++) {
							m[dat[i].parent.toString()].children.push(dat[i].id.toString());
							// populate parent.children_d
							p.children_d.push(dat[i].id.toString());
						}
						// 3) normalize && populate parents and children_d with recursion
						for(i = 0, j = p.children.length; i < j; i++) {
							tmp = parse_flat(m[p.children[i]], par, p.parents.concat());
							dpc.push(tmp);
							if(m[tmp].children_d.length) {
								dpc = dpc.concat(m[tmp].children_d);
							}
						}
						for(i = 0, j = p.parents.length; i < j; i++) {
							m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
						}
						// ?) three_state selection - p.state.selected && t - (if three_state foreach(dat => ch) -> foreach(parents) if(parent.selected) child.selected = true;
						rslt = {
							'cnt' : t_cnt,
							'mod' : m,
							'sel' : sel,
							'par' : par,
							'dpc' : dpc,
							'add' : add
						};
					}
					else {
						for(i = 0, j = dat.length; i < j; i++) {
							tmp = parse_nest(dat[i], par, p.parents.concat());
							if(tmp) {
								chd.push(tmp);
								dpc.push(tmp);
								if(m[tmp].children_d.length) {
									dpc = dpc.concat(m[tmp].children_d);
								}
							}
						}
						p.children = chd;
						p.children_d = dpc;
						for(i = 0, j = p.parents.length; i < j; i++) {
							m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
						}
						rslt = {
							'cnt' : t_cnt,
							'mod' : m,
							'sel' : sel,
							'par' : par,
							'dpc' : dpc,
							'add' : add
						};
					}
					if(typeof window === 'undefined' || typeof window.document === 'undefined') {
						postMessage(rslt);
					}
					else {
						return rslt;
					}
				},
				rslt = function (rslt, worker) {
					if(this.element === null) { return; }
					this._cnt = rslt.cnt;
					var i, m = this._model.data;
					for (i in m) {
						if (m.hasOwnProperty(i) && m[i].state && m[i].state.loading && rslt.mod[i]) {
							rslt.mod[i].state.loading = true;
						}
					}
					this._model.data = rslt.mod; // breaks the reference in load_node - careful

					if(worker) {
						var j, a = rslt.add, r = rslt.sel, s = this._data.core.selected.slice();
						m = this._model.data;
						// if selection was changed while calculating in worker
						if(r.length !== s.length || $.vakata.array_unique(r.concat(s)).length !== r.length) {
							// deselect nodes that are no longer selected
							for(i = 0, j = r.length; i < j; i++) {
								if($.inArray(r[i], a) === -1 && $.inArray(r[i], s) === -1) {
									m[r[i]].state.selected = false;
								}
							}
							// select nodes that were selected in the mean time
							for(i = 0, j = s.length; i < j; i++) {
								if($.inArray(s[i], r) === -1) {
									m[s[i]].state.selected = true;
								}
							}
						}
					}
					if(rslt.add.length) {
						this._data.core.selected = this._data.core.selected.concat(rslt.add);
					}

					this.trigger('model', { "nodes" : rslt.dpc, 'parent' : rslt.par });

					if(rslt.par !== $.jstree.root) {
						this._node_changed(rslt.par);
						this.redraw();
					}
					else {
						// this.get_container_ul().children('.jstree-initial-node').remove();
						this.redraw(true);
					}
					if(rslt.add.length) {
						this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
					}
					cb.call(this, true);
				};
			if(this.settings.core.worker && window.Blob && window.URL && window.Worker) {
				try {
					if(this._wrk === null) {
						this._wrk = window.URL.createObjectURL(
							new window.Blob(
								['self.onmessage = ' + func.toString()],
								{type:"text/javascript"}
							)
						);
					}
					if(!this._data.core.working || force_processing) {
						this._data.core.working = true;
						w = new window.Worker(this._wrk);
						w.onmessage = $.proxy(function (e) {
							rslt.call(this, e.data, true);
							try { w.terminate(); w = null; } catch(ignore) { }
							if(this._data.core.worker_queue.length) {
								this._append_json_data.apply(this, this._data.core.worker_queue.shift());
							}
							else {
								this._data.core.working = false;
							}
						}, this);
						if(!args.par) {
							if(this._data.core.worker_queue.length) {
								this._append_json_data.apply(this, this._data.core.worker_queue.shift());
							}
							else {
								this._data.core.working = false;
							}
						}
						else {
							w.postMessage(args);
						}
					}
					else {
						this._data.core.worker_queue.push([dom, data, cb, true]);
					}
				}
				catch(e) {
					rslt.call(this, func(args), false);
					if(this._data.core.worker_queue.length) {
						this._append_json_data.apply(this, this._data.core.worker_queue.shift());
					}
					else {
						this._data.core.working = false;
					}
				}
			}
			else {
				rslt.call(this, func(args), false);
			}
		},
		/**
		 * parses a node from a jQuery object and appends them to the in memory tree model. Used internally.
		 * @private
		 * @name _parse_model_from_html(d [, p, ps])
		 * @param  {jQuery} d the jQuery object to parse
		 * @param  {String} p the parent ID
		 * @param  {Array} ps list of all parents
		 * @return {String} the ID of the object added to the model
		 */
		_parse_model_from_html : function (d, p, ps) {
			if(!ps) { ps = []; }
			else { ps = [].concat(ps); }
			if(p) { ps.unshift(p); }
			var c, e, m = this._model.data,
				data = {
					id			: false,
					text		: false,
					icon		: true,
					parent		: p,
					parents		: ps,
					children	: [],
					children_d	: [],
					data		: null,
					state		: { },
					li_attr		: { id : false },
					a_attr		: { href : '#' },
					original	: false
				}, i, tmp, tid;
			for(i in this._model.default_state) {
				if(this._model.default_state.hasOwnProperty(i)) {
					data.state[i] = this._model.default_state[i];
				}
			}
			tmp = $.vakata.attributes(d, true);
			$.each(tmp, function (i, v) {
				v = $.trim(v);
				if(!v.length) { return true; }
				data.li_attr[i] = v;
				if(i === 'id') {
					data.id = v.toString();
				}
			});
			tmp = d.children('a').first();
			if(tmp.length) {
				tmp = $.vakata.attributes(tmp, true);
				$.each(tmp, function (i, v) {
					v = $.trim(v);
					if(v.length) {
						data.a_attr[i] = v;
					}
				});
			}
			tmp = d.children("a").first().length ? d.children("a").first().clone() : d.clone();
			tmp.children("ins, i, ul").remove();
			tmp = tmp.html();
			tmp = $('<div />').html(tmp);
			data.text = this.settings.core.force_text ? tmp.text() : tmp.html();
			tmp = d.data();
			data.data = tmp ? $.extend(true, {}, tmp) : null;
			data.state.opened = d.hasClass('jstree-open');
			data.state.selected = d.children('a').hasClass('jstree-clicked');
			data.state.disabled = d.children('a').hasClass('jstree-disabled');
			if(data.data && data.data.jstree) {
				for(i in data.data.jstree) {
					if(data.data.jstree.hasOwnProperty(i)) {
						data.state[i] = data.data.jstree[i];
					}
				}
			}
			tmp = d.children("a").children(".jstree-themeicon");
			if(tmp.length) {
				data.icon = tmp.hasClass('jstree-themeicon-hidden') ? false : tmp.attr('rel');
			}
			if(data.state.icon !== undefined) {
				data.icon = data.state.icon;
			}
			if(data.icon === undefined || data.icon === null || data.icon === "") {
				data.icon = true;
			}
			tmp = d.children("ul").children("li");
			do {
				tid = 'j' + this._id + '_' + (++this._cnt);
			} while(m[tid]);
			data.id = data.li_attr.id ? data.li_attr.id.toString() : tid;
			if(tmp.length) {
				tmp.each($.proxy(function (i, v) {
					c = this._parse_model_from_html($(v), data.id, ps);
					e = this._model.data[c];
					data.children.push(c);
					if(e.children_d.length) {
						data.children_d = data.children_d.concat(e.children_d);
					}
				}, this));
				data.children_d = data.children_d.concat(data.children);
			}
			else {
				if(d.hasClass('jstree-closed')) {
					data.state.loaded = false;
				}
			}
			if(data.li_attr['class']) {
				data.li_attr['class'] = data.li_attr['class'].replace('jstree-closed','').replace('jstree-open','');
			}
			if(data.a_attr['class']) {
				data.a_attr['class'] = data.a_attr['class'].replace('jstree-clicked','').replace('jstree-disabled','');
			}
			m[data.id] = data;
			if(data.state.selected) {
				this._data.core.selected.push(data.id);
			}
			return data.id;
		},
		/**
		 * parses a node from a JSON object (used when dealing with flat data, which has no nesting of children, but has id and parent properties) and appends it to the in memory tree model. Used internally.
		 * @private
		 * @name _parse_model_from_flat_json(d [, p, ps])
		 * @param  {Object} d the JSON object to parse
		 * @param  {String} p the parent ID
		 * @param  {Array} ps list of all parents
		 * @return {String} the ID of the object added to the model
		 */
		_parse_model_from_flat_json : function (d, p, ps) {
			if(!ps) { ps = []; }
			else { ps = ps.concat(); }
			if(p) { ps.unshift(p); }
			var tid = d.id.toString(),
				m = this._model.data,
				df = this._model.default_state,
				i, j, c, e,
				tmp = {
					id			: tid,
					text		: d.text || '',
					icon		: d.icon !== undefined ? d.icon : true,
					parent		: p,
					parents		: ps,
					children	: d.children || [],
					children_d	: d.children_d || [],
					data		: d.data,
					state		: { },
					li_attr		: { id : false },
					a_attr		: { href : '#' },
					original	: false
				};
			for(i in df) {
				if(df.hasOwnProperty(i)) {
					tmp.state[i] = df[i];
				}
			}
			if(d && d.data && d.data.jstree && d.data.jstree.icon) {
				tmp.icon = d.data.jstree.icon;
			}
			if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
				tmp.icon = true;
			}
			if(d && d.data) {
				tmp.data = d.data;
				if(d.data.jstree) {
					for(i in d.data.jstree) {
						if(d.data.jstree.hasOwnProperty(i)) {
							tmp.state[i] = d.data.jstree[i];
						}
					}
				}
			}
			if(d && typeof d.state === 'object') {
				for (i in d.state) {
					if(d.state.hasOwnProperty(i)) {
						tmp.state[i] = d.state[i];
					}
				}
			}
			if(d && typeof d.li_attr === 'object') {
				for (i in d.li_attr) {
					if(d.li_attr.hasOwnProperty(i)) {
						tmp.li_attr[i] = d.li_attr[i];
					}
				}
			}
			if(!tmp.li_attr.id) {
				tmp.li_attr.id = tid;
			}
			if(d && typeof d.a_attr === 'object') {
				for (i in d.a_attr) {
					if(d.a_attr.hasOwnProperty(i)) {
						tmp.a_attr[i] = d.a_attr[i];
					}
				}
			}
			if(d && d.children && d.children === true) {
				tmp.state.loaded = false;
				tmp.children = [];
				tmp.children_d = [];
			}
			m[tmp.id] = tmp;
			for(i = 0, j = tmp.children.length; i < j; i++) {
				c = this._parse_model_from_flat_json(m[tmp.children[i]], tmp.id, ps);
				e = m[c];
				tmp.children_d.push(c);
				if(e.children_d.length) {
					tmp.children_d = tmp.children_d.concat(e.children_d);
				}
			}
			delete d.data;
			delete d.children;
			m[tmp.id].original = d;
			if(tmp.state.selected) {
				this._data.core.selected.push(tmp.id);
			}
			return tmp.id;
		},
		/**
		 * parses a node from a JSON object and appends it to the in memory tree model. Used internally.
		 * @private
		 * @name _parse_model_from_json(d [, p, ps])
		 * @param  {Object} d the JSON object to parse
		 * @param  {String} p the parent ID
		 * @param  {Array} ps list of all parents
		 * @return {String} the ID of the object added to the model
		 */
		_parse_model_from_json : function (d, p, ps) {
			if(!ps) { ps = []; }
			else { ps = ps.concat(); }
			if(p) { ps.unshift(p); }
			var tid = false, i, j, c, e, m = this._model.data, df = this._model.default_state, tmp;
			do {
				tid = 'j' + this._id + '_' + (++this._cnt);
			} while(m[tid]);

			tmp = {
				id			: false,
				text		: typeof d === 'string' ? d : '',
				icon		: typeof d === 'object' && d.icon !== undefined ? d.icon : true,
				parent		: p,
				parents		: ps,
				children	: [],
				children_d	: [],
				data		: null,
				state		: { },
				li_attr		: { id : false },
				a_attr		: { href : '#' },
				original	: false
			};
			for(i in df) {
				if(df.hasOwnProperty(i)) {
					tmp.state[i] = df[i];
				}
			}
			if(d && d.id) { tmp.id = d.id.toString(); }
			if(d && d.text) { tmp.text = d.text; }
			if(d && d.data && d.data.jstree && d.data.jstree.icon) {
				tmp.icon = d.data.jstree.icon;
			}
			if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
				tmp.icon = true;
			}
			if(d && d.data) {
				tmp.data = d.data;
				if(d.data.jstree) {
					for(i in d.data.jstree) {
						if(d.data.jstree.hasOwnProperty(i)) {
							tmp.state[i] = d.data.jstree[i];
						}
					}
				}
			}
			if(d && typeof d.state === 'object') {
				for (i in d.state) {
					if(d.state.hasOwnProperty(i)) {
						tmp.state[i] = d.state[i];
					}
				}
			}
			if(d && typeof d.li_attr === 'object') {
				for (i in d.li_attr) {
					if(d.li_attr.hasOwnProperty(i)) {
						tmp.li_attr[i] = d.li_attr[i];
					}
				}
			}
			if(tmp.li_attr.id && !tmp.id) {
				tmp.id = tmp.li_attr.id.toString();
			}
			if(!tmp.id) {
				tmp.id = tid;
			}
			if(!tmp.li_attr.id) {
				tmp.li_attr.id = tmp.id;
			}
			if(d && typeof d.a_attr === 'object') {
				for (i in d.a_attr) {
					if(d.a_attr.hasOwnProperty(i)) {
						tmp.a_attr[i] = d.a_attr[i];
					}
				}
			}
			if(d && d.children && d.children.length) {
				for(i = 0, j = d.children.length; i < j; i++) {
					c = this._parse_model_from_json(d.children[i], tmp.id, ps);
					e = m[c];
					tmp.children.push(c);
					if(e.children_d.length) {
						tmp.children_d = tmp.children_d.concat(e.children_d);
					}
				}
				tmp.children_d = tmp.children_d.concat(tmp.children);
			}
			if(d && d.children && d.children === true) {
				tmp.state.loaded = false;
				tmp.children = [];
				tmp.children_d = [];
			}
			delete d.data;
			delete d.children;
			tmp.original = d;
			m[tmp.id] = tmp;
			if(tmp.state.selected) {
				this._data.core.selected.push(tmp.id);
			}
			return tmp.id;
		},
		/**
		 * redraws all nodes that need to be redrawn. Used internally.
		 * @private
		 * @name _redraw()
		 * @trigger redraw.jstree
		 */
		_redraw : function () {
			var nodes = this._model.force_full_redraw ? this._model.data[$.jstree.root].children.concat([]) : this._model.changed.concat([]),
				f = document.createElement('UL'), tmp, i, j, fe = this._data.core.focused;
			for(i = 0, j = nodes.length; i < j; i++) {
				tmp = this.redraw_node(nodes[i], true, this._model.force_full_redraw);
				if(tmp && this._model.force_full_redraw) {
					f.appendChild(tmp);
				}
			}
			if(this._model.force_full_redraw) {
				f.className = this.get_container_ul()[0].className;
				f.setAttribute('role','group');
				this.element.empty().append(f);
				//this.get_container_ul()[0].appendChild(f);
			}
			if(fe !== null) {
				tmp = this.get_node(fe, true);
				if(tmp && tmp.length && tmp.children('.jstree-anchor')[0] !== document.activeElement) {
					tmp.children('.jstree-anchor').focus();
				}
				else {
					this._data.core.focused = null;
				}
			}
			this._model.force_full_redraw = false;
			this._model.changed = [];
			/**
			 * triggered after nodes are redrawn
			 * @event
			 * @name redraw.jstree
			 * @param {array} nodes the redrawn nodes
			 */
			this.trigger('redraw', { "nodes" : nodes });
		},
		/**
		 * redraws all nodes that need to be redrawn or optionally - the whole tree
		 * @name redraw([full])
		 * @param {Boolean} full if set to `true` all nodes are redrawn.
		 */
		redraw : function (full) {
			if(full) {
				this._model.force_full_redraw = true;
			}
			//if(this._model.redraw_timeout) {
			//	clearTimeout(this._model.redraw_timeout);
			//}
			//this._model.redraw_timeout = setTimeout($.proxy(this._redraw, this),0);
			this._redraw();
		},
		/**
		 * redraws a single node's children. Used internally.
		 * @private
		 * @name draw_children(node)
		 * @param {mixed} node the node whose children will be redrawn
		 */
		draw_children : function (node) {
			var obj = this.get_node(node),
				i = false,
				j = false,
				k = false,
				d = document;
			if(!obj) { return false; }
			if(obj.id === $.jstree.root) { return this.redraw(true); }
			node = this.get_node(node, true);
			if(!node || !node.length) { return false; } // TODO: quick toggle

			node.children('.jstree-children').remove();
			node = node[0];
			if(obj.children.length && obj.state.loaded) {
				k = d.createElement('UL');
				k.setAttribute('role', 'group');
				k.className = 'jstree-children';
				for(i = 0, j = obj.children.length; i < j; i++) {
					k.appendChild(this.redraw_node(obj.children[i], true, true));
				}
				node.appendChild(k);
			}
		},
		/**
		 * redraws a single node. Used internally.
		 * @private
		 * @name redraw_node(node, deep, is_callback, force_render)
		 * @param {mixed} node the node to redraw
		 * @param {Boolean} deep should child nodes be redrawn too
		 * @param {Boolean} is_callback is this a recursion call
		 * @param {Boolean} force_render should children of closed parents be drawn anyway
		 */
		redraw_node : function (node, deep, is_callback, force_render) {
			var obj = this.get_node(node),
				par = false,
				ind = false,
				old = false,
				i = false,
				j = false,
				k = false,
				c = '',
				d = document,
				m = this._model.data,
				f = false,
				s = false,
				tmp = null,
				t = 0,
				l = 0,
				has_children = false,
				last_sibling = false;
			if(!obj) { return false; }
			if(obj.id === $.jstree.root) {  return this.redraw(true); }
			deep = deep || obj.children.length === 0;
			node = !document.querySelector ? document.getElementById(obj.id) : this.element[0].querySelector('#' + ("0123456789".indexOf(obj.id[0]) !== -1 ? '\\3' + obj.id[0] + ' ' + obj.id.substr(1).replace($.jstree.idregex,'\\$&') : obj.id.replace($.jstree.idregex,'\\$&')) ); //, this.element);
			if(!node) {
				deep = true;
				//node = d.createElement('LI');
				if(!is_callback) {
					par = obj.parent !== $.jstree.root ? $('#' + obj.parent.replace($.jstree.idregex,'\\$&'), this.element)[0] : null;
					if(par !== null && (!par || !m[obj.parent].state.opened)) {
						return false;
					}
					ind = $.inArray(obj.id, par === null ? m[$.jstree.root].children : m[obj.parent].children);
				}
			}
			else {
				node = $(node);
				if(!is_callback) {
					par = node.parent().parent()[0];
					if(par === this.element[0]) {
						par = null;
					}
					ind = node.index();
				}
				// m[obj.id].data = node.data(); // use only node's data, no need to touch jquery storage
				if(!deep && obj.children.length && !node.children('.jstree-children').length) {
					deep = true;
				}
				if(!deep) {
					old = node.children('.jstree-children')[0];
				}
				f = node.children('.jstree-anchor')[0] === document.activeElement;
				node.remove();
				//node = d.createElement('LI');
				//node = node[0];
			}
			node = this._data.core.node.cloneNode(true);
			// node is DOM, deep is boolean

			c = 'jstree-node ';
			for(i in obj.li_attr) {
				if(obj.li_attr.hasOwnProperty(i)) {
					if(i === 'id') { continue; }
					if(i !== 'class') {
						node.setAttribute(i, obj.li_attr[i]);
					}
					else {
						c += obj.li_attr[i];
					}
				}
			}
			if(!obj.a_attr.id) {
				obj.a_attr.id = obj.id + '_anchor';
			}
			node.setAttribute('aria-selected', !!obj.state.selected);
			node.setAttribute('aria-level', obj.parents.length);
			node.setAttribute('aria-labelledby', obj.a_attr.id);
			if(obj.state.disabled) {
				node.setAttribute('aria-disabled', true);
			}

			for(i = 0, j = obj.children.length; i < j; i++) {
				if(!m[obj.children[i]].state.hidden) {
					has_children = true;
					break;
				}
			}
			if(obj.parent !== null && m[obj.parent] && !obj.state.hidden) {
				i = $.inArray(obj.id, m[obj.parent].children);
				last_sibling = obj.id;
				if(i !== -1) {
					i++;
					for(j = m[obj.parent].children.length; i < j; i++) {
						if(!m[m[obj.parent].children[i]].state.hidden) {
							last_sibling = m[obj.parent].children[i];
						}
						if(last_sibling !== obj.id) {
							break;
						}
					}
				}
			}

			if(obj.state.hidden) {
				c += ' jstree-hidden';
			}
			if(obj.state.loaded && !has_children) {
				c += ' jstree-leaf';
			}
			else {
				c += obj.state.opened && obj.state.loaded ? ' jstree-open' : ' jstree-closed';
				node.setAttribute('aria-expanded', (obj.state.opened && obj.state.loaded) );
			}
			if(last_sibling === obj.id) {
				c += ' jstree-last';
			}
			node.id = obj.id;
			node.className = c;
			c = ( obj.state.selected ? ' jstree-clicked' : '') + ( obj.state.disabled ? ' jstree-disabled' : '');
			for(j in obj.a_attr) {
				if(obj.a_attr.hasOwnProperty(j)) {
					if(j === 'href' && obj.a_attr[j] === '#') { continue; }
					if(j !== 'class') {
						node.childNodes[1].setAttribute(j, obj.a_attr[j]);
					}
					else {
						c += ' ' + obj.a_attr[j];
					}
				}
			}
			if(c.length) {
				node.childNodes[1].className = 'jstree-anchor ' + c;
			}
			if((obj.icon && obj.icon !== true) || obj.icon === false) {
				if(obj.icon === false) {
					node.childNodes[1].childNodes[0].className += ' jstree-themeicon-hidden';
				}
				else if(obj.icon.indexOf('/') === -1 && obj.icon.indexOf('.') === -1) {
					node.childNodes[1].childNodes[0].className += ' ' + obj.icon + ' jstree-themeicon-custom';
				}
				else {
					node.childNodes[1].childNodes[0].style.backgroundImage = 'url("'+obj.icon+'")';
					node.childNodes[1].childNodes[0].style.backgroundPosition = 'center center';
					node.childNodes[1].childNodes[0].style.backgroundSize = 'auto';
					node.childNodes[1].childNodes[0].className += ' jstree-themeicon-custom';
				}
			}

			if(this.settings.core.force_text) {
				node.childNodes[1].appendChild(d.createTextNode(obj.text));
			}
			else {
				node.childNodes[1].innerHTML += obj.text;
			}


			if(deep && obj.children.length && (obj.state.opened || force_render) && obj.state.loaded) {
				k = d.createElement('UL');
				k.setAttribute('role', 'group');
				k.className = 'jstree-children';
				for(i = 0, j = obj.children.length; i < j; i++) {
					k.appendChild(this.redraw_node(obj.children[i], deep, true));
				}
				node.appendChild(k);
			}
			if(old) {
				node.appendChild(old);
			}
			if(!is_callback) {
				// append back using par / ind
				if(!par) {
					par = this.element[0];
				}
				for(i = 0, j = par.childNodes.length; i < j; i++) {
					if(par.childNodes[i] && par.childNodes[i].className && par.childNodes[i].className.indexOf('jstree-children') !== -1) {
						tmp = par.childNodes[i];
						break;
					}
				}
				if(!tmp) {
					tmp = d.createElement('UL');
					tmp.setAttribute('role', 'group');
					tmp.className = 'jstree-children';
					par.appendChild(tmp);
				}
				par = tmp;

				if(ind < par.childNodes.length) {
					par.insertBefore(node, par.childNodes[ind]);
				}
				else {
					par.appendChild(node);
				}
				if(f) {
					t = this.element[0].scrollTop;
					l = this.element[0].scrollLeft;
					node.childNodes[1].focus();
					this.element[0].scrollTop = t;
					this.element[0].scrollLeft = l;
				}
			}
			if(obj.state.opened && !obj.state.loaded) {
				obj.state.opened = false;
				setTimeout($.proxy(function () {
					this.open_node(obj.id, false, 0);
				}, this), 0);
			}
			return node;
		},
		/**
		 * opens a node, revaling its children. If the node is not loaded it will be loaded and opened once ready.
		 * @name open_node(obj [, callback, animation])
		 * @param {mixed} obj the node to open
		 * @param {Function} callback a function to execute once the node is opened
		 * @param {Number} animation the animation duration in milliseconds when opening the node (overrides the `core.animation` setting). Use `false` for no animation.
		 * @trigger open_node.jstree, after_open.jstree, before_open.jstree
		 */
		open_node : function (obj, callback, animation) {
			var t1, t2, d, t;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.open_node(obj[t1], callback, animation);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			animation = animation === undefined ? this.settings.core.animation : animation;
			if(!this.is_closed(obj)) {
				if(callback) {
					callback.call(this, obj, false);
				}
				return false;
			}
			if(!this.is_loaded(obj)) {
				if(this.is_loading(obj)) {
					return setTimeout($.proxy(function () {
						this.open_node(obj, callback, animation);
					}, this), 500);
				}
				this.load_node(obj, function (o, ok) {
					return ok ? this.open_node(o, callback, animation) : (callback ? callback.call(this, o, false) : false);
				});
			}
			else {
				d = this.get_node(obj, true);
				t = this;
				if(d.length) {
					if(animation && d.children(".jstree-children").length) {
						d.children(".jstree-children").stop(true, true);
					}
					if(obj.children.length && !this._firstChild(d.children('.jstree-children')[0])) {
						this.draw_children(obj);
						//d = this.get_node(obj, true);
					}
					if(!animation) {
						this.trigger('before_open', { "node" : obj });
						d[0].className = d[0].className.replace('jstree-closed', 'jstree-open');
						d[0].setAttribute("aria-expanded", true);
					}
					else {
						this.trigger('before_open', { "node" : obj });
						d
							.children(".jstree-children").css("display","none").end()
							.removeClass("jstree-closed").addClass("jstree-open").attr("aria-expanded", true)
							.children(".jstree-children").stop(true, true)
								.slideDown(animation, function () {
									this.style.display = "";
									if (t.element) {
										t.trigger("after_open", { "node" : obj });
									}
								});
					}
				}
				obj.state.opened = true;
				if(callback) {
					callback.call(this, obj, true);
				}
				if(!d.length) {
					/**
					 * triggered when a node is about to be opened (if the node is supposed to be in the DOM, it will be, but it won't be visible yet)
					 * @event
					 * @name before_open.jstree
					 * @param {Object} node the opened node
					 */
					this.trigger('before_open', { "node" : obj });
				}
				/**
				 * triggered when a node is opened (if there is an animation it will not be completed yet)
				 * @event
				 * @name open_node.jstree
				 * @param {Object} node the opened node
				 */
				this.trigger('open_node', { "node" : obj });
				if(!animation || !d.length) {
					/**
					 * triggered when a node is opened and the animation is complete
					 * @event
					 * @name after_open.jstree
					 * @param {Object} node the opened node
					 */
					this.trigger("after_open", { "node" : obj });
				}
				return true;
			}
		},
		/**
		 * opens every parent of a node (node should be loaded)
		 * @name _open_to(obj)
		 * @param {mixed} obj the node to reveal
		 * @private
		 */
		_open_to : function (obj) {
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			var i, j, p = obj.parents;
			for(i = 0, j = p.length; i < j; i+=1) {
				if(i !== $.jstree.root) {
					this.open_node(p[i], false, 0);
				}
			}
			return $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
		},
		/**
		 * closes a node, hiding its children
		 * @name close_node(obj [, animation])
		 * @param {mixed} obj the node to close
		 * @param {Number} animation the animation duration in milliseconds when closing the node (overrides the `core.animation` setting). Use `false` for no animation.
		 * @trigger close_node.jstree, after_close.jstree
		 */
		close_node : function (obj, animation) {
			var t1, t2, t, d;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.close_node(obj[t1], animation);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			if(this.is_closed(obj)) {
				return false;
			}
			animation = animation === undefined ? this.settings.core.animation : animation;
			t = this;
			d = this.get_node(obj, true);

			obj.state.opened = false;
			/**
			 * triggered when a node is closed (if there is an animation it will not be complete yet)
			 * @event
			 * @name close_node.jstree
			 * @param {Object} node the closed node
			 */
			this.trigger('close_node',{ "node" : obj });
			if(!d.length) {
				/**
				 * triggered when a node is closed and the animation is complete
				 * @event
				 * @name after_close.jstree
				 * @param {Object} node the closed node
				 */
				this.trigger("after_close", { "node" : obj });
			}
			else {
				if(!animation) {
					d[0].className = d[0].className.replace('jstree-open', 'jstree-closed');
					d.attr("aria-expanded", false).children('.jstree-children').remove();
					this.trigger("after_close", { "node" : obj });
				}
				else {
					d
						.children(".jstree-children").attr("style","display:block !important").end()
						.removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded", false)
						.children(".jstree-children").stop(true, true).slideUp(animation, function () {
							this.style.display = "";
							d.children('.jstree-children').remove();
							if (t.element) {
								t.trigger("after_close", { "node" : obj });
							}
						});
				}
			}
		},
		/**
		 * toggles a node - closing it if it is open, opening it if it is closed
		 * @name toggle_node(obj)
		 * @param {mixed} obj the node to toggle
		 */
		toggle_node : function (obj) {
			var t1, t2;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.toggle_node(obj[t1]);
				}
				return true;
			}
			if(this.is_closed(obj)) {
				return this.open_node(obj);
			}
			if(this.is_open(obj)) {
				return this.close_node(obj);
			}
		},
		/**
		 * opens all nodes within a node (or the tree), revaling their children. If the node is not loaded it will be loaded and opened once ready.
		 * @name open_all([obj, animation, original_obj])
		 * @param {mixed} obj the node to open recursively, omit to open all nodes in the tree
		 * @param {Number} animation the animation duration in milliseconds when opening the nodes, the default is no animation
		 * @param {jQuery} reference to the node that started the process (internal use)
		 * @trigger open_all.jstree
		 */
		open_all : function (obj, animation, original_obj) {
			if(!obj) { obj = $.jstree.root; }
			obj = this.get_node(obj);
			if(!obj) { return false; }
			var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true), i, j, _this;
			if(!dom.length) {
				for(i = 0, j = obj.children_d.length; i < j; i++) {
					if(this.is_closed(this._model.data[obj.children_d[i]])) {
						this._model.data[obj.children_d[i]].state.opened = true;
					}
				}
				return this.trigger('open_all', { "node" : obj });
			}
			original_obj = original_obj || dom;
			_this = this;
			dom = this.is_closed(obj) ? dom.find('.jstree-closed').addBack() : dom.find('.jstree-closed');
			dom.each(function () {
				_this.open_node(
					this,
					function(node, status) { if(status && this.is_parent(node)) { this.open_all(node, animation, original_obj); } },
					animation || 0
				);
			});
			if(original_obj.find('.jstree-closed').length === 0) {
				/**
				 * triggered when an `open_all` call completes
				 * @event
				 * @name open_all.jstree
				 * @param {Object} node the opened node
				 */
				this.trigger('open_all', { "node" : this.get_node(original_obj) });
			}
		},
		/**
		 * closes all nodes within a node (or the tree), revaling their children
		 * @name close_all([obj, animation])
		 * @param {mixed} obj the node to close recursively, omit to close all nodes in the tree
		 * @param {Number} animation the animation duration in milliseconds when closing the nodes, the default is no animation
		 * @trigger close_all.jstree
		 */
		close_all : function (obj, animation) {
			if(!obj) { obj = $.jstree.root; }
			obj = this.get_node(obj);
			if(!obj) { return false; }
			var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true),
				_this = this, i, j;
			if(dom.length) {
				dom = this.is_open(obj) ? dom.find('.jstree-open').addBack() : dom.find('.jstree-open');
				$(dom.get().reverse()).each(function () { _this.close_node(this, animation || 0); });
			}
			for(i = 0, j = obj.children_d.length; i < j; i++) {
				this._model.data[obj.children_d[i]].state.opened = false;
			}
			/**
			 * triggered when an `close_all` call completes
			 * @event
			 * @name close_all.jstree
			 * @param {Object} node the closed node
			 */
			this.trigger('close_all', { "node" : obj });
		},
		/**
		 * checks if a node is disabled (not selectable)
		 * @name is_disabled(obj)
		 * @param  {mixed} obj
		 * @return {Boolean}
		 */
		is_disabled : function (obj) {
			obj = this.get_node(obj);
			return obj && obj.state && obj.state.disabled;
		},
		/**
		 * enables a node - so that it can be selected
		 * @name enable_node(obj)
		 * @param {mixed} obj the node to enable
		 * @trigger enable_node.jstree
		 */
		enable_node : function (obj) {
			var t1, t2;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.enable_node(obj[t1]);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			obj.state.disabled = false;
			this.get_node(obj,true).children('.jstree-anchor').removeClass('jstree-disabled').attr('aria-disabled', false);
			/**
			 * triggered when an node is enabled
			 * @event
			 * @name enable_node.jstree
			 * @param {Object} node the enabled node
			 */
			this.trigger('enable_node', { 'node' : obj });
		},
		/**
		 * disables a node - so that it can not be selected
		 * @name disable_node(obj)
		 * @param {mixed} obj the node to disable
		 * @trigger disable_node.jstree
		 */
		disable_node : function (obj) {
			var t1, t2;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.disable_node(obj[t1]);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			obj.state.disabled = true;
			this.get_node(obj,true).children('.jstree-anchor').addClass('jstree-disabled').attr('aria-disabled', true);
			/**
			 * triggered when an node is disabled
			 * @event
			 * @name disable_node.jstree
			 * @param {Object} node the disabled node
			 */
			this.trigger('disable_node', { 'node' : obj });
		},
		/**
		 * determines if a node is hidden
		 * @name is_hidden(obj)
		 * @param {mixed} obj the node
		 */
		is_hidden : function (obj) {
			obj = this.get_node(obj);
			return obj.state.hidden === true;
		},
		/**
		 * hides a node - it is still in the structure but will not be visible
		 * @name hide_node(obj)
		 * @param {mixed} obj the node to hide
		 * @param {Boolean} skip_redraw internal parameter controlling if redraw is called
		 * @trigger hide_node.jstree
		 */
		hide_node : function (obj, skip_redraw) {
			var t1, t2;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.hide_node(obj[t1], true);
				}
				if (!skip_redraw) {
					this.redraw();
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			if(!obj.state.hidden) {
				obj.state.hidden = true;
				this._node_changed(obj.parent);
				if(!skip_redraw) {
					this.redraw();
				}
				/**
				 * triggered when an node is hidden
				 * @event
				 * @name hide_node.jstree
				 * @param {Object} node the hidden node
				 */
				this.trigger('hide_node', { 'node' : obj });
			}
		},
		/**
		 * shows a node
		 * @name show_node(obj)
		 * @param {mixed} obj the node to show
		 * @param {Boolean} skip_redraw internal parameter controlling if redraw is called
		 * @trigger show_node.jstree
		 */
		show_node : function (obj, skip_redraw) {
			var t1, t2;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.show_node(obj[t1], true);
				}
				if (!skip_redraw) {
					this.redraw();
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			if(obj.state.hidden) {
				obj.state.hidden = false;
				this._node_changed(obj.parent);
				if(!skip_redraw) {
					this.redraw();
				}
				/**
				 * triggered when an node is shown
				 * @event
				 * @name show_node.jstree
				 * @param {Object} node the shown node
				 */
				this.trigger('show_node', { 'node' : obj });
			}
		},
		/**
		 * hides all nodes
		 * @name hide_all()
		 * @trigger hide_all.jstree
		 */
		hide_all : function (skip_redraw) {
			var i, m = this._model.data, ids = [];
			for(i in m) {
				if(m.hasOwnProperty(i) && i !== $.jstree.root && !m[i].state.hidden) {
					m[i].state.hidden = true;
					ids.push(i);
				}
			}
			this._model.force_full_redraw = true;
			if(!skip_redraw) {
				this.redraw();
			}
			/**
			 * triggered when all nodes are hidden
			 * @event
			 * @name hide_all.jstree
			 * @param {Array} nodes the IDs of all hidden nodes
			 */
			this.trigger('hide_all', { 'nodes' : ids });
			return ids;
		},
		/**
		 * shows all nodes
		 * @name show_all()
		 * @trigger show_all.jstree
		 */
		show_all : function (skip_redraw) {
			var i, m = this._model.data, ids = [];
			for(i in m) {
				if(m.hasOwnProperty(i) && i !== $.jstree.root && m[i].state.hidden) {
					m[i].state.hidden = false;
					ids.push(i);
				}
			}
			this._model.force_full_redraw = true;
			if(!skip_redraw) {
				this.redraw();
			}
			/**
			 * triggered when all nodes are shown
			 * @event
			 * @name show_all.jstree
			 * @param {Array} nodes the IDs of all shown nodes
			 */
			this.trigger('show_all', { 'nodes' : ids });
			return ids;
		},
		/**
		 * called when a node is selected by the user. Used internally.
		 * @private
		 * @name activate_node(obj, e)
		 * @param {mixed} obj the node
		 * @param {Object} e the related event
		 * @trigger activate_node.jstree, changed.jstree
		 */
		activate_node : function (obj, e) {
			if(this.is_disabled(obj)) {
				return false;
			}
			if(!e || typeof e !== 'object') {
				e = {};
			}

			// ensure last_clicked is still in the DOM, make it fresh (maybe it was moved?) and make sure it is still selected, if not - make last_clicked the last selected node
			this._data.core.last_clicked = this._data.core.last_clicked && this._data.core.last_clicked.id !== undefined ? this.get_node(this._data.core.last_clicked.id) : null;
			if(this._data.core.last_clicked && !this._data.core.last_clicked.state.selected) { this._data.core.last_clicked = null; }
			if(!this._data.core.last_clicked && this._data.core.selected.length) { this._data.core.last_clicked = this.get_node(this._data.core.selected[this._data.core.selected.length - 1]); }

			if(!this.settings.core.multiple || (!e.metaKey && !e.ctrlKey && !e.shiftKey) || (e.shiftKey && (!this._data.core.last_clicked || !this.get_parent(obj) || this.get_parent(obj) !== this._data.core.last_clicked.parent ) )) {
				if(!this.settings.core.multiple && (e.metaKey || e.ctrlKey || e.shiftKey) && this.is_selected(obj)) {
					this.deselect_node(obj, false, e);
				}
				else {
					this.deselect_all(true);
					this.select_node(obj, false, false, e);
					this._data.core.last_clicked = this.get_node(obj);
				}
			}
			else {
				if(e.shiftKey) {
					var o = this.get_node(obj).id,
						l = this._data.core.last_clicked.id,
						p = this.get_node(this._data.core.last_clicked.parent).children,
						c = false,
						i, j;
					for(i = 0, j = p.length; i < j; i += 1) {
						// separate IFs work whem o and l are the same
						if(p[i] === o) {
							c = !c;
						}
						if(p[i] === l) {
							c = !c;
						}
						if(!this.is_disabled(p[i]) && (c || p[i] === o || p[i] === l)) {
							if (!this.is_hidden(p[i])) {
								this.select_node(p[i], true, false, e);
							}
						}
						else {
							this.deselect_node(p[i], true, e);
						}
					}
					this.trigger('changed', { 'action' : 'select_node', 'node' : this.get_node(obj), 'selected' : this._data.core.selected, 'event' : e });
				}
				else {
					if(!this.is_selected(obj)) {
						this.select_node(obj, false, false, e);
					}
					else {
						this.deselect_node(obj, false, e);
					}
				}
			}
			/**
			 * triggered when an node is clicked or intercated with by the user
			 * @event
			 * @name activate_node.jstree
			 * @param {Object} node
			 * @param {Object} event the ooriginal event (if any) which triggered the call (may be an empty object)
			 */
			this.trigger('activate_node', { 'node' : this.get_node(obj), 'event' : e });
		},
		/**
		 * applies the hover state on a node, called when a node is hovered by the user. Used internally.
		 * @private
		 * @name hover_node(obj)
		 * @param {mixed} obj
		 * @trigger hover_node.jstree
		 */
		hover_node : function (obj) {
			obj = this.get_node(obj, true);
			if(!obj || !obj.length || obj.children('.jstree-hovered').length) {
				return false;
			}
			var o = this.element.find('.jstree-hovered'), t = this.element;
			if(o && o.length) { this.dehover_node(o); }

			obj.children('.jstree-anchor').addClass('jstree-hovered');
			/**
			 * triggered when an node is hovered
			 * @event
			 * @name hover_node.jstree
			 * @param {Object} node
			 */
			this.trigger('hover_node', { 'node' : this.get_node(obj) });
			setTimeout(function () { t.attr('aria-activedescendant', obj[0].id); }, 0);
		},
		/**
		 * removes the hover state from a nodecalled when a node is no longer hovered by the user. Used internally.
		 * @private
		 * @name dehover_node(obj)
		 * @param {mixed} obj
		 * @trigger dehover_node.jstree
		 */
		dehover_node : function (obj) {
			obj = this.get_node(obj, true);
			if(!obj || !obj.length || !obj.children('.jstree-hovered').length) {
				return false;
			}
			obj.children('.jstree-anchor').removeClass('jstree-hovered');
			/**
			 * triggered when an node is no longer hovered
			 * @event
			 * @name dehover_node.jstree
			 * @param {Object} node
			 */
			this.trigger('dehover_node', { 'node' : this.get_node(obj) });
		},
		/**
		 * select a node
		 * @name select_node(obj [, supress_event, prevent_open])
		 * @param {mixed} obj an array can be used to select multiple nodes
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
		 * @param {Boolean} prevent_open if set to `true` parents of the selected node won't be opened
		 * @trigger select_node.jstree, changed.jstree
		 */
		select_node : function (obj, supress_event, prevent_open, e) {
			var dom, t1, t2, th;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.select_node(obj[t1], supress_event, prevent_open, e);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			dom = this.get_node(obj, true);
			if(!obj.state.selected) {
				obj.state.selected = true;
				this._data.core.selected.push(obj.id);
				if(!prevent_open) {
					dom = this._open_to(obj);
				}
				if(dom && dom.length) {
					dom.attr('aria-selected', true).children('.jstree-anchor').addClass('jstree-clicked');
				}
				/**
				 * triggered when an node is selected
				 * @event
				 * @name select_node.jstree
				 * @param {Object} node
				 * @param {Array} selected the current selection
				 * @param {Object} event the event (if any) that triggered this select_node
				 */
				this.trigger('select_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
				if(!supress_event) {
					/**
					 * triggered when selection changes
					 * @event
					 * @name changed.jstree
					 * @param {Object} node
					 * @param {Object} action the action that caused the selection to change
					 * @param {Array} selected the current selection
					 * @param {Object} event the event (if any) that triggered this changed event
					 */
					this.trigger('changed', { 'action' : 'select_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
				}
			}
		},
		/**
		 * deselect a node
		 * @name deselect_node(obj [, supress_event])
		 * @param {mixed} obj an array can be used to deselect multiple nodes
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
		 * @trigger deselect_node.jstree, changed.jstree
		 */
		deselect_node : function (obj, supress_event, e) {
			var t1, t2, dom;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.deselect_node(obj[t1], supress_event, e);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			dom = this.get_node(obj, true);
			if(obj.state.selected) {
				obj.state.selected = false;
				this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.id);
				if(dom.length) {
					dom.attr('aria-selected', false).children('.jstree-anchor').removeClass('jstree-clicked');
				}
				/**
				 * triggered when an node is deselected
				 * @event
				 * @name deselect_node.jstree
				 * @param {Object} node
				 * @param {Array} selected the current selection
				 * @param {Object} event the event (if any) that triggered this deselect_node
				 */
				this.trigger('deselect_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
				if(!supress_event) {
					this.trigger('changed', { 'action' : 'deselect_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
				}
			}
		},
		/**
		 * select all nodes in the tree
		 * @name select_all([supress_event])
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
		 * @trigger select_all.jstree, changed.jstree
		 */
		select_all : function (supress_event) {
			var tmp = this._data.core.selected.concat([]), i, j;
			this._data.core.selected = this._model.data[$.jstree.root].children_d.concat();
			for(i = 0, j = this._data.core.selected.length; i < j; i++) {
				if(this._model.data[this._data.core.selected[i]]) {
					this._model.data[this._data.core.selected[i]].state.selected = true;
				}
			}
			this.redraw(true);
			/**
			 * triggered when all nodes are selected
			 * @event
			 * @name select_all.jstree
			 * @param {Array} selected the current selection
			 */
			this.trigger('select_all', { 'selected' : this._data.core.selected });
			if(!supress_event) {
				this.trigger('changed', { 'action' : 'select_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
			}
		},
		/**
		 * deselect all selected nodes
		 * @name deselect_all([supress_event])
		 * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
		 * @trigger deselect_all.jstree, changed.jstree
		 */
		deselect_all : function (supress_event) {
			var tmp = this._data.core.selected.concat([]), i, j;
			for(i = 0, j = this._data.core.selected.length; i < j; i++) {
				if(this._model.data[this._data.core.selected[i]]) {
					this._model.data[this._data.core.selected[i]].state.selected = false;
				}
			}
			this._data.core.selected = [];
			this.element.find('.jstree-clicked').removeClass('jstree-clicked').parent().attr('aria-selected', false);
			/**
			 * triggered when all nodes are deselected
			 * @event
			 * @name deselect_all.jstree
			 * @param {Object} node the previous selection
			 * @param {Array} selected the current selection
			 */
			this.trigger('deselect_all', { 'selected' : this._data.core.selected, 'node' : tmp });
			if(!supress_event) {
				this.trigger('changed', { 'action' : 'deselect_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
			}
		},
		/**
		 * checks if a node is selected
		 * @name is_selected(obj)
		 * @param  {mixed}  obj
		 * @return {Boolean}
		 */
		is_selected : function (obj) {
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			return obj.state.selected;
		},
		/**
		 * get an array of all selected nodes
		 * @name get_selected([full])
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
		 * @return {Array}
		 */
		get_selected : function (full) {
			return full ? $.map(this._data.core.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.core.selected.slice();
		},
		/**
		 * get an array of all top level selected nodes (ignoring children of selected nodes)
		 * @name get_top_selected([full])
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
		 * @return {Array}
		 */
		get_top_selected : function (full) {
			var tmp = this.get_selected(true),
				obj = {}, i, j, k, l;
			for(i = 0, j = tmp.length; i < j; i++) {
				obj[tmp[i].id] = tmp[i];
			}
			for(i = 0, j = tmp.length; i < j; i++) {
				for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
					if(obj[tmp[i].children_d[k]]) {
						delete obj[tmp[i].children_d[k]];
					}
				}
			}
			tmp = [];
			for(i in obj) {
				if(obj.hasOwnProperty(i)) {
					tmp.push(i);
				}
			}
			return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp;
		},
		/**
		 * get an array of all bottom level selected nodes (ignoring selected parents)
		 * @name get_bottom_selected([full])
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
		 * @return {Array}
		 */
		get_bottom_selected : function (full) {
			var tmp = this.get_selected(true),
				obj = [], i, j;
			for(i = 0, j = tmp.length; i < j; i++) {
				if(!tmp[i].children.length) {
					obj.push(tmp[i].id);
				}
			}
			return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj;
		},
		/**
		 * gets the current state of the tree so that it can be restored later with `set_state(state)`. Used internally.
		 * @name get_state()
		 * @private
		 * @return {Object}
		 */
		get_state : function () {
			var state	= {
				'core' : {
					'open' : [],
					'scroll' : {
						'left' : this.element.scrollLeft(),
						'top' : this.element.scrollTop()
					},
					/*!
					'themes' : {
						'name' : this.get_theme(),
						'icons' : this._data.core.themes.icons,
						'dots' : this._data.core.themes.dots
					},
					*/
					'selected' : []
				}
			}, i;
			for(i in this._model.data) {
				if(this._model.data.hasOwnProperty(i)) {
					if(i !== $.jstree.root) {
						if(this._model.data[i].state.opened) {
							state.core.open.push(i);
						}
						if(this._model.data[i].state.selected) {
							state.core.selected.push(i);
						}
					}
				}
			}
			return state;
		},
		/**
		 * sets the state of the tree. Used internally.
		 * @name set_state(state [, callback])
		 * @private
		 * @param {Object} state the state to restore. Keep in mind this object is passed by reference and jstree will modify it.
		 * @param {Function} callback an optional function to execute once the state is restored.
		 * @trigger set_state.jstree
		 */
		set_state : function (state, callback) {
			if(state) {
				if(state.core) {
					var res, n, t, _this, i;
					if(state.core.open) {
						if(!$.isArray(state.core.open) || !state.core.open.length) {
							delete state.core.open;
							this.set_state(state, callback);
						}
						else {
							this._load_nodes(state.core.open, function (nodes) {
								this.open_node(nodes, false, 0);
								delete state.core.open;
								this.set_state(state, callback);
							});
						}
						return false;
					}
					if(state.core.scroll) {
						if(state.core.scroll && state.core.scroll.left !== undefined) {
							this.element.scrollLeft(state.core.scroll.left);
						}
						if(state.core.scroll && state.core.scroll.top !== undefined) {
							this.element.scrollTop(state.core.scroll.top);
						}
						delete state.core.scroll;
						this.set_state(state, callback);
						return false;
					}
					if(state.core.selected) {
						_this = this;
						this.deselect_all();
						$.each(state.core.selected, function (i, v) {
							_this.select_node(v, false, true);
						});
						delete state.core.selected;
						this.set_state(state, callback);
						return false;
					}
					for(i in state) {
						if(state.hasOwnProperty(i) && i !== "core" && $.inArray(i, this.settings.plugins) === -1) {
							delete state[i];
						}
					}
					if($.isEmptyObject(state.core)) {
						delete state.core;
						this.set_state(state, callback);
						return false;
					}
				}
				if($.isEmptyObject(state)) {
					state = null;
					if(callback) { callback.call(this); }
					/**
					 * triggered when a `set_state` call completes
					 * @event
					 * @name set_state.jstree
					 */
					this.trigger('set_state');
					return false;
				}
				return true;
			}
			return false;
		},
		/**
		 * refreshes the tree - all nodes are reloaded with calls to `load_node`.
		 * @name refresh()
		 * @param {Boolean} skip_loading an option to skip showing the loading indicator
		 * @param {Mixed} forget_state if set to `true` state will not be reapplied, if set to a function (receiving the current state as argument) the result of that function will be used as state
		 * @trigger refresh.jstree
		 */
		refresh : function (skip_loading, forget_state) {
			this._data.core.state = forget_state === true ? {} : this.get_state();
			if(forget_state && $.isFunction(forget_state)) { this._data.core.state = forget_state.call(this, this._data.core.state); }
			this._cnt = 0;
			this._model.data = {};
			this._model.data[$.jstree.root] = {
				id : $.jstree.root,
				parent : null,
				parents : [],
				children : [],
				children_d : [],
				state : { loaded : false }
			};
			this._data.core.selected = [];
			this._data.core.last_clicked = null;
			this._data.core.focused = null;

			var c = this.get_container_ul()[0].className;
			if(!skip_loading) {
				this.element.html("<"+"ul class='"+c+"' role='group'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='treeitem' id='j"+this._id+"_loading'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
				this.element.attr('aria-activedescendant','j'+this._id+'_loading');
			}
			this.load_node($.jstree.root, function (o, s) {
				if(s) {
					this.get_container_ul()[0].className = c;
					if(this._firstChild(this.get_container_ul()[0])) {
						this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
					}
					this.set_state($.extend(true, {}, this._data.core.state), function () {
						/**
						 * triggered when a `refresh` call completes
						 * @event
						 * @name refresh.jstree
						 */
						this.trigger('refresh');
					});
				}
				this._data.core.state = null;
			});
		},
		/**
		 * refreshes a node in the tree (reload its children) all opened nodes inside that node are reloaded with calls to `load_node`.
		 * @name refresh_node(obj)
		 * @param  {mixed} obj the node
		 * @trigger refresh_node.jstree
		 */
		refresh_node : function (obj) {
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }
			var opened = [], to_load = [], s = this._data.core.selected.concat([]);
			to_load.push(obj.id);
			if(obj.state.opened === true) { opened.push(obj.id); }
			this.get_node(obj, true).find('.jstree-open').each(function() { to_load.push(this.id); opened.push(this.id); });
			this._load_nodes(to_load, $.proxy(function (nodes) {
				this.open_node(opened, false, 0);
				this.select_node(s);
				/**
				 * triggered when a node is refreshed
				 * @event
				 * @name refresh_node.jstree
				 * @param {Object} node - the refreshed node
				 * @param {Array} nodes - an array of the IDs of the nodes that were reloaded
				 */
				this.trigger('refresh_node', { 'node' : obj, 'nodes' : nodes });
			}, this), false, true);
		},
		/**
		 * set (change) the ID of a node
		 * @name set_id(obj, id)
		 * @param  {mixed} obj the node
		 * @param  {String} id the new ID
		 * @return {Boolean}
		 * @trigger set_id.jstree
		 */
		set_id : function (obj, id) {
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }
			var i, j, m = this._model.data, old = obj.id;
			id = id.toString();
			// update parents (replace current ID with new one in children and children_d)
			m[obj.parent].children[$.inArray(obj.id, m[obj.parent].children)] = id;
			for(i = 0, j = obj.parents.length; i < j; i++) {
				m[obj.parents[i]].children_d[$.inArray(obj.id, m[obj.parents[i]].children_d)] = id;
			}
			// update children (replace current ID with new one in parent and parents)
			for(i = 0, j = obj.children.length; i < j; i++) {
				m[obj.children[i]].parent = id;
			}
			for(i = 0, j = obj.children_d.length; i < j; i++) {
				m[obj.children_d[i]].parents[$.inArray(obj.id, m[obj.children_d[i]].parents)] = id;
			}
			i = $.inArray(obj.id, this._data.core.selected);
			if(i !== -1) { this._data.core.selected[i] = id; }
			// update model and obj itself (obj.id, this._model.data[KEY])
			i = this.get_node(obj.id, true);
			if(i) {
				i.attr('id', id); //.children('.jstree-anchor').attr('id', id + '_anchor').end().attr('aria-labelledby', id + '_anchor');
				if(this.element.attr('aria-activedescendant') === obj.id) {
					this.element.attr('aria-activedescendant', id);
				}
			}
			delete m[obj.id];
			obj.id = id;
			obj.li_attr.id = id;
			m[id] = obj;
			/**
			 * triggered when a node id value is changed
			 * @event
			 * @name set_id.jstree
			 * @param {Object} node
			 * @param {String} old the old id
			 */
			this.trigger('set_id',{ "node" : obj, "new" : obj.id, "old" : old });
			return true;
		},
		/**
		 * get the text value of a node
		 * @name get_text(obj)
		 * @param  {mixed} obj the node
		 * @return {String}
		 */
		get_text : function (obj) {
			obj = this.get_node(obj);
			return (!obj || obj.id === $.jstree.root) ? false : obj.text;
		},
		/**
		 * set the text value of a node. Used internally, please use `rename_node(obj, val)`.
		 * @private
		 * @name set_text(obj, val)
		 * @param  {mixed} obj the node, you can pass an array to set the text on multiple nodes
		 * @param  {String} val the new text value
		 * @return {Boolean}
		 * @trigger set_text.jstree
		 */
		set_text : function (obj, val) {
			var t1, t2;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.set_text(obj[t1], val);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }
			obj.text = val;
			if(this.get_node(obj, true).length) {
				this.redraw_node(obj.id);
			}
			/**
			 * triggered when a node text value is changed
			 * @event
			 * @name set_text.jstree
			 * @param {Object} obj
			 * @param {String} text the new value
			 */
			this.trigger('set_text',{ "obj" : obj, "text" : val });
			return true;
		},
		/**
		 * gets a JSON representation of a node (or the whole tree)
		 * @name get_json([obj, options])
		 * @param  {mixed} obj
		 * @param  {Object} options
		 * @param  {Boolean} options.no_state do not return state information
		 * @param  {Boolean} options.no_id do not return ID
		 * @param  {Boolean} options.no_children do not include children
		 * @param  {Boolean} options.no_data do not include node data
		 * @param  {Boolean} options.no_li_attr do not include LI attributes
		 * @param  {Boolean} options.no_a_attr do not include A attributes
		 * @param  {Boolean} options.flat return flat JSON instead of nested
		 * @return {Object}
		 */
		get_json : function (obj, options, flat) {
			obj = this.get_node(obj || $.jstree.root);
			if(!obj) { return false; }
			if(options && options.flat && !flat) { flat = []; }
			var tmp = {
				'id' : obj.id,
				'text' : obj.text,
				'icon' : this.get_icon(obj),
				'li_attr' : $.extend(true, {}, obj.li_attr),
				'a_attr' : $.extend(true, {}, obj.a_attr),
				'state' : {},
				'data' : options && options.no_data ? false : $.extend(true, {}, obj.data)
				//( this.get_node(obj, true).length ? this.get_node(obj, true).data() : obj.data ),
			}, i, j;
			if(options && options.flat) {
				tmp.parent = obj.parent;
			}
			else {
				tmp.children = [];
			}
			if(!options || !options.no_state) {
				for(i in obj.state) {
					if(obj.state.hasOwnProperty(i)) {
						tmp.state[i] = obj.state[i];
					}
				}
			} else {
				delete tmp.state;
			}
			if(options && options.no_li_attr) {
				delete tmp.li_attr;
			}
			if(options && options.no_a_attr) {
				delete tmp.a_attr;
			}
			if(options && options.no_id) {
				delete tmp.id;
				if(tmp.li_attr && tmp.li_attr.id) {
					delete tmp.li_attr.id;
				}
				if(tmp.a_attr && tmp.a_attr.id) {
					delete tmp.a_attr.id;
				}
			}
			if(options && options.flat && obj.id !== $.jstree.root) {
				flat.push(tmp);
			}
			if(!options || !options.no_children) {
				for(i = 0, j = obj.children.length; i < j; i++) {
					if(options && options.flat) {
						this.get_json(obj.children[i], options, flat);
					}
					else {
						tmp.children.push(this.get_json(obj.children[i], options));
					}
				}
			}
			return options && options.flat ? flat : (obj.id === $.jstree.root ? tmp.children : tmp);
		},
		/**
		 * create a new node (do not confuse with load_node)
		 * @name create_node([par, node, pos, callback, is_loaded])
		 * @param  {mixed}   par       the parent node (to create a root node use either "#" (string) or `null`)
		 * @param  {mixed}   node      the data for the new node (a valid JSON object, or a simple string with the name)
		 * @param  {mixed}   pos       the index at which to insert the node, "first" and "last" are also supported, default is "last"
		 * @param  {Function} callback a function to be called once the node is created
		 * @param  {Boolean} is_loaded internal argument indicating if the parent node was succesfully loaded
		 * @return {String}            the ID of the newly create node
		 * @trigger model.jstree, create_node.jstree
		 */
		create_node : function (par, node, pos, callback, is_loaded) {
			if(par === null) { par = $.jstree.root; }
			par = this.get_node(par);
			if(!par) { return false; }
			pos = pos === undefined ? "last" : pos;
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
				return this.load_node(par, function () { this.create_node(par, node, pos, callback, true); });
			}
			if(!node) { node = { "text" : this.get_string('New node') }; }
			if(typeof node === "string") { node = { "text" : node }; }
			if(node.text === undefined) { node.text = this.get_string('New node'); }
			var tmp, dpc, i, j;

			if(par.id === $.jstree.root) {
				if(pos === "before") { pos = "first"; }
				if(pos === "after") { pos = "last"; }
			}
			switch(pos) {
				case "before":
					tmp = this.get_node(par.parent);
					pos = $.inArray(par.id, tmp.children);
					par = tmp;
					break;
				case "after" :
					tmp = this.get_node(par.parent);
					pos = $.inArray(par.id, tmp.children) + 1;
					par = tmp;
					break;
				case "inside":
				case "first":
					pos = 0;
					break;
				case "last":
					pos = par.children.length;
					break;
				default:
					if(!pos) { pos = 0; }
					break;
			}
			if(pos > par.children.length) { pos = par.children.length; }
			if(!node.id) { node.id = true; }
			if(!this.check("create_node", node, par, pos)) {
				this.settings.core.error.call(this, this._data.core.last_error);
				return false;
			}
			if(node.id === true) { delete node.id; }
			node = this._parse_model_from_json(node, par.id, par.parents.concat());
			if(!node) { return false; }
			tmp = this.get_node(node);
			dpc = [];
			dpc.push(node);
			dpc = dpc.concat(tmp.children_d);
			this.trigger('model', { "nodes" : dpc, "parent" : par.id });

			par.children_d = par.children_d.concat(dpc);
			for(i = 0, j = par.parents.length; i < j; i++) {
				this._model.data[par.parents[i]].children_d = this._model.data[par.parents[i]].children_d.concat(dpc);
			}
			node = tmp;
			tmp = [];
			for(i = 0, j = par.children.length; i < j; i++) {
				tmp[i >= pos ? i+1 : i] = par.children[i];
			}
			tmp[pos] = node.id;
			par.children = tmp;

			this.redraw_node(par, true);
			if(callback) { callback.call(this, this.get_node(node)); }
			/**
			 * triggered when a node is created
			 * @event
			 * @name create_node.jstree
			 * @param {Object} node
			 * @param {String} parent the parent's ID
			 * @param {Number} position the position of the new node among the parent's children
			 */
			this.trigger('create_node', { "node" : this.get_node(node), "parent" : par.id, "position" : pos });
			return node.id;
		},
		/**
		 * set the text value of a node
		 * @name rename_node(obj, val)
		 * @param  {mixed} obj the node, you can pass an array to rename multiple nodes to the same name
		 * @param  {String} val the new text value
		 * @return {Boolean}
		 * @trigger rename_node.jstree
		 */
		rename_node : function (obj, val) {
			var t1, t2, old;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.rename_node(obj[t1], val);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }
			old = obj.text;
			if(!this.check("rename_node", obj, this.get_parent(obj), val)) {
				this.settings.core.error.call(this, this._data.core.last_error);
				return false;
			}
			this.set_text(obj, val); // .apply(this, Array.prototype.slice.call(arguments))
			/**
			 * triggered when a node is renamed
			 * @event
			 * @name rename_node.jstree
			 * @param {Object} node
			 * @param {String} text the new value
			 * @param {String} old the old value
			 */
			this.trigger('rename_node', { "node" : obj, "text" : val, "old" : old });
			return true;
		},
		/**
		 * remove a node
		 * @name delete_node(obj)
		 * @param  {mixed} obj the node, you can pass an array to delete multiple nodes
		 * @return {Boolean}
		 * @trigger delete_node.jstree, changed.jstree
		 */
		delete_node : function (obj) {
			var t1, t2, par, pos, tmp, i, j, k, l, c, top, lft;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.delete_node(obj[t1]);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }
			par = this.get_node(obj.parent);
			pos = $.inArray(obj.id, par.children);
			c = false;
			if(!this.check("delete_node", obj, par, pos)) {
				this.settings.core.error.call(this, this._data.core.last_error);
				return false;
			}
			if(pos !== -1) {
				par.children = $.vakata.array_remove(par.children, pos);
			}
			tmp = obj.children_d.concat([]);
			tmp.push(obj.id);
			for(i = 0, j = obj.parents.length; i < j; i++) {
				this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) {
					return $.inArray(v, tmp) === -1;
				});
			}
			for(k = 0, l = tmp.length; k < l; k++) {
				if(this._model.data[tmp[k]].state.selected) {
					c = true;
					break;
				}
			}
			if (c) {
				this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) {
					return $.inArray(v, tmp) === -1;
				});
			}
			/**
			 * triggered when a node is deleted
			 * @event
			 * @name delete_node.jstree
			 * @param {Object} node
			 * @param {String} parent the parent's ID
			 */
			this.trigger('delete_node', { "node" : obj, "parent" : par.id });
			if(c) {
				this.trigger('changed', { 'action' : 'delete_node', 'node' : obj, 'selected' : this._data.core.selected, 'parent' : par.id });
			}
			for(k = 0, l = tmp.length; k < l; k++) {
				delete this._model.data[tmp[k]];
			}
			if($.inArray(this._data.core.focused, tmp) !== -1) {
				this._data.core.focused = null;
				top = this.element[0].scrollTop;
				lft = this.element[0].scrollLeft;
				if(par.id === $.jstree.root) {
					if (this._model.data[$.jstree.root].children[0]) {
						this.get_node(this._model.data[$.jstree.root].children[0], true).children('.jstree-anchor').focus();
					}
				}
				else {
					this.get_node(par, true).children('.jstree-anchor').focus();
				}
				this.element[0].scrollTop  = top;
				this.element[0].scrollLeft = lft;
			}
			this.redraw_node(par, true);
			return true;
		},
		/**
		 * check if an operation is premitted on the tree. Used internally.
		 * @private
		 * @name check(chk, obj, par, pos)
		 * @param  {String} chk the operation to check, can be "create_node", "rename_node", "delete_node", "copy_node" or "move_node"
		 * @param  {mixed} obj the node
		 * @param  {mixed} par the parent
		 * @param  {mixed} pos the position to insert at, or if "rename_node" - the new name
		 * @param  {mixed} more some various additional information, for example if a "move_node" operations is triggered by DND this will be the hovered node
		 * @return {Boolean}
		 */
		check : function (chk, obj, par, pos, more) {
			obj = obj && obj.id ? obj : this.get_node(obj);
			par = par && par.id ? par : this.get_node(par);
			var tmp = chk.match(/^move_node|copy_node|create_node$/i) ? par : obj,
				chc = this.settings.core.check_callback;
			if(chk === "move_node" || chk === "copy_node") {
				if((!more || !more.is_multi) && (obj.id === par.id || (chk === "move_node" && $.inArray(obj.id, par.children) === pos) || $.inArray(par.id, obj.children_d) !== -1)) {
					this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_01', 'reason' : 'Moving parent inside child', 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
					return false;
				}
			}
			if(tmp && tmp.data) { tmp = tmp.data; }
			if(tmp && tmp.functions && (tmp.functions[chk] === false || tmp.functions[chk] === true)) {
				if(tmp.functions[chk] === false) {
					this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_02', 'reason' : 'Node data prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
				}
				return tmp.functions[chk];
			}
			if(chc === false || ($.isFunction(chc) && chc.call(this, chk, obj, par, pos, more) === false) || (chc && chc[chk] === false)) {
				this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_03', 'reason' : 'User config for core.check_callback prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
				return false;
			}
			return true;
		},
		/**
		 * get the last error
		 * @name last_error()
		 * @return {Object}
		 */
		last_error : function () {
			return this._data.core.last_error;
		},
		/**
		 * move a node to a new parent
		 * @name move_node(obj, par [, pos, callback, is_loaded])
		 * @param  {mixed} obj the node to move, pass an array to move multiple nodes
		 * @param  {mixed} par the new parent
		 * @param  {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0`
		 * @param  {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
		 * @param  {Boolean} is_loaded internal parameter indicating if the parent node has been loaded
		 * @param  {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn
		 * @param  {Boolean} instance internal parameter indicating if the node comes from another instance
		 * @trigger move_node.jstree
		 */
		move_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {
			var t1, t2, old_par, old_pos, new_par, old_ins, is_multi, dpc, tmp, i, j, k, l, p;

			par = this.get_node(par);
			pos = pos === undefined ? 0 : pos;
			if(!par) { return false; }
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
				return this.load_node(par, function () { this.move_node(obj, par, pos, callback, true, false, origin); });
			}

			if($.isArray(obj)) {
				if(obj.length === 1) {
					obj = obj[0];
				}
				else {
					//obj = obj.slice();
					for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
						if((tmp = this.move_node(obj[t1], par, pos, callback, is_loaded, false, origin))) {
							par = tmp;
							pos = "after";
						}
					}
					this.redraw();
					return true;
				}
			}
			obj = obj && obj.id ? obj : this.get_node(obj);

			if(!obj || obj.id === $.jstree.root) { return false; }

			old_par = (obj.parent || $.jstree.root).toString();
			new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent);
			old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
			is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
			old_pos = old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1;
			if(old_ins && old_ins._id) {
				obj = old_ins._model.data[obj.id];
			}

			if(is_multi) {
				if((tmp = this.copy_node(obj, par, pos, callback, is_loaded, false, origin))) {
					if(old_ins) { old_ins.delete_node(obj); }
					return tmp;
				}
				return false;
			}
			//var m = this._model.data;
			if(par.id === $.jstree.root) {
				if(pos === "before") { pos = "first"; }
				if(pos === "after") { pos = "last"; }
			}
			switch(pos) {
				case "before":
					pos = $.inArray(par.id, new_par.children);
					break;
				case "after" :
					pos = $.inArray(par.id, new_par.children) + 1;
					break;
				case "inside":
				case "first":
					pos = 0;
					break;
				case "last":
					pos = new_par.children.length;
					break;
				default:
					if(!pos) { pos = 0; }
					break;
			}
			if(pos > new_par.children.length) { pos = new_par.children.length; }
			if(!this.check("move_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
				this.settings.core.error.call(this, this._data.core.last_error);
				return false;
			}
			if(obj.parent === new_par.id) {
				dpc = new_par.children.concat();
				tmp = $.inArray(obj.id, dpc);
				if(tmp !== -1) {
					dpc = $.vakata.array_remove(dpc, tmp);
					if(pos > tmp) { pos--; }
				}
				tmp = [];
				for(i = 0, j = dpc.length; i < j; i++) {
					tmp[i >= pos ? i+1 : i] = dpc[i];
				}
				tmp[pos] = obj.id;
				new_par.children = tmp;
				this._node_changed(new_par.id);
				this.redraw(new_par.id === $.jstree.root);
			}
			else {
				// clean old parent and up
				tmp = obj.children_d.concat();
				tmp.push(obj.id);
				for(i = 0, j = obj.parents.length; i < j; i++) {
					dpc = [];
					p = old_ins._model.data[obj.parents[i]].children_d;
					for(k = 0, l = p.length; k < l; k++) {
						if($.inArray(p[k], tmp) === -1) {
							dpc.push(p[k]);
						}
					}
					old_ins._model.data[obj.parents[i]].children_d = dpc;
				}
				old_ins._model.data[old_par].children = $.vakata.array_remove_item(old_ins._model.data[old_par].children, obj.id);

				// insert into new parent and up
				for(i = 0, j = new_par.parents.length; i < j; i++) {
					this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(tmp);
				}
				dpc = [];
				for(i = 0, j = new_par.children.length; i < j; i++) {
					dpc[i >= pos ? i+1 : i] = new_par.children[i];
				}
				dpc[pos] = obj.id;
				new_par.children = dpc;
				new_par.children_d.push(obj.id);
				new_par.children_d = new_par.children_d.concat(obj.children_d);

				// update object
				obj.parent = new_par.id;
				tmp = new_par.parents.concat();
				tmp.unshift(new_par.id);
				p = obj.parents.length;
				obj.parents = tmp;

				// update object children
				tmp = tmp.concat();
				for(i = 0, j = obj.children_d.length; i < j; i++) {
					this._model.data[obj.children_d[i]].parents = this._model.data[obj.children_d[i]].parents.slice(0,p*-1);
					Array.prototype.push.apply(this._model.data[obj.children_d[i]].parents, tmp);
				}

				if(old_par === $.jstree.root || new_par.id === $.jstree.root) {
					this._model.force_full_redraw = true;
				}
				if(!this._model.force_full_redraw) {
					this._node_changed(old_par);
					this._node_changed(new_par.id);
				}
				if(!skip_redraw) {
					this.redraw();
				}
			}
			if(callback) { callback.call(this, obj, new_par, pos); }
			/**
			 * triggered when a node is moved
			 * @event
			 * @name move_node.jstree
			 * @param {Object} node
			 * @param {String} parent the parent's ID
			 * @param {Number} position the position of the node among the parent's children
			 * @param {String} old_parent the old parent of the node
			 * @param {Number} old_position the old position of the node
			 * @param {Boolean} is_multi do the node and new parent belong to different instances
			 * @param {jsTree} old_instance the instance the node came from
			 * @param {jsTree} new_instance the instance of the new parent
			 */
			this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
			return obj.id;
		},
		/**
		 * copy a node to a new parent
		 * @name copy_node(obj, par [, pos, callback, is_loaded])
		 * @param  {mixed} obj the node to copy, pass an array to copy multiple nodes
		 * @param  {mixed} par the new parent
		 * @param  {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0`
		 * @param  {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
		 * @param  {Boolean} is_loaded internal parameter indicating if the parent node has been loaded
		 * @param  {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn
		 * @param  {Boolean} instance internal parameter indicating if the node comes from another instance
		 * @trigger model.jstree copy_node.jstree
		 */
		copy_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {
			var t1, t2, dpc, tmp, i, j, node, old_par, new_par, old_ins, is_multi;

			par = this.get_node(par);
			pos = pos === undefined ? 0 : pos;
			if(!par) { return false; }
			if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
				return this.load_node(par, function () { this.copy_node(obj, par, pos, callback, true, false, origin); });
			}

			if($.isArray(obj)) {
				if(obj.length === 1) {
					obj = obj[0];
				}
				else {
					//obj = obj.slice();
					for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
						if((tmp = this.copy_node(obj[t1], par, pos, callback, is_loaded, true, origin))) {
							par = tmp;
							pos = "after";
						}
					}
					this.redraw();
					return true;
				}
			}
			obj = obj && obj.id ? obj : this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }

			old_par = (obj.parent || $.jstree.root).toString();
			new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent);
			old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
			is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);

			if(old_ins && old_ins._id) {
				obj = old_ins._model.data[obj.id];
			}

			if(par.id === $.jstree.root) {
				if(pos === "before") { pos = "first"; }
				if(pos === "after") { pos = "last"; }
			}
			switch(pos) {
				case "before":
					pos = $.inArray(par.id, new_par.children);
					break;
				case "after" :
					pos = $.inArray(par.id, new_par.children) + 1;
					break;
				case "inside":
				case "first":
					pos = 0;
					break;
				case "last":
					pos = new_par.children.length;
					break;
				default:
					if(!pos) { pos = 0; }
					break;
			}
			if(pos > new_par.children.length) { pos = new_par.children.length; }
			if(!this.check("copy_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
				this.settings.core.error.call(this, this._data.core.last_error);
				return false;
			}
			node = old_ins ? old_ins.get_json(obj, { no_id : true, no_data : true, no_state : true }) : obj;
			if(!node) { return false; }
			if(node.id === true) { delete node.id; }
			node = this._parse_model_from_json(node, new_par.id, new_par.parents.concat());
			if(!node) { return false; }
			tmp = this.get_node(node);
			if(obj && obj.state && obj.state.loaded === false) { tmp.state.loaded = false; }
			dpc = [];
			dpc.push(node);
			dpc = dpc.concat(tmp.children_d);
			this.trigger('model', { "nodes" : dpc, "parent" : new_par.id });

			// insert into new parent and up
			for(i = 0, j = new_par.parents.length; i < j; i++) {
				this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(dpc);
			}
			dpc = [];
			for(i = 0, j = new_par.children.length; i < j; i++) {
				dpc[i >= pos ? i+1 : i] = new_par.children[i];
			}
			dpc[pos] = tmp.id;
			new_par.children = dpc;
			new_par.children_d.push(tmp.id);
			new_par.children_d = new_par.children_d.concat(tmp.children_d);

			if(new_par.id === $.jstree.root) {
				this._model.force_full_redraw = true;
			}
			if(!this._model.force_full_redraw) {
				this._node_changed(new_par.id);
			}
			if(!skip_redraw) {
				this.redraw(new_par.id === $.jstree.root);
			}
			if(callback) { callback.call(this, tmp, new_par, pos); }
			/**
			 * triggered when a node is copied
			 * @event
			 * @name copy_node.jstree
			 * @param {Object} node the copied node
			 * @param {Object} original the original node
			 * @param {String} parent the parent's ID
			 * @param {Number} position the position of the node among the parent's children
			 * @param {String} old_parent the old parent of the node
			 * @param {Number} old_position the position of the original node
			 * @param {Boolean} is_multi do the node and new parent belong to different instances
			 * @param {jsTree} old_instance the instance the node came from
			 * @param {jsTree} new_instance the instance of the new parent
			 */
			this.trigger('copy_node', { "node" : tmp, "original" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1,'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
			return tmp.id;
		},
		/**
		 * cut a node (a later call to `paste(obj)` would move the node)
		 * @name cut(obj)
		 * @param  {mixed} obj multiple objects can be passed using an array
		 * @trigger cut.jstree
		 */
		cut : function (obj) {
			if(!obj) { obj = this._data.core.selected.concat(); }
			if(!$.isArray(obj)) { obj = [obj]; }
			if(!obj.length) { return false; }
			var tmp = [], o, t1, t2;
			for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
				o = this.get_node(obj[t1]);
				if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); }
			}
			if(!tmp.length) { return false; }
			ccp_node = tmp;
			ccp_inst = this;
			ccp_mode = 'move_node';
			/**
			 * triggered when nodes are added to the buffer for moving
			 * @event
			 * @name cut.jstree
			 * @param {Array} node
			 */
			this.trigger('cut', { "node" : obj });
		},
		/**
		 * copy a node (a later call to `paste(obj)` would copy the node)
		 * @name copy(obj)
		 * @param  {mixed} obj multiple objects can be passed using an array
		 * @trigger copy.jstree
		 */
		copy : function (obj) {
			if(!obj) { obj = this._data.core.selected.concat(); }
			if(!$.isArray(obj)) { obj = [obj]; }
			if(!obj.length) { return false; }
			var tmp = [], o, t1, t2;
			for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
				o = this.get_node(obj[t1]);
				if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); }
			}
			if(!tmp.length) { return false; }
			ccp_node = tmp;
			ccp_inst = this;
			ccp_mode = 'copy_node';
			/**
			 * triggered when nodes are added to the buffer for copying
			 * @event
			 * @name copy.jstree
			 * @param {Array} node
			 */
			this.trigger('copy', { "node" : obj });
		},
		/**
		 * get the current buffer (any nodes that are waiting for a paste operation)
		 * @name get_buffer()
		 * @return {Object} an object consisting of `mode` ("copy_node" or "move_node"), `node` (an array of objects) and `inst` (the instance)
		 */
		get_buffer : function () {
			return { 'mode' : ccp_mode, 'node' : ccp_node, 'inst' : ccp_inst };
		},
		/**
		 * check if there is something in the buffer to paste
		 * @name can_paste()
		 * @return {Boolean}
		 */
		can_paste : function () {
			return ccp_mode !== false && ccp_node !== false; // && ccp_inst._model.data[ccp_node];
		},
		/**
		 * copy or move the previously cut or copied nodes to a new parent
		 * @name paste(obj [, pos])
		 * @param  {mixed} obj the new parent
		 * @param  {mixed} pos the position to insert at (besides integer, "first" and "last" are supported), defaults to integer `0`
		 * @trigger paste.jstree
		 */
		paste : function (obj, pos) {
			obj = this.get_node(obj);
			if(!obj || !ccp_mode || !ccp_mode.match(/^(copy_node|move_node)$/) || !ccp_node) { return false; }
			if(this[ccp_mode](ccp_node, obj, pos, false, false, false, ccp_inst)) {
				/**
				 * triggered when paste is invoked
				 * @event
				 * @name paste.jstree
				 * @param {String} parent the ID of the receiving node
				 * @param {Array} node the nodes in the buffer
				 * @param {String} mode the performed operation - "copy_node" or "move_node"
				 */
				this.trigger('paste', { "parent" : obj.id, "node" : ccp_node, "mode" : ccp_mode });
			}
			ccp_node = false;
			ccp_mode = false;
			ccp_inst = false;
		},
		/**
		 * clear the buffer of previously copied or cut nodes
		 * @name clear_buffer()
		 * @trigger clear_buffer.jstree
		 */
		clear_buffer : function () {
			ccp_node = false;
			ccp_mode = false;
			ccp_inst = false;
			/**
			 * triggered when the copy / cut buffer is cleared
			 * @event
			 * @name clear_buffer.jstree
			 */
			this.trigger('clear_buffer');
		},
		/**
		 * put a node in edit mode (input field to rename the node)
		 * @name edit(obj [, default_text, callback])
		 * @param  {mixed} obj
		 * @param  {String} default_text the text to populate the input with (if omitted or set to a non-string value the node's text value is used)
		 * @param  {Function} callback a function to be called once the text box is blurred, it is called in the instance's scope and receives the node, a status parameter (true if the rename is successful, false otherwise) and a boolean indicating if the user cancelled the edit. You can access the node's title using .text
		 */
		edit : function (obj, default_text, callback) {
			var rtl, w, a, s, t, h1, h2, fn, tmp, cancel = false;
			obj = this.get_node(obj);
			if(!obj) { return false; }
			if(this.settings.core.check_callback === false) {
				this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_07', 'reason' : 'Could not edit node because of check_callback' };
				this.settings.core.error.call(this, this._data.core.last_error);
				return false;
			}
			tmp = obj;
			default_text = typeof default_text === 'string' ? default_text : obj.text;
			this.set_text(obj, "");
			obj = this._open_to(obj);
			tmp.text = default_text;

			rtl = this._data.core.rtl;
			w  = this.element.width();
			this._data.core.focused = tmp.id;
			a  = obj.children('.jstree-anchor').focus();
			s  = $('<span>');
			/*!
			oi = obj.children("i:visible"),
			ai = a.children("i:visible"),
			w1 = oi.width() * oi.length,
			w2 = ai.width() * ai.length,
			*/
			t  = default_text;
			h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body");
			h2 = $("<"+"input />", {
						"value" : t,
						"class" : "jstree-rename-input",
						// "size" : t.length,
						"css" : {
							"padding" : "0",
							"border" : "1px solid silver",
							"box-sizing" : "border-box",
							"display" : "inline-block",
							"height" : (this._data.core.li_height) + "px",
							"lineHeight" : (this._data.core.li_height) + "px",
							"width" : "150px" // will be set a bit further down
						},
						"blur" : $.proxy(function (e) {
							e.stopImmediatePropagation();
							e.preventDefault();
							var i = s.children(".jstree-rename-input"),
								v = i.val(),
								f = this.settings.core.force_text,
								nv;
							if(v === "") { v = t; }
							h1.remove();
							s.replaceWith(a);
							s.remove();
							t = f ? t : $('<div></div>').append($.parseHTML(t)).html();
							this.set_text(obj, t);
							nv = !!this.rename_node(obj, f ? $('<div></div>').text(v).text() : $('<div></div>').append($.parseHTML(v)).html());
							if(!nv) {
								this.set_text(obj, t); // move this up? and fix #483
							}
							this._data.core.focused = tmp.id;
							setTimeout($.proxy(function () {
								var node = this.get_node(tmp.id, true);
								if(node.length) {
									this._data.core.focused = tmp.id;
									node.children('.jstree-anchor').focus();
								}
							}, this), 0);
							if(callback) {
								callback.call(this, tmp, nv, cancel);
							}
							h2 = null;
						}, this),
						"keydown" : function (e) {
							var key = e.which;
							if(key === 27) {
								cancel = true;
								this.value = t;
							}
							if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
								e.stopImmediatePropagation();
							}
							if(key === 27 || key === 13) {
								e.preventDefault();
								this.blur();
							}
						},
						"click" : function (e) { e.stopImmediatePropagation(); },
						"mousedown" : function (e) { e.stopImmediatePropagation(); },
						"keyup" : function (e) {
							h2.width(Math.min(h1.text("pW" + this.value).width(),w));
						},
						"keypress" : function(e) {
							if(e.which === 13) { return false; }
						}
					});
				fn = {
						fontFamily		: a.css('fontFamily')		|| '',
						fontSize		: a.css('fontSize')			|| '',
						fontWeight		: a.css('fontWeight')		|| '',
						fontStyle		: a.css('fontStyle')		|| '',
						fontStretch		: a.css('fontStretch')		|| '',
						fontVariant		: a.css('fontVariant')		|| '',
						letterSpacing	: a.css('letterSpacing')	|| '',
						wordSpacing		: a.css('wordSpacing')		|| ''
				};
			s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2);
			a.replaceWith(s);
			h1.css(fn);
			h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
			$(document).one('mousedown.jstree touchstart.jstree dnd_start.vakata', function (e) {
				if (h2 && e.target !== h2) {
					$(h2).blur();
				}
			});
		},


		/**
		 * changes the theme
		 * @name set_theme(theme_name [, theme_url])
		 * @param {String} theme_name the name of the new theme to apply
		 * @param {mixed} theme_url  the location of the CSS file for this theme. Omit or set to `false` if you manually included the file. Set to `true` to autoload from the `core.themes.dir` directory.
		 * @trigger set_theme.jstree
		 */
		set_theme : function (theme_name, theme_url) {
			if(!theme_name) { return false; }
			if(theme_url === true) {
				var dir = this.settings.core.themes.dir;
				if(!dir) { dir = $.jstree.path + '/themes'; }
				theme_url = dir + '/' + theme_name + '/style.css';
			}
			if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
				$('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');
				themes_loaded.push(theme_url);
			}
			if(this._data.core.themes.name) {
				this.element.removeClass('jstree-' + this._data.core.themes.name);
			}
			this._data.core.themes.name = theme_name;
			this.element.addClass('jstree-' + theme_name);
			this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive');
			/**
			 * triggered when a theme is set
			 * @event
			 * @name set_theme.jstree
			 * @param {String} theme the new theme
			 */
			this.trigger('set_theme', { 'theme' : theme_name });
		},
		/**
		 * gets the name of the currently applied theme name
		 * @name get_theme()
		 * @return {String}
		 */
		get_theme : function () { return this._data.core.themes.name; },
		/**
		 * changes the theme variant (if the theme has variants)
		 * @name set_theme_variant(variant_name)
		 * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed)
		 */
		set_theme_variant : function (variant_name) {
			if(this._data.core.themes.variant) {
				this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
			}
			this._data.core.themes.variant = variant_name;
			if(variant_name) {
				this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
			}
		},
		/**
		 * gets the name of the currently applied theme variant
		 * @name get_theme()
		 * @return {String}
		 */
		get_theme_variant : function () { return this._data.core.themes.variant; },
		/**
		 * shows a striped background on the container (if the theme supports it)
		 * @name show_stripes()
		 */
		show_stripes : function () {
			this._data.core.themes.stripes = true;
			this.get_container_ul().addClass("jstree-striped");
			/**
			 * triggered when stripes are shown
			 * @event
			 * @name show_stripes.jstree
			 */
			this.trigger('show_stripes');
		},
		/**
		 * hides the striped background on the container
		 * @name hide_stripes()
		 */
		hide_stripes : function () {
			this._data.core.themes.stripes = false;
			this.get_container_ul().removeClass("jstree-striped");
			/**
			 * triggered when stripes are hidden
			 * @event
			 * @name hide_stripes.jstree
			 */
			this.trigger('hide_stripes');
		},
		/**
		 * toggles the striped background on the container
		 * @name toggle_stripes()
		 */
		toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } },
		/**
		 * shows the connecting dots (if the theme supports it)
		 * @name show_dots()
		 */
		show_dots : function () {
			this._data.core.themes.dots = true;
			this.get_container_ul().removeClass("jstree-no-dots");
			/**
			 * triggered when dots are shown
			 * @event
			 * @name show_dots.jstree
			 */
			this.trigger('show_dots');
		},
		/**
		 * hides the connecting dots
		 * @name hide_dots()
		 */
		hide_dots : function () {
			this._data.core.themes.dots = false;
			this.get_container_ul().addClass("jstree-no-dots");
			/**
			 * triggered when dots are hidden
			 * @event
			 * @name hide_dots.jstree
			 */
			this.trigger('hide_dots');
		},
		/**
		 * toggles the connecting dots
		 * @name toggle_dots()
		 */
		toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
		/**
		 * show the node icons
		 * @name show_icons()
		 */
		show_icons : function () {
			this._data.core.themes.icons = true;
			this.get_container_ul().removeClass("jstree-no-icons");
			/**
			 * triggered when icons are shown
			 * @event
			 * @name show_icons.jstree
			 */
			this.trigger('show_icons');
		},
		/**
		 * hide the node icons
		 * @name hide_icons()
		 */
		hide_icons : function () {
			this._data.core.themes.icons = false;
			this.get_container_ul().addClass("jstree-no-icons");
			/**
			 * triggered when icons are hidden
			 * @event
			 * @name hide_icons.jstree
			 */
			this.trigger('hide_icons');
		},
		/**
		 * toggle the node icons
		 * @name toggle_icons()
		 */
		toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } },
		/**
		 * show the node ellipsis
		 * @name show_icons()
		 */
		show_ellipsis : function () {
			this._data.core.themes.ellipsis = true;
			this.get_container_ul().addClass("jstree-ellipsis");
			/**
			 * triggered when ellisis is shown
			 * @event
			 * @name show_ellipsis.jstree
			 */
			this.trigger('show_ellipsis');
		},
		/**
		 * hide the node ellipsis
		 * @name hide_ellipsis()
		 */
		hide_ellipsis : function () {
			this._data.core.themes.ellipsis = false;
			this.get_container_ul().removeClass("jstree-ellipsis");
			/**
			 * triggered when ellisis is hidden
			 * @event
			 * @name hide_ellipsis.jstree
			 */
			this.trigger('hide_ellipsis');
		},
		/**
		 * toggle the node ellipsis
		 * @name toggle_icons()
		 */
		toggle_ellipsis : function () { if(this._data.core.themes.ellipsis) { this.hide_ellipsis(); } else { this.show_ellipsis(); } },
		/**
		 * set the node icon for a node
		 * @name set_icon(obj, icon)
		 * @param {mixed} obj
		 * @param {String} icon the new icon - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
		 */
		set_icon : function (obj, icon) {
			var t1, t2, dom, old;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.set_icon(obj[t1], icon);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }
			old = obj.icon;
			obj.icon = icon === true || icon === null || icon === undefined || icon === '' ? true : icon;
			dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon");
			if(icon === false) {
				this.hide_icon(obj);
			}
			else if(icon === true || icon === null || icon === undefined || icon === '') {
				dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
				if(old === false) { this.show_icon(obj); }
			}
			else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) {
				dom.removeClass(old).css("background","");
				dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon);
				if(old === false) { this.show_icon(obj); }
			}
			else {
				dom.removeClass(old).css("background","");
				dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon);
				if(old === false) { this.show_icon(obj); }
			}
			return true;
		},
		/**
		 * get the node icon for a node
		 * @name get_icon(obj)
		 * @param {mixed} obj
		 * @return {String}
		 */
		get_icon : function (obj) {
			obj = this.get_node(obj);
			return (!obj || obj.id === $.jstree.root) ? false : obj.icon;
		},
		/**
		 * hide the icon on an individual node
		 * @name hide_icon(obj)
		 * @param {mixed} obj
		 */
		hide_icon : function (obj) {
			var t1, t2;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.hide_icon(obj[t1]);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj === $.jstree.root) { return false; }
			obj.icon = false;
			this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon").addClass('jstree-themeicon-hidden');
			return true;
		},
		/**
		 * show the icon on an individual node
		 * @name show_icon(obj)
		 * @param {mixed} obj
		 */
		show_icon : function (obj) {
			var t1, t2, dom;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.show_icon(obj[t1]);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj === $.jstree.root) { return false; }
			dom = this.get_node(obj, true);
			obj.icon = dom.length ? dom.children(".jstree-anchor").children(".jstree-themeicon").attr('rel') : true;
			if(!obj.icon) { obj.icon = true; }
			dom.children(".jstree-anchor").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden');
			return true;
		}
	};

	// helpers
	$.vakata = {};
	// collect attributes
	$.vakata.attributes = function(node, with_values) {
		node = $(node)[0];
		var attr = with_values ? {} : [];
		if(node && node.attributes) {
			$.each(node.attributes, function (i, v) {
				if($.inArray(v.name.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; }
				if(v.value !== null && $.trim(v.value) !== '') {
					if(with_values) { attr[v.name] = v.value; }
					else { attr.push(v.name); }
				}
			});
		}
		return attr;
	};
	$.vakata.array_unique = function(array) {
		var a = [], i, j, l, o = {};
		for(i = 0, l = array.length; i < l; i++) {
			if(o[array[i]] === undefined) {
				a.push(array[i]);
				o[array[i]] = true;
			}
		}
		return a;
	};
	// remove item from array
	$.vakata.array_remove = function(array, from) {
		array.splice(from, 1);
		return array;
		//var rest = array.slice((to || from) + 1 || array.length);
		//array.length = from < 0 ? array.length + from : from;
		//array.push.apply(array, rest);
		//return array;
	};
	// remove item from array
	$.vakata.array_remove_item = function(array, item) {
		var tmp = $.inArray(item, array);
		return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array;
	};
	$.vakata.array_filter = function(c,a,b,d,e) {
		if (c.filter) {
			return c.filter(a, b);
		}
		d=[];
		for (e in c) {
			if (~~e+''===e+'' && e>=0 && a.call(b,c[e],+e,c)) {
				d.push(c[e]);
			}
		}
		return d;
	};


/**
 * ### Changed plugin
 *
 * This plugin adds more information to the `changed.jstree` event. The new data is contained in the `changed` event data property, and contains a lists of `selected` and `deselected` nodes.
 */

	$.jstree.plugins.changed = function (options, parent) {
		var last = [];
		this.trigger = function (ev, data) {
			var i, j;
			if(!data) {
				data = {};
			}
			if(ev.replace('.jstree','') === 'changed') {
				data.changed = { selected : [], deselected : [] };
				var tmp = {};
				for(i = 0, j = last.length; i < j; i++) {
					tmp[last[i]] = 1;
				}
				for(i = 0, j = data.selected.length; i < j; i++) {
					if(!tmp[data.selected[i]]) {
						data.changed.selected.push(data.selected[i]);
					}
					else {
						tmp[data.selected[i]] = 2;
					}
				}
				for(i = 0, j = last.length; i < j; i++) {
					if(tmp[last[i]] === 1) {
						data.changed.deselected.push(last[i]);
					}
				}
				last = data.selected.slice();
			}
			/**
			 * triggered when selection changes (the "changed" plugin enhances the original event with more data)
			 * @event
			 * @name changed.jstree
			 * @param {Object} node
			 * @param {Object} action the action that caused the selection to change
			 * @param {Array} selected the current selection
			 * @param {Object} changed an object containing two properties `selected` and `deselected` - both arrays of node IDs, which were selected or deselected since the last changed event
			 * @param {Object} event the event (if any) that triggered this changed event
			 * @plugin changed
			 */
			parent.trigger.call(this, ev, data);
		};
		this.refresh = function (skip_loading, forget_state) {
			last = [];
			return parent.refresh.apply(this, arguments);
		};
	};

/**
 * ### Checkbox plugin
 *
 * This plugin renders checkbox icons in front of each node, making multiple selection much easier.
 * It also supports tri-state behavior, meaning that if a node has a few of its children checked it will be rendered as undetermined, and state will be propagated up.
 */

	var _i = document.createElement('I');
	_i.className = 'jstree-icon jstree-checkbox';
	_i.setAttribute('role', 'presentation');
	/**
	 * stores all defaults for the checkbox plugin
	 * @name $.jstree.defaults.checkbox
	 * @plugin checkbox
	 */
	$.jstree.defaults.checkbox = {
		/**
		 * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`.
		 * @name $.jstree.defaults.checkbox.visible
		 * @plugin checkbox
		 */
		visible				: true,
		/**
		 * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`.
		 * @name $.jstree.defaults.checkbox.three_state
		 * @plugin checkbox
		 */
		three_state			: true,
		/**
		 * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`.
		 * @name $.jstree.defaults.checkbox.whole_node
		 * @plugin checkbox
		 */
		whole_node			: true,
		/**
		 * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`.
		 * @name $.jstree.defaults.checkbox.keep_selected_style
		 * @plugin checkbox
		 */
		keep_selected_style	: true,
		/**
		 * This setting controls how cascading and undetermined nodes are applied.
		 * If 'up' is in the string - cascading up is enabled, if 'down' is in the string - cascading down is enabled, if 'undetermined' is in the string - undetermined nodes will be used.
		 * If `three_state` is set to `true` this setting is automatically set to 'up+down+undetermined'. Defaults to ''.
		 * @name $.jstree.defaults.checkbox.cascade
		 * @plugin checkbox
		 */
		cascade				: '',
		/**
		 * This setting controls if checkbox are bound to the general tree selection or to an internal array maintained by the checkbox plugin. Defaults to `true`, only set to `false` if you know exactly what you are doing.
		 * @name $.jstree.defaults.checkbox.tie_selection
		 * @plugin checkbox
		 */
		tie_selection		: true
	};
	$.jstree.plugins.checkbox = function (options, parent) {
		this.bind = function () {
			parent.bind.call(this);
			this._data.checkbox.uto = false;
			this._data.checkbox.selected = [];
			if(this.settings.checkbox.three_state) {
				this.settings.checkbox.cascade = 'up+down+undetermined';
			}
			this.element
				.on("init.jstree", $.proxy(function () {
						this._data.checkbox.visible = this.settings.checkbox.visible;
						if(!this.settings.checkbox.keep_selected_style) {
							this.element.addClass('jstree-checkbox-no-clicked');
						}
						if(this.settings.checkbox.tie_selection) {
							this.element.addClass('jstree-checkbox-selection');
						}
					}, this))
				.on("loading.jstree", $.proxy(function () {
						this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
					}, this));
			if(this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
				this.element
					.on('changed.jstree uncheck_node.jstree check_node.jstree uncheck_all.jstree check_all.jstree move_node.jstree copy_node.jstree redraw.jstree open_node.jstree', $.proxy(function () {
							// only if undetermined is in setting
							if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
							this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
						}, this));
			}
			if(!this.settings.checkbox.tie_selection) {
				this.element
					.on('model.jstree', $.proxy(function (e, data) {
						var m = this._model.data,
							p = m[data.parent],
							dpc = data.nodes,
							i, j;
						for(i = 0, j = dpc.length; i < j; i++) {
							m[dpc[i]].state.checked = m[dpc[i]].state.checked || (m[dpc[i]].original && m[dpc[i]].original.state && m[dpc[i]].original.state.checked);
							if(m[dpc[i]].state.checked) {
								this._data.checkbox.selected.push(dpc[i]);
							}
						}
					}, this));
			}
			if(this.settings.checkbox.cascade.indexOf('up') !== -1 || this.settings.checkbox.cascade.indexOf('down') !== -1) {
				this.element
					.on('model.jstree', $.proxy(function (e, data) {
							var m = this._model.data,
								p = m[data.parent],
								dpc = data.nodes,
								chd = [],
								c, i, j, k, l, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;

							if(s.indexOf('down') !== -1) {
								// apply down
								if(p.state[ t ? 'selected' : 'checked' ]) {
									for(i = 0, j = dpc.length; i < j; i++) {
										m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true;
									}
									this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc);
								}
								else {
									for(i = 0, j = dpc.length; i < j; i++) {
										if(m[dpc[i]].state[ t ? 'selected' : 'checked' ]) {
											for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) {
												m[m[dpc[i]].children_d[k]].state[ t ? 'selected' : 'checked' ] = true;
											}
											this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(m[dpc[i]].children_d);
										}
									}
								}
							}

							if(s.indexOf('up') !== -1) {
								// apply up
								for(i = 0, j = p.children_d.length; i < j; i++) {
									if(!m[p.children_d[i]].children.length) {
										chd.push(m[p.children_d[i]].parent);
									}
								}
								chd = $.vakata.array_unique(chd);
								for(k = 0, l = chd.length; k < l; k++) {
									p = m[chd[k]];
									while(p && p.id !== $.jstree.root) {
										c = 0;
										for(i = 0, j = p.children.length; i < j; i++) {
											c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
										}
										if(c === j) {
											p.state[ t ? 'selected' : 'checked' ] = true;
											this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
											tmp = this.get_node(p, true);
											if(tmp && tmp.length) {
												tmp.attr('aria-selected', true).children('.jstree-anchor').addClass( t ? 'jstree-clicked' : 'jstree-checked');
											}
										}
										else {
											break;
										}
										p = this.get_node(p.parent);
									}
								}
							}

							this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected);
						}, this))
					.on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', $.proxy(function (e, data) {
							var obj = data.node,
								m = this._model.data,
								par = this.get_node(obj.parent),
								dom = this.get_node(obj, true),
								i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
								sel = {}, cur = this._data[ t ? 'core' : 'checkbox' ].selected;

							for (i = 0, j = cur.length; i < j; i++) {
								sel[cur[i]] = true;
							}
							// apply down
							if(s.indexOf('down') !== -1) {
								//this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d));
								for(i = 0, j = obj.children_d.length; i < j; i++) {
									sel[obj.children_d[i]] = true;
									tmp = m[obj.children_d[i]];
									tmp.state[ t ? 'selected' : 'checked' ] = true;
									if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
										tmp.original.state.undetermined = false;
									}
								}
							}

							// apply up
							if(s.indexOf('up') !== -1) {
								while(par && par.id !== $.jstree.root) {
									c = 0;
									for(i = 0, j = par.children.length; i < j; i++) {
										c += m[par.children[i]].state[ t ? 'selected' : 'checked' ];
									}
									if(c === j) {
										par.state[ t ? 'selected' : 'checked' ] = true;
										sel[par.id] = true;
										//this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id);
										tmp = this.get_node(par, true);
										if(tmp && tmp.length) {
											tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
										}
									}
									else {
										break;
									}
									par = this.get_node(par.parent);
								}
							}

							cur = [];
							for (i in sel) {
								if (sel.hasOwnProperty(i)) {
									cur.push(i);
								}
							}
							this._data[ t ? 'core' : 'checkbox' ].selected = cur;

							// apply down (process .children separately?)
							if(s.indexOf('down') !== -1 && dom.length) {
								dom.find('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked').parent().attr('aria-selected', true);
							}
						}, this))
					.on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', $.proxy(function (e, data) {
							var obj = this.get_node($.jstree.root),
								m = this._model.data,
								i, j, tmp;
							for(i = 0, j = obj.children_d.length; i < j; i++) {
								tmp = m[obj.children_d[i]];
								if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
									tmp.original.state.undetermined = false;
								}
							}
						}, this))
					.on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', $.proxy(function (e, data) {
							var obj = data.node,
								dom = this.get_node(obj, true),
								i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
								cur = this._data[ t ? 'core' : 'checkbox' ].selected, sel = {};
							if(obj && obj.original && obj.original.state && obj.original.state.undetermined) {
								obj.original.state.undetermined = false;
							}

							// apply down
							if(s.indexOf('down') !== -1) {
								for(i = 0, j = obj.children_d.length; i < j; i++) {
									tmp = this._model.data[obj.children_d[i]];
									tmp.state[ t ? 'selected' : 'checked' ] = false;
									if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
										tmp.original.state.undetermined = false;
									}
								}
							}

							// apply up
							if(s.indexOf('up') !== -1) {
								for(i = 0, j = obj.parents.length; i < j; i++) {
									tmp = this._model.data[obj.parents[i]];
									tmp.state[ t ? 'selected' : 'checked' ] = false;
									if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
										tmp.original.state.undetermined = false;
									}
									tmp = this.get_node(obj.parents[i], true);
									if(tmp && tmp.length) {
										tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
									}
								}
							}
							sel = {};
							for(i = 0, j = cur.length; i < j; i++) {
								// apply down + apply up
								if(
									(s.indexOf('down') === -1 || $.inArray(cur[i], obj.children_d) === -1) &&
									(s.indexOf('up') === -1 || $.inArray(cur[i], obj.parents) === -1)
								) {
									sel[cur[i]] = true;
								}
							}
							cur = [];
							for (i in sel) {
								if (sel.hasOwnProperty(i)) {
									cur.push(i);
								}
							}
							this._data[ t ? 'core' : 'checkbox' ].selected = cur;
							
							// apply down (process .children separately?)
							if(s.indexOf('down') !== -1 && dom.length) {
								dom.find('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked').parent().attr('aria-selected', false);
							}
						}, this));
			}
			if(this.settings.checkbox.cascade.indexOf('up') !== -1) {
				this.element
					.on('delete_node.jstree', $.proxy(function (e, data) {
							// apply up (whole handler)
							var p = this.get_node(data.parent),
								m = this._model.data,
								i, j, c, tmp, t = this.settings.checkbox.tie_selection;
							while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
								c = 0;
								for(i = 0, j = p.children.length; i < j; i++) {
									c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
								}
								if(j > 0 && c === j) {
									p.state[ t ? 'selected' : 'checked' ] = true;
									this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
									tmp = this.get_node(p, true);
									if(tmp && tmp.length) {
										tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
									}
								}
								else {
									break;
								}
								p = this.get_node(p.parent);
							}
						}, this))
					.on('move_node.jstree', $.proxy(function (e, data) {
							// apply up (whole handler)
							var is_multi = data.is_multi,
								old_par = data.old_parent,
								new_par = this.get_node(data.parent),
								m = this._model.data,
								p, c, i, j, tmp, t = this.settings.checkbox.tie_selection;
							if(!is_multi) {
								p = this.get_node(old_par);
								while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
									c = 0;
									for(i = 0, j = p.children.length; i < j; i++) {
										c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
									}
									if(j > 0 && c === j) {
										p.state[ t ? 'selected' : 'checked' ] = true;
										this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
										tmp = this.get_node(p, true);
										if(tmp && tmp.length) {
											tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
										}
									}
									else {
										break;
									}
									p = this.get_node(p.parent);
								}
							}
							p = new_par;
							while(p && p.id !== $.jstree.root) {
								c = 0;
								for(i = 0, j = p.children.length; i < j; i++) {
									c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
								}
								if(c === j) {
									if(!p.state[ t ? 'selected' : 'checked' ]) {
										p.state[ t ? 'selected' : 'checked' ] = true;
										this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
										tmp = this.get_node(p, true);
										if(tmp && tmp.length) {
											tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
										}
									}
								}
								else {
									if(p.state[ t ? 'selected' : 'checked' ]) {
										p.state[ t ? 'selected' : 'checked' ] = false;
										this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_remove_item(this._data[ t ? 'core' : 'checkbox' ].selected, p.id);
										tmp = this.get_node(p, true);
										if(tmp && tmp.length) {
											tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
										}
									}
									else {
										break;
									}
								}
								p = this.get_node(p.parent);
							}
						}, this));
			}
		};
		/**
		 * set the undetermined state where and if necessary. Used internally.
		 * @private
		 * @name _undetermined()
		 * @plugin checkbox
		 */
		this._undetermined = function () {
			if(this.element === null) { return; }
			var i, j, k, l, o = {}, m = this._model.data, t = this.settings.checkbox.tie_selection, s = this._data[ t ? 'core' : 'checkbox' ].selected, p = [], tt = this;
			for(i = 0, j = s.length; i < j; i++) {
				if(m[s[i]] && m[s[i]].parents) {
					for(k = 0, l = m[s[i]].parents.length; k < l; k++) {
						if(o[m[s[i]].parents[k]] !== undefined) {
							break;
						}
						if(m[s[i]].parents[k] !== $.jstree.root) {
							o[m[s[i]].parents[k]] = true;
							p.push(m[s[i]].parents[k]);
						}
					}
				}
			}
			// attempt for server side undetermined state
			this.element.find('.jstree-closed').not(':has(.jstree-children)')
				.each(function () {
					var tmp = tt.get_node(this), tmp2;
					if(!tmp.state.loaded) {
						if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
							if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) {
								o[tmp.id] = true;
								p.push(tmp.id);
							}
							for(k = 0, l = tmp.parents.length; k < l; k++) {
								if(o[tmp.parents[k]] === undefined && tmp.parents[k] !== $.jstree.root) {
									o[tmp.parents[k]] = true;
									p.push(tmp.parents[k]);
								}
							}
						}
					}
					else {
						for(i = 0, j = tmp.children_d.length; i < j; i++) {
							tmp2 = m[tmp.children_d[i]];
							if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) {
								if(o[tmp2.id] === undefined && tmp2.id !== $.jstree.root) {
									o[tmp2.id] = true;
									p.push(tmp2.id);
								}
								for(k = 0, l = tmp2.parents.length; k < l; k++) {
									if(o[tmp2.parents[k]] === undefined && tmp2.parents[k] !== $.jstree.root) {
										o[tmp2.parents[k]] = true;
										p.push(tmp2.parents[k]);
									}
								}
							}
						}
					}
				});

			this.element.find('.jstree-undetermined').removeClass('jstree-undetermined');
			for(i = 0, j = p.length; i < j; i++) {
				if(!m[p[i]].state[ t ? 'selected' : 'checked' ]) {
					s = this.get_node(p[i], true);
					if(s && s.length) {
						s.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-undetermined');
					}
				}
			}
		};
		this.redraw_node = function(obj, deep, is_callback, force_render) {
			obj = parent.redraw_node.apply(this, arguments);
			if(obj) {
				var i, j, tmp = null, icon = null;
				for(i = 0, j = obj.childNodes.length; i < j; i++) {
					if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
						tmp = obj.childNodes[i];
						break;
					}
				}
				if(tmp) {
					if(!this.settings.checkbox.tie_selection && this._model.data[obj.id].state.checked) { tmp.className += ' jstree-checked'; }
					icon = _i.cloneNode(false);
					if(this._model.data[obj.id].state.checkbox_disabled) { icon.className += ' jstree-checkbox-disabled'; }
					tmp.insertBefore(icon, tmp.childNodes[0]);
				}
			}
			if(!is_callback && this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
				if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
				this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
			}
			return obj;
		};
		/**
		 * show the node checkbox icons
		 * @name show_checkboxes()
		 * @plugin checkbox
		 */
		this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.get_container_ul().removeClass("jstree-no-checkboxes"); };
		/**
		 * hide the node checkbox icons
		 * @name hide_checkboxes()
		 * @plugin checkbox
		 */
		this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.get_container_ul().addClass("jstree-no-checkboxes"); };
		/**
		 * toggle the node icons
		 * @name toggle_checkboxes()
		 * @plugin checkbox
		 */
		this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } };
		/**
		 * checks if a node is in an undetermined state
		 * @name is_undetermined(obj)
		 * @param  {mixed} obj
		 * @return {Boolean}
		 */
		this.is_undetermined = function (obj) {
			obj = this.get_node(obj);
			var s = this.settings.checkbox.cascade, i, j, t = this.settings.checkbox.tie_selection, d = this._data[ t ? 'core' : 'checkbox' ].selected, m = this._model.data;
			if(!obj || obj.state[ t ? 'selected' : 'checked' ] === true || s.indexOf('undetermined') === -1 || (s.indexOf('down') === -1 && s.indexOf('up') === -1)) {
				return false;
			}
			if(!obj.state.loaded && obj.original.state.undetermined === true) {
				return true;
			}
			for(i = 0, j = obj.children_d.length; i < j; i++) {
				if($.inArray(obj.children_d[i], d) !== -1 || (!m[obj.children_d[i]].state.loaded && m[obj.children_d[i]].original.state.undetermined)) {
					return true;
				}
			}
			return false;
		};
		/**
		 * disable a node's checkbox
		 * @name disable_checkbox(obj)
		 * @param {mixed} obj an array can be used too
		 * @trigger disable_checkbox.jstree
		 * @plugin checkbox
		 */
		this.disable_checkbox = function (obj) {
			var t1, t2, dom;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.disable_checkbox(obj[t1]);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			dom = this.get_node(obj, true);
			if(!obj.state.checkbox_disabled) {
				obj.state.checkbox_disabled = true;
				if(dom && dom.length) {
					dom.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-checkbox-disabled');
				}
				/**
				 * triggered when an node's checkbox is disabled
				 * @event
				 * @name disable_checkbox.jstree
				 * @param {Object} node
				 * @plugin checkbox
				 */
				this.trigger('disable_checkbox', { 'node' : obj });
			}
		};
		/**
		 * enable a node's checkbox
		 * @name disable_checkbox(obj)
		 * @param {mixed} obj an array can be used too
		 * @trigger enable_checkbox.jstree
		 * @plugin checkbox
		 */
		this.enable_checkbox = function (obj) {
			var t1, t2, dom;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.enable_checkbox(obj[t1]);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			dom = this.get_node(obj, true);
			if(obj.state.checkbox_disabled) {
				obj.state.checkbox_disabled = false;
				if(dom && dom.length) {
					dom.children('.jstree-anchor').children('.jstree-checkbox').removeClass('jstree-checkbox-disabled');
				}
				/**
				 * triggered when an node's checkbox is enabled
				 * @event
				 * @name enable_checkbox.jstree
				 * @param {Object} node
				 * @plugin checkbox
				 */
				this.trigger('enable_checkbox', { 'node' : obj });
			}
		};

		this.activate_node = function (obj, e) {
			if($(e.target).hasClass('jstree-checkbox-disabled')) {
				return false;
			}
			if(this.settings.checkbox.tie_selection && (this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox'))) {
				e.ctrlKey = true;
			}
			if(this.settings.checkbox.tie_selection || (!this.settings.checkbox.whole_node && !$(e.target).hasClass('jstree-checkbox'))) {
				return parent.activate_node.call(this, obj, e);
			}
			if(this.is_disabled(obj)) {
				return false;
			}
			if(this.is_checked(obj)) {
				this.uncheck_node(obj, e);
			}
			else {
				this.check_node(obj, e);
			}
			this.trigger('activate_node', { 'node' : this.get_node(obj) });
		};

		/**
		 * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally)
		 * @name check_node(obj)
		 * @param {mixed} obj an array can be used to check multiple nodes
		 * @trigger check_node.jstree
		 * @plugin checkbox
		 */
		this.check_node = function (obj, e) {
			if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); }
			var dom, t1, t2, th;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.check_node(obj[t1], e);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			dom = this.get_node(obj, true);
			if(!obj.state.checked) {
				obj.state.checked = true;
				this._data.checkbox.selected.push(obj.id);
				if(dom && dom.length) {
					dom.children('.jstree-anchor').addClass('jstree-checked');
				}
				/**
				 * triggered when an node is checked (only if tie_selection in checkbox settings is false)
				 * @event
				 * @name check_node.jstree
				 * @param {Object} node
				 * @param {Array} selected the current selection
				 * @param {Object} event the event (if any) that triggered this check_node
				 * @plugin checkbox
				 */
				this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
			}
		};
		/**
		 * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally)
		 * @name uncheck_node(obj)
		 * @param {mixed} obj an array can be used to uncheck multiple nodes
		 * @trigger uncheck_node.jstree
		 * @plugin checkbox
		 */
		this.uncheck_node = function (obj, e) {
			if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); }
			var t1, t2, dom;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.uncheck_node(obj[t1], e);
				}
				return true;
			}
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) {
				return false;
			}
			dom = this.get_node(obj, true);
			if(obj.state.checked) {
				obj.state.checked = false;
				this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id);
				if(dom.length) {
					dom.children('.jstree-anchor').removeClass('jstree-checked');
				}
				/**
				 * triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
				 * @event
				 * @name uncheck_node.jstree
				 * @param {Object} node
				 * @param {Array} selected the current selection
				 * @param {Object} event the event (if any) that triggered this uncheck_node
				 * @plugin checkbox
				 */
				this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
			}
		};
		/**
		 * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally)
		 * @name check_all()
		 * @trigger check_all.jstree, changed.jstree
		 * @plugin checkbox
		 */
		this.check_all = function () {
			if(this.settings.checkbox.tie_selection) { return this.select_all(); }
			var tmp = this._data.checkbox.selected.concat([]), i, j;
			this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat();
			for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
				if(this._model.data[this._data.checkbox.selected[i]]) {
					this._model.data[this._data.checkbox.selected[i]].state.checked = true;
				}
			}
			this.redraw(true);
			/**
			 * triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
			 * @event
			 * @name check_all.jstree
			 * @param {Array} selected the current selection
			 * @plugin checkbox
			 */
			this.trigger('check_all', { 'selected' : this._data.checkbox.selected });
		};
		/**
		 * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally)
		 * @name uncheck_all()
		 * @trigger uncheck_all.jstree
		 * @plugin checkbox
		 */
		this.uncheck_all = function () {
			if(this.settings.checkbox.tie_selection) { return this.deselect_all(); }
			var tmp = this._data.checkbox.selected.concat([]), i, j;
			for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
				if(this._model.data[this._data.checkbox.selected[i]]) {
					this._model.data[this._data.checkbox.selected[i]].state.checked = false;
				}
			}
			this._data.checkbox.selected = [];
			this.element.find('.jstree-checked').removeClass('jstree-checked');
			/**
			 * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
			 * @event
			 * @name uncheck_all.jstree
			 * @param {Object} node the previous selection
			 * @param {Array} selected the current selection
			 * @plugin checkbox
			 */
			this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp });
		};
		/**
		 * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected)
		 * @name is_checked(obj)
		 * @param  {mixed}  obj
		 * @return {Boolean}
		 * @plugin checkbox
		 */
		this.is_checked = function (obj) {
			if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); }
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }
			return obj.state.checked;
		};
		/**
		 * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected)
		 * @name get_checked([full])
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
		 * @return {Array}
		 * @plugin checkbox
		 */
		this.get_checked = function (full) {
			if(this.settings.checkbox.tie_selection) { return this.get_selected(full); }
			return full ? $.map(this._data.checkbox.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.checkbox.selected;
		};
		/**
		 * get an array of all top level checked nodes (ignoring children of checked nodes) (if tie_selection is on in the settings this function will return the same as get_top_selected)
		 * @name get_top_checked([full])
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
		 * @return {Array}
		 * @plugin checkbox
		 */
		this.get_top_checked = function (full) {
			if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); }
			var tmp = this.get_checked(true),
				obj = {}, i, j, k, l;
			for(i = 0, j = tmp.length; i < j; i++) {
				obj[tmp[i].id] = tmp[i];
			}
			for(i = 0, j = tmp.length; i < j; i++) {
				for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
					if(obj[tmp[i].children_d[k]]) {
						delete obj[tmp[i].children_d[k]];
					}
				}
			}
			tmp = [];
			for(i in obj) {
				if(obj.hasOwnProperty(i)) {
					tmp.push(i);
				}
			}
			return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp;
		};
		/**
		 * get an array of all bottom level checked nodes (ignoring selected parents) (if tie_selection is on in the settings this function will return the same as get_bottom_selected)
		 * @name get_bottom_checked([full])
		 * @param  {mixed}  full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
		 * @return {Array}
		 * @plugin checkbox
		 */
		this.get_bottom_checked = function (full) {
			if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); }
			var tmp = this.get_checked(true),
				obj = [], i, j;
			for(i = 0, j = tmp.length; i < j; i++) {
				if(!tmp[i].children.length) {
					obj.push(tmp[i].id);
				}
			}
			return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj;
		};
		this.load_node = function (obj, callback) {
			var k, l, i, j, c, tmp;
			if(!$.isArray(obj) && !this.settings.checkbox.tie_selection) {
				tmp = this.get_node(obj);
				if(tmp && tmp.state.loaded) {
					for(k = 0, l = tmp.children_d.length; k < l; k++) {
						if(this._model.data[tmp.children_d[k]].state.checked) {
							c = true;
							this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]);
						}
					}
				}
			}
			return parent.load_node.apply(this, arguments);
		};
		this.get_state = function () {
			var state = parent.get_state.apply(this, arguments);
			if(this.settings.checkbox.tie_selection) { return state; }
			state.checkbox = this._data.checkbox.selected.slice();
			return state;
		};
		this.set_state = function (state, callback) {
			var res = parent.set_state.apply(this, arguments);
			if(res && state.checkbox) {
				if(!this.settings.checkbox.tie_selection) {
					this.uncheck_all();
					var _this = this;
					$.each(state.checkbox, function (i, v) {
						_this.check_node(v);
					});
				}
				delete state.checkbox;
				this.set_state(state, callback);
				return false;
			}
			return res;
		};
		this.refresh = function (skip_loading, forget_state) {
			if(!this.settings.checkbox.tie_selection) {
				this._data.checkbox.selected = [];
			}
			return parent.refresh.apply(this, arguments);
		};
	};

	// include the checkbox plugin by default
	// $.jstree.defaults.plugins.push("checkbox");

/**
 * ### Conditionalselect plugin
 *
 * This plugin allows defining a callback to allow or deny node selection by user input (activate node method).
 */

	/**
	 * a callback (function) which is invoked in the instance's scope and receives two arguments - the node and the event that triggered the `activate_node` call. Returning false prevents working with the node, returning true allows invoking activate_node. Defaults to returning `true`.
	 * @name $.jstree.defaults.checkbox.visible
	 * @plugin checkbox
	 */
	$.jstree.defaults.conditionalselect = function () { return true; };
	$.jstree.plugins.conditionalselect = function (options, parent) {
		// own function
		this.activate_node = function (obj, e) {
			if(this.settings.conditionalselect.call(this, this.get_node(obj), e)) {
				parent.activate_node.call(this, obj, e);
			}
		};
	};


/**
 * ### Contextmenu plugin
 *
 * Shows a context menu when a node is right-clicked.
 */

	/**
	 * stores all defaults for the contextmenu plugin
	 * @name $.jstree.defaults.contextmenu
	 * @plugin contextmenu
	 */
	$.jstree.defaults.contextmenu = {
		/**
		 * a boolean indicating if the node should be selected when the context menu is invoked on it. Defaults to `true`.
		 * @name $.jstree.defaults.contextmenu.select_node
		 * @plugin contextmenu
		 */
		select_node : true,
		/**
		 * a boolean indicating if the menu should be shown aligned with the node. Defaults to `true`, otherwise the mouse coordinates are used.
		 * @name $.jstree.defaults.contextmenu.show_at_node
		 * @plugin contextmenu
		 */
		show_at_node : true,
		/**
		 * an object of actions, or a function that accepts a node and a callback function and calls the callback function with an object of actions available for that node (you can also return the items too).
		 *
		 * Each action consists of a key (a unique name) and a value which is an object with the following properties (only label and action are required). Once a menu item is activated the `action` function will be invoked with an object containing the following keys: item - the contextmenu item definition as seen below, reference - the DOM node that was used (the tree node), element - the contextmenu DOM element, position - an object with x/y properties indicating the position of the menu.
		 *
		 * * `separator_before` - a boolean indicating if there should be a separator before this item
		 * * `separator_after` - a boolean indicating if there should be a separator after this item
		 * * `_disabled` - a boolean indicating if this action should be disabled
		 * * `label` - a string - the name of the action (could be a function returning a string)
		 * * `title` - a string - an optional tooltip for the item
		 * * `action` - a function to be executed if this item is chosen, the function will receive 
		 * * `icon` - a string, can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
		 * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2)
		 * * `shortcut_label` - shortcut label (like for example `F2` for rename)
		 * * `submenu` - an object with the same structure as $.jstree.defaults.contextmenu.items which can be used to create a submenu - each key will be rendered as a separate option in a submenu that will appear once the current item is hovered
		 *
		 * @name $.jstree.defaults.contextmenu.items
		 * @plugin contextmenu
		 */
		items : function (o, cb) { // Could be an object directly
			return {
				"create" : {
					"separator_before"	: false,
					"separator_after"	: true,
					"_disabled"			: false, //(this.check("create_node", data.reference, {}, "last")),
					"label"				: "Create",
					"action"			: function (data) {
						var inst = $.jstree.reference(data.reference),
							obj = inst.get_node(data.reference);
						inst.create_node(obj, {}, "last", function (new_node) {
							setTimeout(function () { inst.edit(new_node); },0);
						});
					}
				},
				"rename" : {
					"separator_before"	: false,
					"separator_after"	: false,
					"_disabled"			: false, //(this.check("rename_node", data.reference, this.get_parent(data.reference), "")),
					"label"				: "Rename",
					/*!
					"shortcut"			: 113,
					"shortcut_label"	: 'F2',
					"icon"				: "glyphicon glyphicon-leaf",
					*/
					"action"			: function (data) {
						var inst = $.jstree.reference(data.reference),
							obj = inst.get_node(data.reference);
						inst.edit(obj);
					}
				},
				"remove" : {
					"separator_before"	: false,
					"icon"				: false,
					"separator_after"	: false,
					"_disabled"			: false, //(this.check("delete_node", data.reference, this.get_parent(data.reference), "")),
					"label"				: "Delete",
					"action"			: function (data) {
						var inst = $.jstree.reference(data.reference),
							obj = inst.get_node(data.reference);
						if(inst.is_selected(obj)) {
							inst.delete_node(inst.get_selected());
						}
						else {
							inst.delete_node(obj);
						}
					}
				},
				"ccp" : {
					"separator_before"	: true,
					"icon"				: false,
					"separator_after"	: false,
					"label"				: "Edit",
					"action"			: false,
					"submenu" : {
						"cut" : {
							"separator_before"	: false,
							"separator_after"	: false,
							"label"				: "Cut",
							"action"			: function (data) {
								var inst = $.jstree.reference(data.reference),
									obj = inst.get_node(data.reference);
								if(inst.is_selected(obj)) {
									inst.cut(inst.get_top_selected());
								}
								else {
									inst.cut(obj);
								}
							}
						},
						"copy" : {
							"separator_before"	: false,
							"icon"				: false,
							"separator_after"	: false,
							"label"				: "Copy",
							"action"			: function (data) {
								var inst = $.jstree.reference(data.reference),
									obj = inst.get_node(data.reference);
								if(inst.is_selected(obj)) {
									inst.copy(inst.get_top_selected());
								}
								else {
									inst.copy(obj);
								}
							}
						},
						"paste" : {
							"separator_before"	: false,
							"icon"				: false,
							"_disabled"			: function (data) {
								return !$.jstree.reference(data.reference).can_paste();
							},
							"separator_after"	: false,
							"label"				: "Paste",
							"action"			: function (data) {
								var inst = $.jstree.reference(data.reference),
									obj = inst.get_node(data.reference);
								inst.paste(obj);
							}
						}
					}
				}
			};
		}
	};

	$.jstree.plugins.contextmenu = function (options, parent) {
		this.bind = function () {
			parent.bind.call(this);

			var last_ts = 0, cto = null, ex, ey;
			this.element
				.on("contextmenu.jstree", ".jstree-anchor", $.proxy(function (e, data) {
						if (e.target.tagName.toLowerCase() === 'input') {
							return;
						}
						e.preventDefault();
						last_ts = e.ctrlKey ? +new Date() : 0;
						if(data || cto) {
							last_ts = (+new Date()) + 10000;
						}
						if(cto) {
							clearTimeout(cto);
						}
						if(!this.is_loading(e.currentTarget)) {
							this.show_contextmenu(e.currentTarget, e.pageX, e.pageY, e);
						}
					}, this))
				.on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
						if(this._data.contextmenu.visible && (!last_ts || (+new Date()) - last_ts > 250)) { // work around safari & macOS ctrl+click
							$.vakata.context.hide();
						}
						last_ts = 0;
					}, this))
				.on("touchstart.jstree", ".jstree-anchor", function (e) {
						if(!e.originalEvent || !e.originalEvent.changedTouches || !e.originalEvent.changedTouches[0]) {
							return;
						}
						ex = e.originalEvent.changedTouches[0].clientX;
						ey = e.originalEvent.changedTouches[0].clientY;
						cto = setTimeout(function () {
							$(e.currentTarget).trigger('contextmenu', true);
						}, 750);
					})
				.on('touchmove.vakata.jstree', function (e) {
						if(cto && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0] && (Math.abs(ex - e.originalEvent.changedTouches[0].clientX) > 50 || Math.abs(ey - e.originalEvent.changedTouches[0].clientY) > 50)) {
							clearTimeout(cto);
						}
					})
				.on('touchend.vakata.jstree', function (e) {
						if(cto) {
							clearTimeout(cto);
						}
					});

			/*!
			if(!('oncontextmenu' in document.body) && ('ontouchstart' in document.body)) {
				var el = null, tm = null;
				this.element
					.on("touchstart", ".jstree-anchor", function (e) {
						el = e.currentTarget;
						tm = +new Date();
						$(document).one("touchend", function (e) {
							e.target = document.elementFromPoint(e.originalEvent.targetTouches[0].pageX - window.pageXOffset, e.originalEvent.targetTouches[0].pageY - window.pageYOffset);
							e.currentTarget = e.target;
							tm = ((+(new Date())) - tm);
							if(e.target === el && tm > 600 && tm < 1000) {
								e.preventDefault();
								$(el).trigger('contextmenu', e);
							}
							el = null;
							tm = null;
						});
					});
			}
			*/
			$(document).on("context_hide.vakata.jstree", $.proxy(function (e, data) {
				this._data.contextmenu.visible = false;
				$(data.reference).removeClass('jstree-context');
			}, this));
		};
		this.teardown = function () {
			if(this._data.contextmenu.visible) {
				$.vakata.context.hide();
			}
			parent.teardown.call(this);
		};

		/**
		 * prepare and show the context menu for a node
		 * @name show_contextmenu(obj [, x, y])
		 * @param {mixed} obj the node
		 * @param {Number} x the x-coordinate relative to the document to show the menu at
		 * @param {Number} y the y-coordinate relative to the document to show the menu at
		 * @param {Object} e the event if available that triggered the contextmenu
		 * @plugin contextmenu
		 * @trigger show_contextmenu.jstree
		 */
		this.show_contextmenu = function (obj, x, y, e) {
			obj = this.get_node(obj);
			if(!obj || obj.id === $.jstree.root) { return false; }
			var s = this.settings.contextmenu,
				d = this.get_node(obj, true),
				a = d.children(".jstree-anchor"),
				o = false,
				i = false;
			if(s.show_at_node || x === undefined || y === undefined) {
				o = a.offset();
				x = o.left;
				y = o.top + this._data.core.li_height;
			}
			if(this.settings.contextmenu.select_node && !this.is_selected(obj)) {
				this.activate_node(obj, e);
			}

			i = s.items;
			if($.isFunction(i)) {
				i = i.call(this, obj, $.proxy(function (i) {
					this._show_contextmenu(obj, x, y, i);
				}, this));
			}
			if($.isPlainObject(i)) {
				this._show_contextmenu(obj, x, y, i);
			}
		};
		/**
		 * show the prepared context menu for a node
		 * @name _show_contextmenu(obj, x, y, i)
		 * @param {mixed} obj the node
		 * @param {Number} x the x-coordinate relative to the document to show the menu at
		 * @param {Number} y the y-coordinate relative to the document to show the menu at
		 * @param {Number} i the object of items to show
		 * @plugin contextmenu
		 * @trigger show_contextmenu.jstree
		 * @private
		 */
		this._show_contextmenu = function (obj, x, y, i) {
			var d = this.get_node(obj, true),
				a = d.children(".jstree-anchor");
			$(document).one("context_show.vakata.jstree", $.proxy(function (e, data) {
				var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu';
				$(data.element).addClass(cls);
				a.addClass('jstree-context');
			}, this));
			this._data.contextmenu.visible = true;
			$.vakata.context.show(a, { 'x' : x, 'y' : y }, i);
			/**
			 * triggered when the contextmenu is shown for a node
			 * @event
			 * @name show_contextmenu.jstree
			 * @param {Object} node the node
			 * @param {Number} x the x-coordinate of the menu relative to the document
			 * @param {Number} y the y-coordinate of the menu relative to the document
			 * @plugin contextmenu
			 */
			this.trigger('show_contextmenu', { "node" : obj, "x" : x, "y" : y });
		};
	};

	// contextmenu helper
	(function ($) {
		var right_to_left = false,
			vakata_context = {
				element		: false,
				reference	: false,
				position_x	: 0,
				position_y	: 0,
				items		: [],
				html		: "",
				is_visible	: false
			};

		$.vakata.context = {
			settings : {
				hide_onmouseleave	: 0,
				icons				: true
			},
			_trigger : function (event_name) {
				$(document).triggerHandler("context_" + event_name + ".vakata", {
					"reference"	: vakata_context.reference,
					"element"	: vakata_context.element,
					"position"	: {
						"x" : vakata_context.position_x,
						"y" : vakata_context.position_y
					}
				});
			},
			_execute : function (i) {
				i = vakata_context.items[i];
				return i && (!i._disabled || ($.isFunction(i._disabled) && !i._disabled({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }))) && i.action ? i.action.call(null, {
							"item"		: i,
							"reference"	: vakata_context.reference,
							"element"	: vakata_context.element,
							"position"	: {
								"x" : vakata_context.position_x,
								"y" : vakata_context.position_y
							}
						}) : false;
			},
			_parse : function (o, is_callback) {
				if(!o) { return false; }
				if(!is_callback) {
					vakata_context.html		= "";
					vakata_context.items	= [];
				}
				var str = "",
					sep = false,
					tmp;

				if(is_callback) { str += "<"+"ul>"; }
				$.each(o, function (i, val) {
					if(!val) { return true; }
					vakata_context.items.push(val);
					if(!sep && val.separator_before) {
						str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + ">&#160;<"+"/a><"+"/li>";
					}
					sep = false;
					str += "<"+"li class='" + (val._class || "") + (val._disabled === true || ($.isFunction(val._disabled) && val._disabled({ "item" : val, "reference" : vakata_context.reference, "element" : vakata_context.element })) ? " vakata-contextmenu-disabled " : "") + "' "+(val.shortcut?" data-shortcut='"+val.shortcut+"' ":'')+">";
					str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "' " + (val.title ? "title='" + val.title + "'" : "") + ">";
					if($.vakata.context.settings.icons) {
						str += "<"+"i ";
						if(val.icon) {
							if(val.icon.indexOf("/") !== -1 || val.icon.indexOf(".") !== -1) { str += " style='background:url(\"" + val.icon + "\") center center no-repeat' "; }
							else { str += " class='" + val.icon + "' "; }
						}
						str += "><"+"/i><"+"span class='vakata-contextmenu-sep'>&#160;<"+"/span>";
					}
					str += ($.isFunction(val.label) ? val.label({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }) : val.label) + (val.shortcut?' <span class="vakata-contextmenu-shortcut vakata-contextmenu-shortcut-'+val.shortcut+'">'+ (val.shortcut_label || '') +'</span>':'') + "<"+"/a>";
					if(val.submenu) {
						tmp = $.vakata.context._parse(val.submenu, true);
						if(tmp) { str += tmp; }
					}
					str += "<"+"/li>";
					if(val.separator_after) {
						str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + ">&#160;<"+"/a><"+"/li>";
						sep = true;
					}
				});
				str  = str.replace(/<li class\='vakata-context-separator'\><\/li\>$/,"");
				if(is_callback) { str += "</ul>"; }
				/**
				 * triggered on the document when the contextmenu is parsed (HTML is built)
				 * @event
				 * @plugin contextmenu
				 * @name context_parse.vakata
				 * @param {jQuery} reference the element that was right clicked
				 * @param {jQuery} element the DOM element of the menu itself
				 * @param {Object} position the x & y coordinates of the menu
				 */
				if(!is_callback) { vakata_context.html = str; $.vakata.context._trigger("parse"); }
				return str.length > 10 ? str : false;
			},
			_show_submenu : function (o) {
				o = $(o);
				if(!o.length || !o.children("ul").length) { return; }
				var e = o.children("ul"),
					xl = o.offset().left,
					x = xl + o.outerWidth(),
					y = o.offset().top,
					w = e.width(),
					h = e.height(),
					dw = $(window).width() + $(window).scrollLeft(),
					dh = $(window).height() + $(window).scrollTop();
				// може да се спести е една проверка - дали няма някой от класовете вече нагоре
				if(right_to_left) {
					o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left");
				}
				else {
					o[x + w > dw  && xl > dw - x ? "addClass" : "removeClass"]("vakata-context-right");
				}
				if(y + h + 10 > dh) {
					e.css("bottom","-1px");
				}

				//if does not fit - stick it to the side
				if (o.hasClass('vakata-context-right')) {
					if (xl < w) {
						e.css("margin-right", xl - w);
					}
				} else {
					if (dw - x < w) {
						e.css("margin-left", dw - x - w);
					}
				}

				e.show();
			},
			show : function (reference, position, data) {
				var o, e, x, y, w, h, dw, dh, cond = true;
				if(vakata_context.element && vakata_context.element.length) {
					vakata_context.element.width('');
				}
				switch(cond) {
					case (!position && !reference):
						return false;
					case (!!position && !!reference):
						vakata_context.reference	= reference;
						vakata_context.position_x	= position.x;
						vakata_context.position_y	= position.y;
						break;
					case (!position && !!reference):
						vakata_context.reference	= reference;
						o = reference.offset();
						vakata_context.position_x	= o.left + reference.outerHeight();
						vakata_context.position_y	= o.top;
						break;
					case (!!position && !reference):
						vakata_context.position_x	= position.x;
						vakata_context.position_y	= position.y;
						break;
				}
				if(!!reference && !data && $(reference).data('vakata_contextmenu')) {
					data = $(reference).data('vakata_contextmenu');
				}
				if($.vakata.context._parse(data)) {
					vakata_context.element.html(vakata_context.html);
				}
				if(vakata_context.items.length) {
					vakata_context.element.appendTo("body");
					e = vakata_context.element;
					x = vakata_context.position_x;
					y = vakata_context.position_y;
					w = e.width();
					h = e.height();
					dw = $(window).width() + $(window).scrollLeft();
					dh = $(window).height() + $(window).scrollTop();
					if(right_to_left) {
						x -= (e.outerWidth() - $(reference).outerWidth());
						if(x < $(window).scrollLeft() + 20) {
							x = $(window).scrollLeft() + 20;
						}
					}
					if(x + w + 20 > dw) {
						x = dw - (w + 20);
					}
					if(y + h + 20 > dh) {
						y = dh - (h + 20);
					}

					vakata_context.element
						.css({ "left" : x, "top" : y })
						.show()
						.find('a').first().focus().parent().addClass("vakata-context-hover");
					vakata_context.is_visible = true;
					/**
					 * triggered on the document when the contextmenu is shown
					 * @event
					 * @plugin contextmenu
					 * @name context_show.vakata
					 * @param {jQuery} reference the element that was right clicked
					 * @param {jQuery} element the DOM element of the menu itself
					 * @param {Object} position the x & y coordinates of the menu
					 */
					$.vakata.context._trigger("show");
				}
			},
			hide : function () {
				if(vakata_context.is_visible) {
					vakata_context.element.hide().find("ul").hide().end().find(':focus').blur().end().detach();
					vakata_context.is_visible = false;
					/**
					 * triggered on the document when the contextmenu is hidden
					 * @event
					 * @plugin contextmenu
					 * @name context_hide.vakata
					 * @param {jQuery} reference the element that was right clicked
					 * @param {jQuery} element the DOM element of the menu itself
					 * @param {Object} position the x & y coordinates of the menu
					 */
					$.vakata.context._trigger("hide");
				}
			}
		};
		$(function () {
			right_to_left = $("body").css("direction") === "rtl";
			var to = false;

			vakata_context.element = $("<ul class='vakata-context'></ul>");
			vakata_context.element
				.on("mouseenter", "li", function (e) {
					e.stopImmediatePropagation();

					if($.contains(this, e.relatedTarget)) {
						// премахнато заради delegate mouseleave по-долу
						// $(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
						return;
					}

					if(to) { clearTimeout(to); }
					vakata_context.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end();

					$(this)
						.siblings().find("ul").hide().end().end()
						.parentsUntil(".vakata-context", "li").addBack().addClass("vakata-context-hover");
					$.vakata.context._show_submenu(this);
				})
				// тестово - дали не натоварва?
				.on("mouseleave", "li", function (e) {
					if($.contains(this, e.relatedTarget)) { return; }
					$(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover");
				})
				.on("mouseleave", function (e) {
					$(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
					if($.vakata.context.settings.hide_onmouseleave) {
						to = setTimeout(
							(function (t) {
								return function () { $.vakata.context.hide(); };
							}(this)), $.vakata.context.settings.hide_onmouseleave);
					}
				})
				.on("click", "a", function (e) {
					e.preventDefault();
				//})
				//.on("mouseup", "a", function (e) {
					if(!$(this).blur().parent().hasClass("vakata-context-disabled") && $.vakata.context._execute($(this).attr("rel")) !== false) {
						$.vakata.context.hide();
					}
				})
				.on('keydown', 'a', function (e) {
						var o = null;
						switch(e.which) {
							case 13:
							case 32:
								e.type = "click";
								e.preventDefault();
								$(e.currentTarget).trigger(e);
								break;
							case 37:
								if(vakata_context.is_visible) {
									vakata_context.element.find(".vakata-context-hover").last().closest("li").first().find("ul").hide().find(".vakata-context-hover").removeClass("vakata-context-hover").end().end().children('a').focus();
									e.stopImmediatePropagation();
									e.preventDefault();
								}
								break;
							case 38:
								if(vakata_context.is_visible) {
									o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first();
									if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last(); }
									o.addClass("vakata-context-hover").children('a').focus();
									e.stopImmediatePropagation();
									e.preventDefault();
								}
								break;
							case 39:
								if(vakata_context.is_visible) {
									vakata_context.element.find(".vakata-context-hover").last().children("ul").show().children("li:not(.vakata-context-separator)").removeClass("vakata-context-hover").first().addClass("vakata-context-hover").children('a').focus();
									e.stopImmediatePropagation();
									e.preventDefault();
								}
								break;
							case 40:
								if(vakata_context.is_visible) {
									o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first();
									if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first(); }
									o.addClass("vakata-context-hover").children('a').focus();
									e.stopImmediatePropagation();
									e.preventDefault();
								}
								break;
							case 27:
								$.vakata.context.hide();
								e.preventDefault();
								break;
							default:
								//console.log(e.which);
								break;
						}
					})
				.on('keydown', function (e) {
					e.preventDefault();
					var a = vakata_context.element.find('.vakata-contextmenu-shortcut-' + e.which).parent();
					if(a.parent().not('.vakata-context-disabled')) {
						a.click();
					}
				});

			$(document)
				.on("mousedown.vakata.jstree", function (e) {
					if(vakata_context.is_visible && !$.contains(vakata_context.element[0], e.target)) {
						$.vakata.context.hide();
					}
				})
				.on("context_show.vakata.jstree", function (e, data) {
					vakata_context.element.find("li:has(ul)").children("a").addClass("vakata-context-parent");
					if(right_to_left) {
						vakata_context.element.addClass("vakata-context-rtl").css("direction", "rtl");
					}
					// also apply a RTL class?
					vakata_context.element.find("ul").hide().end();
				});
		});
	}($));
	// $.jstree.defaults.plugins.push("contextmenu");


/**
 * ### Drag'n'drop plugin
 *
 * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations.
 */

	/**
	 * stores all defaults for the drag'n'drop plugin
	 * @name $.jstree.defaults.dnd
	 * @plugin dnd
	 */
	$.jstree.defaults.dnd = {
		/**
		 * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`.
		 * @name $.jstree.defaults.dnd.copy
		 * @plugin dnd
		 */
		copy : true,
		/**
		 * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`.
		 * @name $.jstree.defaults.dnd.open_timeout
		 * @plugin dnd
		 */
		open_timeout : 500,
		/**
		 * a function invoked each time a node is about to be dragged, invoked in the tree's scope and receives the nodes about to be dragged as an argument (array) and the event that started the drag - return `false` to prevent dragging
		 * @name $.jstree.defaults.dnd.is_draggable
		 * @plugin dnd
		 */
		is_draggable : true,
		/**
		 * a boolean indicating if checks should constantly be made while the user is dragging the node (as opposed to checking only on drop), default is `true`
		 * @name $.jstree.defaults.dnd.check_while_dragging
		 * @plugin dnd
		 */
		check_while_dragging : true,
		/**
		 * a boolean indicating if nodes from this tree should only be copied with dnd (as opposed to moved), default is `false`
		 * @name $.jstree.defaults.dnd.always_copy
		 * @plugin dnd
		 */
		always_copy : false,
		/**
		 * when dropping a node "inside", this setting indicates the position the node should go to - it can be an integer or a string: "first" (same as 0) or "last", default is `0`
		 * @name $.jstree.defaults.dnd.inside_pos
		 * @plugin dnd
		 */
		inside_pos : 0,
		/**
		 * when starting the drag on a node that is selected this setting controls if all selected nodes are dragged or only the single node, default is `true`, which means all selected nodes are dragged when the drag is started on a selected node
		 * @name $.jstree.defaults.dnd.drag_selection
		 * @plugin dnd
		 */
		drag_selection : true,
		/**
		 * controls whether dnd works on touch devices. If left as boolean true dnd will work the same as in desktop browsers, which in some cases may impair scrolling. If set to boolean false dnd will not work on touch devices. There is a special third option - string "selected" which means only selected nodes can be dragged on touch devices.
		 * @name $.jstree.defaults.dnd.touch
		 * @plugin dnd
		 */
		touch : true,
		/**
		 * controls whether items can be dropped anywhere on the node, not just on the anchor, by default only the node anchor is a valid drop target. Works best with the wholerow plugin. If enabled on mobile depending on the interface it might be hard for the user to cancel the drop, since the whole tree container will be a valid drop target.
		 * @name $.jstree.defaults.dnd.large_drop_target
		 * @plugin dnd
		 */
		large_drop_target : false,
		/**
		 * controls whether a drag can be initiated from any part of the node and not just the text/icon part, works best with the wholerow plugin. Keep in mind it can cause problems with tree scrolling on mobile depending on the interface - in that case set the touch option to "selected".
		 * @name $.jstree.defaults.dnd.large_drag_target
		 * @plugin dnd
		 */
		large_drag_target : false,
		/**
		 * controls whether use HTML5 dnd api instead of classical. That will allow better integration of dnd events with other HTML5 controls.
		 * @reference http://caniuse.com/#feat=dragndrop
		 * @name $.jstree.defaults.dnd.use_html5
		 * @plugin dnd
		 */
		use_html5: false
	};
	var drg, elm;
	// TODO: now check works by checking for each node individually, how about max_children, unique, etc?
	$.jstree.plugins.dnd = function (options, parent) {
		this.init = function (el, options) {
			parent.init.call(this, el, options);
			this.settings.dnd.use_html5 = this.settings.dnd.use_html5 && ('draggable' in document.createElement('span'));
		};
		this.bind = function () {
			parent.bind.call(this);

			this.element
				.on(this.settings.dnd.use_html5 ? 'dragstart.jstree' : 'mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
						if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) {
							return true;
						}
						if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) {
							return true;
						}
						var obj = this.get_node(e.target),
							mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1,
							txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget));
						if(this.settings.core.force_text) {
							txt = $.vakata.html.escape(txt);
						}
						if(obj && obj.id && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart" || e.type === "dragstart") &&
							(this.settings.dnd.is_draggable === true || ($.isFunction(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, (mlt > 1 ? this.get_top_selected(true) : [obj]), e)))
						) {
							drg = { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] };
							elm = e.currentTarget;
							if (this.settings.dnd.use_html5) {
								$.vakata.dnd._trigger('start', e, { 'helper': $(), 'element': elm, 'data': drg });
							} else {
								this.element.trigger('mousedown.jstree');
								return $.vakata.dnd.start(e, drg, '<div id="jstree-dnd" class="jstree-' + this.get_theme() + ' jstree-' + this.get_theme() + '-' + this.get_theme_variant() + ' ' + ( this.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ) + '"><i class="jstree-icon jstree-er"></i>' + txt + '<ins class="jstree-copy" style="display:none;">+</ins></div>');
							}
						}
					}, this));
			if (this.settings.dnd.use_html5) {
				this.element
					.on('dragover.jstree', function (e) {
							e.preventDefault();
							$.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
							return false;
						})
					//.on('dragenter.jstree', this.settings.dnd.large_drop_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
					//		e.preventDefault();
					//		$.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
					//		return false;
					//	}, this))
					.on('drop.jstree', $.proxy(function (e) {
							e.preventDefault();
							$.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
							return false;
						}, this));
			}
		};
		this.redraw_node = function(obj, deep, callback, force_render) {
			obj = parent.redraw_node.apply(this, arguments);
			if (obj && this.settings.dnd.use_html5) {
				if (this.settings.dnd.large_drag_target) {
					obj.setAttribute('draggable', true);
				} else {
					var i, j, tmp = null;
					for(i = 0, j = obj.childNodes.length; i < j; i++) {
						if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
							tmp = obj.childNodes[i];
							break;
						}
					}
					if(tmp) {
						tmp.setAttribute('draggable', true);
					}
				}
			}
			return obj;
		};
	};

	$(function() {
		// bind only once for all instances
		var lastmv = false,
			laster = false,
			lastev = false,
			opento = false,
			marker = $('<div id="jstree-marker">&#160;</div>').hide(); //.appendTo('body');

		$(document)
			.on('dnd_start.vakata.jstree', function (e, data) {
				lastmv = false;
				lastev = false;
				if(!data || !data.data || !data.data.jstree) { return; }
				marker.appendTo('body'); //.show();
			})
			.on('dnd_move.vakata.jstree', function (e, data) {
				if(opento) {
					if (!data.event || data.event.type !== 'dragover' || data.event.target !== lastev.target) {
						clearTimeout(opento);
					}
				}
				if(!data || !data.data || !data.data.jstree) { return; }

				// if we are hovering the marker image do nothing (can happen on "inside" drags)
				if(data.event.target.id && data.event.target.id === 'jstree-marker') {
					return;
				}
				lastev = data.event;

				var ins = $.jstree.reference(data.event.target),
					ref = false,
					off = false,
					rel = false,
					tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm, is_copy, pn;
				// if we are over an instance
				if(ins && ins._data && ins._data.dnd) {
					marker.attr('class', 'jstree-' + ins.get_theme() + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ));
					is_copy = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)));
					data.helper
						.children().attr('class', 'jstree-' + ins.get_theme() + ' jstree-' + ins.get_theme() + '-' + ins.get_theme_variant() + ' ' + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ))
						.find('.jstree-copy').first()[ is_copy ? 'show' : 'hide' ]();

					// if are hovering the container itself add a new root node
					//console.log(data.event);
					if( (data.event.target === ins.element[0] || data.event.target === ins.get_container_ul()[0]) && ins.get_container_ul().children().length === 0) {
						ok = true;
						for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
							ok = ok && ins.check( (data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)) ) ? "copy_node" : "move_node"), (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), $.jstree.root, 'last', { 'dnd' : true, 'ref' : ins.get_node($.jstree.root), 'pos' : 'i', 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) });
							if(!ok) { break; }
						}
						if(ok) {
							lastmv = { 'ins' : ins, 'par' : $.jstree.root, 'pos' : 'last' };
							marker.hide();
							data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
							if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
								data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
							}
							return;
						}
					}
					else {
						// if we are hovering a tree node
						ref = ins.settings.dnd.large_drop_target ? $(data.event.target).closest('.jstree-node').children('.jstree-anchor') : $(data.event.target).closest('.jstree-anchor');
						if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) {
							off = ref.offset();
							rel = (data.event.pageY !== undefined ? data.event.pageY : data.event.originalEvent.pageY) - off.top;
							h = ref.outerHeight();
							if(rel < h / 3) {
								o = ['b', 'i', 'a'];
							}
							else if(rel > h - h / 3) {
								o = ['a', 'i', 'b'];
							}
							else {
								o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a'];
							}
							$.each(o, function (j, v) {
								switch(v) {
									case 'b':
										l = off.left - 6;
										t = off.top;
										p = ins.get_parent(ref);
										i = ref.parent().index();
										break;
									case 'i':
										ip = ins.settings.dnd.inside_pos;
										tm = ins.get_node(ref.parent());
										l = off.left - 2;
										t = off.top + h / 2 + 1;
										p = tm.id;
										i = ip === 'first' ? 0 : (ip === 'last' ? tm.children.length : Math.min(ip, tm.children.length));
										break;
									case 'a':
										l = off.left - 6;
										t = off.top + h;
										p = ins.get_parent(ref);
										i = ref.parent().index() + 1;
										break;
								}
								ok = true;
								for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
									op = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? "copy_node" : "move_node";
									ps = i;
									if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) {
										pr = ins.get_node(p);
										if(ps > $.inArray(data.data.nodes[t1], pr.children)) {
											ps -= 1;
										}
									}
									ok = ok && ( (ins && ins.settings && ins.settings.dnd && ins.settings.dnd.check_while_dragging === false) || ins.check(op, (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), p, ps, { 'dnd' : true, 'ref' : ins.get_node(ref.parent()), 'pos' : v, 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) }) );
									if(!ok) {
										if(ins && ins.last_error) { laster = ins.last_error(); }
										break;
									}
								}
								if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) {
									opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
								}
								if(ok) {
									pn = ins.get_node(p, true);
									if (!pn.hasClass('.jstree-dnd-parent')) {
										$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
										pn.addClass('jstree-dnd-parent');
									}
									lastmv = { 'ins' : ins, 'par' : p, 'pos' : v === 'i' && ip === 'last' && i === 0 && !ins.is_loaded(tm) ? 'last' : i };
									marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show();
									data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
									if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
										data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
									}
									laster = {};
									o = true;
									return false;
								}
							});
							if(o === true) { return; }
						}
					}
				}
				$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
				lastmv = false;
				data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er');
				if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
					data.event.originalEvent.dataTransfer.dropEffect = 'none';
				}
				marker.hide();
			})
			.on('dnd_scroll.vakata.jstree', function (e, data) {
				if(!data || !data.data || !data.data.jstree) { return; }
				marker.hide();
				lastmv = false;
				lastev = false;
				data.helper.find('.jstree-icon').first().removeClass('jstree-ok').addClass('jstree-er');
			})
			.on('dnd_stop.vakata.jstree', function (e, data) {
				$('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
				if(opento) { clearTimeout(opento); }
				if(!data || !data.data || !data.data.jstree) { return; }
				marker.hide().detach();
				var i, j, nodes = [];
				if(lastmv) {
					for(i = 0, j = data.data.nodes.length; i < j; i++) {
						nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i];
					}
					lastmv.ins[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? 'copy_node' : 'move_node' ](nodes, lastmv.par, lastmv.pos, false, false, false, data.data.origin);
				}
				else {
					i = $(data.event.target).closest('.jstree');
					if(i.length && laster && laster.error && laster.error === 'check') {
						i = i.jstree(true);
						if(i) {
							i.settings.core.error.call(this, laster);
						}
					}
				}
				lastev = false;
				lastmv = false;
			})
			.on('keyup.jstree keydown.jstree', function (e, data) {
				data = $.vakata.dnd._get();
				if(data && data.data && data.data.jstree) {
					if (e.type === "keyup" && e.which === 27) {
						if (opento) { clearTimeout(opento); }
						lastmv = false;
						laster = false;
						lastev = false;
						opento = false;
						marker.hide().detach();
						$.vakata.dnd._clean();
					} else {
						data.helper.find('.jstree-copy').first()[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey))) ? 'show' : 'hide' ]();
						if(lastev) {
							lastev.metaKey = e.metaKey;
							lastev.ctrlKey = e.ctrlKey;
							$.vakata.dnd._trigger('move', lastev);
						}
					}
				}
			});
	});

	// helpers
	(function ($) {
		$.vakata.html = {
			div : $('<div />'),
			escape : function (str) {
				return $.vakata.html.div.text(str).html();
			},
			strip : function (str) {
				return $.vakata.html.div.empty().append($.parseHTML(str)).text();
			}
		};
		// private variable
		var vakata_dnd = {
			element	: false,
			target	: false,
			is_down	: false,
			is_drag	: false,
			helper	: false,
			helper_w: 0,
			data	: false,
			init_x	: 0,
			init_y	: 0,
			scroll_l: 0,
			scroll_t: 0,
			scroll_e: false,
			scroll_i: false,
			is_touch: false
		};
		$.vakata.dnd = {
			settings : {
				scroll_speed		: 10,
				scroll_proximity	: 20,
				helper_left			: 5,
				helper_top			: 10,
				threshold			: 5,
				threshold_touch		: 50
			},
			_trigger : function (event_name, e, data) {
				if (data === undefined) {
					data = $.vakata.dnd._get();
				}
				data.event = e;
				$(document).triggerHandler("dnd_" + event_name + ".vakata", data);
			},
			_get : function () {
				return {
					"data"		: vakata_dnd.data,
					"element"	: vakata_dnd.element,
					"helper"	: vakata_dnd.helper
				};
			},
			_clean : function () {
				if(vakata_dnd.helper) { vakata_dnd.helper.remove(); }
				if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
				vakata_dnd = {
					element	: false,
					target	: false,
					is_down	: false,
					is_drag	: false,
					helper	: false,
					helper_w: 0,
					data	: false,
					init_x	: 0,
					init_y	: 0,
					scroll_l: 0,
					scroll_t: 0,
					scroll_e: false,
					scroll_i: false,
					is_touch: false
				};
				$(document).off("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
				$(document).off("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
			},
			_scroll : function (init_only) {
				if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) {
					if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
					return false;
				}
				if(!vakata_dnd.scroll_i) {
					vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100);
					return false;
				}
				if(init_only === true) { return false; }

				var i = vakata_dnd.scroll_e.scrollTop(),
					j = vakata_dnd.scroll_e.scrollLeft();
				vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed);
				vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed);
				if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) {
					/**
					 * triggered on the document when a drag causes an element to scroll
					 * @event
					 * @plugin dnd
					 * @name dnd_scroll.vakata
					 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
					 * @param {DOM} element the DOM element being dragged
					 * @param {jQuery} helper the helper shown next to the mouse
					 * @param {jQuery} event the element that is scrolling
					 */
					$.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e);
				}
			},
			start : function (e, data, html) {
				if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
					e.pageX = e.originalEvent.changedTouches[0].pageX;
					e.pageY = e.originalEvent.changedTouches[0].pageY;
					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
				}
				if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); }
				try {
					e.currentTarget.unselectable = "on";
					e.currentTarget.onselectstart = function() { return false; };
					if(e.currentTarget.style) {
						e.currentTarget.style.touchAction = "none";
						e.currentTarget.style.msTouchAction = "none";
						e.currentTarget.style.MozUserSelect = "none";
					}
				} catch(ignore) { }
				vakata_dnd.init_x	= e.pageX;
				vakata_dnd.init_y	= e.pageY;
				vakata_dnd.data		= data;
				vakata_dnd.is_down	= true;
				vakata_dnd.element	= e.currentTarget;
				vakata_dnd.target	= e.target;
				vakata_dnd.is_touch	= e.type === "touchstart";
				if(html !== false) {
					vakata_dnd.helper = $("<div id='vakata-dnd'></div>").html(html).css({
						"display"		: "block",
						"margin"		: "0",
						"padding"		: "0",
						"position"		: "absolute",
						"top"			: "-2000px",
						"lineHeight"	: "16px",
						"zIndex"		: "10000"
					});
				}
				$(document).on("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
				$(document).on("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
				return false;
			},
			drag : function (e) {
				if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
					e.pageX = e.originalEvent.changedTouches[0].pageX;
					e.pageY = e.originalEvent.changedTouches[0].pageY;
					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
				}
				if(!vakata_dnd.is_down) { return; }
				if(!vakata_dnd.is_drag) {
					if(
						Math.abs(e.pageX - vakata_dnd.init_x) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) ||
						Math.abs(e.pageY - vakata_dnd.init_y) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold)
					) {
						if(vakata_dnd.helper) {
							vakata_dnd.helper.appendTo("body");
							vakata_dnd.helper_w = vakata_dnd.helper.outerWidth();
						}
						vakata_dnd.is_drag = true;
						$(vakata_dnd.target).one('click.vakata', false);
						/**
						 * triggered on the document when a drag starts
						 * @event
						 * @plugin dnd
						 * @name dnd_start.vakata
						 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
						 * @param {DOM} element the DOM element being dragged
						 * @param {jQuery} helper the helper shown next to the mouse
						 * @param {Object} event the event that caused the start (probably mousemove)
						 */
						$.vakata.dnd._trigger("start", e);
					}
					else { return; }
				}

				var d  = false, w  = false,
					dh = false, wh = false,
					dw = false, ww = false,
					dt = false, dl = false,
					ht = false, hl = false;

				vakata_dnd.scroll_t = 0;
				vakata_dnd.scroll_l = 0;
				vakata_dnd.scroll_e = false;
				$($(e.target).parentsUntil("body").addBack().get().reverse())
					.filter(function () {
						return	(/^auto|scroll$/).test($(this).css("overflow")) &&
								(this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth);
					})
					.each(function () {
						var t = $(this), o = t.offset();
						if(this.scrollHeight > this.offsetHeight) {
							if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_t = 1; }
							if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity)				{ vakata_dnd.scroll_t = -1; }
						}
						if(this.scrollWidth > this.offsetWidth) {
							if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_l = 1; }
							if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity)				{ vakata_dnd.scroll_l = -1; }
						}
						if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
							vakata_dnd.scroll_e = $(this);
							return false;
						}
					});

				if(!vakata_dnd.scroll_e) {
					d  = $(document); w = $(window);
					dh = d.height(); wh = w.height();
					dw = d.width(); ww = w.width();
					dt = d.scrollTop(); dl = d.scrollLeft();
					if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity)		{ vakata_dnd.scroll_t = -1;  }
					if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_t = 1; }
					if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity)		{ vakata_dnd.scroll_l = -1; }
					if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity)	{ vakata_dnd.scroll_l = 1; }
					if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
						vakata_dnd.scroll_e = d;
					}
				}
				if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); }

				if(vakata_dnd.helper) {
					ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10);
					hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10);
					if(dh && ht + 25 > dh) { ht = dh - 50; }
					if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); }
					vakata_dnd.helper.css({
						left	: hl + "px",
						top		: ht + "px"
					});
				}
				/**
				 * triggered on the document when a drag is in progress
				 * @event
				 * @plugin dnd
				 * @name dnd_move.vakata
				 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
				 * @param {DOM} element the DOM element being dragged
				 * @param {jQuery} helper the helper shown next to the mouse
				 * @param {Object} event the event that caused this to trigger (most likely mousemove)
				 */
				$.vakata.dnd._trigger("move", e);
				return false;
			},
			stop : function (e) {
				if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
					e.pageX = e.originalEvent.changedTouches[0].pageX;
					e.pageY = e.originalEvent.changedTouches[0].pageY;
					e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
				}
				if(vakata_dnd.is_drag) {
					/**
					 * triggered on the document when a drag stops (the dragged element is dropped)
					 * @event
					 * @plugin dnd
					 * @name dnd_stop.vakata
					 * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
					 * @param {DOM} element the DOM element being dragged
					 * @param {jQuery} helper the helper shown next to the mouse
					 * @param {Object} event the event that caused the stop
					 */
					if (e.target !== vakata_dnd.target) {
						$(vakata_dnd.target).off('click.vakata');
					}
					$.vakata.dnd._trigger("stop", e);
				}
				else {
					if(e.type === "touchend" && e.target === vakata_dnd.target) {
						var to = setTimeout(function () { $(e.target).click(); }, 100);
						$(e.target).one('click', function() { if(to) { clearTimeout(to); } });
					}
				}
				$.vakata.dnd._clean();
				return false;
			}
		};
	}($));

	// include the dnd plugin by default
	// $.jstree.defaults.plugins.push("dnd");


/**
 * ### Massload plugin
 *
 * Adds massload functionality to jsTree, so that multiple nodes can be loaded in a single request (only useful with lazy loading).
 */

	/**
	 * massload configuration
	 *
	 * It is possible to set this to a standard jQuery-like AJAX config.
	 * In addition to the standard jQuery ajax options here you can supply functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node IDs need to be loaded, the return value of those functions will be used.
	 *
	 * You can also set this to a function, that function will receive the node IDs being loaded as argument and a second param which is a function (callback) which should be called with the result.
	 *
	 * Both the AJAX and the function approach rely on the same return value - an object where the keys are the node IDs, and the value is the children of that node as an array.
	 *
	 *	{
	 *		"id1" : [{ "text" : "Child of ID1", "id" : "c1" }, { "text" : "Another child of ID1", "id" : "c2" }],
	 *		"id2" : [{ "text" : "Child of ID2", "id" : "c3" }]
	 *	}
	 * 
	 * @name $.jstree.defaults.massload
	 * @plugin massload
	 */
	$.jstree.defaults.massload = null;
	$.jstree.plugins.massload = function (options, parent) {
		this.init = function (el, options) {
			this._data.massload = {};
			parent.init.call(this, el, options);
		};
		this._load_nodes = function (nodes, callback, is_callback, force_reload) {
			var s = this.settings.massload,
				nodesString = JSON.stringify(nodes),
				toLoad = [],
				m = this._model.data,
				i, j, dom;
			if (!is_callback) {
				for(i = 0, j = nodes.length; i < j; i++) {
					if(!m[nodes[i]] || ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || force_reload) ) {
						toLoad.push(nodes[i]);
						dom = this.get_node(nodes[i], true);
						if (dom && dom.length) {
							dom.addClass("jstree-loading").attr('aria-busy',true);
						}
					}
				}
				this._data.massload = {};
				if (toLoad.length) {
					if($.isFunction(s)) {
						return s.call(this, toLoad, $.proxy(function (data) {
							var i, j;
							if(data) {
								for(i in data) {
									if(data.hasOwnProperty(i)) {
										this._data.massload[i] = data[i];
									}
								}
							}
							for(i = 0, j = nodes.length; i < j; i++) {
								dom = this.get_node(nodes[i], true);
								if (dom && dom.length) {
									dom.removeClass("jstree-loading").attr('aria-busy',false);
								}
							}
							parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
						}, this));
					}
					if(typeof s === 'object' && s && s.url) {
						s = $.extend(true, {}, s);
						if($.isFunction(s.url)) {
							s.url = s.url.call(this, toLoad);
						}
						if($.isFunction(s.data)) {
							s.data = s.data.call(this, toLoad);
						}
						return $.ajax(s)
							.done($.proxy(function (data,t,x) {
									var i, j;
									if(data) {
										for(i in data) {
											if(data.hasOwnProperty(i)) {
												this._data.massload[i] = data[i];
											}
										}
									}
									for(i = 0, j = nodes.length; i < j; i++) {
										dom = this.get_node(nodes[i], true);
										if (dom && dom.length) {
											dom.removeClass("jstree-loading").attr('aria-busy',false);
										}
									}
									parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
								}, this))
							.fail($.proxy(function (f) {
									parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
								}, this));
					}
				}
			}
			return parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
		};
		this._load_node = function (obj, callback) {
			var data = this._data.massload[obj.id],
				rslt = null, dom;
			if(data) {
				rslt = this[typeof data === 'string' ? '_append_html_data' : '_append_json_data'](
					obj,
					typeof data === 'string' ? $($.parseHTML(data)).filter(function () { return this.nodeType !== 3; }) : data,
					function (status) { callback.call(this, status); }
				);
				dom = this.get_node(obj.id, true);
				if (dom && dom.length) {
					dom.removeClass("jstree-loading").attr('aria-busy',false);
				}
				delete this._data.massload[obj.id];
				return rslt;
			}
			return parent._load_node.call(this, obj, callback);
		};
	};

/**
 * ### Search plugin
 *
 * Adds search functionality to jsTree.
 */

	/**
	 * stores all defaults for the search plugin
	 * @name $.jstree.defaults.search
	 * @plugin search
	 */
	$.jstree.defaults.search = {
		/**
		 * a jQuery-like AJAX config, which jstree uses if a server should be queried for results.
		 *
		 * A `str` (which is the search string) parameter will be added with the request, an optional `inside` parameter will be added if the search is limited to a node id. The expected result is a JSON array with nodes that need to be opened so that matching nodes will be revealed.
		 * Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 3 parameters - the search string, the callback to call with the array of nodes to load, and the optional node ID to limit the search to
		 * @name $.jstree.defaults.search.ajax
		 * @plugin search
		 */
		ajax : false,
		/**
		 * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `false`.
		 * @name $.jstree.defaults.search.fuzzy
		 * @plugin search
		 */
		fuzzy : false,
		/**
		 * Indicates if the search should be case sensitive. Default is `false`.
		 * @name $.jstree.defaults.search.case_sensitive
		 * @plugin search
		 */
		case_sensitive : false,
		/**
		 * Indicates if the tree should be filtered (by default) to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers).
		 * This setting can be changed at runtime when calling the search method. Default is `false`.
		 * @name $.jstree.defaults.search.show_only_matches
		 * @plugin search
		 */
		show_only_matches : false,
		/**
		 * Indicates if the children of matched element are shown (when show_only_matches is true)
		 * This setting can be changed at runtime when calling the search method. Default is `false`.
		 * @name $.jstree.defaults.search.show_only_matches_children
		 * @plugin search
		 */
		show_only_matches_children : false,
		/**
		 * Indicates if all nodes opened to reveal the search result, should be closed when the search is cleared or a new search is performed. Default is `true`.
		 * @name $.jstree.defaults.search.close_opened_onclear
		 * @plugin search
		 */
		close_opened_onclear : true,
		/**
		 * Indicates if only leaf nodes should be included in search results. Default is `false`.
		 * @name $.jstree.defaults.search.search_leaves_only
		 * @plugin search
		 */
		search_leaves_only : false,
		/**
		 * If set to a function it wil be called in the instance's scope with two arguments - search string and node (where node will be every node in the structure, so use with caution).
		 * If the function returns a truthy value the node will be considered a match (it might not be displayed if search_only_leaves is set to true and the node is not a leaf). Default is `false`.
		 * @name $.jstree.defaults.search.search_callback
		 * @plugin search
		 */
		search_callback : false
	};

	$.jstree.plugins.search = function (options, parent) {
		this.bind = function () {
			parent.bind.call(this);

			this._data.search.str = "";
			this._data.search.dom = $();
			this._data.search.res = [];
			this._data.search.opn = [];
			this._data.search.som = false;
			this._data.search.smc = false;
			this._data.search.hdn = [];

			this.element
				.on("search.jstree", $.proxy(function (e, data) {
						if(this._data.search.som && data.res.length) {
							var m = this._model.data, i, j, p = [], k, l;
							for(i = 0, j = data.res.length; i < j; i++) {
								if(m[data.res[i]] && !m[data.res[i]].state.hidden) {
									p.push(data.res[i]);
									p = p.concat(m[data.res[i]].parents);
									if(this._data.search.smc) {
										for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) {
											if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) {
												p.push(m[data.res[i]].children_d[k]);
											}
										}
									}
								}
							}
							p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root);
							this._data.search.hdn = this.hide_all(true);
							this.show_node(p, true);
							this.redraw(true);
						}
					}, this))
				.on("clear_search.jstree", $.proxy(function (e, data) {
						if(this._data.search.som && data.res.length) {
							this.show_node(this._data.search.hdn, true);
							this.redraw(true);
						}
					}, this));
		};
		/**
		 * used to search the tree nodes for a given string
		 * @name search(str [, skip_async])
		 * @param {String} str the search string
		 * @param {Boolean} skip_async if set to true server will not be queried even if configured
		 * @param {Boolean} show_only_matches if set to true only matching nodes will be shown (keep in mind this can be very slow on large trees or old browsers)
		 * @param {mixed} inside an optional node to whose children to limit the search
		 * @param {Boolean} append if set to true the results of this search are appended to the previous search
		 * @plugin search
		 * @trigger search.jstree
		 */
		this.search = function (str, skip_async, show_only_matches, inside, append, show_only_matches_children) {
			if(str === false || $.trim(str.toString()) === "") {
				return this.clear_search();
			}
			inside = this.get_node(inside);
			inside = inside && inside.id ? inside.id : null;
			str = str.toString();
			var s = this.settings.search,
				a = s.ajax ? s.ajax : false,
				m = this._model.data,
				f = null,
				r = [],
				p = [], i, j;
			if(this._data.search.res.length && !append) {
				this.clear_search();
			}
			if(show_only_matches === undefined) {
				show_only_matches = s.show_only_matches;
			}
			if(show_only_matches_children === undefined) {
				show_only_matches_children = s.show_only_matches_children;
			}
			if(!skip_async && a !== false) {
				if($.isFunction(a)) {
					return a.call(this, str, $.proxy(function (d) {
							if(d && d.d) { d = d.d; }
							this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () {
								this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
							});
						}, this), inside);
				}
				else {
					a = $.extend({}, a);
					if(!a.data) { a.data = {}; }
					a.data.str = str;
					if(inside) {
						a.data.inside = inside;
					}
					if (this._data.search.lastRequest) {
						this._data.search.lastRequest.abort();
					}
					this._data.search.lastRequest = $.ajax(a)
						.fail($.proxy(function () {
							this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) };
							this.settings.core.error.call(this, this._data.core.last_error);
						}, this))
						.done($.proxy(function (d) {
							if(d && d.d) { d = d.d; }
							this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () {
								this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
							});
						}, this));
					return this._data.search.lastRequest;
				}
			}
			if(!append) {
				this._data.search.str = str;
				this._data.search.dom = $();
				this._data.search.res = [];
				this._data.search.opn = [];
				this._data.search.som = show_only_matches;
				this._data.search.smc = show_only_matches_children;
			}

			f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy });
			$.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) {
				var v = m[i];
				if(v.text && !v.state.hidden && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) && ( (s.search_callback && s.search_callback.call(this, str, v)) || (!s.search_callback && f.search(v.text).isMatch) ) ) {
					r.push(i);
					p = p.concat(v.parents);
				}
			});
			if(r.length) {
				p = $.vakata.array_unique(p);
				for(i = 0, j = p.length; i < j; i++) {
					if(p[i] !== $.jstree.root && m[p[i]] && this.open_node(p[i], null, 0) === true) {
						this._data.search.opn.push(p[i]);
					}
				}
				if(!append) {
					this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #')));
					this._data.search.res = r;
				}
				else {
					this._data.search.dom = this._data.search.dom.add($(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #'))));
					this._data.search.res = $.vakata.array_unique(this._data.search.res.concat(r));
				}
				this._data.search.dom.children(".jstree-anchor").addClass('jstree-search');
			}
			/**
			 * triggered after search is complete
			 * @event
			 * @name search.jstree
			 * @param {jQuery} nodes a jQuery collection of matching nodes
			 * @param {String} str the search string
			 * @param {Array} res a collection of objects represeing the matching nodes
			 * @plugin search
			 */
			this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res, show_only_matches : show_only_matches });
		};
		/**
		 * used to clear the last search (removes classes and shows all nodes if filtering is on)
		 * @name clear_search()
		 * @plugin search
		 * @trigger clear_search.jstree
		 */
		this.clear_search = function () {
			if(this.settings.search.close_opened_onclear) {
				this.close_node(this._data.search.opn, 0);
			}
			/**
			 * triggered after search is complete
			 * @event
			 * @name clear_search.jstree
			 * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search)
			 * @param {String} str the search string (the last search string)
			 * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search)
			 * @plugin search
			 */
			this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res });
			if(this._data.search.res.length) {
				this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(this._data.search.res, function (v) {
					return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&');
				}).join(', #')));
				this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search");
			}
			this._data.search.str = "";
			this._data.search.res = [];
			this._data.search.opn = [];
			this._data.search.dom = $();
		};

		this.redraw_node = function(obj, deep, callback, force_render) {
			obj = parent.redraw_node.apply(this, arguments);
			if(obj) {
				if($.inArray(obj.id, this._data.search.res) !== -1) {
					var i, j, tmp = null;
					for(i = 0, j = obj.childNodes.length; i < j; i++) {
						if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
							tmp = obj.childNodes[i];
							break;
						}
					}
					if(tmp) {
						tmp.className += ' jstree-search';
					}
				}
			}
			return obj;
		};
	};

	// helpers
	(function ($) {
		// from http://kiro.me/projects/fuse.html
		$.vakata.search = function(pattern, txt, options) {
			options = options || {};
			options = $.extend({}, $.vakata.search.defaults, options);
			if(options.fuzzy !== false) {
				options.fuzzy = true;
			}
			pattern = options.caseSensitive ? pattern : pattern.toLowerCase();
			var MATCH_LOCATION	= options.location,
				MATCH_DISTANCE	= options.distance,
				MATCH_THRESHOLD	= options.threshold,
				patternLen = pattern.length,
				matchmask, pattern_alphabet, match_bitapScore, search;
			if(patternLen > 32) {
				options.fuzzy = false;
			}
			if(options.fuzzy) {
				matchmask = 1 << (patternLen - 1);
				pattern_alphabet = (function () {
					var mask = {},
						i = 0;
					for (i = 0; i < patternLen; i++) {
						mask[pattern.charAt(i)] = 0;
					}
					for (i = 0; i < patternLen; i++) {
						mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1);
					}
					return mask;
				}());
				match_bitapScore = function (e, x) {
					var accuracy = e / patternLen,
						proximity = Math.abs(MATCH_LOCATION - x);
					if(!MATCH_DISTANCE) {
						return proximity ? 1.0 : accuracy;
					}
					return accuracy + (proximity / MATCH_DISTANCE);
				};
			}
			search = function (text) {
				text = options.caseSensitive ? text : text.toLowerCase();
				if(pattern === text || text.indexOf(pattern) !== -1) {
					return {
						isMatch: true,
						score: 0
					};
				}
				if(!options.fuzzy) {
					return {
						isMatch: false,
						score: 1
					};
				}
				var i, j,
					textLen = text.length,
					scoreThreshold = MATCH_THRESHOLD,
					bestLoc = text.indexOf(pattern, MATCH_LOCATION),
					binMin, binMid,
					binMax = patternLen + textLen,
					lastRd, start, finish, rd, charMatch,
					score = 1,
					locations = [];
				if (bestLoc !== -1) {
					scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
					bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen);
					if (bestLoc !== -1) {
						scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
					}
				}
				bestLoc = -1;
				for (i = 0; i < patternLen; i++) {
					binMin = 0;
					binMid = binMax;
					while (binMin < binMid) {
						if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) {
							binMin = binMid;
						} else {
							binMax = binMid;
						}
						binMid = Math.floor((binMax - binMin) / 2 + binMin);
					}
					binMax = binMid;
					start = Math.max(1, MATCH_LOCATION - binMid + 1);
					finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen;
					rd = new Array(finish + 2);
					rd[finish + 1] = (1 << i) - 1;
					for (j = finish; j >= start; j--) {
						charMatch = pattern_alphabet[text.charAt(j - 1)];
						if (i === 0) {
							rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
						} else {
							rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
						}
						if (rd[j] & matchmask) {
							score = match_bitapScore(i, j - 1);
							if (score <= scoreThreshold) {
								scoreThreshold = score;
								bestLoc = j - 1;
								locations.push(bestLoc);
								if (bestLoc > MATCH_LOCATION) {
									start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
								} else {
									break;
								}
							}
						}
					}
					if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
						break;
					}
					lastRd = rd;
				}
				return {
					isMatch: bestLoc >= 0,
					score: score
				};
			};
			return txt === true ? { 'search' : search } : search(txt);
		};
		$.vakata.search.defaults = {
			location : 0,
			distance : 100,
			threshold : 0.6,
			fuzzy : false,
			caseSensitive : false
		};
	}($));

	// include the search plugin by default
	// $.jstree.defaults.plugins.push("search");


/**
 * ### Sort plugin
 *
 * Automatically sorts all siblings in the tree according to a sorting function.
 */

	/**
	 * the settings function used to sort the nodes.
	 * It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`.
	 * @name $.jstree.defaults.sort
	 * @plugin sort
	 */
	$.jstree.defaults.sort = function (a, b) {
		//return this.get_type(a) === this.get_type(b) ? (this.get_text(a) > this.get_text(b) ? 1 : -1) : this.get_type(a) >= this.get_type(b);
		return this.get_text(a) > this.get_text(b) ? 1 : -1;
	};
	$.jstree.plugins.sort = function (options, parent) {
		this.bind = function () {
			parent.bind.call(this);
			this.element
				.on("model.jstree", $.proxy(function (e, data) {
						this.sort(data.parent, true);
					}, this))
				.on("rename_node.jstree create_node.jstree", $.proxy(function (e, data) {
						this.sort(data.parent || data.node.parent, false);
						this.redraw_node(data.parent || data.node.parent, true);
					}, this))
				.on("move_node.jstree copy_node.jstree", $.proxy(function (e, data) {
						this.sort(data.parent, false);
						this.redraw_node(data.parent, true);
					}, this));
		};
		/**
		 * used to sort a node's children
		 * @private
		 * @name sort(obj [, deep])
		 * @param  {mixed} obj the node
		 * @param {Boolean} deep if set to `true` nodes are sorted recursively.
		 * @plugin sort
		 * @trigger search.jstree
		 */
		this.sort = function (obj, deep) {
			var i, j;
			obj = this.get_node(obj);
			if(obj && obj.children && obj.children.length) {
				obj.children.sort($.proxy(this.settings.sort, this));
				if(deep) {
					for(i = 0, j = obj.children_d.length; i < j; i++) {
						this.sort(obj.children_d[i], false);
					}
				}
			}
		};
	};

	// include the sort plugin by default
	// $.jstree.defaults.plugins.push("sort");

/**
 * ### State plugin
 *
 * Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc)
 */

	var to = false;
	/**
	 * stores all defaults for the state plugin
	 * @name $.jstree.defaults.state
	 * @plugin state
	 */
	$.jstree.defaults.state = {
		/**
		 * A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`.
		 * @name $.jstree.defaults.state.key
		 * @plugin state
		 */
		key		: 'jstree',
		/**
		 * A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`.
		 * @name $.jstree.defaults.state.events
		 * @plugin state
		 */
		events	: 'changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree',
		/**
		 * Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire.
		 * @name $.jstree.defaults.state.ttl
		 * @plugin state
		 */
		ttl		: false,
		/**
		 * A function that will be executed prior to restoring state with one argument - the state object. Can be used to clear unwanted parts of the state.
		 * @name $.jstree.defaults.state.filter
		 * @plugin state
		 */
		filter	: false
	};
	$.jstree.plugins.state = function (options, parent) {
		this.bind = function () {
			parent.bind.call(this);
			var bind = $.proxy(function () {
				this.element.on(this.settings.state.events, $.proxy(function () {
					if(to) { clearTimeout(to); }
					to = setTimeout($.proxy(function () { this.save_state(); }, this), 100);
				}, this));
				/**
				 * triggered when the state plugin is finished restoring the state (and immediately after ready if there is no state to restore).
				 * @event
				 * @name state_ready.jstree
				 * @plugin state
				 */
				this.trigger('state_ready');
			}, this);
			this.element
				.on("ready.jstree", $.proxy(function (e, data) {
						this.element.one("restore_state.jstree", bind);
						if(!this.restore_state()) { bind(); }
					}, this));
		};
		/**
		 * save the state
		 * @name save_state()
		 * @plugin state
		 */
		this.save_state = function () {
			var st = { 'state' : this.get_state(), 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) };
			$.vakata.storage.set(this.settings.state.key, JSON.stringify(st));
		};
		/**
		 * restore the state from the user's computer
		 * @name restore_state()
		 * @plugin state
		 */
		this.restore_state = function () {
			var k = $.vakata.storage.get(this.settings.state.key);
			if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } }
			if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; }
			if(!!k && k.state) { k = k.state; }
			if(!!k && $.isFunction(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); }
			if(!!k) {
				this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); });
				this.set_state(k);
				return true;
			}
			return false;
		};
		/**
		 * clear the state on the user's computer
		 * @name clear_state()
		 * @plugin state
		 */
		this.clear_state = function () {
			return $.vakata.storage.del(this.settings.state.key);
		};
	};

	(function ($, undefined) {
		$.vakata.storage = {
			// simply specifying the functions in FF throws an error
			set : function (key, val) { return window.localStorage.setItem(key, val); },
			get : function (key) { return window.localStorage.getItem(key); },
			del : function (key) { return window.localStorage.removeItem(key); }
		};
	}($));

	// include the state plugin by default
	// $.jstree.defaults.plugins.push("state");

/**
 * ### Types plugin
 *
 * Makes it possible to add predefined types for groups of nodes, which make it possible to easily control nesting rules and icon for each group.
 */

	/**
	 * An object storing all types as key value pairs, where the key is the type name and the value is an object that could contain following keys (all optional).
	 *
	 * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited.
	 * * `max_depth` the maximum number of nesting this node type can have. A value of `1` would mean that the node can have children, but no grandchildren. Do not specify or set to `-1` for unlimited.
	 * * `valid_children` an array of node type strings, that nodes of this type can have as children. Do not specify or set to `-1` for no limits.
	 * * `icon` a string - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class. Omit to use the default icon from your theme.
	 * * `li_attr` an object of values which will be used to add HTML attributes on the resulting LI DOM node (merged with the node's own data)
	 * * `a_attr` an object of values which will be used to add HTML attributes on the resulting A DOM node (merged with the node's own data)
	 *
	 * There are two predefined types:
	 *
	 * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes.
	 * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified.
	 *
	 * @name $.jstree.defaults.types
	 * @plugin types
	 */
	$.jstree.defaults.types = {
		'default' : {}
	};
	$.jstree.defaults.types[$.jstree.root] = {};

	$.jstree.plugins.types = function (options, parent) {
		this.init = function (el, options) {
			var i, j;
			if(options && options.types && options.types['default']) {
				for(i in options.types) {
					if(i !== "default" && i !== $.jstree.root && options.types.hasOwnProperty(i)) {
						for(j in options.types['default']) {
							if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) {
								options.types[i][j] = options.types['default'][j];
							}
						}
					}
				}
			}
			parent.init.call(this, el, options);
			this._model.data[$.jstree.root].type = $.jstree.root;
		};
		this.refresh = function (skip_loading, forget_state) {
			parent.refresh.call(this, skip_loading, forget_state);
			this._model.data[$.jstree.root].type = $.jstree.root;
		};
		this.bind = function () {
			this.element
				.on('model.jstree', $.proxy(function (e, data) {
						var m = this._model.data,
							dpc = data.nodes,
							t = this.settings.types,
							i, j, c = 'default', k;
						for(i = 0, j = dpc.length; i < j; i++) {
							c = 'default';
							if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
								c = m[dpc[i]].original.type;
							}
							if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) {
								c = m[dpc[i]].data.jstree.type;
							}
							m[dpc[i]].type = c;
							if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
								m[dpc[i]].icon = t[c].icon;
							}
							if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') {
								for (k in t[c].li_attr) {
									if (t[c].li_attr.hasOwnProperty(k)) {
										if (k === 'id') {
											continue;
										}
										else if (m[dpc[i]].li_attr[k] === undefined) {
											m[dpc[i]].li_attr[k] = t[c].li_attr[k];
										}
										else if (k === 'class') {
											m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class'];
										}
									}
								}
							}
							if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') {
								for (k in t[c].a_attr) {
									if (t[c].a_attr.hasOwnProperty(k)) {
										if (k === 'id') {
											continue;
										}
										else if (m[dpc[i]].a_attr[k] === undefined) {
											m[dpc[i]].a_attr[k] = t[c].a_attr[k];
										}
										else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') {
											m[dpc[i]].a_attr['href'] = t[c].a_attr['href'];
										}
										else if (k === 'class') {
											m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class'];
										}
									}
								}
							}
						}
						m[$.jstree.root].type = $.jstree.root;
					}, this));
			parent.bind.call(this);
		};
		this.get_json = function (obj, options, flat) {
			var i, j,
				m = this._model.data,
				opt = options ? $.extend(true, {}, options, {no_id:false}) : {},
				tmp = parent.get_json.call(this, obj, opt, flat);
			if(tmp === false) { return false; }
			if($.isArray(tmp)) {
				for(i = 0, j = tmp.length; i < j; i++) {
					tmp[i].type = tmp[i].id && m[tmp[i].id] && m[tmp[i].id].type ? m[tmp[i].id].type : "default";
					if(options && options.no_id) {
						delete tmp[i].id;
						if(tmp[i].li_attr && tmp[i].li_attr.id) {
							delete tmp[i].li_attr.id;
						}
						if(tmp[i].a_attr && tmp[i].a_attr.id) {
							delete tmp[i].a_attr.id;
						}
					}
				}
			}
			else {
				tmp.type = tmp.id && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default";
				if(options && options.no_id) {
					tmp = this._delete_ids(tmp);
				}
			}
			return tmp;
		};
		this._delete_ids = function (tmp) {
			if($.isArray(tmp)) {
				for(var i = 0, j = tmp.length; i < j; i++) {
					tmp[i] = this._delete_ids(tmp[i]);
				}
				return tmp;
			}
			delete tmp.id;
			if(tmp.li_attr && tmp.li_attr.id) {
				delete tmp.li_attr.id;
			}
			if(tmp.a_attr && tmp.a_attr.id) {
				delete tmp.a_attr.id;
			}
			if(tmp.children && $.isArray(tmp.children)) {
				tmp.children = this._delete_ids(tmp.children);
			}
			return tmp;
		};
		this.check = function (chk, obj, par, pos, more) {
			if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
			obj = obj && obj.id ? obj : this.get_node(obj);
			par = par && par.id ? par : this.get_node(par);
			var m = obj && obj.id ? (more && more.origin ? more.origin : $.jstree.reference(obj.id)) : null, tmp, d, i, j;
			m = m && m._model && m._model.data ? m._model.data : null;
			switch(chk) {
				case "create_node":
				case "move_node":
				case "copy_node":
					if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) {
						tmp = this.get_rules(par);
						if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) {
							this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_01', 'reason' : 'max_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
							return false;
						}
						if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray((obj.type || 'default'), tmp.valid_children) === -1) {
							this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_02', 'reason' : 'valid_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
							return false;
						}
						if(m && obj.children_d && obj.parents) {
							d = 0;
							for(i = 0, j = obj.children_d.length; i < j; i++) {
								d = Math.max(d, m[obj.children_d[i]].parents.length);
							}
							d = d - obj.parents.length + 1;
						}
						if(d <= 0 || d === undefined) { d = 1; }
						do {
							if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) {
								this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_03', 'reason' : 'max_depth prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
								return false;
							}
							par = this.get_node(par.parent);
							tmp = this.get_rules(par);
							d++;
						} while(par);
					}
					break;
			}
			return true;
		};
		/**
		 * used to retrieve the type settings object for a node
		 * @name get_rules(obj)
		 * @param {mixed} obj the node to find the rules for
		 * @return {Object}
		 * @plugin types
		 */
		this.get_rules = function (obj) {
			obj = this.get_node(obj);
			if(!obj) { return false; }
			var tmp = this.get_type(obj, true);
			if(tmp.max_depth === undefined) { tmp.max_depth = -1; }
			if(tmp.max_children === undefined) { tmp.max_children = -1; }
			if(tmp.valid_children === undefined) { tmp.valid_children = -1; }
			return tmp;
		};
		/**
		 * used to retrieve the type string or settings object for a node
		 * @name get_type(obj [, rules])
		 * @param {mixed} obj the node to find the rules for
		 * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned
		 * @return {String|Object}
		 * @plugin types
		 */
		this.get_type = function (obj, rules) {
			obj = this.get_node(obj);
			return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type);
		};
		/**
		 * used to change a node's type
		 * @name set_type(obj, type)
		 * @param {mixed} obj the node to change
		 * @param {String} type the new type
		 * @plugin types
		 */
		this.set_type = function (obj, type) {
			var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a;
			if($.isArray(obj)) {
				obj = obj.slice();
				for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
					this.set_type(obj[t1], type);
				}
				return true;
			}
			t = this.settings.types;
			obj = this.get_node(obj);
			if(!t[type] || !obj) { return false; }
			d = this.get_node(obj, true);
			if (d && d.length) {
				a = d.children('.jstree-anchor');
			}
			old_type = obj.type;
			old_icon = this.get_icon(obj);
			obj.type = type;
			if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) {
				this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
			}

			// remove old type props
			if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') {
				for (k in t[old_type].li_attr) {
					if (t[old_type].li_attr.hasOwnProperty(k)) {
						if (k === 'id') {
							continue;
						}
						else if (k === 'class') {
							m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], '');
							if (d) { d.removeClass(t[old_type].li_attr[k]); }
						}
						else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) {
							m[obj.id].li_attr[k] = null;
							if (d) { d.removeAttr(k); }
						}
					}
				}
			}
			if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') {
				for (k in t[old_type].a_attr) {
					if (t[old_type].a_attr.hasOwnProperty(k)) {
						if (k === 'id') {
							continue;
						}
						else if (k === 'class') {
							m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], '');
							if (a) { a.removeClass(t[old_type].a_attr[k]); }
						}
						else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) {
							if (k === 'href') {
								m[obj.id].a_attr[k] = '#';
								if (a) { a.attr('href', '#'); }
							}
							else {
								delete m[obj.id].a_attr[k];
								if (a) { a.removeAttr(k); }
							}
						}
					}
				}
			}

			// add new props
			if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') {
				for (k in t[type].li_attr) {
					if (t[type].li_attr.hasOwnProperty(k)) {
						if (k === 'id') {
							continue;
						}
						else if (m[obj.id].li_attr[k] === undefined) {
							m[obj.id].li_attr[k] = t[type].li_attr[k];
							if (d) {
								if (k === 'class') {
									d.addClass(t[type].li_attr[k]);
								}
								else {
									d.attr(k, t[type].li_attr[k]);
								}
							}
						}
						else if (k === 'class') {
							m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class'];
							if (d) { d.addClass(t[type].li_attr[k]); }
						}
					}
				}
			}
			if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') {
				for (k in t[type].a_attr) {
					if (t[type].a_attr.hasOwnProperty(k)) {
						if (k === 'id') {
							continue;
						}
						else if (m[obj.id].a_attr[k] === undefined) {
							m[obj.id].a_attr[k] = t[type].a_attr[k];
							if (a) {
								if (k === 'class') {
									a.addClass(t[type].a_attr[k]);
								}
								else {
									a.attr(k, t[type].a_attr[k]);
								}
							}
						}
						else if (k === 'href' && m[obj.id].a_attr[k] === '#') {
							m[obj.id].a_attr['href'] = t[type].a_attr['href'];
							if (a) { a.attr('href', t[type].a_attr['href']); }
						}
						else if (k === 'class') {
							m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class'];
							if (a) { a.addClass(t[type].a_attr[k]); }
						}
					}
				}
			}

			return true;
		};
	};
	// include the types plugin by default
	// $.jstree.defaults.plugins.push("types");


/**
 * ### Unique plugin
 *
 * Enforces that no nodes with the same name can coexist as siblings.
 */

	/**
	 * stores all defaults for the unique plugin
	 * @name $.jstree.defaults.unique
	 * @plugin unique
	 */
	$.jstree.defaults.unique = {
		/**
		 * Indicates if the comparison should be case sensitive. Default is `false`.
		 * @name $.jstree.defaults.unique.case_sensitive
		 * @plugin unique
		 */
		case_sensitive : false,
		/**
		 * A callback executed in the instance's scope when a new node is created and the name is already taken, the two arguments are the conflicting name and the counter. The default will produce results like `New node (2)`.
		 * @name $.jstree.defaults.unique.duplicate
		 * @plugin unique
		 */
		duplicate : function (name, counter) {
			return name + ' (' + counter + ')';
		}
	};

	$.jstree.plugins.unique = function (options, parent) {
		this.check = function (chk, obj, par, pos, more) {
			if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
			obj = obj && obj.id ? obj : this.get_node(obj);
			par = par && par.id ? par : this.get_node(par);
			if(!par || !par.children) { return true; }
			var n = chk === "rename_node" ? pos : obj.text,
				c = [],
				s = this.settings.unique.case_sensitive,
				m = this._model.data, i, j;
			for(i = 0, j = par.children.length; i < j; i++) {
				c.push(s ? m[par.children[i]].text : m[par.children[i]].text.toLowerCase());
			}
			if(!s) { n = n.toLowerCase(); }
			switch(chk) {
				case "delete_node":
					return true;
				case "rename_node":
					i = ($.inArray(n, c) === -1 || (obj.text && obj.text[ s ? 'toString' : 'toLowerCase']() === n));
					if(!i) {
						this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_01', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
					}
					return i;
				case "create_node":
					i = ($.inArray(n, c) === -1);
					if(!i) {
						this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_04', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
					}
					return i;
				case "copy_node":
					i = ($.inArray(n, c) === -1);
					if(!i) {
						this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_02', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
					}
					return i;
				case "move_node":
					i = ( (obj.parent === par.id && (!more || !more.is_multi)) || $.inArray(n, c) === -1);
					if(!i) {
						this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_03', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
					}
					return i;
			}
			return true;
		};
		this.create_node = function (par, node, pos, callback, is_loaded) {
			if(!node || node.text === undefined) {
				if(par === null) {
					par = $.jstree.root;
				}
				par = this.get_node(par);
				if(!par) {
					return parent.create_node.call(this, par, node, pos, callback, is_loaded);
				}
				pos = pos === undefined ? "last" : pos;
				if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
					return parent.create_node.call(this, par, node, pos, callback, is_loaded);
				}
				if(!node) { node = {}; }
				var tmp, n, dpc, i, j, m = this._model.data, s = this.settings.unique.case_sensitive, cb = this.settings.unique.duplicate;
				n = tmp = this.get_string('New node');
				dpc = [];
				for(i = 0, j = par.children.length; i < j; i++) {
					dpc.push(s ? m[par.children[i]].text : m[par.children[i]].text.toLowerCase());
				}
				i = 1;
				while($.inArray(s ? n : n.toLowerCase(), dpc) !== -1) {
					n = cb.call(this, tmp, (++i)).toString();
				}
				node.text = n;
			}
			return parent.create_node.call(this, par, node, pos, callback, is_loaded);
		};
	};

	// include the unique plugin by default
	// $.jstree.defaults.plugins.push("unique");


/**
 * ### Wholerow plugin
 *
 * Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers.
 */

	var div = document.createElement('DIV');
	div.setAttribute('unselectable','on');
	div.setAttribute('role','presentation');
	div.className = 'jstree-wholerow';
	div.innerHTML = '&#160;';
	$.jstree.plugins.wholerow = function (options, parent) {
		this.bind = function () {
			parent.bind.call(this);

			this.element
				.on('ready.jstree set_state.jstree', $.proxy(function () {
						this.hide_dots();
					}, this))
				.on("init.jstree loading.jstree ready.jstree", $.proxy(function () {
						//div.style.height = this._data.core.li_height + 'px';
						this.get_container_ul().addClass('jstree-wholerow-ul');
					}, this))
				.on("deselect_all.jstree", $.proxy(function (e, data) {
						this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
					}, this))
				.on("changed.jstree", $.proxy(function (e, data) {
						this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
						var tmp = false, i, j;
						for(i = 0, j = data.selected.length; i < j; i++) {
							tmp = this.get_node(data.selected[i], true);
							if(tmp && tmp.length) {
								tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
							}
						}
					}, this))
				.on("open_node.jstree", $.proxy(function (e, data) {
						this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
					}, this))
				.on("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) {
						if(e.type === "hover_node" && this.is_disabled(data.node)) { return; }
						this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered');
					}, this))
				.on("contextmenu.jstree", ".jstree-wholerow", $.proxy(function (e) {
						if (this._data.contextmenu) {
							e.preventDefault();
							var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY });
							$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp);
						}
					}, this))
				/*!
				.on("mousedown.jstree touchstart.jstree", ".jstree-wholerow", function (e) {
						if(e.target === e.currentTarget) {
							var a = $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor");
							e.target = a[0];
							a.trigger(e);
						}
					})
				*/
				.on("click.jstree", ".jstree-wholerow", function (e) {
						e.stopImmediatePropagation();
						var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
					})
				.on("dblclick.jstree", ".jstree-wholerow", function (e) {
						e.stopImmediatePropagation();
						var tmp = $.Event('dblclick', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
					})
				.on("click.jstree", ".jstree-leaf > .jstree-ocl", $.proxy(function (e) {
						e.stopImmediatePropagation();
						var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
						$(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
					}, this))
				.on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", $.proxy(function (e) {
						e.stopImmediatePropagation();
						if(!this.is_disabled(e.currentTarget)) {
							this.hover_node(e.currentTarget);
						}
						return false;
					}, this))
				.on("mouseleave.jstree", ".jstree-node", $.proxy(function (e) {
						this.dehover_node(e.currentTarget);
					}, this));
		};
		this.teardown = function () {
			if(this.settings.wholerow) {
				this.element.find(".jstree-wholerow").remove();
			}
			parent.teardown.call(this);
		};
		this.redraw_node = function(obj, deep, callback, force_render) {
			obj = parent.redraw_node.apply(this, arguments);
			if(obj) {
				var tmp = div.cloneNode(true);
				//tmp.style.height = this._data.core.li_height + 'px';
				if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; }
				if(this._data.core.focused && this._data.core.focused === obj.id) { tmp.className += ' jstree-wholerow-hovered'; }
				obj.insertBefore(tmp, obj.childNodes[0]);
			}
			return obj;
		};
	};
	// include the wholerow plugin by default
	// $.jstree.defaults.plugins.push("wholerow");
	if(document.registerElement && Object && Object.create) {
		var proto = Object.create(HTMLElement.prototype);
		proto.createdCallback = function () {
			var c = { core : {}, plugins : [] }, i;
			for(i in $.jstree.plugins) {
				if($.jstree.plugins.hasOwnProperty(i) && this.attributes[i]) {
					c.plugins.push(i);
					if(this.getAttribute(i) && JSON.parse(this.getAttribute(i))) {
						c[i] = JSON.parse(this.getAttribute(i));
					}
				}
			}
			for(i in $.jstree.defaults.core) {
				if($.jstree.defaults.core.hasOwnProperty(i) && this.attributes[i]) {
					c.core[i] = JSON.parse(this.getAttribute(i)) || this.getAttribute(i);
				}
			}
			$(this).jstree(c);
		};
		// proto.attributeChangedCallback = function (name, previous, value) { };
		try {
			document.registerElement("vakata-jstree", { prototype: proto });
		} catch(ignore) { }
	}

}));/*! jsTree - v3.3.3 - 2016-10-31 - (MIT) */
!function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):"undefined"!=typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a,b){"use strict";if(!a.jstree){var c=0,d=!1,e=!1,f=!1,g=[],h=a("script:last").attr("src"),i=window.document;a.jstree={version:"3.3.3",defaults:{plugins:[]},plugins:{},path:h&&-1!==h.indexOf("/")?h.replace(/\/[^\/]+$/,""):"",idregex:/[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g,root:"#"},a.jstree.create=function(b,d){var e=new a.jstree.core(++c),f=d;return d=a.extend(!0,{},a.jstree.defaults,d),f&&f.plugins&&(d.plugins=f.plugins),a.each(d.plugins,function(a,b){"core"!==a&&(e=e.plugin(b,d[b]))}),a(b).data("jstree",e),e.init(b,d),e},a.jstree.destroy=function(){a(".jstree:jstree").jstree("destroy"),a(i).off(".jstree")},a.jstree.core=function(a){this._id=a,this._cnt=0,this._wrk=null,this._data={core:{themes:{name:!1,dots:!1,icons:!1,ellipsis:!1},selected:[],last_error:{},working:!1,worker_queue:[],focused:null}}},a.jstree.reference=function(b){var c=null,d=null;if(!b||!b.id||b.tagName&&b.nodeType||(b=b.id),!d||!d.length)try{d=a(b)}catch(e){}if(!d||!d.length)try{d=a("#"+b.replace(a.jstree.idregex,"\\$&"))}catch(e){}return d&&d.length&&(d=d.closest(".jstree")).length&&(d=d.data("jstree"))?c=d:a(".jstree").each(function(){var d=a(this).data("jstree");return d&&d._model.data[b]?(c=d,!1):void 0}),c},a.fn.jstree=function(c){var d="string"==typeof c,e=Array.prototype.slice.call(arguments,1),f=null;return c!==!0||this.length?(this.each(function(){var g=a.jstree.reference(this),h=d&&g?g[c]:null;return f=d&&h?h.apply(g,e):null,g||d||c!==b&&!a.isPlainObject(c)||a.jstree.create(this,c),(g&&!d||c===!0)&&(f=g||!1),null!==f&&f!==b?!1:void 0}),null!==f&&f!==b?f:this):!1},a.expr.pseudos.jstree=a.expr.createPseudo(function(c){return function(c){return a(c).hasClass("jstree")&&a(c).data("jstree")!==b}}),a.jstree.defaults.core={data:!1,strings:!1,check_callback:!1,error:a.noop,animation:200,multiple:!0,themes:{name:!1,url:!1,dir:!1,dots:!0,icons:!0,ellipsis:!1,stripes:!1,variant:!1,responsive:!1},expand_selected_onload:!0,worker:!0,force_text:!1,dblclick_toggle:!0},a.jstree.core.prototype={plugin:function(b,c){var d=a.jstree.plugins[b];return d?(this._data[b]={},d.prototype=this,new d(c,this)):this},init:function(b,c){this._model={data:{},changed:[],force_full_redraw:!1,redraw_timeout:!1,default_state:{loaded:!0,opened:!1,selected:!1,disabled:!1}},this._model.data[a.jstree.root]={id:a.jstree.root,parent:null,parents:[],children:[],children_d:[],state:{loaded:!1}},this.element=a(b).addClass("jstree jstree-"+this._id),this.settings=c,this._data.core.ready=!1,this._data.core.loaded=!1,this._data.core.rtl="rtl"===this.element.css("direction"),this.element[this._data.core.rtl?"addClass":"removeClass"]("jstree-rtl"),this.element.attr("role","tree"),this.settings.core.multiple&&this.element.attr("aria-multiselectable",!0),this.element.attr("tabindex")||this.element.attr("tabindex","0"),this.bind(),this.trigger("init"),this._data.core.original_container_html=this.element.find(" > ul > li").clone(!0),this._data.core.original_container_html.find("li").addBack().contents().filter(function(){return 3===this.nodeType&&(!this.nodeValue||/^\s+$/.test(this.nodeValue))}).remove(),this.element.html("<ul class='jstree-container-ul jstree-children' role='group'><li id='j"+this._id+"_loading' class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='tree-item'><i class='jstree-icon jstree-ocl'></i><a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>"+this.get_string("Loading ...")+"</a></li></ul>"),this.element.attr("aria-activedescendant","j"+this._id+"_loading"),this._data.core.li_height=this.get_container_ul().children("li").first().height()||24,this._data.core.node=this._create_prototype_node(),this.trigger("loading"),this.load_node(a.jstree.root)},destroy:function(a){if(this._wrk)try{window.URL.revokeObjectURL(this._wrk),this._wrk=null}catch(b){}a||this.element.empty(),this.teardown()},_create_prototype_node:function(){var a=i.createElement("LI"),b,c;return a.setAttribute("role","treeitem"),b=i.createElement("I"),b.className="jstree-icon jstree-ocl",b.setAttribute("role","presentation"),a.appendChild(b),b=i.createElement("A"),b.className="jstree-anchor",b.setAttribute("href","#"),b.setAttribute("tabindex","-1"),c=i.createElement("I"),c.className="jstree-icon jstree-themeicon",c.setAttribute("role","presentation"),b.appendChild(c),a.appendChild(b),b=c=null,a},teardown:function(){this.unbind(),this.element.removeClass("jstree").removeData("jstree").find("[class^='jstree']").addBack().attr("class",function(){return this.className.replace(/jstree[^ ]*|$/gi,"")}),this.element=null},bind:function(){var b="",c=null,d=0;this.element.on("dblclick.jstree",function(a){if(a.target.tagName&&"input"===a.target.tagName.toLowerCase())return!0;if(i.selection&&i.selection.empty)i.selection.empty();else if(window.getSelection){var b=window.getSelection();try{b.removeAllRanges(),b.collapse()}catch(c){}}}).on("mousedown.jstree",a.proxy(function(a){a.target===this.element[0]&&(a.preventDefault(),d=+new Date)},this)).on("mousedown.jstree",".jstree-ocl",function(a){a.preventDefault()}).on("click.jstree",".jstree-ocl",a.proxy(function(a){this.toggle_node(a.target)},this)).on("dblclick.jstree",".jstree-anchor",a.proxy(function(a){return a.target.tagName&&"input"===a.target.tagName.toLowerCase()?!0:void(this.settings.core.dblclick_toggle&&this.toggle_node(a.target))},this)).on("click.jstree",".jstree-anchor",a.proxy(function(b){b.preventDefault(),b.currentTarget!==i.activeElement&&a(b.currentTarget).focus(),this.activate_node(b.currentTarget,b)},this)).on("keydown.jstree",".jstree-anchor",a.proxy(function(b){if(b.target.tagName&&"input"===b.target.tagName.toLowerCase())return!0;if(32!==b.which&&13!==b.which&&(b.shiftKey||b.ctrlKey||b.altKey||b.metaKey))return!0;var c=null;switch(this._data.core.rtl&&(37===b.which?b.which=39:39===b.which&&(b.which=37)),b.which){case 32:b.ctrlKey&&(b.type="click",a(b.currentTarget).trigger(b));break;case 13:b.type="click",a(b.currentTarget).trigger(b);break;case 37:b.preventDefault(),this.is_open(b.currentTarget)?this.close_node(b.currentTarget):(c=this.get_parent(b.currentTarget),c&&c.id!==a.jstree.root&&this.get_node(c,!0).children(".jstree-anchor").focus());break;case 38:b.preventDefault(),c=this.get_prev_dom(b.currentTarget),c&&c.length&&c.children(".jstree-anchor").focus();break;case 39:b.preventDefault(),this.is_closed(b.currentTarget)?this.open_node(b.currentTarget,function(a){this.get_node(a,!0).children(".jstree-anchor").focus()}):this.is_open(b.currentTarget)&&(c=this.get_node(b.currentTarget,!0).children(".jstree-children")[0],c&&a(this._firstChild(c)).children(".jstree-anchor").focus());break;case 40:b.preventDefault(),c=this.get_next_dom(b.currentTarget),c&&c.length&&c.children(".jstree-anchor").focus();break;case 106:this.open_all();break;case 36:b.preventDefault(),c=this._firstChild(this.get_container_ul()[0]),c&&a(c).children(".jstree-anchor").filter(":visible").focus();break;case 35:b.preventDefault(),this.element.find(".jstree-anchor").filter(":visible").last().focus();break;case 113:b.preventDefault(),this.edit(b.currentTarget)}},this)).on("load_node.jstree",a.proxy(function(b,c){c.status&&(c.node.id!==a.jstree.root||this._data.core.loaded||(this._data.core.loaded=!0,this._firstChild(this.get_container_ul()[0])&&this.element.attr("aria-activedescendant",this._firstChild(this.get_container_ul()[0]).id),this.trigger("loaded")),this._data.core.ready||setTimeout(a.proxy(function(){if(this.element&&!this.get_container_ul().find(".jstree-loading").length){if(this._data.core.ready=!0,this._data.core.selected.length){if(this.settings.core.expand_selected_onload){var b=[],c,d;for(c=0,d=this._data.core.selected.length;d>c;c++)b=b.concat(this._model.data[this._data.core.selected[c]].parents);for(b=a.vakata.array_unique(b),c=0,d=b.length;d>c;c++)this.open_node(b[c],!1,0)}this.trigger("changed",{action:"ready",selected:this._data.core.selected})}this.trigger("ready")}},this),0))},this)).on("keypress.jstree",a.proxy(function(d){if(d.target.tagName&&"input"===d.target.tagName.toLowerCase())return!0;c&&clearTimeout(c),c=setTimeout(function(){b=""},500);var e=String.fromCharCode(d.which).toLowerCase(),f=this.element.find(".jstree-anchor").filter(":visible"),g=f.index(i.activeElement)||0,h=!1;if(b+=e,b.length>1){if(f.slice(g).each(a.proxy(function(c,d){return 0===a(d).text().toLowerCase().indexOf(b)?(a(d).focus(),h=!0,!1):void 0},this)),h)return;if(f.slice(0,g).each(a.proxy(function(c,d){return 0===a(d).text().toLowerCase().indexOf(b)?(a(d).focus(),h=!0,!1):void 0},this)),h)return}if(new RegExp("^"+e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")+"+$").test(b)){if(f.slice(g+1).each(a.proxy(function(b,c){return a(c).text().toLowerCase().charAt(0)===e?(a(c).focus(),h=!0,!1):void 0},this)),h)return;if(f.slice(0,g+1).each(a.proxy(function(b,c){return a(c).text().toLowerCase().charAt(0)===e?(a(c).focus(),h=!0,!1):void 0},this)),h)return}},this)).on("init.jstree",a.proxy(function(){var a=this.settings.core.themes;this._data.core.themes.dots=a.dots,this._data.core.themes.stripes=a.stripes,this._data.core.themes.icons=a.icons,this._data.core.themes.ellipsis=a.ellipsis,this.set_theme(a.name||"default",a.url),this.set_theme_variant(a.variant)},this)).on("loading.jstree",a.proxy(function(){this[this._data.core.themes.dots?"show_dots":"hide_dots"](),this[this._data.core.themes.icons?"show_icons":"hide_icons"](),this[this._data.core.themes.stripes?"show_stripes":"hide_stripes"](),this[this._data.core.themes.ellipsis?"show_ellipsis":"hide_ellipsis"]()},this)).on("blur.jstree",".jstree-anchor",a.proxy(function(b){this._data.core.focused=null,a(b.currentTarget).filter(".jstree-hovered").mouseleave(),this.element.attr("tabindex","0")},this)).on("focus.jstree",".jstree-anchor",a.proxy(function(b){var c=this.get_node(b.currentTarget);c&&c.id&&(this._data.core.focused=c.id),this.element.find(".jstree-hovered").not(b.currentTarget).mouseleave(),a(b.currentTarget).mouseenter(),this.element.attr("tabindex","-1")},this)).on("focus.jstree",a.proxy(function(){if(+new Date-d>500&&!this._data.core.focused){d=0;var a=this.get_node(this.element.attr("aria-activedescendant"),!0);a&&a.find("> .jstree-anchor").focus()}},this)).on("mouseenter.jstree",".jstree-anchor",a.proxy(function(a){this.hover_node(a.currentTarget)},this)).on("mouseleave.jstree",".jstree-anchor",a.proxy(function(a){this.dehover_node(a.currentTarget)},this))},unbind:function(){this.element.off(".jstree"),a(i).off(".jstree-"+this._id)},trigger:function(a,b){b||(b={}),b.instance=this,this.element.triggerHandler(a.replace(".jstree","")+".jstree",b)},get_container:function(){return this.element},get_container_ul:function(){return this.element.children(".jstree-children").first()},get_string:function(b){var c=this.settings.core.strings;return a.isFunction(c)?c.call(this,b):c&&c[b]?c[b]:b},_firstChild:function(a){a=a?a.firstChild:null;while(null!==a&&1!==a.nodeType)a=a.nextSibling;return a},_nextSibling:function(a){a=a?a.nextSibling:null;while(null!==a&&1!==a.nodeType)a=a.nextSibling;return a},_previousSibling:function(a){a=a?a.previousSibling:null;while(null!==a&&1!==a.nodeType)a=a.previousSibling;return a},get_node:function(b,c){b&&b.id&&(b=b.id);var d;try{if(this._model.data[b])b=this._model.data[b];else if("string"==typeof b&&this._model.data[b.replace(/^#/,"")])b=this._model.data[b.replace(/^#/,"")];else if("string"==typeof b&&(d=a("#"+b.replace(a.jstree.idregex,"\\$&"),this.element)).length&&this._model.data[d.closest(".jstree-node").attr("id")])b=this._model.data[d.closest(".jstree-node").attr("id")];else if((d=a(b,this.element)).length&&this._model.data[d.closest(".jstree-node").attr("id")])b=this._model.data[d.closest(".jstree-node").attr("id")];else{if(!(d=a(b,this.element)).length||!d.hasClass("jstree"))return!1;b=this._model.data[a.jstree.root]}return c&&(b=b.id===a.jstree.root?this.element:a("#"+b.id.replace(a.jstree.idregex,"\\$&"),this.element)),b}catch(e){return!1}},get_path:function(b,c,d){if(b=b.parents?b:this.get_node(b),!b||b.id===a.jstree.root||!b.parents)return!1;var e,f,g=[];for(g.push(d?b.id:b.text),e=0,f=b.parents.length;f>e;e++)g.push(d?b.parents[e]:this.get_text(b.parents[e]));return g=g.reverse().slice(1),c?g.join(c):g},get_next_dom:function(b,c){var d;if(b=this.get_node(b,!0),b[0]===this.element[0]){d=this._firstChild(this.get_container_ul()[0]);while(d&&0===d.offsetHeight)d=this._nextSibling(d);return d?a(d):!1}if(!b||!b.length)return!1;if(c){d=b[0];do d=this._nextSibling(d);while(d&&0===d.offsetHeight);return d?a(d):!1}if(b.hasClass("jstree-open")){d=this._firstChild(b.children(".jstree-children")[0]);while(d&&0===d.offsetHeight)d=this._nextSibling(d);if(null!==d)return a(d)}d=b[0];do d=this._nextSibling(d);while(d&&0===d.offsetHeight);return null!==d?a(d):b.parentsUntil(".jstree",".jstree-node").nextAll(".jstree-node:visible").first()},get_prev_dom:function(b,c){var d;if(b=this.get_node(b,!0),b[0]===this.element[0]){d=this.get_container_ul()[0].lastChild;while(d&&0===d.offsetHeight)d=this._previousSibling(d);return d?a(d):!1}if(!b||!b.length)return!1;if(c){d=b[0];do d=this._previousSibling(d);while(d&&0===d.offsetHeight);return d?a(d):!1}d=b[0];do d=this._previousSibling(d);while(d&&0===d.offsetHeight);if(null!==d){b=a(d);while(b.hasClass("jstree-open"))b=b.children(".jstree-children").first().children(".jstree-node:visible:last");return b}return d=b[0].parentNode.parentNode,d&&d.className&&-1!==d.className.indexOf("jstree-node")?a(d):!1},get_parent:function(b){return b=this.get_node(b),b&&b.id!==a.jstree.root?b.parent:!1},get_children_dom:function(a){return a=this.get_node(a,!0),a[0]===this.element[0]?this.get_container_ul().children(".jstree-node"):a&&a.length?a.children(".jstree-children").children(".jstree-node"):!1},is_parent:function(a){return a=this.get_node(a),a&&(a.state.loaded===!1||a.children.length>0)},is_loaded:function(a){return a=this.get_node(a),a&&a.state.loaded},is_loading:function(a){return a=this.get_node(a),a&&a.state&&a.state.loading},is_open:function(a){return a=this.get_node(a),a&&a.state.opened},is_closed:function(a){return a=this.get_node(a),a&&this.is_parent(a)&&!a.state.opened},is_leaf:function(a){return!this.is_parent(a)},load_node:function(b,c){var d,e,f,g,h;if(a.isArray(b))return this._load_nodes(b.slice(),c),!0;if(b=this.get_node(b),!b)return c&&c.call(this,b,!1),!1;if(b.state.loaded){for(b.state.loaded=!1,f=0,g=b.parents.length;g>f;f++)this._model.data[b.parents[f]].children_d=a.vakata.array_filter(this._model.data[b.parents[f]].children_d,function(c){return-1===a.inArray(c,b.children_d)});for(d=0,e=b.children_d.length;e>d;d++)this._model.data[b.children_d[d]].state.selected&&(h=!0),delete this._model.data[b.children_d[d]];h&&(this._data.core.selected=a.vakata.array_filter(this._data.core.selected,function(c){return-1===a.inArray(c,b.children_d)})),b.children=[],b.children_d=[],h&&this.trigger("changed",{action:"load_node",node:b,selected:this._data.core.selected})}return b.state.failed=!1,b.state.loading=!0,this.get_node(b,!0).addClass("jstree-loading").attr("aria-busy",!0),this._load_node(b,a.proxy(function(a){b=this._model.data[b.id],b.state.loading=!1,b.state.loaded=a,b.state.failed=!b.state.loaded;var d=this.get_node(b,!0),e=0,f=0,g=this._model.data,h=!1;for(e=0,f=b.children.length;f>e;e++)if(g[b.children[e]]&&!g[b.children[e]].state.hidden){h=!0;break}b.state.loaded&&d&&d.length&&(d.removeClass("jstree-closed jstree-open jstree-leaf"),h?"#"!==b.id&&d.addClass(b.state.opened?"jstree-open":"jstree-closed"):d.addClass("jstree-leaf")),d.removeClass("jstree-loading").attr("aria-busy",!1),this.trigger("load_node",{node:b,status:a}),c&&c.call(this,b,a)},this)),!0},_load_nodes:function(a,b,c,d){var e=!0,f=function(){this._load_nodes(a,b,!0)},g=this._model.data,h,i,j=[];for(h=0,i=a.length;i>h;h++)g[a[h]]&&(!g[a[h]].state.loaded&&!g[a[h]].state.failed||!c&&d)&&(this.is_loading(a[h])||this.load_node(a[h],f),e=!1);if(e){for(h=0,i=a.length;i>h;h++)g[a[h]]&&g[a[h]].state.loaded&&j.push(a[h]);b&&!b.done&&(b.call(this,j),b.done=!0)}},load_all:function(b,c){if(b||(b=a.jstree.root),b=this.get_node(b),!b)return!1;var d=[],e=this._model.data,f=e[b.id].children_d,g,h;for(b.state&&!b.state.loaded&&d.push(b.id),g=0,h=f.length;h>g;g++)e[f[g]]&&e[f[g]].state&&!e[f[g]].state.loaded&&d.push(f[g]);d.length?this._load_nodes(d,function(){this.load_all(b,c)}):(c&&c.call(this,b),this.trigger("load_all",{node:b}))},_load_node:function(b,c){var d=this.settings.core.data,e,f=function g(){return 3!==this.nodeType&&8!==this.nodeType};return d?a.isFunction(d)?d.call(this,b,a.proxy(function(d){d===!1?c.call(this,!1):this["string"==typeof d?"_append_html_data":"_append_json_data"](b,"string"==typeof d?a(a.parseHTML(d)).filter(f):d,function(a){c.call(this,a)})},this)):"object"==typeof d?d.url?(d=a.extend(!0,{},d),a.isFunction(d.url)&&(d.url=d.url.call(this,b)),a.isFunction(d.data)&&(d.data=d.data.call(this,b)),a.ajax(d).done(a.proxy(function(d,e,g){var h=g.getResponseHeader("Content-Type");return h&&-1!==h.indexOf("json")||"object"==typeof d?this._append_json_data(b,d,function(a){c.call(this,a)}):h&&-1!==h.indexOf("html")||"string"==typeof d?this._append_html_data(b,a(a.parseHTML(d)).filter(f),function(a){c.call(this,a)}):(this._data.core.last_error={error:"ajax",plugin:"core",id:"core_04",reason:"Could not load node",data:JSON.stringify({id:b.id,xhr:g})},this.settings.core.error.call(this,this._data.core.last_error),c.call(this,!1))},this)).fail(a.proxy(function(a){c.call(this,!1),this._data.core.last_error={error:"ajax",plugin:"core",id:"core_04",reason:"Could not load node",data:JSON.stringify({id:b.id,xhr:a})},this.settings.core.error.call(this,this._data.core.last_error)},this))):(e=a.isArray(d)||a.isPlainObject(d)?JSON.parse(JSON.stringify(d)):d,b.id===a.jstree.root?this._append_json_data(b,e,function(a){c.call(this,a)}):(this._data.core.last_error={error:"nodata",plugin:"core",id:"core_05",reason:"Could not load node",data:JSON.stringify({id:b.id})},this.settings.core.error.call(this,this._data.core.last_error),c.call(this,!1))):"string"==typeof d?b.id===a.jstree.root?this._append_html_data(b,a(a.parseHTML(d)).filter(f),function(a){c.call(this,a)}):(this._data.core.last_error={error:"nodata",plugin:"core",id:"core_06",reason:"Could not load node",data:JSON.stringify({id:b.id})},this.settings.core.error.call(this,this._data.core.last_error),c.call(this,!1)):c.call(this,!1):b.id===a.jstree.root?this._append_html_data(b,this._data.core.original_container_html.clone(!0),function(a){c.call(this,a)}):c.call(this,!1)},_node_changed:function(a){a=this.get_node(a),a&&this._model.changed.push(a.id)},_append_html_data:function(b,c,d){b=this.get_node(b),b.children=[],b.children_d=[];var e=c.is("ul")?c.children():c,f=b.id,g=[],h=[],i=this._model.data,j=i[f],k=this._data.core.selected.length,l,m,n;for(e.each(a.proxy(function(b,c){l=this._parse_model_from_html(a(c),f,j.parents.concat()),l&&(g.push(l),h.push(l),i[l].children_d.length&&(h=h.concat(i[l].children_d)))},this)),j.children=g,j.children_d=h,m=0,n=j.parents.length;n>m;m++)i[j.parents[m]].children_d=i[j.parents[m]].children_d.concat(h);this.trigger("model",{nodes:h,parent:f}),f!==a.jstree.root?(this._node_changed(f),this.redraw()):(this.get_container_ul().children(".jstree-initial-node").remove(),this.redraw(!0)),this._data.core.selected.length!==k&&this.trigger("changed",{action:"model",selected:this._data.core.selected}),d.call(this,!0)},_append_json_data:function(b,c,d,e){if(null!==this.element){b=this.get_node(b),b.children=[],b.children_d=[],c.d&&(c=c.d,"string"==typeof c&&(c=JSON.parse(c))),a.isArray(c)||(c=[c]);var f=null,g={df:this._model.default_state,dat:c,par:b.id,m:this._model.data,t_id:this._id,t_cnt:this._cnt,sel:this._data.core.selected},h=function(a,b){a.data&&(a=a.data);var c=a.dat,d=a.par,e=[],f=[],g=[],h=a.df,i=a.t_id,j=a.t_cnt,k=a.m,l=k[d],m=a.sel,n,o,p,q,r=function(a,c,d){d=d?d.concat():[],c&&d.unshift(c);var e=a.id.toString(),f,i,j,l,m={id:e,text:a.text||"",icon:a.icon!==b?a.icon:!0,parent:c,parents:d,children:a.children||[],children_d:a.children_d||[],data:a.data,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1};for(f in h)h.hasOwnProperty(f)&&(m.state[f]=h[f]);if(a&&a.data&&a.data.jstree&&a.data.jstree.icon&&(m.icon=a.data.jstree.icon),(m.icon===b||null===m.icon||""===m.icon)&&(m.icon=!0),a&&a.data&&(m.data=a.data,a.data.jstree))for(f in a.data.jstree)a.data.jstree.hasOwnProperty(f)&&(m.state[f]=a.data.jstree[f]);if(a&&"object"==typeof a.state)for(f in a.state)a.state.hasOwnProperty(f)&&(m.state[f]=a.state[f]);if(a&&"object"==typeof a.li_attr)for(f in a.li_attr)a.li_attr.hasOwnProperty(f)&&(m.li_attr[f]=a.li_attr[f]);if(m.li_attr.id||(m.li_attr.id=e),a&&"object"==typeof a.a_attr)for(f in a.a_attr)a.a_attr.hasOwnProperty(f)&&(m.a_attr[f]=a.a_attr[f]);for(a&&a.children&&a.children===!0&&(m.state.loaded=!1,m.children=[],m.children_d=[]),k[m.id]=m,f=0,i=m.children.length;i>f;f++)j=r(k[m.children[f]],m.id,d),l=k[j],m.children_d.push(j),l.children_d.length&&(m.children_d=m.children_d.concat(l.children_d));return delete a.data,delete a.children,k[m.id].original=a,m.state.selected&&g.push(m.id),m.id},s=function(a,c,d){d=d?d.concat():[],c&&d.unshift(c);var e=!1,f,l,m,n,o;do e="j"+i+"_"+ ++j;while(k[e]);o={id:!1,text:"string"==typeof a?a:"",icon:"object"==typeof a&&a.icon!==b?a.icon:!0,parent:c,parents:d,children:[],children_d:[],data:null,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1};for(f in h)h.hasOwnProperty(f)&&(o.state[f]=h[f]);if(a&&a.id&&(o.id=a.id.toString()),a&&a.text&&(o.text=a.text),a&&a.data&&a.data.jstree&&a.data.jstree.icon&&(o.icon=a.data.jstree.icon),(o.icon===b||null===o.icon||""===o.icon)&&(o.icon=!0),a&&a.data&&(o.data=a.data,a.data.jstree))for(f in a.data.jstree)a.data.jstree.hasOwnProperty(f)&&(o.state[f]=a.data.jstree[f]);if(a&&"object"==typeof a.state)for(f in a.state)a.state.hasOwnProperty(f)&&(o.state[f]=a.state[f]);if(a&&"object"==typeof a.li_attr)for(f in a.li_attr)a.li_attr.hasOwnProperty(f)&&(o.li_attr[f]=a.li_attr[f]);if(o.li_attr.id&&!o.id&&(o.id=o.li_attr.id.toString()),o.id||(o.id=e),o.li_attr.id||(o.li_attr.id=o.id),a&&"object"==typeof a.a_attr)for(f in a.a_attr)a.a_attr.hasOwnProperty(f)&&(o.a_attr[f]=a.a_attr[f]);if(a&&a.children&&a.children.length){for(f=0,l=a.children.length;l>f;f++)m=s(a.children[f],o.id,d),n=k[m],o.children.push(m),n.children_d.length&&(o.children_d=o.children_d.concat(n.children_d));o.children_d=o.children_d.concat(o.children)}return a&&a.children&&a.children===!0&&(o.state.loaded=!1,o.children=[],o.children_d=[]),delete a.data,delete a.children,o.original=a,k[o.id]=o,o.state.selected&&g.push(o.id),o.id};if(c.length&&c[0].id!==b&&c[0].parent!==b){for(o=0,p=c.length;p>o;o++)c[o].children||(c[o].children=[]),k[c[o].id.toString()]=c[o];for(o=0,p=c.length;p>o;o++)k[c[o].parent.toString()].children.push(c[o].id.toString()),l.children_d.push(c[o].id.toString());for(o=0,p=l.children.length;p>o;o++)n=r(k[l.children[o]],d,l.parents.concat()),f.push(n),k[n].children_d.length&&(f=f.concat(k[n].children_d));for(o=0,p=l.parents.length;p>o;o++)k[l.parents[o]].children_d=k[l.parents[o]].children_d.concat(f);q={cnt:j,mod:k,sel:m,par:d,dpc:f,add:g}}else{for(o=0,p=c.length;p>o;o++)n=s(c[o],d,l.parents.concat()),n&&(e.push(n),f.push(n),k[n].children_d.length&&(f=f.concat(k[n].children_d)));for(l.children=e,l.children_d=f,o=0,p=l.parents.length;p>o;o++)k[l.parents[o]].children_d=k[l.parents[o]].children_d.concat(f);q={cnt:j,mod:k,sel:m,par:d,dpc:f,add:g}}return"undefined"!=typeof window&&"undefined"!=typeof window.document?q:void postMessage(q)},i=function(b,c){if(null!==this.element){this._cnt=b.cnt;var e,f=this._model.data;for(e in f)f.hasOwnProperty(e)&&f[e].state&&f[e].state.loading&&b.mod[e]&&(b.mod[e].state.loading=!0);if(this._model.data=b.mod,c){var g,h=b.add,i=b.sel,j=this._data.core.selected.slice();if(f=this._model.data,i.length!==j.length||a.vakata.array_unique(i.concat(j)).length!==i.length){for(e=0,g=i.length;g>e;e++)-1===a.inArray(i[e],h)&&-1===a.inArray(i[e],j)&&(f[i[e]].state.selected=!1);for(e=0,g=j.length;g>e;e++)-1===a.inArray(j[e],i)&&(f[j[e]].state.selected=!0)}}b.add.length&&(this._data.core.selected=this._data.core.selected.concat(b.add)),this.trigger("model",{nodes:b.dpc,parent:b.par}),b.par!==a.jstree.root?(this._node_changed(b.par),this.redraw()):this.redraw(!0),b.add.length&&this.trigger("changed",{action:"model",selected:this._data.core.selected}),d.call(this,!0)}};if(this.settings.core.worker&&window.Blob&&window.URL&&window.Worker)try{null===this._wrk&&(this._wrk=window.URL.createObjectURL(new window.Blob(["self.onmessage = "+h.toString()],{type:"text/javascript"}))),!this._data.core.working||e?(this._data.core.working=!0,f=new window.Worker(this._wrk),f.onmessage=a.proxy(function(a){i.call(this,a.data,!0);try{f.terminate(),f=null}catch(b){}this._data.core.worker_queue.length?this._append_json_data.apply(this,this._data.core.worker_queue.shift()):this._data.core.working=!1},this),g.par?f.postMessage(g):this._data.core.worker_queue.length?this._append_json_data.apply(this,this._data.core.worker_queue.shift()):this._data.core.working=!1):this._data.core.worker_queue.push([b,c,d,!0])}catch(j){i.call(this,h(g),!1),this._data.core.worker_queue.length?this._append_json_data.apply(this,this._data.core.worker_queue.shift()):this._data.core.working=!1}else i.call(this,h(g),!1)}},_parse_model_from_html:function(c,d,e){e=e?[].concat(e):[],d&&e.unshift(d);var f,g,h=this._model.data,i={id:!1,text:!1,icon:!0,parent:d,parents:e,children:[],children_d:[],data:null,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1},j,k,l;for(j in this._model.default_state)this._model.default_state.hasOwnProperty(j)&&(i.state[j]=this._model.default_state[j]);if(k=a.vakata.attributes(c,!0),a.each(k,function(b,c){return c=a.trim(c),c.length?(i.li_attr[b]=c,void("id"===b&&(i.id=c.toString()))):!0}),k=c.children("a").first(),k.length&&(k=a.vakata.attributes(k,!0),a.each(k,function(b,c){c=a.trim(c),c.length&&(i.a_attr[b]=c)})),k=c.children("a").first().length?c.children("a").first().clone():c.clone(),k.children("ins, i, ul").remove(),k=k.html(),k=a("<div />").html(k),i.text=this.settings.core.force_text?k.text():k.html(),k=c.data(),i.data=k?a.extend(!0,{},k):null,i.state.opened=c.hasClass("jstree-open"),i.state.selected=c.children("a").hasClass("jstree-clicked"),i.state.disabled=c.children("a").hasClass("jstree-disabled"),i.data&&i.data.jstree)for(j in i.data.jstree)i.data.jstree.hasOwnProperty(j)&&(i.state[j]=i.data.jstree[j]);k=c.children("a").children(".jstree-themeicon"),k.length&&(i.icon=k.hasClass("jstree-themeicon-hidden")?!1:k.attr("rel")),i.state.icon!==b&&(i.icon=i.state.icon),(i.icon===b||null===i.icon||""===i.icon)&&(i.icon=!0),k=c.children("ul").children("li");do l="j"+this._id+"_"+ ++this._cnt;while(h[l]);return i.id=i.li_attr.id?i.li_attr.id.toString():l,k.length?(k.each(a.proxy(function(b,c){f=this._parse_model_from_html(a(c),i.id,e),g=this._model.data[f],i.children.push(f),g.children_d.length&&(i.children_d=i.children_d.concat(g.children_d))},this)),i.children_d=i.children_d.concat(i.children)):c.hasClass("jstree-closed")&&(i.state.loaded=!1),i.li_attr["class"]&&(i.li_attr["class"]=i.li_attr["class"].replace("jstree-closed","").replace("jstree-open","")),i.a_attr["class"]&&(i.a_attr["class"]=i.a_attr["class"].replace("jstree-clicked","").replace("jstree-disabled","")),h[i.id]=i,i.state.selected&&this._data.core.selected.push(i.id),i.id},_parse_model_from_flat_json:function(a,c,d){d=d?d.concat():[],c&&d.unshift(c);var e=a.id.toString(),f=this._model.data,g=this._model.default_state,h,i,j,k,l={id:e,text:a.text||"",icon:a.icon!==b?a.icon:!0,parent:c,parents:d,children:a.children||[],children_d:a.children_d||[],data:a.data,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1};for(h in g)g.hasOwnProperty(h)&&(l.state[h]=g[h]);if(a&&a.data&&a.data.jstree&&a.data.jstree.icon&&(l.icon=a.data.jstree.icon),(l.icon===b||null===l.icon||""===l.icon)&&(l.icon=!0),a&&a.data&&(l.data=a.data,a.data.jstree))for(h in a.data.jstree)a.data.jstree.hasOwnProperty(h)&&(l.state[h]=a.data.jstree[h]);if(a&&"object"==typeof a.state)for(h in a.state)a.state.hasOwnProperty(h)&&(l.state[h]=a.state[h]);if(a&&"object"==typeof a.li_attr)for(h in a.li_attr)a.li_attr.hasOwnProperty(h)&&(l.li_attr[h]=a.li_attr[h]);if(l.li_attr.id||(l.li_attr.id=e),a&&"object"==typeof a.a_attr)for(h in a.a_attr)a.a_attr.hasOwnProperty(h)&&(l.a_attr[h]=a.a_attr[h]);for(a&&a.children&&a.children===!0&&(l.state.loaded=!1,l.children=[],l.children_d=[]),f[l.id]=l,h=0,i=l.children.length;i>h;h++)j=this._parse_model_from_flat_json(f[l.children[h]],l.id,d),k=f[j],l.children_d.push(j),k.children_d.length&&(l.children_d=l.children_d.concat(k.children_d));return delete a.data,delete a.children,f[l.id].original=a,l.state.selected&&this._data.core.selected.push(l.id),l.id},_parse_model_from_json:function(a,c,d){d=d?d.concat():[],c&&d.unshift(c);var e=!1,f,g,h,i,j=this._model.data,k=this._model.default_state,l;do e="j"+this._id+"_"+ ++this._cnt;while(j[e]);l={id:!1,text:"string"==typeof a?a:"",icon:"object"==typeof a&&a.icon!==b?a.icon:!0,parent:c,parents:d,children:[],children_d:[],data:null,state:{},li_attr:{id:!1},a_attr:{href:"#"},original:!1};for(f in k)k.hasOwnProperty(f)&&(l.state[f]=k[f]);if(a&&a.id&&(l.id=a.id.toString()),a&&a.text&&(l.text=a.text),a&&a.data&&a.data.jstree&&a.data.jstree.icon&&(l.icon=a.data.jstree.icon),(l.icon===b||null===l.icon||""===l.icon)&&(l.icon=!0),a&&a.data&&(l.data=a.data,a.data.jstree))for(f in a.data.jstree)a.data.jstree.hasOwnProperty(f)&&(l.state[f]=a.data.jstree[f]);if(a&&"object"==typeof a.state)for(f in a.state)a.state.hasOwnProperty(f)&&(l.state[f]=a.state[f]);if(a&&"object"==typeof a.li_attr)for(f in a.li_attr)a.li_attr.hasOwnProperty(f)&&(l.li_attr[f]=a.li_attr[f]);if(l.li_attr.id&&!l.id&&(l.id=l.li_attr.id.toString()),l.id||(l.id=e),l.li_attr.id||(l.li_attr.id=l.id),a&&"object"==typeof a.a_attr)for(f in a.a_attr)a.a_attr.hasOwnProperty(f)&&(l.a_attr[f]=a.a_attr[f]);if(a&&a.children&&a.children.length){for(f=0,g=a.children.length;g>f;f++)h=this._parse_model_from_json(a.children[f],l.id,d),i=j[h],l.children.push(h),i.children_d.length&&(l.children_d=l.children_d.concat(i.children_d));l.children_d=l.children_d.concat(l.children)}return a&&a.children&&a.children===!0&&(l.state.loaded=!1,l.children=[],l.children_d=[]),delete a.data,delete a.children,l.original=a,j[l.id]=l,l.state.selected&&this._data.core.selected.push(l.id),l.id},_redraw:function(){var b=this._model.force_full_redraw?this._model.data[a.jstree.root].children.concat([]):this._model.changed.concat([]),c=i.createElement("UL"),d,e,f,g=this._data.core.focused;for(e=0,f=b.length;f>e;e++)d=this.redraw_node(b[e],!0,this._model.force_full_redraw),d&&this._model.force_full_redraw&&c.appendChild(d);this._model.force_full_redraw&&(c.className=this.get_container_ul()[0].className,c.setAttribute("role","group"),this.element.empty().append(c)),null!==g&&(d=this.get_node(g,!0),d&&d.length&&d.children(".jstree-anchor")[0]!==i.activeElement?d.children(".jstree-anchor").focus():this._data.core.focused=null),this._model.force_full_redraw=!1,this._model.changed=[],this.trigger("redraw",{nodes:b})},redraw:function(a){a&&(this._model.force_full_redraw=!0),this._redraw()},draw_children:function(b){var c=this.get_node(b),d=!1,e=!1,f=!1,g=i;if(!c)return!1;if(c.id===a.jstree.root)return this.redraw(!0);if(b=this.get_node(b,!0),!b||!b.length)return!1;if(b.children(".jstree-children").remove(),b=b[0],c.children.length&&c.state.loaded){for(f=g.createElement("UL"),f.setAttribute("role","group"),f.className="jstree-children",d=0,e=c.children.length;e>d;d++)f.appendChild(this.redraw_node(c.children[d],!0,!0));b.appendChild(f)}},redraw_node:function(b,c,d,e){var f=this.get_node(b),g=!1,h=!1,j=!1,k=!1,l=!1,m=!1,n="",o=i,p=this._model.data,q=!1,r=!1,s=null,t=0,u=0,v=!1,w=!1;
if(!f)return!1;if(f.id===a.jstree.root)return this.redraw(!0);if(c=c||0===f.children.length,b=i.querySelector?this.element[0].querySelector("#"+(-1!=="0123456789".indexOf(f.id[0])?"\\3"+f.id[0]+" "+f.id.substr(1).replace(a.jstree.idregex,"\\$&"):f.id.replace(a.jstree.idregex,"\\$&"))):i.getElementById(f.id))b=a(b),d||(g=b.parent().parent()[0],g===this.element[0]&&(g=null),h=b.index()),c||!f.children.length||b.children(".jstree-children").length||(c=!0),c||(j=b.children(".jstree-children")[0]),q=b.children(".jstree-anchor")[0]===i.activeElement,b.remove();else if(c=!0,!d){if(g=f.parent!==a.jstree.root?a("#"+f.parent.replace(a.jstree.idregex,"\\$&"),this.element)[0]:null,!(null===g||g&&p[f.parent].state.opened))return!1;h=a.inArray(f.id,null===g?p[a.jstree.root].children:p[f.parent].children)}b=this._data.core.node.cloneNode(!0),n="jstree-node ";for(k in f.li_attr)if(f.li_attr.hasOwnProperty(k)){if("id"===k)continue;"class"!==k?b.setAttribute(k,f.li_attr[k]):n+=f.li_attr[k]}for(f.a_attr.id||(f.a_attr.id=f.id+"_anchor"),b.setAttribute("aria-selected",!!f.state.selected),b.setAttribute("aria-level",f.parents.length),b.setAttribute("aria-labelledby",f.a_attr.id),f.state.disabled&&b.setAttribute("aria-disabled",!0),k=0,l=f.children.length;l>k;k++)if(!p[f.children[k]].state.hidden){v=!0;break}if(null!==f.parent&&p[f.parent]&&!f.state.hidden&&(k=a.inArray(f.id,p[f.parent].children),w=f.id,-1!==k))for(k++,l=p[f.parent].children.length;l>k;k++)if(p[p[f.parent].children[k]].state.hidden||(w=p[f.parent].children[k]),w!==f.id)break;f.state.hidden&&(n+=" jstree-hidden"),f.state.loaded&&!v?n+=" jstree-leaf":(n+=f.state.opened&&f.state.loaded?" jstree-open":" jstree-closed",b.setAttribute("aria-expanded",f.state.opened&&f.state.loaded)),w===f.id&&(n+=" jstree-last"),b.id=f.id,b.className=n,n=(f.state.selected?" jstree-clicked":"")+(f.state.disabled?" jstree-disabled":"");for(l in f.a_attr)if(f.a_attr.hasOwnProperty(l)){if("href"===l&&"#"===f.a_attr[l])continue;"class"!==l?b.childNodes[1].setAttribute(l,f.a_attr[l]):n+=" "+f.a_attr[l]}if(n.length&&(b.childNodes[1].className="jstree-anchor "+n),(f.icon&&f.icon!==!0||f.icon===!1)&&(f.icon===!1?b.childNodes[1].childNodes[0].className+=" jstree-themeicon-hidden":-1===f.icon.indexOf("/")&&-1===f.icon.indexOf(".")?b.childNodes[1].childNodes[0].className+=" "+f.icon+" jstree-themeicon-custom":(b.childNodes[1].childNodes[0].style.backgroundImage='url("'+f.icon+'")',b.childNodes[1].childNodes[0].style.backgroundPosition="center center",b.childNodes[1].childNodes[0].style.backgroundSize="auto",b.childNodes[1].childNodes[0].className+=" jstree-themeicon-custom")),this.settings.core.force_text?b.childNodes[1].appendChild(o.createTextNode(f.text)):b.childNodes[1].innerHTML+=f.text,c&&f.children.length&&(f.state.opened||e)&&f.state.loaded){for(m=o.createElement("UL"),m.setAttribute("role","group"),m.className="jstree-children",k=0,l=f.children.length;l>k;k++)m.appendChild(this.redraw_node(f.children[k],c,!0));b.appendChild(m)}if(j&&b.appendChild(j),!d){for(g||(g=this.element[0]),k=0,l=g.childNodes.length;l>k;k++)if(g.childNodes[k]&&g.childNodes[k].className&&-1!==g.childNodes[k].className.indexOf("jstree-children")){s=g.childNodes[k];break}s||(s=o.createElement("UL"),s.setAttribute("role","group"),s.className="jstree-children",g.appendChild(s)),g=s,h<g.childNodes.length?g.insertBefore(b,g.childNodes[h]):g.appendChild(b),q&&(t=this.element[0].scrollTop,u=this.element[0].scrollLeft,b.childNodes[1].focus(),this.element[0].scrollTop=t,this.element[0].scrollLeft=u)}return f.state.opened&&!f.state.loaded&&(f.state.opened=!1,setTimeout(a.proxy(function(){this.open_node(f.id,!1,0)},this),0)),b},open_node:function(c,d,e){var f,g,h,i;if(a.isArray(c)){for(c=c.slice(),f=0,g=c.length;g>f;f++)this.open_node(c[f],d,e);return!0}return c=this.get_node(c),c&&c.id!==a.jstree.root?(e=e===b?this.settings.core.animation:e,this.is_closed(c)?this.is_loaded(c)?(h=this.get_node(c,!0),i=this,h.length&&(e&&h.children(".jstree-children").length&&h.children(".jstree-children").stop(!0,!0),c.children.length&&!this._firstChild(h.children(".jstree-children")[0])&&this.draw_children(c),e?(this.trigger("before_open",{node:c}),h.children(".jstree-children").css("display","none").end().removeClass("jstree-closed").addClass("jstree-open").attr("aria-expanded",!0).children(".jstree-children").stop(!0,!0).slideDown(e,function(){this.style.display="",i.element&&i.trigger("after_open",{node:c})})):(this.trigger("before_open",{node:c}),h[0].className=h[0].className.replace("jstree-closed","jstree-open"),h[0].setAttribute("aria-expanded",!0))),c.state.opened=!0,d&&d.call(this,c,!0),h.length||this.trigger("before_open",{node:c}),this.trigger("open_node",{node:c}),e&&h.length||this.trigger("after_open",{node:c}),!0):this.is_loading(c)?setTimeout(a.proxy(function(){this.open_node(c,d,e)},this),500):void this.load_node(c,function(a,b){return b?this.open_node(a,d,e):d?d.call(this,a,!1):!1}):(d&&d.call(this,c,!1),!1)):!1},_open_to:function(b){if(b=this.get_node(b),!b||b.id===a.jstree.root)return!1;var c,d,e=b.parents;for(c=0,d=e.length;d>c;c+=1)c!==a.jstree.root&&this.open_node(e[c],!1,0);return a("#"+b.id.replace(a.jstree.idregex,"\\$&"),this.element)},close_node:function(c,d){var e,f,g,h;if(a.isArray(c)){for(c=c.slice(),e=0,f=c.length;f>e;e++)this.close_node(c[e],d);return!0}return c=this.get_node(c),c&&c.id!==a.jstree.root?this.is_closed(c)?!1:(d=d===b?this.settings.core.animation:d,g=this,h=this.get_node(c,!0),c.state.opened=!1,this.trigger("close_node",{node:c}),void(h.length?d?h.children(".jstree-children").attr("style","display:block !important").end().removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded",!1).children(".jstree-children").stop(!0,!0).slideUp(d,function(){this.style.display="",h.children(".jstree-children").remove(),g.element&&g.trigger("after_close",{node:c})}):(h[0].className=h[0].className.replace("jstree-open","jstree-closed"),h.attr("aria-expanded",!1).children(".jstree-children").remove(),this.trigger("after_close",{node:c})):this.trigger("after_close",{node:c}))):!1},toggle_node:function(b){var c,d;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.toggle_node(b[c]);return!0}return this.is_closed(b)?this.open_node(b):this.is_open(b)?this.close_node(b):void 0},open_all:function(b,c,d){if(b||(b=a.jstree.root),b=this.get_node(b),!b)return!1;var e=b.id===a.jstree.root?this.get_container_ul():this.get_node(b,!0),f,g,h;if(!e.length){for(f=0,g=b.children_d.length;g>f;f++)this.is_closed(this._model.data[b.children_d[f]])&&(this._model.data[b.children_d[f]].state.opened=!0);return this.trigger("open_all",{node:b})}d=d||e,h=this,e=this.is_closed(b)?e.find(".jstree-closed").addBack():e.find(".jstree-closed"),e.each(function(){h.open_node(this,function(a,b){b&&this.is_parent(a)&&this.open_all(a,c,d)},c||0)}),0===d.find(".jstree-closed").length&&this.trigger("open_all",{node:this.get_node(d)})},close_all:function(b,c){if(b||(b=a.jstree.root),b=this.get_node(b),!b)return!1;var d=b.id===a.jstree.root?this.get_container_ul():this.get_node(b,!0),e=this,f,g;for(d.length&&(d=this.is_open(b)?d.find(".jstree-open").addBack():d.find(".jstree-open"),a(d.get().reverse()).each(function(){e.close_node(this,c||0)})),f=0,g=b.children_d.length;g>f;f++)this._model.data[b.children_d[f]].state.opened=!1;this.trigger("close_all",{node:b})},is_disabled:function(a){return a=this.get_node(a),a&&a.state&&a.state.disabled},enable_node:function(b){var c,d;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.enable_node(b[c]);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(b.state.disabled=!1,this.get_node(b,!0).children(".jstree-anchor").removeClass("jstree-disabled").attr("aria-disabled",!1),void this.trigger("enable_node",{node:b})):!1},disable_node:function(b){var c,d;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.disable_node(b[c]);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(b.state.disabled=!0,this.get_node(b,!0).children(".jstree-anchor").addClass("jstree-disabled").attr("aria-disabled",!0),void this.trigger("disable_node",{node:b})):!1},is_hidden:function(a){return a=this.get_node(a),a.state.hidden===!0},hide_node:function(b,c){var d,e;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.hide_node(b[d],!0);return c||this.redraw(),!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?void(b.state.hidden||(b.state.hidden=!0,this._node_changed(b.parent),c||this.redraw(),this.trigger("hide_node",{node:b}))):!1},show_node:function(b,c){var d,e;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.show_node(b[d],!0);return c||this.redraw(),!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?void(b.state.hidden&&(b.state.hidden=!1,this._node_changed(b.parent),c||this.redraw(),this.trigger("show_node",{node:b}))):!1},hide_all:function(b){var c,d=this._model.data,e=[];for(c in d)d.hasOwnProperty(c)&&c!==a.jstree.root&&!d[c].state.hidden&&(d[c].state.hidden=!0,e.push(c));return this._model.force_full_redraw=!0,b||this.redraw(),this.trigger("hide_all",{nodes:e}),e},show_all:function(b){var c,d=this._model.data,e=[];for(c in d)d.hasOwnProperty(c)&&c!==a.jstree.root&&d[c].state.hidden&&(d[c].state.hidden=!1,e.push(c));return this._model.force_full_redraw=!0,b||this.redraw(),this.trigger("show_all",{nodes:e}),e},activate_node:function(a,c){if(this.is_disabled(a))return!1;if(c&&"object"==typeof c||(c={}),this._data.core.last_clicked=this._data.core.last_clicked&&this._data.core.last_clicked.id!==b?this.get_node(this._data.core.last_clicked.id):null,this._data.core.last_clicked&&!this._data.core.last_clicked.state.selected&&(this._data.core.last_clicked=null),!this._data.core.last_clicked&&this._data.core.selected.length&&(this._data.core.last_clicked=this.get_node(this._data.core.selected[this._data.core.selected.length-1])),this.settings.core.multiple&&(c.metaKey||c.ctrlKey||c.shiftKey)&&(!c.shiftKey||this._data.core.last_clicked&&this.get_parent(a)&&this.get_parent(a)===this._data.core.last_clicked.parent))if(c.shiftKey){var d=this.get_node(a).id,e=this._data.core.last_clicked.id,f=this.get_node(this._data.core.last_clicked.parent).children,g=!1,h,i;for(h=0,i=f.length;i>h;h+=1)f[h]===d&&(g=!g),f[h]===e&&(g=!g),this.is_disabled(f[h])||!g&&f[h]!==d&&f[h]!==e?this.deselect_node(f[h],!0,c):this.is_hidden(f[h])||this.select_node(f[h],!0,!1,c);this.trigger("changed",{action:"select_node",node:this.get_node(a),selected:this._data.core.selected,event:c})}else this.is_selected(a)?this.deselect_node(a,!1,c):this.select_node(a,!1,!1,c);else!this.settings.core.multiple&&(c.metaKey||c.ctrlKey||c.shiftKey)&&this.is_selected(a)?this.deselect_node(a,!1,c):(this.deselect_all(!0),this.select_node(a,!1,!1,c),this._data.core.last_clicked=this.get_node(a));this.trigger("activate_node",{node:this.get_node(a),event:c})},hover_node:function(a){if(a=this.get_node(a,!0),!a||!a.length||a.children(".jstree-hovered").length)return!1;var b=this.element.find(".jstree-hovered"),c=this.element;b&&b.length&&this.dehover_node(b),a.children(".jstree-anchor").addClass("jstree-hovered"),this.trigger("hover_node",{node:this.get_node(a)}),setTimeout(function(){c.attr("aria-activedescendant",a[0].id)},0)},dehover_node:function(a){return a=this.get_node(a,!0),a&&a.length&&a.children(".jstree-hovered").length?(a.children(".jstree-anchor").removeClass("jstree-hovered"),void this.trigger("dehover_node",{node:this.get_node(a)})):!1},select_node:function(b,c,d,e){var f,g,h,i;if(a.isArray(b)){for(b=b.slice(),g=0,h=b.length;h>g;g++)this.select_node(b[g],c,d,e);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(f=this.get_node(b,!0),void(b.state.selected||(b.state.selected=!0,this._data.core.selected.push(b.id),d||(f=this._open_to(b)),f&&f.length&&f.attr("aria-selected",!0).children(".jstree-anchor").addClass("jstree-clicked"),this.trigger("select_node",{node:b,selected:this._data.core.selected,event:e}),c||this.trigger("changed",{action:"select_node",node:b,selected:this._data.core.selected,event:e})))):!1},deselect_node:function(b,c,d){var e,f,g;if(a.isArray(b)){for(b=b.slice(),e=0,f=b.length;f>e;e++)this.deselect_node(b[e],c,d);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(g=this.get_node(b,!0),void(b.state.selected&&(b.state.selected=!1,this._data.core.selected=a.vakata.array_remove_item(this._data.core.selected,b.id),g.length&&g.attr("aria-selected",!1).children(".jstree-anchor").removeClass("jstree-clicked"),this.trigger("deselect_node",{node:b,selected:this._data.core.selected,event:d}),c||this.trigger("changed",{action:"deselect_node",node:b,selected:this._data.core.selected,event:d})))):!1},select_all:function(b){var c=this._data.core.selected.concat([]),d,e;for(this._data.core.selected=this._model.data[a.jstree.root].children_d.concat(),d=0,e=this._data.core.selected.length;e>d;d++)this._model.data[this._data.core.selected[d]]&&(this._model.data[this._data.core.selected[d]].state.selected=!0);this.redraw(!0),this.trigger("select_all",{selected:this._data.core.selected}),b||this.trigger("changed",{action:"select_all",selected:this._data.core.selected,old_selection:c})},deselect_all:function(a){var b=this._data.core.selected.concat([]),c,d;for(c=0,d=this._data.core.selected.length;d>c;c++)this._model.data[this._data.core.selected[c]]&&(this._model.data[this._data.core.selected[c]].state.selected=!1);this._data.core.selected=[],this.element.find(".jstree-clicked").removeClass("jstree-clicked").parent().attr("aria-selected",!1),this.trigger("deselect_all",{selected:this._data.core.selected,node:b}),a||this.trigger("changed",{action:"deselect_all",selected:this._data.core.selected,old_selection:b})},is_selected:function(b){return b=this.get_node(b),b&&b.id!==a.jstree.root?b.state.selected:!1},get_selected:function(b){return b?a.map(this._data.core.selected,a.proxy(function(a){return this.get_node(a)},this)):this._data.core.selected.slice()},get_top_selected:function(b){var c=this.get_selected(!0),d={},e,f,g,h;for(e=0,f=c.length;f>e;e++)d[c[e].id]=c[e];for(e=0,f=c.length;f>e;e++)for(g=0,h=c[e].children_d.length;h>g;g++)d[c[e].children_d[g]]&&delete d[c[e].children_d[g]];c=[];for(e in d)d.hasOwnProperty(e)&&c.push(e);return b?a.map(c,a.proxy(function(a){return this.get_node(a)},this)):c},get_bottom_selected:function(b){var c=this.get_selected(!0),d=[],e,f;for(e=0,f=c.length;f>e;e++)c[e].children.length||d.push(c[e].id);return b?a.map(d,a.proxy(function(a){return this.get_node(a)},this)):d},get_state:function(){var b={core:{open:[],scroll:{left:this.element.scrollLeft(),top:this.element.scrollTop()},selected:[]}},c;for(c in this._model.data)this._model.data.hasOwnProperty(c)&&c!==a.jstree.root&&(this._model.data[c].state.opened&&b.core.open.push(c),this._model.data[c].state.selected&&b.core.selected.push(c));return b},set_state:function(c,d){if(c){if(c.core){var e,f,g,h,i;if(c.core.open)return a.isArray(c.core.open)&&c.core.open.length?this._load_nodes(c.core.open,function(a){this.open_node(a,!1,0),delete c.core.open,this.set_state(c,d)}):(delete c.core.open,this.set_state(c,d)),!1;if(c.core.scroll)return c.core.scroll&&c.core.scroll.left!==b&&this.element.scrollLeft(c.core.scroll.left),c.core.scroll&&c.core.scroll.top!==b&&this.element.scrollTop(c.core.scroll.top),delete c.core.scroll,this.set_state(c,d),!1;if(c.core.selected)return h=this,this.deselect_all(),a.each(c.core.selected,function(a,b){h.select_node(b,!1,!0)}),delete c.core.selected,this.set_state(c,d),!1;for(i in c)c.hasOwnProperty(i)&&"core"!==i&&-1===a.inArray(i,this.settings.plugins)&&delete c[i];if(a.isEmptyObject(c.core))return delete c.core,this.set_state(c,d),!1}return a.isEmptyObject(c)?(c=null,d&&d.call(this),this.trigger("set_state"),!1):!0}return!1},refresh:function(b,c){this._data.core.state=c===!0?{}:this.get_state(),c&&a.isFunction(c)&&(this._data.core.state=c.call(this,this._data.core.state)),this._cnt=0,this._model.data={},this._model.data[a.jstree.root]={id:a.jstree.root,parent:null,parents:[],children:[],children_d:[],state:{loaded:!1}},this._data.core.selected=[],this._data.core.last_clicked=null,this._data.core.focused=null;var d=this.get_container_ul()[0].className;b||(this.element.html("<ul class='"+d+"' role='group'><li class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='treeitem' id='j"+this._id+"_loading'><i class='jstree-icon jstree-ocl'></i><a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>"+this.get_string("Loading ...")+"</a></li></ul>"),this.element.attr("aria-activedescendant","j"+this._id+"_loading")),this.load_node(a.jstree.root,function(b,c){c&&(this.get_container_ul()[0].className=d,this._firstChild(this.get_container_ul()[0])&&this.element.attr("aria-activedescendant",this._firstChild(this.get_container_ul()[0]).id),this.set_state(a.extend(!0,{},this._data.core.state),function(){this.trigger("refresh")})),this._data.core.state=null})},refresh_node:function(b){if(b=this.get_node(b),!b||b.id===a.jstree.root)return!1;var c=[],d=[],e=this._data.core.selected.concat([]);d.push(b.id),b.state.opened===!0&&c.push(b.id),this.get_node(b,!0).find(".jstree-open").each(function(){d.push(this.id),c.push(this.id)}),this._load_nodes(d,a.proxy(function(a){this.open_node(c,!1,0),this.select_node(e),this.trigger("refresh_node",{node:b,nodes:a})},this),!1,!0)},set_id:function(b,c){if(b=this.get_node(b),!b||b.id===a.jstree.root)return!1;var d,e,f=this._model.data,g=b.id;for(c=c.toString(),f[b.parent].children[a.inArray(b.id,f[b.parent].children)]=c,d=0,e=b.parents.length;e>d;d++)f[b.parents[d]].children_d[a.inArray(b.id,f[b.parents[d]].children_d)]=c;for(d=0,e=b.children.length;e>d;d++)f[b.children[d]].parent=c;for(d=0,e=b.children_d.length;e>d;d++)f[b.children_d[d]].parents[a.inArray(b.id,f[b.children_d[d]].parents)]=c;return d=a.inArray(b.id,this._data.core.selected),-1!==d&&(this._data.core.selected[d]=c),d=this.get_node(b.id,!0),d&&(d.attr("id",c),this.element.attr("aria-activedescendant")===b.id&&this.element.attr("aria-activedescendant",c)),delete f[b.id],b.id=c,b.li_attr.id=c,f[c]=b,this.trigger("set_id",{node:b,"new":b.id,old:g}),!0},get_text:function(b){return b=this.get_node(b),b&&b.id!==a.jstree.root?b.text:!1},set_text:function(b,c){var d,e;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.set_text(b[d],c);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(b.text=c,this.get_node(b,!0).length&&this.redraw_node(b.id),this.trigger("set_text",{obj:b,text:c}),!0):!1},get_json:function(b,c,d){if(b=this.get_node(b||a.jstree.root),!b)return!1;c&&c.flat&&!d&&(d=[]);var e={id:b.id,text:b.text,icon:this.get_icon(b),li_attr:a.extend(!0,{},b.li_attr),a_attr:a.extend(!0,{},b.a_attr),state:{},data:c&&c.no_data?!1:a.extend(!0,{},b.data)},f,g;if(c&&c.flat?e.parent=b.parent:e.children=[],c&&c.no_state)delete e.state;else for(f in b.state)b.state.hasOwnProperty(f)&&(e.state[f]=b.state[f]);if(c&&c.no_li_attr&&delete e.li_attr,c&&c.no_a_attr&&delete e.a_attr,c&&c.no_id&&(delete e.id,e.li_attr&&e.li_attr.id&&delete e.li_attr.id,e.a_attr&&e.a_attr.id&&delete e.a_attr.id),c&&c.flat&&b.id!==a.jstree.root&&d.push(e),!c||!c.no_children)for(f=0,g=b.children.length;g>f;f++)c&&c.flat?this.get_json(b.children[f],c,d):e.children.push(this.get_json(b.children[f],c));return c&&c.flat?d:b.id===a.jstree.root?e.children:e},create_node:function(c,d,e,f,g){if(null===c&&(c=a.jstree.root),c=this.get_node(c),!c)return!1;if(e=e===b?"last":e,!e.toString().match(/^(before|after)$/)&&!g&&!this.is_loaded(c))return this.load_node(c,function(){this.create_node(c,d,e,f,!0)});d||(d={text:this.get_string("New node")}),"string"==typeof d&&(d={text:d}),d.text===b&&(d.text=this.get_string("New node"));var h,i,j,k;switch(c.id===a.jstree.root&&("before"===e&&(e="first"),"after"===e&&(e="last")),e){case"before":h=this.get_node(c.parent),e=a.inArray(c.id,h.children),c=h;break;case"after":h=this.get_node(c.parent),e=a.inArray(c.id,h.children)+1,c=h;break;case"inside":case"first":e=0;break;case"last":e=c.children.length;break;default:e||(e=0)}if(e>c.children.length&&(e=c.children.length),d.id||(d.id=!0),!this.check("create_node",d,c,e))return this.settings.core.error.call(this,this._data.core.last_error),!1;if(d.id===!0&&delete d.id,d=this._parse_model_from_json(d,c.id,c.parents.concat()),!d)return!1;for(h=this.get_node(d),i=[],i.push(d),i=i.concat(h.children_d),this.trigger("model",{nodes:i,parent:c.id}),c.children_d=c.children_d.concat(i),j=0,k=c.parents.length;k>j;j++)this._model.data[c.parents[j]].children_d=this._model.data[c.parents[j]].children_d.concat(i);for(d=h,h=[],j=0,k=c.children.length;k>j;j++)h[j>=e?j+1:j]=c.children[j];return h[e]=d.id,c.children=h,this.redraw_node(c,!0),f&&f.call(this,this.get_node(d)),this.trigger("create_node",{node:this.get_node(d),parent:c.id,position:e}),d.id},rename_node:function(b,c){var d,e,f;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.rename_node(b[d],c);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(f=b.text,this.check("rename_node",b,this.get_parent(b),c)?(this.set_text(b,c),this.trigger("rename_node",{node:b,text:c,old:f}),!0):(this.settings.core.error.call(this,this._data.core.last_error),!1)):!1},delete_node:function(b){var c,d,e,f,g,h,i,j,k,l,m,n;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.delete_node(b[c]);return!0}if(b=this.get_node(b),!b||b.id===a.jstree.root)return!1;if(e=this.get_node(b.parent),f=a.inArray(b.id,e.children),l=!1,!this.check("delete_node",b,e,f))return this.settings.core.error.call(this,this._data.core.last_error),!1;for(-1!==f&&(e.children=a.vakata.array_remove(e.children,f)),g=b.children_d.concat([]),g.push(b.id),h=0,i=b.parents.length;i>h;h++)this._model.data[b.parents[h]].children_d=a.vakata.array_filter(this._model.data[b.parents[h]].children_d,function(b){return-1===a.inArray(b,g)});for(j=0,k=g.length;k>j;j++)if(this._model.data[g[j]].state.selected){l=!0;break}for(l&&(this._data.core.selected=a.vakata.array_filter(this._data.core.selected,function(b){return-1===a.inArray(b,g)})),this.trigger("delete_node",{node:b,parent:e.id}),l&&this.trigger("changed",{action:"delete_node",node:b,selected:this._data.core.selected,parent:e.id}),j=0,k=g.length;k>j;j++)delete this._model.data[g[j]];return-1!==a.inArray(this._data.core.focused,g)&&(this._data.core.focused=null,m=this.element[0].scrollTop,n=this.element[0].scrollLeft,e.id===a.jstree.root?this._model.data[a.jstree.root].children[0]&&this.get_node(this._model.data[a.jstree.root].children[0],!0).children(".jstree-anchor").focus():this.get_node(e,!0).children(".jstree-anchor").focus(),this.element[0].scrollTop=m,this.element[0].scrollLeft=n),this.redraw_node(e,!0),!0},check:function(b,c,d,e,f){c=c&&c.id?c:this.get_node(c),d=d&&d.id?d:this.get_node(d);var g=b.match(/^move_node|copy_node|create_node$/i)?d:c,h=this.settings.core.check_callback;return"move_node"!==b&&"copy_node"!==b||f&&f.is_multi||c.id!==d.id&&("move_node"!==b||a.inArray(c.id,d.children)!==e)&&-1===a.inArray(d.id,c.children_d)?(g&&g.data&&(g=g.data),g&&g.functions&&(g.functions[b]===!1||g.functions[b]===!0)?(g.functions[b]===!1&&(this._data.core.last_error={error:"check",plugin:"core",id:"core_02",reason:"Node data prevents function: "+b,data:JSON.stringify({chk:b,pos:e,obj:c&&c.id?c.id:!1,par:d&&d.id?d.id:!1})}),g.functions[b]):h===!1||a.isFunction(h)&&h.call(this,b,c,d,e,f)===!1||h&&h[b]===!1?(this._data.core.last_error={error:"check",plugin:"core",id:"core_03",reason:"User config for core.check_callback prevents function: "+b,data:JSON.stringify({chk:b,pos:e,obj:c&&c.id?c.id:!1,par:d&&d.id?d.id:!1})},!1):!0):(this._data.core.last_error={error:"check",plugin:"core",id:"core_01",reason:"Moving parent inside child",data:JSON.stringify({chk:b,pos:e,obj:c&&c.id?c.id:!1,par:d&&d.id?d.id:!1})},!1)},last_error:function(){return this._data.core.last_error},move_node:function(c,d,e,f,g,h,i){var j,k,l,m,n,o,p,q,r,s,t,u,v,w;if(d=this.get_node(d),e=e===b?0:e,!d)return!1;if(!e.toString().match(/^(before|after)$/)&&!g&&!this.is_loaded(d))return this.load_node(d,function(){this.move_node(c,d,e,f,!0,!1,i)});if(a.isArray(c)){if(1!==c.length){for(j=0,k=c.length;k>j;j++)(r=this.move_node(c[j],d,e,f,g,!1,i))&&(d=r,e="after");return this.redraw(),!0}c=c[0]}if(c=c&&c.id?c:this.get_node(c),!c||c.id===a.jstree.root)return!1;if(l=(c.parent||a.jstree.root).toString(),n=e.toString().match(/^(before|after)$/)&&d.id!==a.jstree.root?this.get_node(d.parent):d,o=i?i:this._model.data[c.id]?this:a.jstree.reference(c.id),p=!o||!o._id||this._id!==o._id,m=o&&o._id&&l&&o._model.data[l]&&o._model.data[l].children?a.inArray(c.id,o._model.data[l].children):-1,o&&o._id&&(c=o._model.data[c.id]),p)return(r=this.copy_node(c,d,e,f,g,!1,i))?(o&&o.delete_node(c),r):!1;switch(d.id===a.jstree.root&&("before"===e&&(e="first"),"after"===e&&(e="last")),e){case"before":e=a.inArray(d.id,n.children);break;case"after":e=a.inArray(d.id,n.children)+1;break;case"inside":case"first":e=0;break;case"last":e=n.children.length;break;default:e||(e=0)}if(e>n.children.length&&(e=n.children.length),!this.check("move_node",c,n,e,{core:!0,origin:i,is_multi:o&&o._id&&o._id!==this._id,is_foreign:!o||!o._id}))return this.settings.core.error.call(this,this._data.core.last_error),!1;if(c.parent===n.id){for(q=n.children.concat(),r=a.inArray(c.id,q),-1!==r&&(q=a.vakata.array_remove(q,r),e>r&&e--),r=[],s=0,t=q.length;t>s;s++)r[s>=e?s+1:s]=q[s];r[e]=c.id,n.children=r,this._node_changed(n.id),this.redraw(n.id===a.jstree.root)}else{for(r=c.children_d.concat(),r.push(c.id),s=0,t=c.parents.length;t>s;s++){for(q=[],w=o._model.data[c.parents[s]].children_d,u=0,v=w.length;v>u;u++)-1===a.inArray(w[u],r)&&q.push(w[u]);o._model.data[c.parents[s]].children_d=q}for(o._model.data[l].children=a.vakata.array_remove_item(o._model.data[l].children,c.id),s=0,t=n.parents.length;t>s;s++)this._model.data[n.parents[s]].children_d=this._model.data[n.parents[s]].children_d.concat(r);for(q=[],s=0,t=n.children.length;t>s;s++)q[s>=e?s+1:s]=n.children[s];for(q[e]=c.id,n.children=q,n.children_d.push(c.id),n.children_d=n.children_d.concat(c.children_d),c.parent=n.id,r=n.parents.concat(),r.unshift(n.id),w=c.parents.length,c.parents=r,r=r.concat(),s=0,t=c.children_d.length;t>s;s++)this._model.data[c.children_d[s]].parents=this._model.data[c.children_d[s]].parents.slice(0,-1*w),Array.prototype.push.apply(this._model.data[c.children_d[s]].parents,r);(l===a.jstree.root||n.id===a.jstree.root)&&(this._model.force_full_redraw=!0),this._model.force_full_redraw||(this._node_changed(l),this._node_changed(n.id)),h||this.redraw()}return f&&f.call(this,c,n,e),this.trigger("move_node",{node:c,parent:n.id,position:e,old_parent:l,old_position:m,is_multi:o&&o._id&&o._id!==this._id,is_foreign:!o||!o._id,old_instance:o,new_instance:this}),c.id},copy_node:function(c,d,e,f,g,h,i){var j,k,l,m,n,o,p,q,r,s,t;if(d=this.get_node(d),e=e===b?0:e,!d)return!1;if(!e.toString().match(/^(before|after)$/)&&!g&&!this.is_loaded(d))return this.load_node(d,function(){this.copy_node(c,d,e,f,!0,!1,i)});if(a.isArray(c)){if(1!==c.length){for(j=0,k=c.length;k>j;j++)(m=this.copy_node(c[j],d,e,f,g,!0,i))&&(d=m,e="after");return this.redraw(),!0}c=c[0]}if(c=c&&c.id?c:this.get_node(c),!c||c.id===a.jstree.root)return!1;switch(q=(c.parent||a.jstree.root).toString(),r=e.toString().match(/^(before|after)$/)&&d.id!==a.jstree.root?this.get_node(d.parent):d,s=i?i:this._model.data[c.id]?this:a.jstree.reference(c.id),t=!s||!s._id||this._id!==s._id,s&&s._id&&(c=s._model.data[c.id]),d.id===a.jstree.root&&("before"===e&&(e="first"),"after"===e&&(e="last")),e){case"before":e=a.inArray(d.id,r.children);break;case"after":e=a.inArray(d.id,r.children)+1;break;case"inside":case"first":e=0;break;case"last":e=r.children.length;break;default:e||(e=0)}if(e>r.children.length&&(e=r.children.length),!this.check("copy_node",c,r,e,{core:!0,origin:i,is_multi:s&&s._id&&s._id!==this._id,is_foreign:!s||!s._id}))return this.settings.core.error.call(this,this._data.core.last_error),!1;if(p=s?s.get_json(c,{no_id:!0,no_data:!0,no_state:!0}):c,!p)return!1;if(p.id===!0&&delete p.id,p=this._parse_model_from_json(p,r.id,r.parents.concat()),!p)return!1;for(m=this.get_node(p),c&&c.state&&c.state.loaded===!1&&(m.state.loaded=!1),l=[],l.push(p),l=l.concat(m.children_d),this.trigger("model",{nodes:l,parent:r.id}),n=0,o=r.parents.length;o>n;n++)this._model.data[r.parents[n]].children_d=this._model.data[r.parents[n]].children_d.concat(l);for(l=[],n=0,o=r.children.length;o>n;n++)l[n>=e?n+1:n]=r.children[n];return l[e]=m.id,r.children=l,r.children_d.push(m.id),r.children_d=r.children_d.concat(m.children_d),r.id===a.jstree.root&&(this._model.force_full_redraw=!0),this._model.force_full_redraw||this._node_changed(r.id),h||this.redraw(r.id===a.jstree.root),f&&f.call(this,m,r,e),this.trigger("copy_node",{node:m,original:c,parent:r.id,position:e,old_parent:q,old_position:s&&s._id&&q&&s._model.data[q]&&s._model.data[q].children?a.inArray(c.id,s._model.data[q].children):-1,is_multi:s&&s._id&&s._id!==this._id,is_foreign:!s||!s._id,old_instance:s,new_instance:this}),m.id},cut:function(b){if(b||(b=this._data.core.selected.concat()),a.isArray(b)||(b=[b]),!b.length)return!1;var c=[],g,h,i;for(h=0,i=b.length;i>h;h++)g=this.get_node(b[h]),g&&g.id&&g.id!==a.jstree.root&&c.push(g);return c.length?(d=c,f=this,e="move_node",void this.trigger("cut",{node:b})):!1},copy:function(b){if(b||(b=this._data.core.selected.concat()),a.isArray(b)||(b=[b]),!b.length)return!1;var c=[],g,h,i;for(h=0,i=b.length;i>h;h++)g=this.get_node(b[h]),g&&g.id&&g.id!==a.jstree.root&&c.push(g);return c.length?(d=c,f=this,e="copy_node",void this.trigger("copy",{node:b})):!1},get_buffer:function(){return{mode:e,node:d,inst:f}},can_paste:function(){return e!==!1&&d!==!1},paste:function(a,b){return a=this.get_node(a),a&&e&&e.match(/^(copy_node|move_node)$/)&&d?(this[e](d,a,b,!1,!1,!1,f)&&this.trigger("paste",{parent:a.id,node:d,mode:e}),d=!1,e=!1,void(f=!1)):!1},clear_buffer:function(){d=!1,e=!1,f=!1,this.trigger("clear_buffer")},edit:function(b,c,d){var e,f,g,h,j,k,l,m,n,o=!1;return(b=this.get_node(b))?this.settings.core.check_callback===!1?(this._data.core.last_error={error:"check",plugin:"core",id:"core_07",reason:"Could not edit node because of check_callback"},this.settings.core.error.call(this,this._data.core.last_error),!1):(n=b,c="string"==typeof c?c:b.text,this.set_text(b,""),b=this._open_to(b),n.text=c,e=this._data.core.rtl,f=this.element.width(),this._data.core.focused=n.id,g=b.children(".jstree-anchor").focus(),h=a("<span>"),j=c,k=a("<div />",{css:{position:"absolute",top:"-200px",left:e?"0px":"-1000px",visibility:"hidden"}}).appendTo("body"),l=a("<input />",{value:j,"class":"jstree-rename-input",css:{padding:"0",border:"1px solid silver","box-sizing":"border-box",display:"inline-block",height:this._data.core.li_height+"px",lineHeight:this._data.core.li_height+"px",width:"150px"},blur:a.proxy(function(c){c.stopImmediatePropagation(),c.preventDefault();var e=h.children(".jstree-rename-input"),f=e.val(),i=this.settings.core.force_text,m;""===f&&(f=j),k.remove(),h.replaceWith(g),h.remove(),j=i?j:a("<div></div>").append(a.parseHTML(j)).html(),this.set_text(b,j),m=!!this.rename_node(b,i?a("<div></div>").text(f).text():a("<div></div>").append(a.parseHTML(f)).html()),m||this.set_text(b,j),this._data.core.focused=n.id,setTimeout(a.proxy(function(){var a=this.get_node(n.id,!0);a.length&&(this._data.core.focused=n.id,a.children(".jstree-anchor").focus())},this),0),d&&d.call(this,n,m,o),l=null},this),keydown:function(a){var b=a.which;27===b&&(o=!0,this.value=j),(27===b||13===b||37===b||38===b||39===b||40===b||32===b)&&a.stopImmediatePropagation(),(27===b||13===b)&&(a.preventDefault(),this.blur())},click:function(a){a.stopImmediatePropagation()},mousedown:function(a){a.stopImmediatePropagation()},keyup:function(a){l.width(Math.min(k.text("pW"+this.value).width(),f))},keypress:function(a){return 13===a.which?!1:void 0}}),m={fontFamily:g.css("fontFamily")||"",fontSize:g.css("fontSize")||"",
fontWeight:g.css("fontWeight")||"",fontStyle:g.css("fontStyle")||"",fontStretch:g.css("fontStretch")||"",fontVariant:g.css("fontVariant")||"",letterSpacing:g.css("letterSpacing")||"",wordSpacing:g.css("wordSpacing")||""},h.attr("class",g.attr("class")).append(g.contents().clone()).append(l),g.replaceWith(h),k.css(m),l.css(m).width(Math.min(k.text("pW"+l[0].value).width(),f))[0].select(),void a(i).one("mousedown.jstree touchstart.jstree dnd_start.vakata",function(b){l&&b.target!==l&&a(l).blur()})):!1},set_theme:function(b,c){if(!b)return!1;if(c===!0){var d=this.settings.core.themes.dir;d||(d=a.jstree.path+"/themes"),c=d+"/"+b+"/style.css"}c&&-1===a.inArray(c,g)&&(a("head").append('<link rel="stylesheet" href="'+c+'" type="text/css" />'),g.push(c)),this._data.core.themes.name&&this.element.removeClass("jstree-"+this._data.core.themes.name),this._data.core.themes.name=b,this.element.addClass("jstree-"+b),this.element[this.settings.core.themes.responsive?"addClass":"removeClass"]("jstree-"+b+"-responsive"),this.trigger("set_theme",{theme:b})},get_theme:function(){return this._data.core.themes.name},set_theme_variant:function(a){this._data.core.themes.variant&&this.element.removeClass("jstree-"+this._data.core.themes.name+"-"+this._data.core.themes.variant),this._data.core.themes.variant=a,a&&this.element.addClass("jstree-"+this._data.core.themes.name+"-"+this._data.core.themes.variant)},get_theme_variant:function(){return this._data.core.themes.variant},show_stripes:function(){this._data.core.themes.stripes=!0,this.get_container_ul().addClass("jstree-striped"),this.trigger("show_stripes")},hide_stripes:function(){this._data.core.themes.stripes=!1,this.get_container_ul().removeClass("jstree-striped"),this.trigger("hide_stripes")},toggle_stripes:function(){this._data.core.themes.stripes?this.hide_stripes():this.show_stripes()},show_dots:function(){this._data.core.themes.dots=!0,this.get_container_ul().removeClass("jstree-no-dots"),this.trigger("show_dots")},hide_dots:function(){this._data.core.themes.dots=!1,this.get_container_ul().addClass("jstree-no-dots"),this.trigger("hide_dots")},toggle_dots:function(){this._data.core.themes.dots?this.hide_dots():this.show_dots()},show_icons:function(){this._data.core.themes.icons=!0,this.get_container_ul().removeClass("jstree-no-icons"),this.trigger("show_icons")},hide_icons:function(){this._data.core.themes.icons=!1,this.get_container_ul().addClass("jstree-no-icons"),this.trigger("hide_icons")},toggle_icons:function(){this._data.core.themes.icons?this.hide_icons():this.show_icons()},show_ellipsis:function(){this._data.core.themes.ellipsis=!0,this.get_container_ul().addClass("jstree-ellipsis"),this.trigger("show_ellipsis")},hide_ellipsis:function(){this._data.core.themes.ellipsis=!1,this.get_container_ul().removeClass("jstree-ellipsis"),this.trigger("hide_ellipsis")},toggle_ellipsis:function(){this._data.core.themes.ellipsis?this.hide_ellipsis():this.show_ellipsis()},set_icon:function(c,d){var e,f,g,h;if(a.isArray(c)){for(c=c.slice(),e=0,f=c.length;f>e;e++)this.set_icon(c[e],d);return!0}return c=this.get_node(c),c&&c.id!==a.jstree.root?(h=c.icon,c.icon=d===!0||null===d||d===b||""===d?!0:d,g=this.get_node(c,!0).children(".jstree-anchor").children(".jstree-themeicon"),d===!1?this.hide_icon(c):d===!0||null===d||d===b||""===d?(g.removeClass("jstree-themeicon-custom "+h).css("background","").removeAttr("rel"),h===!1&&this.show_icon(c)):-1===d.indexOf("/")&&-1===d.indexOf(".")?(g.removeClass(h).css("background",""),g.addClass(d+" jstree-themeicon-custom").attr("rel",d),h===!1&&this.show_icon(c)):(g.removeClass(h).css("background",""),g.addClass("jstree-themeicon-custom").css("background","url('"+d+"') center center no-repeat").attr("rel",d),h===!1&&this.show_icon(c)),!0):!1},get_icon:function(b){return b=this.get_node(b),b&&b.id!==a.jstree.root?b.icon:!1},hide_icon:function(b){var c,d;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.hide_icon(b[c]);return!0}return b=this.get_node(b),b&&b!==a.jstree.root?(b.icon=!1,this.get_node(b,!0).children(".jstree-anchor").children(".jstree-themeicon").addClass("jstree-themeicon-hidden"),!0):!1},show_icon:function(b){var c,d,e;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.show_icon(b[c]);return!0}return b=this.get_node(b),b&&b!==a.jstree.root?(e=this.get_node(b,!0),b.icon=e.length?e.children(".jstree-anchor").children(".jstree-themeicon").attr("rel"):!0,b.icon||(b.icon=!0),e.children(".jstree-anchor").children(".jstree-themeicon").removeClass("jstree-themeicon-hidden"),!0):!1}},a.vakata={},a.vakata.attributes=function(b,c){b=a(b)[0];var d=c?{}:[];return b&&b.attributes&&a.each(b.attributes,function(b,e){-1===a.inArray(e.name.toLowerCase(),["style","contenteditable","hasfocus","tabindex"])&&null!==e.value&&""!==a.trim(e.value)&&(c?d[e.name]=e.value:d.push(e.name))}),d},a.vakata.array_unique=function(a){var c=[],d,e,f,g={};for(d=0,f=a.length;f>d;d++)g[a[d]]===b&&(c.push(a[d]),g[a[d]]=!0);return c},a.vakata.array_remove=function(a,b){return a.splice(b,1),a},a.vakata.array_remove_item=function(b,c){var d=a.inArray(c,b);return-1!==d?a.vakata.array_remove(b,d):b},a.vakata.array_filter=function(a,b,c,d,e){if(a.filter)return a.filter(b,c);d=[];for(e in a)~~e+""==e+""&&e>=0&&b.call(c,a[e],+e,a)&&d.push(a[e]);return d},a.jstree.plugins.changed=function(a,b){var c=[];this.trigger=function(a,d){var e,f;if(d||(d={}),"changed"===a.replace(".jstree","")){d.changed={selected:[],deselected:[]};var g={};for(e=0,f=c.length;f>e;e++)g[c[e]]=1;for(e=0,f=d.selected.length;f>e;e++)g[d.selected[e]]?g[d.selected[e]]=2:d.changed.selected.push(d.selected[e]);for(e=0,f=c.length;f>e;e++)1===g[c[e]]&&d.changed.deselected.push(c[e]);c=d.selected.slice()}b.trigger.call(this,a,d)},this.refresh=function(a,d){return c=[],b.refresh.apply(this,arguments)}};var j=i.createElement("I");j.className="jstree-icon jstree-checkbox",j.setAttribute("role","presentation"),a.jstree.defaults.checkbox={visible:!0,three_state:!0,whole_node:!0,keep_selected_style:!0,cascade:"",tie_selection:!0},a.jstree.plugins.checkbox=function(c,d){this.bind=function(){d.bind.call(this),this._data.checkbox.uto=!1,this._data.checkbox.selected=[],this.settings.checkbox.three_state&&(this.settings.checkbox.cascade="up+down+undetermined"),this.element.on("init.jstree",a.proxy(function(){this._data.checkbox.visible=this.settings.checkbox.visible,this.settings.checkbox.keep_selected_style||this.element.addClass("jstree-checkbox-no-clicked"),this.settings.checkbox.tie_selection&&this.element.addClass("jstree-checkbox-selection")},this)).on("loading.jstree",a.proxy(function(){this[this._data.checkbox.visible?"show_checkboxes":"hide_checkboxes"]()},this)),-1!==this.settings.checkbox.cascade.indexOf("undetermined")&&this.element.on("changed.jstree uncheck_node.jstree check_node.jstree uncheck_all.jstree check_all.jstree move_node.jstree copy_node.jstree redraw.jstree open_node.jstree",a.proxy(function(){this._data.checkbox.uto&&clearTimeout(this._data.checkbox.uto),this._data.checkbox.uto=setTimeout(a.proxy(this._undetermined,this),50)},this)),this.settings.checkbox.tie_selection||this.element.on("model.jstree",a.proxy(function(a,b){var c=this._model.data,d=c[b.parent],e=b.nodes,f,g;for(f=0,g=e.length;g>f;f++)c[e[f]].state.checked=c[e[f]].state.checked||c[e[f]].original&&c[e[f]].original.state&&c[e[f]].original.state.checked,c[e[f]].state.checked&&this._data.checkbox.selected.push(e[f])},this)),(-1!==this.settings.checkbox.cascade.indexOf("up")||-1!==this.settings.checkbox.cascade.indexOf("down"))&&this.element.on("model.jstree",a.proxy(function(b,c){var d=this._model.data,e=d[c.parent],f=c.nodes,g=[],h,i,j,k,l,m,n=this.settings.checkbox.cascade,o=this.settings.checkbox.tie_selection;if(-1!==n.indexOf("down"))if(e.state[o?"selected":"checked"]){for(i=0,j=f.length;j>i;i++)d[f[i]].state[o?"selected":"checked"]=!0;this._data[o?"core":"checkbox"].selected=this._data[o?"core":"checkbox"].selected.concat(f)}else for(i=0,j=f.length;j>i;i++)if(d[f[i]].state[o?"selected":"checked"]){for(k=0,l=d[f[i]].children_d.length;l>k;k++)d[d[f[i]].children_d[k]].state[o?"selected":"checked"]=!0;this._data[o?"core":"checkbox"].selected=this._data[o?"core":"checkbox"].selected.concat(d[f[i]].children_d)}if(-1!==n.indexOf("up")){for(i=0,j=e.children_d.length;j>i;i++)d[e.children_d[i]].children.length||g.push(d[e.children_d[i]].parent);for(g=a.vakata.array_unique(g),k=0,l=g.length;l>k;k++){e=d[g[k]];while(e&&e.id!==a.jstree.root){for(h=0,i=0,j=e.children.length;j>i;i++)h+=d[e.children[i]].state[o?"selected":"checked"];if(h!==j)break;e.state[o?"selected":"checked"]=!0,this._data[o?"core":"checkbox"].selected.push(e.id),m=this.get_node(e,!0),m&&m.length&&m.attr("aria-selected",!0).children(".jstree-anchor").addClass(o?"jstree-clicked":"jstree-checked"),e=this.get_node(e.parent)}}}this._data[o?"core":"checkbox"].selected=a.vakata.array_unique(this._data[o?"core":"checkbox"].selected)},this)).on(this.settings.checkbox.tie_selection?"select_node.jstree":"check_node.jstree",a.proxy(function(b,c){var d=c.node,e=this._model.data,f=this.get_node(d.parent),g=this.get_node(d,!0),h,i,j,k,l=this.settings.checkbox.cascade,m=this.settings.checkbox.tie_selection,n={},o=this._data[m?"core":"checkbox"].selected;for(h=0,i=o.length;i>h;h++)n[o[h]]=!0;if(-1!==l.indexOf("down"))for(h=0,i=d.children_d.length;i>h;h++)n[d.children_d[h]]=!0,k=e[d.children_d[h]],k.state[m?"selected":"checked"]=!0,k&&k.original&&k.original.state&&k.original.state.undetermined&&(k.original.state.undetermined=!1);if(-1!==l.indexOf("up"))while(f&&f.id!==a.jstree.root){for(j=0,h=0,i=f.children.length;i>h;h++)j+=e[f.children[h]].state[m?"selected":"checked"];if(j!==i)break;f.state[m?"selected":"checked"]=!0,n[f.id]=!0,k=this.get_node(f,!0),k&&k.length&&k.attr("aria-selected",!0).children(".jstree-anchor").addClass(m?"jstree-clicked":"jstree-checked"),f=this.get_node(f.parent)}o=[];for(h in n)n.hasOwnProperty(h)&&o.push(h);this._data[m?"core":"checkbox"].selected=o,-1!==l.indexOf("down")&&g.length&&g.find(".jstree-anchor").addClass(m?"jstree-clicked":"jstree-checked").parent().attr("aria-selected",!0)},this)).on(this.settings.checkbox.tie_selection?"deselect_all.jstree":"uncheck_all.jstree",a.proxy(function(b,c){var d=this.get_node(a.jstree.root),e=this._model.data,f,g,h;for(f=0,g=d.children_d.length;g>f;f++)h=e[d.children_d[f]],h&&h.original&&h.original.state&&h.original.state.undetermined&&(h.original.state.undetermined=!1)},this)).on(this.settings.checkbox.tie_selection?"deselect_node.jstree":"uncheck_node.jstree",a.proxy(function(b,c){var d=c.node,e=this.get_node(d,!0),f,g,h,i=this.settings.checkbox.cascade,j=this.settings.checkbox.tie_selection,k=this._data[j?"core":"checkbox"].selected,l={};if(d&&d.original&&d.original.state&&d.original.state.undetermined&&(d.original.state.undetermined=!1),-1!==i.indexOf("down"))for(f=0,g=d.children_d.length;g>f;f++)h=this._model.data[d.children_d[f]],h.state[j?"selected":"checked"]=!1,h&&h.original&&h.original.state&&h.original.state.undetermined&&(h.original.state.undetermined=!1);if(-1!==i.indexOf("up"))for(f=0,g=d.parents.length;g>f;f++)h=this._model.data[d.parents[f]],h.state[j?"selected":"checked"]=!1,h&&h.original&&h.original.state&&h.original.state.undetermined&&(h.original.state.undetermined=!1),h=this.get_node(d.parents[f],!0),h&&h.length&&h.attr("aria-selected",!1).children(".jstree-anchor").removeClass(j?"jstree-clicked":"jstree-checked");for(l={},f=0,g=k.length;g>f;f++)-1!==i.indexOf("down")&&-1!==a.inArray(k[f],d.children_d)||-1!==i.indexOf("up")&&-1!==a.inArray(k[f],d.parents)||(l[k[f]]=!0);k=[];for(f in l)l.hasOwnProperty(f)&&k.push(f);this._data[j?"core":"checkbox"].selected=k,-1!==i.indexOf("down")&&e.length&&e.find(".jstree-anchor").removeClass(j?"jstree-clicked":"jstree-checked").parent().attr("aria-selected",!1)},this)),-1!==this.settings.checkbox.cascade.indexOf("up")&&this.element.on("delete_node.jstree",a.proxy(function(b,c){var d=this.get_node(c.parent),e=this._model.data,f,g,h,i,j=this.settings.checkbox.tie_selection;while(d&&d.id!==a.jstree.root&&!d.state[j?"selected":"checked"]){for(h=0,f=0,g=d.children.length;g>f;f++)h+=e[d.children[f]].state[j?"selected":"checked"];if(!(g>0&&h===g))break;d.state[j?"selected":"checked"]=!0,this._data[j?"core":"checkbox"].selected.push(d.id),i=this.get_node(d,!0),i&&i.length&&i.attr("aria-selected",!0).children(".jstree-anchor").addClass(j?"jstree-clicked":"jstree-checked"),d=this.get_node(d.parent)}},this)).on("move_node.jstree",a.proxy(function(b,c){var d=c.is_multi,e=c.old_parent,f=this.get_node(c.parent),g=this._model.data,h,i,j,k,l,m=this.settings.checkbox.tie_selection;if(!d){h=this.get_node(e);while(h&&h.id!==a.jstree.root&&!h.state[m?"selected":"checked"]){for(i=0,j=0,k=h.children.length;k>j;j++)i+=g[h.children[j]].state[m?"selected":"checked"];if(!(k>0&&i===k))break;h.state[m?"selected":"checked"]=!0,this._data[m?"core":"checkbox"].selected.push(h.id),l=this.get_node(h,!0),l&&l.length&&l.attr("aria-selected",!0).children(".jstree-anchor").addClass(m?"jstree-clicked":"jstree-checked"),h=this.get_node(h.parent)}}h=f;while(h&&h.id!==a.jstree.root){for(i=0,j=0,k=h.children.length;k>j;j++)i+=g[h.children[j]].state[m?"selected":"checked"];if(i===k)h.state[m?"selected":"checked"]||(h.state[m?"selected":"checked"]=!0,this._data[m?"core":"checkbox"].selected.push(h.id),l=this.get_node(h,!0),l&&l.length&&l.attr("aria-selected",!0).children(".jstree-anchor").addClass(m?"jstree-clicked":"jstree-checked"));else{if(!h.state[m?"selected":"checked"])break;h.state[m?"selected":"checked"]=!1,this._data[m?"core":"checkbox"].selected=a.vakata.array_remove_item(this._data[m?"core":"checkbox"].selected,h.id),l=this.get_node(h,!0),l&&l.length&&l.attr("aria-selected",!1).children(".jstree-anchor").removeClass(m?"jstree-clicked":"jstree-checked")}h=this.get_node(h.parent)}},this))},this._undetermined=function(){if(null!==this.element){var c,d,e,f,g={},h=this._model.data,i=this.settings.checkbox.tie_selection,j=this._data[i?"core":"checkbox"].selected,k=[],l=this;for(c=0,d=j.length;d>c;c++)if(h[j[c]]&&h[j[c]].parents)for(e=0,f=h[j[c]].parents.length;f>e;e++){if(g[h[j[c]].parents[e]]!==b)break;h[j[c]].parents[e]!==a.jstree.root&&(g[h[j[c]].parents[e]]=!0,k.push(h[j[c]].parents[e]))}for(this.element.find(".jstree-closed").not(":has(.jstree-children)").each(function(){var i=l.get_node(this),j;if(i.state.loaded){for(c=0,d=i.children_d.length;d>c;c++)if(j=h[i.children_d[c]],!j.state.loaded&&j.original&&j.original.state&&j.original.state.undetermined&&j.original.state.undetermined===!0)for(g[j.id]===b&&j.id!==a.jstree.root&&(g[j.id]=!0,k.push(j.id)),e=0,f=j.parents.length;f>e;e++)g[j.parents[e]]===b&&j.parents[e]!==a.jstree.root&&(g[j.parents[e]]=!0,k.push(j.parents[e]))}else if(i.original&&i.original.state&&i.original.state.undetermined&&i.original.state.undetermined===!0)for(g[i.id]===b&&i.id!==a.jstree.root&&(g[i.id]=!0,k.push(i.id)),e=0,f=i.parents.length;f>e;e++)g[i.parents[e]]===b&&i.parents[e]!==a.jstree.root&&(g[i.parents[e]]=!0,k.push(i.parents[e]))}),this.element.find(".jstree-undetermined").removeClass("jstree-undetermined"),c=0,d=k.length;d>c;c++)h[k[c]].state[i?"selected":"checked"]||(j=this.get_node(k[c],!0),j&&j.length&&j.children(".jstree-anchor").children(".jstree-checkbox").addClass("jstree-undetermined"))}},this.redraw_node=function(b,c,e,f){if(b=d.redraw_node.apply(this,arguments)){var g,h,i=null,k=null;for(g=0,h=b.childNodes.length;h>g;g++)if(b.childNodes[g]&&b.childNodes[g].className&&-1!==b.childNodes[g].className.indexOf("jstree-anchor")){i=b.childNodes[g];break}i&&(!this.settings.checkbox.tie_selection&&this._model.data[b.id].state.checked&&(i.className+=" jstree-checked"),k=j.cloneNode(!1),this._model.data[b.id].state.checkbox_disabled&&(k.className+=" jstree-checkbox-disabled"),i.insertBefore(k,i.childNodes[0]))}return e||-1===this.settings.checkbox.cascade.indexOf("undetermined")||(this._data.checkbox.uto&&clearTimeout(this._data.checkbox.uto),this._data.checkbox.uto=setTimeout(a.proxy(this._undetermined,this),50)),b},this.show_checkboxes=function(){this._data.core.themes.checkboxes=!0,this.get_container_ul().removeClass("jstree-no-checkboxes")},this.hide_checkboxes=function(){this._data.core.themes.checkboxes=!1,this.get_container_ul().addClass("jstree-no-checkboxes")},this.toggle_checkboxes=function(){this._data.core.themes.checkboxes?this.hide_checkboxes():this.show_checkboxes()},this.is_undetermined=function(b){b=this.get_node(b);var c=this.settings.checkbox.cascade,d,e,f=this.settings.checkbox.tie_selection,g=this._data[f?"core":"checkbox"].selected,h=this._model.data;if(!b||b.state[f?"selected":"checked"]===!0||-1===c.indexOf("undetermined")||-1===c.indexOf("down")&&-1===c.indexOf("up"))return!1;if(!b.state.loaded&&b.original.state.undetermined===!0)return!0;for(d=0,e=b.children_d.length;e>d;d++)if(-1!==a.inArray(b.children_d[d],g)||!h[b.children_d[d]].state.loaded&&h[b.children_d[d]].original.state.undetermined)return!0;return!1},this.disable_checkbox=function(b){var c,d,e;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.disable_checkbox(b[c]);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(e=this.get_node(b,!0),void(b.state.checkbox_disabled||(b.state.checkbox_disabled=!0,e&&e.length&&e.children(".jstree-anchor").children(".jstree-checkbox").addClass("jstree-checkbox-disabled"),this.trigger("disable_checkbox",{node:b})))):!1},this.enable_checkbox=function(b){var c,d,e;if(a.isArray(b)){for(b=b.slice(),c=0,d=b.length;d>c;c++)this.enable_checkbox(b[c]);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(e=this.get_node(b,!0),void(b.state.checkbox_disabled&&(b.state.checkbox_disabled=!1,e&&e.length&&e.children(".jstree-anchor").children(".jstree-checkbox").removeClass("jstree-checkbox-disabled"),this.trigger("enable_checkbox",{node:b})))):!1},this.activate_node=function(b,c){return a(c.target).hasClass("jstree-checkbox-disabled")?!1:(this.settings.checkbox.tie_selection&&(this.settings.checkbox.whole_node||a(c.target).hasClass("jstree-checkbox"))&&(c.ctrlKey=!0),this.settings.checkbox.tie_selection||!this.settings.checkbox.whole_node&&!a(c.target).hasClass("jstree-checkbox")?d.activate_node.call(this,b,c):this.is_disabled(b)?!1:(this.is_checked(b)?this.uncheck_node(b,c):this.check_node(b,c),void this.trigger("activate_node",{node:this.get_node(b)})))},this.check_node=function(b,c){if(this.settings.checkbox.tie_selection)return this.select_node(b,!1,!0,c);var d,e,f,g;if(a.isArray(b)){for(b=b.slice(),e=0,f=b.length;f>e;e++)this.check_node(b[e],c);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(d=this.get_node(b,!0),void(b.state.checked||(b.state.checked=!0,this._data.checkbox.selected.push(b.id),d&&d.length&&d.children(".jstree-anchor").addClass("jstree-checked"),this.trigger("check_node",{node:b,selected:this._data.checkbox.selected,event:c})))):!1},this.uncheck_node=function(b,c){if(this.settings.checkbox.tie_selection)return this.deselect_node(b,!1,c);var d,e,f;if(a.isArray(b)){for(b=b.slice(),d=0,e=b.length;e>d;d++)this.uncheck_node(b[d],c);return!0}return b=this.get_node(b),b&&b.id!==a.jstree.root?(f=this.get_node(b,!0),void(b.state.checked&&(b.state.checked=!1,this._data.checkbox.selected=a.vakata.array_remove_item(this._data.checkbox.selected,b.id),f.length&&f.children(".jstree-anchor").removeClass("jstree-checked"),this.trigger("uncheck_node",{node:b,selected:this._data.checkbox.selected,event:c})))):!1},this.check_all=function(){if(this.settings.checkbox.tie_selection)return this.select_all();var b=this._data.checkbox.selected.concat([]),c,d;for(this._data.checkbox.selected=this._model.data[a.jstree.root].children_d.concat(),c=0,d=this._data.checkbox.selected.length;d>c;c++)this._model.data[this._data.checkbox.selected[c]]&&(this._model.data[this._data.checkbox.selected[c]].state.checked=!0);this.redraw(!0),this.trigger("check_all",{selected:this._data.checkbox.selected})},this.uncheck_all=function(){if(this.settings.checkbox.tie_selection)return this.deselect_all();var a=this._data.checkbox.selected.concat([]),b,c;for(b=0,c=this._data.checkbox.selected.length;c>b;b++)this._model.data[this._data.checkbox.selected[b]]&&(this._model.data[this._data.checkbox.selected[b]].state.checked=!1);this._data.checkbox.selected=[],this.element.find(".jstree-checked").removeClass("jstree-checked"),this.trigger("uncheck_all",{selected:this._data.checkbox.selected,node:a})},this.is_checked=function(b){return this.settings.checkbox.tie_selection?this.is_selected(b):(b=this.get_node(b),b&&b.id!==a.jstree.root?b.state.checked:!1)},this.get_checked=function(b){return this.settings.checkbox.tie_selection?this.get_selected(b):b?a.map(this._data.checkbox.selected,a.proxy(function(a){return this.get_node(a)},this)):this._data.checkbox.selected},this.get_top_checked=function(b){if(this.settings.checkbox.tie_selection)return this.get_top_selected(b);var c=this.get_checked(!0),d={},e,f,g,h;for(e=0,f=c.length;f>e;e++)d[c[e].id]=c[e];for(e=0,f=c.length;f>e;e++)for(g=0,h=c[e].children_d.length;h>g;g++)d[c[e].children_d[g]]&&delete d[c[e].children_d[g]];c=[];for(e in d)d.hasOwnProperty(e)&&c.push(e);return b?a.map(c,a.proxy(function(a){return this.get_node(a)},this)):c},this.get_bottom_checked=function(b){if(this.settings.checkbox.tie_selection)return this.get_bottom_selected(b);var c=this.get_checked(!0),d=[],e,f;for(e=0,f=c.length;f>e;e++)c[e].children.length||d.push(c[e].id);return b?a.map(d,a.proxy(function(a){return this.get_node(a)},this)):d},this.load_node=function(b,c){var e,f,g,h,i,j;if(!a.isArray(b)&&!this.settings.checkbox.tie_selection&&(j=this.get_node(b),j&&j.state.loaded))for(e=0,f=j.children_d.length;f>e;e++)this._model.data[j.children_d[e]].state.checked&&(i=!0,this._data.checkbox.selected=a.vakata.array_remove_item(this._data.checkbox.selected,j.children_d[e]));return d.load_node.apply(this,arguments)},this.get_state=function(){var a=d.get_state.apply(this,arguments);return this.settings.checkbox.tie_selection?a:(a.checkbox=this._data.checkbox.selected.slice(),a)},this.set_state=function(b,c){var e=d.set_state.apply(this,arguments);if(e&&b.checkbox){if(!this.settings.checkbox.tie_selection){this.uncheck_all();var f=this;a.each(b.checkbox,function(a,b){f.check_node(b)})}return delete b.checkbox,this.set_state(b,c),!1}return e},this.refresh=function(a,b){return this.settings.checkbox.tie_selection||(this._data.checkbox.selected=[]),d.refresh.apply(this,arguments)}},a.jstree.defaults.conditionalselect=function(){return!0},a.jstree.plugins.conditionalselect=function(a,b){this.activate_node=function(a,c){this.settings.conditionalselect.call(this,this.get_node(a),c)&&b.activate_node.call(this,a,c)}},a.jstree.defaults.contextmenu={select_node:!0,show_at_node:!0,items:function(b,c){return{create:{separator_before:!1,separator_after:!0,_disabled:!1,label:"Create",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.create_node(d,{},"last",function(a){setTimeout(function(){c.edit(a)},0)})}},rename:{separator_before:!1,separator_after:!1,_disabled:!1,label:"Rename",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.edit(d)}},remove:{separator_before:!1,icon:!1,separator_after:!1,_disabled:!1,label:"Delete",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.is_selected(d)?c.delete_node(c.get_selected()):c.delete_node(d)}},ccp:{separator_before:!0,icon:!1,separator_after:!1,label:"Edit",action:!1,submenu:{cut:{separator_before:!1,separator_after:!1,label:"Cut",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.is_selected(d)?c.cut(c.get_top_selected()):c.cut(d)}},copy:{separator_before:!1,icon:!1,separator_after:!1,label:"Copy",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.is_selected(d)?c.copy(c.get_top_selected()):c.copy(d)}},paste:{separator_before:!1,icon:!1,_disabled:function(b){return!a.jstree.reference(b.reference).can_paste()},separator_after:!1,label:"Paste",action:function(b){var c=a.jstree.reference(b.reference),d=c.get_node(b.reference);c.paste(d)}}}}}}},a.jstree.plugins.contextmenu=function(c,d){this.bind=function(){d.bind.call(this);var b=0,c=null,e,f;this.element.on("contextmenu.jstree",".jstree-anchor",a.proxy(function(a,d){"input"!==a.target.tagName.toLowerCase()&&(a.preventDefault(),b=a.ctrlKey?+new Date:0,(d||c)&&(b=+new Date+1e4),c&&clearTimeout(c),this.is_loading(a.currentTarget)||this.show_contextmenu(a.currentTarget,a.pageX,a.pageY,a))},this)).on("click.jstree",".jstree-anchor",a.proxy(function(c){this._data.contextmenu.visible&&(!b||+new Date-b>250)&&a.vakata.context.hide(),b=0},this)).on("touchstart.jstree",".jstree-anchor",function(b){b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(e=b.originalEvent.changedTouches[0].clientX,f=b.originalEvent.changedTouches[0].clientY,c=setTimeout(function(){a(b.currentTarget).trigger("contextmenu",!0)},750))}).on("touchmove.vakata.jstree",function(a){c&&a.originalEvent&&a.originalEvent.changedTouches&&a.originalEvent.changedTouches[0]&&(Math.abs(e-a.originalEvent.changedTouches[0].clientX)>50||Math.abs(f-a.originalEvent.changedTouches[0].clientY)>50)&&clearTimeout(c)}).on("touchend.vakata.jstree",function(a){c&&clearTimeout(c)}),a(i).on("context_hide.vakata.jstree",a.proxy(function(b,c){this._data.contextmenu.visible=!1,a(c.reference).removeClass("jstree-context")},this))},this.teardown=function(){this._data.contextmenu.visible&&a.vakata.context.hide(),d.teardown.call(this)},this.show_contextmenu=function(c,d,e,f){if(c=this.get_node(c),!c||c.id===a.jstree.root)return!1;var g=this.settings.contextmenu,h=this.get_node(c,!0),i=h.children(".jstree-anchor"),j=!1,k=!1;(g.show_at_node||d===b||e===b)&&(j=i.offset(),d=j.left,e=j.top+this._data.core.li_height),this.settings.contextmenu.select_node&&!this.is_selected(c)&&this.activate_node(c,f),k=g.items,a.isFunction(k)&&(k=k.call(this,c,a.proxy(function(a){this._show_contextmenu(c,d,e,a)},this))),a.isPlainObject(k)&&this._show_contextmenu(c,d,e,k)},this._show_contextmenu=function(b,c,d,e){var f=this.get_node(b,!0),g=f.children(".jstree-anchor");a(i).one("context_show.vakata.jstree",a.proxy(function(b,c){var d="jstree-contextmenu jstree-"+this.get_theme()+"-contextmenu";a(c.element).addClass(d),g.addClass("jstree-context")},this)),this._data.contextmenu.visible=!0,a.vakata.context.show(g,{x:c,y:d},e),this.trigger("show_contextmenu",{node:b,x:c,y:d})}},function(a){var b=!1,c={element:!1,reference:!1,position_x:0,position_y:0,items:[],html:"",is_visible:!1};a.vakata.context={settings:{hide_onmouseleave:0,icons:!0},_trigger:function(b){a(i).triggerHandler("context_"+b+".vakata",{reference:c.reference,element:c.element,position:{x:c.position_x,y:c.position_y}})},_execute:function(b){return b=c.items[b],b&&(!b._disabled||a.isFunction(b._disabled)&&!b._disabled({item:b,reference:c.reference,element:c.element}))&&b.action?b.action.call(null,{item:b,reference:c.reference,element:c.element,position:{x:c.position_x,y:c.position_y}}):!1},_parse:function(b,d){if(!b)return!1;d||(c.html="",c.items=[]);var e="",f=!1,g;return d&&(e+="<ul>"),a.each(b,function(b,d){return d?(c.items.push(d),!f&&d.separator_before&&(e+="<li class='vakata-context-separator'><a href='#' "+(a.vakata.context.settings.icons?"":'style="margin-left:0px;"')+">&#160;</a></li>"),f=!1,e+="<li class='"+(d._class||"")+(d._disabled===!0||a.isFunction(d._disabled)&&d._disabled({item:d,reference:c.reference,element:c.element})?" vakata-contextmenu-disabled ":"")+"' "+(d.shortcut?" data-shortcut='"+d.shortcut+"' ":"")+">",e+="<a href='#' rel='"+(c.items.length-1)+"' "+(d.title?"title='"+d.title+"'":"")+">",a.vakata.context.settings.icons&&(e+="<i ",d.icon&&(e+=-1!==d.icon.indexOf("/")||-1!==d.icon.indexOf(".")?" style='background:url(\""+d.icon+"\") center center no-repeat' ":" class='"+d.icon+"' "),e+="></i><span class='vakata-contextmenu-sep'>&#160;</span>"),e+=(a.isFunction(d.label)?d.label({item:b,reference:c.reference,element:c.element}):d.label)+(d.shortcut?' <span class="vakata-contextmenu-shortcut vakata-contextmenu-shortcut-'+d.shortcut+'">'+(d.shortcut_label||"")+"</span>":"")+"</a>",d.submenu&&(g=a.vakata.context._parse(d.submenu,!0),g&&(e+=g)),e+="</li>",void(d.separator_after&&(e+="<li class='vakata-context-separator'><a href='#' "+(a.vakata.context.settings.icons?"":'style="margin-left:0px;"')+">&#160;</a></li>",f=!0))):!0}),e=e.replace(/<li class\='vakata-context-separator'\><\/li\>$/,""),d&&(e+="</ul>"),d||(c.html=e,a.vakata.context._trigger("parse")),e.length>10?e:!1},_show_submenu:function(c){if(c=a(c),c.length&&c.children("ul").length){var d=c.children("ul"),e=c.offset().left,f=e+c.outerWidth(),g=c.offset().top,h=d.width(),i=d.height(),j=a(window).width()+a(window).scrollLeft(),k=a(window).height()+a(window).scrollTop();b?c[f-(h+10+c.outerWidth())<0?"addClass":"removeClass"]("vakata-context-left"):c[f+h>j&&e>j-f?"addClass":"removeClass"]("vakata-context-right"),g+i+10>k&&d.css("bottom","-1px"),c.hasClass("vakata-context-right")?h>e&&d.css("margin-right",e-h):h>j-f&&d.css("margin-left",j-f-h),d.show()}},show:function(d,e,f){var g,h,i,j,k,l,m,n,o=!0;switch(c.element&&c.element.length&&c.element.width(""),o){case!e&&!d:return!1;case!!e&&!!d:c.reference=d,c.position_x=e.x,c.position_y=e.y;break;case!e&&!!d:c.reference=d,g=d.offset(),c.position_x=g.left+d.outerHeight(),c.position_y=g.top;break;case!!e&&!d:c.position_x=e.x,c.position_y=e.y}d&&!f&&a(d).data("vakata_contextmenu")&&(f=a(d).data("vakata_contextmenu")),a.vakata.context._parse(f)&&c.element.html(c.html),c.items.length&&(c.element.appendTo("body"),h=c.element,i=c.position_x,j=c.position_y,k=h.width(),l=h.height(),m=a(window).width()+a(window).scrollLeft(),n=a(window).height()+a(window).scrollTop(),b&&(i-=h.outerWidth()-a(d).outerWidth(),i<a(window).scrollLeft()+20&&(i=a(window).scrollLeft()+20)),i+k+20>m&&(i=m-(k+20)),j+l+20>n&&(j=n-(l+20)),c.element.css({left:i,top:j}).show().find("a").first().focus().parent().addClass("vakata-context-hover"),c.is_visible=!0,a.vakata.context._trigger("show"))},hide:function(){c.is_visible&&(c.element.hide().find("ul").hide().end().find(":focus").blur().end().detach(),c.is_visible=!1,a.vakata.context._trigger("hide"))}},a(function(){b="rtl"===a("body").css("direction");var d=!1;c.element=a("<ul class='vakata-context'></ul>"),c.element.on("mouseenter","li",function(b){b.stopImmediatePropagation(),a.contains(this,b.relatedTarget)||(d&&clearTimeout(d),c.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end(),a(this).siblings().find("ul").hide().end().end().parentsUntil(".vakata-context","li").addBack().addClass("vakata-context-hover"),a.vakata.context._show_submenu(this))}).on("mouseleave","li",function(b){a.contains(this,b.relatedTarget)||a(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover")}).on("mouseleave",function(b){a(this).find(".vakata-context-hover").removeClass("vakata-context-hover"),a.vakata.context.settings.hide_onmouseleave&&(d=setTimeout(function(b){return function(){a.vakata.context.hide()}}(this),a.vakata.context.settings.hide_onmouseleave))}).on("click","a",function(b){b.preventDefault(),a(this).blur().parent().hasClass("vakata-context-disabled")||a.vakata.context._execute(a(this).attr("rel"))===!1||a.vakata.context.hide()}).on("keydown","a",function(b){var d=null;switch(b.which){case 13:case 32:b.type="click",b.preventDefault(),a(b.currentTarget).trigger(b);break;case 37:c.is_visible&&(c.element.find(".vakata-context-hover").last().closest("li").first().find("ul").hide().find(".vakata-context-hover").removeClass("vakata-context-hover").end().end().children("a").focus(),b.stopImmediatePropagation(),b.preventDefault());break;case 38:c.is_visible&&(d=c.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first(),
d.length||(d=c.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last()),d.addClass("vakata-context-hover").children("a").focus(),b.stopImmediatePropagation(),b.preventDefault());break;case 39:c.is_visible&&(c.element.find(".vakata-context-hover").last().children("ul").show().children("li:not(.vakata-context-separator)").removeClass("vakata-context-hover").first().addClass("vakata-context-hover").children("a").focus(),b.stopImmediatePropagation(),b.preventDefault());break;case 40:c.is_visible&&(d=c.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first(),d.length||(d=c.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first()),d.addClass("vakata-context-hover").children("a").focus(),b.stopImmediatePropagation(),b.preventDefault());break;case 27:a.vakata.context.hide(),b.preventDefault()}}).on("keydown",function(a){a.preventDefault();var b=c.element.find(".vakata-contextmenu-shortcut-"+a.which).parent();b.parent().not(".vakata-context-disabled")&&b.click()}),a(i).on("mousedown.vakata.jstree",function(b){c.is_visible&&!a.contains(c.element[0],b.target)&&a.vakata.context.hide()}).on("context_show.vakata.jstree",function(a,d){c.element.find("li:has(ul)").children("a").addClass("vakata-context-parent"),b&&c.element.addClass("vakata-context-rtl").css("direction","rtl"),c.element.find("ul").hide().end()})})}(a),a.jstree.defaults.dnd={copy:!0,open_timeout:500,is_draggable:!0,check_while_dragging:!0,always_copy:!1,inside_pos:0,drag_selection:!0,touch:!0,large_drop_target:!1,large_drag_target:!1,use_html5:!1};var k,l;a.jstree.plugins.dnd=function(b,c){this.init=function(a,b){c.init.call(this,a,b),this.settings.dnd.use_html5=this.settings.dnd.use_html5&&"draggable"in i.createElement("span")},this.bind=function(){c.bind.call(this),this.element.on(this.settings.dnd.use_html5?"dragstart.jstree":"mousedown.jstree touchstart.jstree",this.settings.dnd.large_drag_target?".jstree-node":".jstree-anchor",a.proxy(function(b){if(this.settings.dnd.large_drag_target&&a(b.target).closest(".jstree-node")[0]!==b.currentTarget)return!0;if("touchstart"===b.type&&(!this.settings.dnd.touch||"selected"===this.settings.dnd.touch&&!a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").hasClass("jstree-clicked")))return!0;var c=this.get_node(b.target),d=this.is_selected(c)&&this.settings.dnd.drag_selection?this.get_top_selected().length:1,e=d>1?d+" "+this.get_string("nodes"):this.get_text(b.currentTarget);if(this.settings.core.force_text&&(e=a.vakata.html.escape(e)),c&&c.id&&c.id!==a.jstree.root&&(1===b.which||"touchstart"===b.type||"dragstart"===b.type)&&(this.settings.dnd.is_draggable===!0||a.isFunction(this.settings.dnd.is_draggable)&&this.settings.dnd.is_draggable.call(this,d>1?this.get_top_selected(!0):[c],b))){if(k={jstree:!0,origin:this,obj:this.get_node(c,!0),nodes:d>1?this.get_top_selected():[c.id]},l=b.currentTarget,!this.settings.dnd.use_html5)return this.element.trigger("mousedown.jstree"),a.vakata.dnd.start(b,k,'<div id="jstree-dnd" class="jstree-'+this.get_theme()+" jstree-"+this.get_theme()+"-"+this.get_theme_variant()+" "+(this.settings.core.themes.responsive?" jstree-dnd-responsive":"")+'"><i class="jstree-icon jstree-er"></i>'+e+'<ins class="jstree-copy" style="display:none;">+</ins></div>');a.vakata.dnd._trigger("start",b,{helper:a(),element:l,data:k})}},this)),this.settings.dnd.use_html5&&this.element.on("dragover.jstree",function(b){return b.preventDefault(),a.vakata.dnd._trigger("move",b,{helper:a(),element:l,data:k}),!1}).on("drop.jstree",a.proxy(function(b){return b.preventDefault(),a.vakata.dnd._trigger("stop",b,{helper:a(),element:l,data:k}),!1},this))},this.redraw_node=function(a,b,d,e){if(a=c.redraw_node.apply(this,arguments),a&&this.settings.dnd.use_html5)if(this.settings.dnd.large_drag_target)a.setAttribute("draggable",!0);else{var f,g,h=null;for(f=0,g=a.childNodes.length;g>f;f++)if(a.childNodes[f]&&a.childNodes[f].className&&-1!==a.childNodes[f].className.indexOf("jstree-anchor")){h=a.childNodes[f];break}h&&h.setAttribute("draggable",!0)}return a}},a(function(){var c=!1,d=!1,e=!1,f=!1,g=a('<div id="jstree-marker">&#160;</div>').hide();a(i).on("dnd_start.vakata.jstree",function(a,b){c=!1,e=!1,b&&b.data&&b.data.jstree&&g.appendTo("body")}).on("dnd_move.vakata.jstree",function(h,i){if(f&&(i.event&&"dragover"===i.event.type&&i.event.target===e.target||clearTimeout(f)),i&&i.data&&i.data.jstree&&(!i.event.target.id||"jstree-marker"!==i.event.target.id)){e=i.event;var j=a.jstree.reference(i.event.target),k=!1,l=!1,m=!1,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D;if(j&&j._data&&j._data.dnd)if(g.attr("class","jstree-"+j.get_theme()+(j.settings.core.themes.responsive?" jstree-dnd-responsive":"")),C=i.data.origin&&(i.data.origin.settings.dnd.always_copy||i.data.origin.settings.dnd.copy&&(i.event.metaKey||i.event.ctrlKey)),i.helper.children().attr("class","jstree-"+j.get_theme()+" jstree-"+j.get_theme()+"-"+j.get_theme_variant()+" "+(j.settings.core.themes.responsive?" jstree-dnd-responsive":"")).find(".jstree-copy").first()[C?"show":"hide"](),i.event.target!==j.element[0]&&i.event.target!==j.get_container_ul()[0]||0!==j.get_container_ul().children().length){if(k=j.settings.dnd.large_drop_target?a(i.event.target).closest(".jstree-node").children(".jstree-anchor"):a(i.event.target).closest(".jstree-anchor"),k&&k.length&&k.parent().is(".jstree-closed, .jstree-open, .jstree-leaf")&&(l=k.offset(),m=(i.event.pageY!==b?i.event.pageY:i.event.originalEvent.pageY)-l.top,q=k.outerHeight(),t=q/3>m?["b","i","a"]:m>q-q/3?["a","i","b"]:m>q/2?["i","a","b"]:["i","b","a"],a.each(t,function(b,e){switch(e){case"b":o=l.left-6,p=l.top,r=j.get_parent(k),s=k.parent().index();break;case"i":A=j.settings.dnd.inside_pos,B=j.get_node(k.parent()),o=l.left-2,p=l.top+q/2+1,r=B.id,s="first"===A?0:"last"===A?B.children.length:Math.min(A,B.children.length);break;case"a":o=l.left-6,p=l.top+q,r=j.get_parent(k),s=k.parent().index()+1}for(u=!0,v=0,w=i.data.nodes.length;w>v;v++)if(x=i.data.origin&&(i.data.origin.settings.dnd.always_copy||i.data.origin.settings.dnd.copy&&(i.event.metaKey||i.event.ctrlKey))?"copy_node":"move_node",y=s,"move_node"===x&&"a"===e&&i.data.origin&&i.data.origin===j&&r===j.get_parent(i.data.nodes[v])&&(z=j.get_node(r),y>a.inArray(i.data.nodes[v],z.children)&&(y-=1)),u=u&&(j&&j.settings&&j.settings.dnd&&j.settings.dnd.check_while_dragging===!1||j.check(x,i.data.origin&&i.data.origin!==j?i.data.origin.get_node(i.data.nodes[v]):i.data.nodes[v],r,y,{dnd:!0,ref:j.get_node(k.parent()),pos:e,origin:i.data.origin,is_multi:i.data.origin&&i.data.origin!==j,is_foreign:!i.data.origin})),!u){j&&j.last_error&&(d=j.last_error());break}return"i"===e&&k.parent().is(".jstree-closed")&&j.settings.dnd.open_timeout&&(f=setTimeout(function(a,b){return function(){a.open_node(b)}}(j,k),j.settings.dnd.open_timeout)),u?(D=j.get_node(r,!0),D.hasClass(".jstree-dnd-parent")||(a(".jstree-dnd-parent").removeClass("jstree-dnd-parent"),D.addClass("jstree-dnd-parent")),c={ins:j,par:r,pos:"i"!==e||"last"!==A||0!==s||j.is_loaded(B)?s:"last"},g.css({left:o+"px",top:p+"px"}).show(),i.helper.find(".jstree-icon").first().removeClass("jstree-er").addClass("jstree-ok"),i.event.originalEvent&&i.event.originalEvent.dataTransfer&&(i.event.originalEvent.dataTransfer.dropEffect=C?"copy":"move"),d={},t=!0,!1):void 0}),t===!0))return}else{for(u=!0,v=0,w=i.data.nodes.length;w>v;v++)if(u=u&&j.check(i.data.origin&&(i.data.origin.settings.dnd.always_copy||i.data.origin.settings.dnd.copy&&(i.event.metaKey||i.event.ctrlKey))?"copy_node":"move_node",i.data.origin&&i.data.origin!==j?i.data.origin.get_node(i.data.nodes[v]):i.data.nodes[v],a.jstree.root,"last",{dnd:!0,ref:j.get_node(a.jstree.root),pos:"i",origin:i.data.origin,is_multi:i.data.origin&&i.data.origin!==j,is_foreign:!i.data.origin}),!u)break;if(u)return c={ins:j,par:a.jstree.root,pos:"last"},g.hide(),i.helper.find(".jstree-icon").first().removeClass("jstree-er").addClass("jstree-ok"),void(i.event.originalEvent&&i.event.originalEvent.dataTransfer&&(i.event.originalEvent.dataTransfer.dropEffect=C?"copy":"move"))}a(".jstree-dnd-parent").removeClass("jstree-dnd-parent"),c=!1,i.helper.find(".jstree-icon").removeClass("jstree-ok").addClass("jstree-er"),i.event.originalEvent&&i.event.originalEvent.dataTransfer&&(i.event.originalEvent.dataTransfer.dropEffect="none"),g.hide()}}).on("dnd_scroll.vakata.jstree",function(a,b){b&&b.data&&b.data.jstree&&(g.hide(),c=!1,e=!1,b.helper.find(".jstree-icon").first().removeClass("jstree-ok").addClass("jstree-er"))}).on("dnd_stop.vakata.jstree",function(b,h){if(a(".jstree-dnd-parent").removeClass("jstree-dnd-parent"),f&&clearTimeout(f),h&&h.data&&h.data.jstree){g.hide().detach();var i,j,k=[];if(c){for(i=0,j=h.data.nodes.length;j>i;i++)k[i]=h.data.origin?h.data.origin.get_node(h.data.nodes[i]):h.data.nodes[i];c.ins[h.data.origin&&(h.data.origin.settings.dnd.always_copy||h.data.origin.settings.dnd.copy&&(h.event.metaKey||h.event.ctrlKey))?"copy_node":"move_node"](k,c.par,c.pos,!1,!1,!1,h.data.origin)}else i=a(h.event.target).closest(".jstree"),i.length&&d&&d.error&&"check"===d.error&&(i=i.jstree(!0),i&&i.settings.core.error.call(this,d));e=!1,c=!1}}).on("keyup.jstree keydown.jstree",function(b,h){h=a.vakata.dnd._get(),h&&h.data&&h.data.jstree&&("keyup"===b.type&&27===b.which?(f&&clearTimeout(f),c=!1,d=!1,e=!1,f=!1,g.hide().detach(),a.vakata.dnd._clean()):(h.helper.find(".jstree-copy").first()[h.data.origin&&(h.data.origin.settings.dnd.always_copy||h.data.origin.settings.dnd.copy&&(b.metaKey||b.ctrlKey))?"show":"hide"](),e&&(e.metaKey=b.metaKey,e.ctrlKey=b.ctrlKey,a.vakata.dnd._trigger("move",e))))})}),function(a){a.vakata.html={div:a("<div />"),escape:function(b){return a.vakata.html.div.text(b).html()},strip:function(b){return a.vakata.html.div.empty().append(a.parseHTML(b)).text()}};var c={element:!1,target:!1,is_down:!1,is_drag:!1,helper:!1,helper_w:0,data:!1,init_x:0,init_y:0,scroll_l:0,scroll_t:0,scroll_e:!1,scroll_i:!1,is_touch:!1};a.vakata.dnd={settings:{scroll_speed:10,scroll_proximity:20,helper_left:5,helper_top:10,threshold:5,threshold_touch:50},_trigger:function(c,d,e){e===b&&(e=a.vakata.dnd._get()),e.event=d,a(i).triggerHandler("dnd_"+c+".vakata",e)},_get:function(){return{data:c.data,element:c.element,helper:c.helper}},_clean:function(){c.helper&&c.helper.remove(),c.scroll_i&&(clearInterval(c.scroll_i),c.scroll_i=!1),c={element:!1,target:!1,is_down:!1,is_drag:!1,helper:!1,helper_w:0,data:!1,init_x:0,init_y:0,scroll_l:0,scroll_t:0,scroll_e:!1,scroll_i:!1,is_touch:!1},a(i).off("mousemove.vakata.jstree touchmove.vakata.jstree",a.vakata.dnd.drag),a(i).off("mouseup.vakata.jstree touchend.vakata.jstree",a.vakata.dnd.stop)},_scroll:function(b){if(!c.scroll_e||!c.scroll_l&&!c.scroll_t)return c.scroll_i&&(clearInterval(c.scroll_i),c.scroll_i=!1),!1;if(!c.scroll_i)return c.scroll_i=setInterval(a.vakata.dnd._scroll,100),!1;if(b===!0)return!1;var d=c.scroll_e.scrollTop(),e=c.scroll_e.scrollLeft();c.scroll_e.scrollTop(d+c.scroll_t*a.vakata.dnd.settings.scroll_speed),c.scroll_e.scrollLeft(e+c.scroll_l*a.vakata.dnd.settings.scroll_speed),(d!==c.scroll_e.scrollTop()||e!==c.scroll_e.scrollLeft())&&a.vakata.dnd._trigger("scroll",c.scroll_e)},start:function(b,d,e){"touchstart"===b.type&&b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(b.pageX=b.originalEvent.changedTouches[0].pageX,b.pageY=b.originalEvent.changedTouches[0].pageY,b.target=i.elementFromPoint(b.originalEvent.changedTouches[0].pageX-window.pageXOffset,b.originalEvent.changedTouches[0].pageY-window.pageYOffset)),c.is_drag&&a.vakata.dnd.stop({});try{b.currentTarget.unselectable="on",b.currentTarget.onselectstart=function(){return!1},b.currentTarget.style&&(b.currentTarget.style.touchAction="none",b.currentTarget.style.msTouchAction="none",b.currentTarget.style.MozUserSelect="none")}catch(f){}return c.init_x=b.pageX,c.init_y=b.pageY,c.data=d,c.is_down=!0,c.element=b.currentTarget,c.target=b.target,c.is_touch="touchstart"===b.type,e!==!1&&(c.helper=a("<div id='vakata-dnd'></div>").html(e).css({display:"block",margin:"0",padding:"0",position:"absolute",top:"-2000px",lineHeight:"16px",zIndex:"10000"})),a(i).on("mousemove.vakata.jstree touchmove.vakata.jstree",a.vakata.dnd.drag),a(i).on("mouseup.vakata.jstree touchend.vakata.jstree",a.vakata.dnd.stop),!1},drag:function(b){if("touchmove"===b.type&&b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(b.pageX=b.originalEvent.changedTouches[0].pageX,b.pageY=b.originalEvent.changedTouches[0].pageY,b.target=i.elementFromPoint(b.originalEvent.changedTouches[0].pageX-window.pageXOffset,b.originalEvent.changedTouches[0].pageY-window.pageYOffset)),c.is_down){if(!c.is_drag){if(!(Math.abs(b.pageX-c.init_x)>(c.is_touch?a.vakata.dnd.settings.threshold_touch:a.vakata.dnd.settings.threshold)||Math.abs(b.pageY-c.init_y)>(c.is_touch?a.vakata.dnd.settings.threshold_touch:a.vakata.dnd.settings.threshold)))return;c.helper&&(c.helper.appendTo("body"),c.helper_w=c.helper.outerWidth()),c.is_drag=!0,a(c.target).one("click.vakata",!1),a.vakata.dnd._trigger("start",b)}var d=!1,e=!1,f=!1,g=!1,h=!1,j=!1,k=!1,l=!1,m=!1,n=!1;return c.scroll_t=0,c.scroll_l=0,c.scroll_e=!1,a(a(b.target).parentsUntil("body").addBack().get().reverse()).filter(function(){return/^auto|scroll$/.test(a(this).css("overflow"))&&(this.scrollHeight>this.offsetHeight||this.scrollWidth>this.offsetWidth)}).each(function(){var d=a(this),e=d.offset();return this.scrollHeight>this.offsetHeight&&(e.top+d.height()-b.pageY<a.vakata.dnd.settings.scroll_proximity&&(c.scroll_t=1),b.pageY-e.top<a.vakata.dnd.settings.scroll_proximity&&(c.scroll_t=-1)),this.scrollWidth>this.offsetWidth&&(e.left+d.width()-b.pageX<a.vakata.dnd.settings.scroll_proximity&&(c.scroll_l=1),b.pageX-e.left<a.vakata.dnd.settings.scroll_proximity&&(c.scroll_l=-1)),c.scroll_t||c.scroll_l?(c.scroll_e=a(this),!1):void 0}),c.scroll_e||(d=a(i),e=a(window),f=d.height(),g=e.height(),h=d.width(),j=e.width(),k=d.scrollTop(),l=d.scrollLeft(),f>g&&b.pageY-k<a.vakata.dnd.settings.scroll_proximity&&(c.scroll_t=-1),f>g&&g-(b.pageY-k)<a.vakata.dnd.settings.scroll_proximity&&(c.scroll_t=1),h>j&&b.pageX-l<a.vakata.dnd.settings.scroll_proximity&&(c.scroll_l=-1),h>j&&j-(b.pageX-l)<a.vakata.dnd.settings.scroll_proximity&&(c.scroll_l=1),(c.scroll_t||c.scroll_l)&&(c.scroll_e=d)),c.scroll_e&&a.vakata.dnd._scroll(!0),c.helper&&(m=parseInt(b.pageY+a.vakata.dnd.settings.helper_top,10),n=parseInt(b.pageX+a.vakata.dnd.settings.helper_left,10),f&&m+25>f&&(m=f-50),h&&n+c.helper_w>h&&(n=h-(c.helper_w+2)),c.helper.css({left:n+"px",top:m+"px"})),a.vakata.dnd._trigger("move",b),!1}},stop:function(b){if("touchend"===b.type&&b.originalEvent&&b.originalEvent.changedTouches&&b.originalEvent.changedTouches[0]&&(b.pageX=b.originalEvent.changedTouches[0].pageX,b.pageY=b.originalEvent.changedTouches[0].pageY,b.target=i.elementFromPoint(b.originalEvent.changedTouches[0].pageX-window.pageXOffset,b.originalEvent.changedTouches[0].pageY-window.pageYOffset)),c.is_drag)b.target!==c.target&&a(c.target).off("click.vakata"),a.vakata.dnd._trigger("stop",b);else if("touchend"===b.type&&b.target===c.target){var d=setTimeout(function(){a(b.target).click()},100);a(b.target).one("click",function(){d&&clearTimeout(d)})}return a.vakata.dnd._clean(),!1}}}(a),a.jstree.defaults.massload=null,a.jstree.plugins.massload=function(b,c){this.init=function(a,b){this._data.massload={},c.init.call(this,a,b)},this._load_nodes=function(b,d,e,f){var g=this.settings.massload,h=JSON.stringify(b),i=[],j=this._model.data,k,l,m;if(!e){for(k=0,l=b.length;l>k;k++)(!j[b[k]]||!j[b[k]].state.loaded&&!j[b[k]].state.failed||f)&&(i.push(b[k]),m=this.get_node(b[k],!0),m&&m.length&&m.addClass("jstree-loading").attr("aria-busy",!0));if(this._data.massload={},i.length){if(a.isFunction(g))return g.call(this,i,a.proxy(function(a){var g,h;if(a)for(g in a)a.hasOwnProperty(g)&&(this._data.massload[g]=a[g]);for(g=0,h=b.length;h>g;g++)m=this.get_node(b[g],!0),m&&m.length&&m.removeClass("jstree-loading").attr("aria-busy",!1);c._load_nodes.call(this,b,d,e,f)},this));if("object"==typeof g&&g&&g.url)return g=a.extend(!0,{},g),a.isFunction(g.url)&&(g.url=g.url.call(this,i)),a.isFunction(g.data)&&(g.data=g.data.call(this,i)),a.ajax(g).done(a.proxy(function(a,g,h){var i,j;if(a)for(i in a)a.hasOwnProperty(i)&&(this._data.massload[i]=a[i]);for(i=0,j=b.length;j>i;i++)m=this.get_node(b[i],!0),m&&m.length&&m.removeClass("jstree-loading").attr("aria-busy",!1);c._load_nodes.call(this,b,d,e,f)},this)).fail(a.proxy(function(a){c._load_nodes.call(this,b,d,e,f)},this))}}return c._load_nodes.call(this,b,d,e,f)},this._load_node=function(b,d){var e=this._data.massload[b.id],f=null,g;return e?(f=this["string"==typeof e?"_append_html_data":"_append_json_data"](b,"string"==typeof e?a(a.parseHTML(e)).filter(function(){return 3!==this.nodeType}):e,function(a){d.call(this,a)}),g=this.get_node(b.id,!0),g&&g.length&&g.removeClass("jstree-loading").attr("aria-busy",!1),delete this._data.massload[b.id],f):c._load_node.call(this,b,d)}},a.jstree.defaults.search={ajax:!1,fuzzy:!1,case_sensitive:!1,show_only_matches:!1,show_only_matches_children:!1,close_opened_onclear:!0,search_leaves_only:!1,search_callback:!1},a.jstree.plugins.search=function(c,d){this.bind=function(){d.bind.call(this),this._data.search.str="",this._data.search.dom=a(),this._data.search.res=[],this._data.search.opn=[],this._data.search.som=!1,this._data.search.smc=!1,this._data.search.hdn=[],this.element.on("search.jstree",a.proxy(function(b,c){if(this._data.search.som&&c.res.length){var d=this._model.data,e,f,g=[],h,i;for(e=0,f=c.res.length;f>e;e++)if(d[c.res[e]]&&!d[c.res[e]].state.hidden&&(g.push(c.res[e]),g=g.concat(d[c.res[e]].parents),this._data.search.smc))for(h=0,i=d[c.res[e]].children_d.length;i>h;h++)d[d[c.res[e]].children_d[h]]&&!d[d[c.res[e]].children_d[h]].state.hidden&&g.push(d[c.res[e]].children_d[h]);g=a.vakata.array_remove_item(a.vakata.array_unique(g),a.jstree.root),this._data.search.hdn=this.hide_all(!0),this.show_node(g,!0),this.redraw(!0)}},this)).on("clear_search.jstree",a.proxy(function(a,b){this._data.search.som&&b.res.length&&(this.show_node(this._data.search.hdn,!0),this.redraw(!0))},this))},this.search=function(c,d,e,f,g,h){if(c===!1||""===a.trim(c.toString()))return this.clear_search();f=this.get_node(f),f=f&&f.id?f.id:null,c=c.toString();var i=this.settings.search,j=i.ajax?i.ajax:!1,k=this._model.data,l=null,m=[],n=[],o,p;if(this._data.search.res.length&&!g&&this.clear_search(),e===b&&(e=i.show_only_matches),h===b&&(h=i.show_only_matches_children),!d&&j!==!1)return a.isFunction(j)?j.call(this,c,a.proxy(function(b){b&&b.d&&(b=b.d),this._load_nodes(a.isArray(b)?a.vakata.array_unique(b):[],function(){this.search(c,!0,e,f,g,h)})},this),f):(j=a.extend({},j),j.data||(j.data={}),j.data.str=c,f&&(j.data.inside=f),this._data.search.lastRequest&&this._data.search.lastRequest.abort(),this._data.search.lastRequest=a.ajax(j).fail(a.proxy(function(){this._data.core.last_error={error:"ajax",plugin:"search",id:"search_01",reason:"Could not load search parents",data:JSON.stringify(j)},this.settings.core.error.call(this,this._data.core.last_error)},this)).done(a.proxy(function(b){b&&b.d&&(b=b.d),this._load_nodes(a.isArray(b)?a.vakata.array_unique(b):[],function(){this.search(c,!0,e,f,g,h)})},this)),this._data.search.lastRequest);if(g||(this._data.search.str=c,this._data.search.dom=a(),this._data.search.res=[],this._data.search.opn=[],this._data.search.som=e,this._data.search.smc=h),l=new a.vakata.search(c,!0,{caseSensitive:i.case_sensitive,fuzzy:i.fuzzy}),a.each(k[f?f:a.jstree.root].children_d,function(a,b){var d=k[b];d.text&&!d.state.hidden&&(!i.search_leaves_only||d.state.loaded&&0===d.children.length)&&(i.search_callback&&i.search_callback.call(this,c,d)||!i.search_callback&&l.search(d.text).isMatch)&&(m.push(b),n=n.concat(d.parents))}),m.length){for(n=a.vakata.array_unique(n),o=0,p=n.length;p>o;o++)n[o]!==a.jstree.root&&k[n[o]]&&this.open_node(n[o],null,0)===!0&&this._data.search.opn.push(n[o]);g?(this._data.search.dom=this._data.search.dom.add(a(this.element[0].querySelectorAll("#"+a.map(m,function(b){return-1!=="0123456789".indexOf(b[0])?"\\3"+b[0]+" "+b.substr(1).replace(a.jstree.idregex,"\\$&"):b.replace(a.jstree.idregex,"\\$&")}).join(", #")))),this._data.search.res=a.vakata.array_unique(this._data.search.res.concat(m))):(this._data.search.dom=a(this.element[0].querySelectorAll("#"+a.map(m,function(b){return-1!=="0123456789".indexOf(b[0])?"\\3"+b[0]+" "+b.substr(1).replace(a.jstree.idregex,"\\$&"):b.replace(a.jstree.idregex,"\\$&")}).join(", #"))),this._data.search.res=m),this._data.search.dom.children(".jstree-anchor").addClass("jstree-search")}this.trigger("search",{nodes:this._data.search.dom,str:c,res:this._data.search.res,show_only_matches:e})},this.clear_search=function(){this.settings.search.close_opened_onclear&&this.close_node(this._data.search.opn,0),this.trigger("clear_search",{nodes:this._data.search.dom,str:this._data.search.str,res:this._data.search.res}),this._data.search.res.length&&(this._data.search.dom=a(this.element[0].querySelectorAll("#"+a.map(this._data.search.res,function(b){return-1!=="0123456789".indexOf(b[0])?"\\3"+b[0]+" "+b.substr(1).replace(a.jstree.idregex,"\\$&"):b.replace(a.jstree.idregex,"\\$&")}).join(", #"))),this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search")),this._data.search.str="",this._data.search.res=[],this._data.search.opn=[],this._data.search.dom=a()},this.redraw_node=function(b,c,e,f){if(b=d.redraw_node.apply(this,arguments),b&&-1!==a.inArray(b.id,this._data.search.res)){var g,h,i=null;for(g=0,h=b.childNodes.length;h>g;g++)if(b.childNodes[g]&&b.childNodes[g].className&&-1!==b.childNodes[g].className.indexOf("jstree-anchor")){i=b.childNodes[g];break}i&&(i.className+=" jstree-search")}return b}},function(a){a.vakata.search=function(b,c,d){d=d||{},d=a.extend({},a.vakata.search.defaults,d),d.fuzzy!==!1&&(d.fuzzy=!0),b=d.caseSensitive?b:b.toLowerCase();var e=d.location,f=d.distance,g=d.threshold,h=b.length,i,j,k,l;return h>32&&(d.fuzzy=!1),d.fuzzy&&(i=1<<h-1,j=function(){var a={},c=0;for(c=0;h>c;c++)a[b.charAt(c)]=0;for(c=0;h>c;c++)a[b.charAt(c)]|=1<<h-c-1;return a}(),k=function(a,b){var c=a/h,d=Math.abs(e-b);return f?c+d/f:d?1:c}),l=function(a){if(a=d.caseSensitive?a:a.toLowerCase(),b===a||-1!==a.indexOf(b))return{isMatch:!0,score:0};if(!d.fuzzy)return{isMatch:!1,score:1};var c,f,l=a.length,m=g,n=a.indexOf(b,e),o,p,q=h+l,r,s,t,u,v,w=1,x=[];for(-1!==n&&(m=Math.min(k(0,n),m),n=a.lastIndexOf(b,e+h),-1!==n&&(m=Math.min(k(0,n),m))),n=-1,c=0;h>c;c++){o=0,p=q;while(p>o)k(c,e+p)<=m?o=p:q=p,p=Math.floor((q-o)/2+o);for(q=p,s=Math.max(1,e-p+1),t=Math.min(e+p,l)+h,u=new Array(t+2),u[t+1]=(1<<c)-1,f=t;f>=s;f--)if(v=j[a.charAt(f-1)],0===c?u[f]=(u[f+1]<<1|1)&v:u[f]=(u[f+1]<<1|1)&v|((r[f+1]|r[f])<<1|1)|r[f+1],u[f]&i&&(w=k(c,f-1),m>=w)){if(m=w,n=f-1,x.push(n),!(n>e))break;s=Math.max(1,2*e-n)}if(k(c+1,e)>m)break;r=u}return{isMatch:n>=0,score:w}},c===!0?{search:l}:l(c)},a.vakata.search.defaults={location:0,distance:100,threshold:.6,fuzzy:!1,caseSensitive:!1}}(a),a.jstree.defaults.sort=function(a,b){return this.get_text(a)>this.get_text(b)?1:-1},a.jstree.plugins.sort=function(b,c){this.bind=function(){c.bind.call(this),this.element.on("model.jstree",a.proxy(function(a,b){this.sort(b.parent,!0)},this)).on("rename_node.jstree create_node.jstree",a.proxy(function(a,b){this.sort(b.parent||b.node.parent,!1),this.redraw_node(b.parent||b.node.parent,!0)},this)).on("move_node.jstree copy_node.jstree",a.proxy(function(a,b){this.sort(b.parent,!1),this.redraw_node(b.parent,!0)},this))},this.sort=function(b,c){var d,e;if(b=this.get_node(b),b&&b.children&&b.children.length&&(b.children.sort(a.proxy(this.settings.sort,this)),c))for(d=0,e=b.children_d.length;e>d;d++)this.sort(b.children_d[d],!1)}};var m=!1;a.jstree.defaults.state={key:"jstree",events:"changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree",ttl:!1,filter:!1},a.jstree.plugins.state=function(b,c){this.bind=function(){c.bind.call(this);var b=a.proxy(function(){this.element.on(this.settings.state.events,a.proxy(function(){m&&clearTimeout(m),m=setTimeout(a.proxy(function(){this.save_state()},this),100)},this)),this.trigger("state_ready")},this);this.element.on("ready.jstree",a.proxy(function(a,c){this.element.one("restore_state.jstree",b),this.restore_state()||b()},this))},this.save_state=function(){var b={state:this.get_state(),ttl:this.settings.state.ttl,sec:+new Date};a.vakata.storage.set(this.settings.state.key,JSON.stringify(b))},this.restore_state=function(){var b=a.vakata.storage.get(this.settings.state.key);if(b)try{b=JSON.parse(b)}catch(c){return!1}return b&&b.ttl&&b.sec&&+new Date-b.sec>b.ttl?!1:(b&&b.state&&(b=b.state),b&&a.isFunction(this.settings.state.filter)&&(b=this.settings.state.filter.call(this,b)),b?(this.element.one("set_state.jstree",function(c,d){d.instance.trigger("restore_state",{state:a.extend(!0,{},b)})}),this.set_state(b),!0):!1)},this.clear_state=function(){return a.vakata.storage.del(this.settings.state.key)}},function(a,b){a.vakata.storage={set:function(a,b){return window.localStorage.setItem(a,b)},get:function(a){return window.localStorage.getItem(a)},del:function(a){return window.localStorage.removeItem(a)}}}(a),a.jstree.defaults.types={"default":{}},a.jstree.defaults.types[a.jstree.root]={},a.jstree.plugins.types=function(c,d){this.init=function(c,e){var f,g;if(e&&e.types&&e.types["default"])for(f in e.types)if("default"!==f&&f!==a.jstree.root&&e.types.hasOwnProperty(f))for(g in e.types["default"])e.types["default"].hasOwnProperty(g)&&e.types[f][g]===b&&(e.types[f][g]=e.types["default"][g]);d.init.call(this,c,e),this._model.data[a.jstree.root].type=a.jstree.root},this.refresh=function(b,c){d.refresh.call(this,b,c),this._model.data[a.jstree.root].type=a.jstree.root},this.bind=function(){this.element.on("model.jstree",a.proxy(function(c,d){var e=this._model.data,f=d.nodes,g=this.settings.types,h,i,j="default",k;for(h=0,i=f.length;i>h;h++){if(j="default",e[f[h]].original&&e[f[h]].original.type&&g[e[f[h]].original.type]&&(j=e[f[h]].original.type),e[f[h]].data&&e[f[h]].data.jstree&&e[f[h]].data.jstree.type&&g[e[f[h]].data.jstree.type]&&(j=e[f[h]].data.jstree.type),e[f[h]].type=j,e[f[h]].icon===!0&&g[j].icon!==b&&(e[f[h]].icon=g[j].icon),g[j].li_attr!==b&&"object"==typeof g[j].li_attr)for(k in g[j].li_attr)if(g[j].li_attr.hasOwnProperty(k)){if("id"===k)continue;e[f[h]].li_attr[k]===b?e[f[h]].li_attr[k]=g[j].li_attr[k]:"class"===k&&(e[f[h]].li_attr["class"]=g[j].li_attr["class"]+" "+e[f[h]].li_attr["class"])}if(g[j].a_attr!==b&&"object"==typeof g[j].a_attr)for(k in g[j].a_attr)if(g[j].a_attr.hasOwnProperty(k)){if("id"===k)continue;e[f[h]].a_attr[k]===b?e[f[h]].a_attr[k]=g[j].a_attr[k]:"href"===k&&"#"===e[f[h]].a_attr[k]?e[f[h]].a_attr.href=g[j].a_attr.href:"class"===k&&(e[f[h]].a_attr["class"]=g[j].a_attr["class"]+" "+e[f[h]].a_attr["class"])}}e[a.jstree.root].type=a.jstree.root},this)),d.bind.call(this)},this.get_json=function(b,c,e){var f,g,h=this._model.data,i=c?a.extend(!0,{},c,{no_id:!1}):{},j=d.get_json.call(this,b,i,e);if(j===!1)return!1;if(a.isArray(j))for(f=0,g=j.length;g>f;f++)j[f].type=j[f].id&&h[j[f].id]&&h[j[f].id].type?h[j[f].id].type:"default",c&&c.no_id&&(delete j[f].id,j[f].li_attr&&j[f].li_attr.id&&delete j[f].li_attr.id,j[f].a_attr&&j[f].a_attr.id&&delete j[f].a_attr.id);else j.type=j.id&&h[j.id]&&h[j.id].type?h[j.id].type:"default",c&&c.no_id&&(j=this._delete_ids(j));return j},this._delete_ids=function(b){if(a.isArray(b)){for(var c=0,d=b.length;d>c;c++)b[c]=this._delete_ids(b[c]);return b}return delete b.id,b.li_attr&&b.li_attr.id&&delete b.li_attr.id,b.a_attr&&b.a_attr.id&&delete b.a_attr.id,b.children&&a.isArray(b.children)&&(b.children=this._delete_ids(b.children)),b},this.check=function(c,e,f,g,h){if(d.check.call(this,c,e,f,g,h)===!1)return!1;e=e&&e.id?e:this.get_node(e),f=f&&f.id?f:this.get_node(f);var i=e&&e.id?h&&h.origin?h.origin:a.jstree.reference(e.id):null,j,k,l,m;switch(i=i&&i._model&&i._model.data?i._model.data:null,c){case"create_node":case"move_node":case"copy_node":if("move_node"!==c||-1===a.inArray(e.id,f.children)){if(j=this.get_rules(f),j.max_children!==b&&-1!==j.max_children&&j.max_children===f.children.length)return this._data.core.last_error={error:"check",plugin:"types",id:"types_01",reason:"max_children prevents function: "+c,data:JSON.stringify({chk:c,pos:g,obj:e&&e.id?e.id:!1,par:f&&f.id?f.id:!1})},!1;if(j.valid_children!==b&&-1!==j.valid_children&&-1===a.inArray(e.type||"default",j.valid_children))return this._data.core.last_error={error:"check",plugin:"types",id:"types_02",reason:"valid_children prevents function: "+c,data:JSON.stringify({chk:c,pos:g,obj:e&&e.id?e.id:!1,par:f&&f.id?f.id:!1})},!1;if(i&&e.children_d&&e.parents){for(k=0,l=0,m=e.children_d.length;m>l;l++)k=Math.max(k,i[e.children_d[l]].parents.length);k=k-e.parents.length+1}(0>=k||k===b)&&(k=1);do{if(j.max_depth!==b&&-1!==j.max_depth&&j.max_depth<k)return this._data.core.last_error={error:"check",plugin:"types",id:"types_03",reason:"max_depth prevents function: "+c,data:JSON.stringify({chk:c,pos:g,obj:e&&e.id?e.id:!1,par:f&&f.id?f.id:!1})},!1;f=this.get_node(f.parent),j=this.get_rules(f),k++}while(f)}}return!0},this.get_rules=function(a){if(a=this.get_node(a),!a)return!1;var c=this.get_type(a,!0);return c.max_depth===b&&(c.max_depth=-1),c.max_children===b&&(c.max_children=-1),c.valid_children===b&&(c.valid_children=-1),c},this.get_type=function(b,c){return b=this.get_node(b),b?c?a.extend({type:b.type},this.settings.types[b.type]):b.type:!1},this.set_type=function(c,d){var e=this._model.data,f,g,h,i,j,k,l,m;if(a.isArray(c)){for(c=c.slice(),g=0,h=c.length;h>g;g++)this.set_type(c[g],d);return!0}if(f=this.settings.types,c=this.get_node(c),!f[d]||!c)return!1;if(l=this.get_node(c,!0),l&&l.length&&(m=l.children(".jstree-anchor")),i=c.type,j=this.get_icon(c),c.type=d,(j===!0||!f[i]||f[i].icon!==b&&j===f[i].icon)&&this.set_icon(c,f[d].icon!==b?f[d].icon:!0),f[i]&&f[i].li_attr!==b&&"object"==typeof f[i].li_attr)for(k in f[i].li_attr)if(f[i].li_attr.hasOwnProperty(k)){if("id"===k)continue;"class"===k?(e[c.id].li_attr["class"]=(e[c.id].li_attr["class"]||"").replace(f[i].li_attr[k],""),l&&l.removeClass(f[i].li_attr[k])):e[c.id].li_attr[k]===f[i].li_attr[k]&&(e[c.id].li_attr[k]=null,l&&l.removeAttr(k))}if(f[i]&&f[i].a_attr!==b&&"object"==typeof f[i].a_attr)for(k in f[i].a_attr)if(f[i].a_attr.hasOwnProperty(k)){if("id"===k)continue;"class"===k?(e[c.id].a_attr["class"]=(e[c.id].a_attr["class"]||"").replace(f[i].a_attr[k],""),m&&m.removeClass(f[i].a_attr[k])):e[c.id].a_attr[k]===f[i].a_attr[k]&&("href"===k?(e[c.id].a_attr[k]="#",m&&m.attr("href","#")):(delete e[c.id].a_attr[k],m&&m.removeAttr(k)))}if(f[d].li_attr!==b&&"object"==typeof f[d].li_attr)for(k in f[d].li_attr)if(f[d].li_attr.hasOwnProperty(k)){if("id"===k)continue;e[c.id].li_attr[k]===b?(e[c.id].li_attr[k]=f[d].li_attr[k],l&&("class"===k?l.addClass(f[d].li_attr[k]):l.attr(k,f[d].li_attr[k]))):"class"===k&&(e[c.id].li_attr["class"]=f[d].li_attr[k]+" "+e[c.id].li_attr["class"],l&&l.addClass(f[d].li_attr[k]))}if(f[d].a_attr!==b&&"object"==typeof f[d].a_attr)for(k in f[d].a_attr)if(f[d].a_attr.hasOwnProperty(k)){if("id"===k)continue;e[c.id].a_attr[k]===b?(e[c.id].a_attr[k]=f[d].a_attr[k],m&&("class"===k?m.addClass(f[d].a_attr[k]):m.attr(k,f[d].a_attr[k]))):"href"===k&&"#"===e[c.id].a_attr[k]?(e[c.id].a_attr.href=f[d].a_attr.href,m&&m.attr("href",f[d].a_attr.href)):"class"===k&&(e[c.id].a_attr["class"]=f[d].a_attr["class"]+" "+e[c.id].a_attr["class"],m&&m.addClass(f[d].a_attr[k]))}return!0}},a.jstree.defaults.unique={case_sensitive:!1,duplicate:function(a,b){return a+" ("+b+")"}},a.jstree.plugins.unique=function(c,d){this.check=function(b,c,e,f,g){if(d.check.call(this,b,c,e,f,g)===!1)return!1;
if(c=c&&c.id?c:this.get_node(c),e=e&&e.id?e:this.get_node(e),!e||!e.children)return!0;var h="rename_node"===b?f:c.text,i=[],j=this.settings.unique.case_sensitive,k=this._model.data,l,m;for(l=0,m=e.children.length;m>l;l++)i.push(j?k[e.children[l]].text:k[e.children[l]].text.toLowerCase());switch(j||(h=h.toLowerCase()),b){case"delete_node":return!0;case"rename_node":return l=-1===a.inArray(h,i)||c.text&&c.text[j?"toString":"toLowerCase"]()===h,l||(this._data.core.last_error={error:"check",plugin:"unique",id:"unique_01",reason:"Child with name "+h+" already exists. Preventing: "+b,data:JSON.stringify({chk:b,pos:f,obj:c&&c.id?c.id:!1,par:e&&e.id?e.id:!1})}),l;case"create_node":return l=-1===a.inArray(h,i),l||(this._data.core.last_error={error:"check",plugin:"unique",id:"unique_04",reason:"Child with name "+h+" already exists. Preventing: "+b,data:JSON.stringify({chk:b,pos:f,obj:c&&c.id?c.id:!1,par:e&&e.id?e.id:!1})}),l;case"copy_node":return l=-1===a.inArray(h,i),l||(this._data.core.last_error={error:"check",plugin:"unique",id:"unique_02",reason:"Child with name "+h+" already exists. Preventing: "+b,data:JSON.stringify({chk:b,pos:f,obj:c&&c.id?c.id:!1,par:e&&e.id?e.id:!1})}),l;case"move_node":return l=c.parent===e.id&&(!g||!g.is_multi)||-1===a.inArray(h,i),l||(this._data.core.last_error={error:"check",plugin:"unique",id:"unique_03",reason:"Child with name "+h+" already exists. Preventing: "+b,data:JSON.stringify({chk:b,pos:f,obj:c&&c.id?c.id:!1,par:e&&e.id?e.id:!1})}),l}return!0},this.create_node=function(c,e,f,g,h){if(!e||e.text===b){if(null===c&&(c=a.jstree.root),c=this.get_node(c),!c)return d.create_node.call(this,c,e,f,g,h);if(f=f===b?"last":f,!f.toString().match(/^(before|after)$/)&&!h&&!this.is_loaded(c))return d.create_node.call(this,c,e,f,g,h);e||(e={});var i,j,k,l,m,n=this._model.data,o=this.settings.unique.case_sensitive,p=this.settings.unique.duplicate;for(j=i=this.get_string("New node"),k=[],l=0,m=c.children.length;m>l;l++)k.push(o?n[c.children[l]].text:n[c.children[l]].text.toLowerCase());l=1;while(-1!==a.inArray(o?j:j.toLowerCase(),k))j=p.call(this,i,++l).toString();e.text=j}return d.create_node.call(this,c,e,f,g,h)}};var n=i.createElement("DIV");if(n.setAttribute("unselectable","on"),n.setAttribute("role","presentation"),n.className="jstree-wholerow",n.innerHTML="&#160;",a.jstree.plugins.wholerow=function(b,c){this.bind=function(){c.bind.call(this),this.element.on("ready.jstree set_state.jstree",a.proxy(function(){this.hide_dots()},this)).on("init.jstree loading.jstree ready.jstree",a.proxy(function(){this.get_container_ul().addClass("jstree-wholerow-ul")},this)).on("deselect_all.jstree",a.proxy(function(a,b){this.element.find(".jstree-wholerow-clicked").removeClass("jstree-wholerow-clicked")},this)).on("changed.jstree",a.proxy(function(a,b){this.element.find(".jstree-wholerow-clicked").removeClass("jstree-wholerow-clicked");var c=!1,d,e;for(d=0,e=b.selected.length;e>d;d++)c=this.get_node(b.selected[d],!0),c&&c.length&&c.children(".jstree-wholerow").addClass("jstree-wholerow-clicked")},this)).on("open_node.jstree",a.proxy(function(a,b){this.get_node(b.node,!0).find(".jstree-clicked").parent().children(".jstree-wholerow").addClass("jstree-wholerow-clicked")},this)).on("hover_node.jstree dehover_node.jstree",a.proxy(function(a,b){"hover_node"===a.type&&this.is_disabled(b.node)||this.get_node(b.node,!0).children(".jstree-wholerow")["hover_node"===a.type?"addClass":"removeClass"]("jstree-wholerow-hovered")},this)).on("contextmenu.jstree",".jstree-wholerow",a.proxy(function(b){if(this._data.contextmenu){b.preventDefault();var c=a.Event("contextmenu",{metaKey:b.metaKey,ctrlKey:b.ctrlKey,altKey:b.altKey,shiftKey:b.shiftKey,pageX:b.pageX,pageY:b.pageY});a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(c)}},this)).on("click.jstree",".jstree-wholerow",function(b){b.stopImmediatePropagation();var c=a.Event("click",{metaKey:b.metaKey,ctrlKey:b.ctrlKey,altKey:b.altKey,shiftKey:b.shiftKey});a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(c).focus()}).on("dblclick.jstree",".jstree-wholerow",function(b){b.stopImmediatePropagation();var c=a.Event("dblclick",{metaKey:b.metaKey,ctrlKey:b.ctrlKey,altKey:b.altKey,shiftKey:b.shiftKey});a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(c).focus()}).on("click.jstree",".jstree-leaf > .jstree-ocl",a.proxy(function(b){b.stopImmediatePropagation();var c=a.Event("click",{metaKey:b.metaKey,ctrlKey:b.ctrlKey,altKey:b.altKey,shiftKey:b.shiftKey});a(b.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(c).focus()},this)).on("mouseover.jstree",".jstree-wholerow, .jstree-icon",a.proxy(function(a){return a.stopImmediatePropagation(),this.is_disabled(a.currentTarget)||this.hover_node(a.currentTarget),!1},this)).on("mouseleave.jstree",".jstree-node",a.proxy(function(a){this.dehover_node(a.currentTarget)},this))},this.teardown=function(){this.settings.wholerow&&this.element.find(".jstree-wholerow").remove(),c.teardown.call(this)},this.redraw_node=function(b,d,e,f){if(b=c.redraw_node.apply(this,arguments)){var g=n.cloneNode(!0);-1!==a.inArray(b.id,this._data.core.selected)&&(g.className+=" jstree-wholerow-clicked"),this._data.core.focused&&this._data.core.focused===b.id&&(g.className+=" jstree-wholerow-hovered"),b.insertBefore(g,b.childNodes[0])}return b}},i.registerElement&&Object&&Object.create){var o=Object.create(HTMLElement.prototype);o.createdCallback=function(){var b={core:{},plugins:[]},c;for(c in a.jstree.plugins)a.jstree.plugins.hasOwnProperty(c)&&this.attributes[c]&&(b.plugins.push(c),this.getAttribute(c)&&JSON.parse(this.getAttribute(c))&&(b[c]=JSON.parse(this.getAttribute(c))));for(c in a.jstree.defaults.core)a.jstree.defaults.core.hasOwnProperty(c)&&this.attributes[c]&&(b.core[c]=JSON.parse(this.getAttribute(c))||this.getAttribute(c));a(this).jstree(b)};try{i.registerElement("vakata-jstree",{prototype:o})}catch(p){}}}});PNG

   
IHDR  @   `   [[   PLTE   ZU`  999   eG	  
k~~~[Ub   [ߟe^
uw|  qX.		vw|/$--!!*,U=tM Y+烃|aAfIaktYz^JopggIIYYcc  ڝW8i&&&ޝcUW[$ \cpȍƘU5 6ҷܻמɳ{ģFmC6ԥgtv{- 2 ]*/ }HZB    9  ˢ@5MO믯Jٲ"xBǠހph1^F' `,#  ff*nK̽n{ZܙmwZqWŵ  ww&&ֺ==Qe7ab`ߊxo8ϸͭ޶bC11̩Q:վl؇n?U^{oѵ;41),R||Ωö÷YuuuvPx{lb   tRNS .ZA&	[5"\G9ELn٬`شݜك"WjfT  IDATxyXfz (sιM&[hQPYJ[Q"(8FA
 SйMqל^t9w;}L~IYB'4<ϛi>|oJյr馻?I $$$$jπh$	*0v}^zc{vkaHIQ`ViV9cvc@1O`v/)
ɳX,^(h3gΜRRb˒_A))	\ %GP߉ud$L߉
m3BLX_곚_>mH`%+^_y\$n/#CbHF
)l!"P`Iהl/փ[PT_{U4OX`e0$+2mZSF
i(pǼO'!6QطCsKߘH'KJ^},00CF=d%PFX2dh.f߬vќ^9	9f#q| 3K=@/'~|_nGF,֖<]RR;rt
>S9mڴM4,0	t"%	Khzp^2&ӧO:uC=rfcn{|2"2׳cjB+
$%%=(H 3u&p{*'sgRhVW9!C|_=S_Gx{.ǉĹsv8P@\!!}GH.pmq88q|wvnnzNJzveA1PO5@YYaS0 Ʈ7cf&h#s(6F4=m"H	$I $@H 	$	$H; 	Iln0zpoHO9/5/Ϥ&lm0 m}i6<z y)U	,0"lԛ	,q`1(wa2!h)6(޿pc2\9\	,nV 	⣓2(\RyMBD͢ ;I$23
|WXZփ[:G0b;?򕄀mԿYZ$0h+XsN@ _XW(0`W M2(,嚅eƎdP)y7v8BP j]zZ;jz,ӈLHN[VJ%Е3X &U@a_}CF[}K@gVVr1L1x/o1@ ޅ p6h1mC`Y:=<EKpƁ]Ai&w@\q q4uh,@-l5)D_@1p&H :/aKd*gjm-jm5UJ#ta.|Ry ;BH 	$$@$@H 5</$rU$^D^u
Zau堿_F^EA."B'b0޶e	`pH-c##oܳ;ؠ6##2w7s5ksxS	8`[Ǡ݂FtӨWT<߾[\A^v8
ƄsÁCϾnݾK!ؠu):Ly	pq=3xZ."qJ_?7*^r(!8&<$J_Ly368"UNaN`\zQX~
ml,on[Q1{t=3X ȟڷbG.:_0F.J?1<TՃߊ2:zPcUjI,`4ԃBԃbØS9?Fi[Hx֯J_3Ǥ,;E6"w:<Zh$@$9r
n    IENDB`PNG

   
IHDR        *   ?PLTE   333333333333333333333333333333333333333333333333<<<{n`f   tRNS \Y!
]PS  IDATx&ab'rt5:cCt/FZxj4MiS0`0D핉JxEǫgG^8{*pu*qxUg窼Uzq(o+;7~˝smkS~n	m}Jύ5*;7ԫSG˰W6K_hY1u-xU7^P NJGBli)ƛ6jZ^mg^!u~X0Xs7}nZĺ-)KXe-/%^RĴxK+;F0`0`0`,K,"^OeHadmu6(kS^8+dH`QgU5	KYQ`Q
*I3v
2=-gbkb?͒H,;bFCp#I"#Kf]K	 5AKsKu	7dӥV_ZORv<H[9xXRp{ @xn
`0`0`0`6! ÖCJpA+!'wm_)[^ώ;iIzv;L .ճni;ޒzvN."7W"Uޒכm_up/7(Cpu{t~TK~X+r~%݂k{^^ӗVgr8ڵl
K=<4-3n{ `Y| ^>5b}E>e~2M>i}T?m}B\`=Ab=Efd=m)Ɍ'`?0`0`0`0`8._Ep4#{ΉdS'O~ },1n_T3
0`p:70` 
	ćj7n4ބxxI-ҖRĴtֵ 0`0`쭂0`: x8_?b?eX#]Oc4p
;_?1Z?kq?84~ZπSo0`0` S4d<+0`0`0`=.NcWv0[6Zqan me\U]6L2=iWm.߿j{2uઝWMބ&~1ޕwnYPU!pbҥ#vI)ةᚄbk]˽I'<x<5-Hү!Iւ\/uTBւ}jZ}֦"51
o"mA7U KZ>EOb97O2Z*`վ.u8.QrKS1;&ef%
Ã"EGOI|uvxXL [,Ļ?Wv<Z^ۧ _/b>e,HRpA!x+._p'o8x-= 0`[J    IENDB`/* jsTree default theme */
.jstree-node,
.jstree-children,
.jstree-container-ul {
  display: block;
  margin: 0;
  padding: 0;
  list-style-type: none;
  list-style-image: none;
}
.jstree-node {
  white-space: nowrap;
}
.jstree-anchor {
  display: inline-block;
  color: black;
  white-space: nowrap;
  padding: 0 4px 0 1px;
  margin: 0;
  vertical-align: top;
}
.jstree-anchor:focus {
  outline: 0;
}
.jstree-anchor,
.jstree-anchor:link,
.jstree-anchor:visited,
.jstree-anchor:hover,
.jstree-anchor:active {
  text-decoration: none;
  color: inherit;
}
.jstree-icon {
  display: inline-block;
  text-decoration: none;
  margin: 0;
  padding: 0;
  vertical-align: top;
  text-align: center;
}
.jstree-icon:empty {
  display: inline-block;
  text-decoration: none;
  margin: 0;
  padding: 0;
  vertical-align: top;
  text-align: center;
}
.jstree-ocl {
  cursor: pointer;
}
.jstree-leaf > .jstree-ocl {
  cursor: default;
}
.jstree .jstree-open > .jstree-children {
  display: block;
}
.jstree .jstree-closed > .jstree-children,
.jstree .jstree-leaf > .jstree-children {
  display: none;
}
.jstree-anchor > .jstree-themeicon {
  margin-right: 2px;
}
.jstree-no-icons .jstree-themeicon,
.jstree-anchor > .jstree-themeicon-hidden {
  display: none;
}
.jstree-hidden,
.jstree-node.jstree-hidden {
  display: none;
}
.jstree-rtl .jstree-anchor {
  padding: 0 1px 0 4px;
}
.jstree-rtl .jstree-anchor > .jstree-themeicon {
  margin-left: 2px;
  margin-right: 0;
}
.jstree-rtl .jstree-node {
  margin-left: 0;
}
.jstree-rtl .jstree-container-ul > .jstree-node {
  margin-right: 0;
}
.jstree-wholerow-ul {
  position: relative;
  display: inline-block;
  min-width: 100%;
}
.jstree-wholerow-ul .jstree-leaf > .jstree-ocl {
  cursor: pointer;
}
.jstree-wholerow-ul .jstree-anchor,
.jstree-wholerow-ul .jstree-icon {
  position: relative;
}
.jstree-wholerow-ul .jstree-wholerow {
  width: 100%;
  cursor: pointer;
  position: absolute;
  left: 0;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.vakata-context {
  display: none;
}
.vakata-context,
.vakata-context ul {
  margin: 0;
  padding: 2px;
  position: absolute;
  background: #f5f5f5;
  border: 1px solid #979797;
  box-shadow: 2px 2px 2px #999999;
}
.vakata-context ul {
  list-style: none;
  left: 100%;
  margin-top: -2.7em;
  margin-left: -4px;
}
.vakata-context .vakata-context-right ul {
  left: auto;
  right: 100%;
  margin-left: auto;
  margin-right: -4px;
}
.vakata-context li {
  list-style: none;
}
.vakata-context li > a {
  display: block;
  padding: 0 2em 0 2em;
  text-decoration: none;
  width: auto;
  color: black;
  white-space: nowrap;
  line-height: 2.4em;
  text-shadow: 1px 1px 0 white;
  border-radius: 1px;
}
.vakata-context li > a:hover {
  position: relative;
  background-color: #e8eff7;
  box-shadow: 0 0 2px #0a6aa1;
}
.vakata-context li > a.vakata-context-parent {
  background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==");
  background-position: right center;
  background-repeat: no-repeat;
}
.vakata-context li > a:focus {
  outline: 0;
}
.vakata-context .vakata-context-hover > a {
  position: relative;
  background-color: #e8eff7;
  box-shadow: 0 0 2px #0a6aa1;
}
.vakata-context .vakata-context-separator > a,
.vakata-context .vakata-context-separator > a:hover {
  background: white;
  border: 0;
  border-top: 1px solid #e2e3e3;
  height: 1px;
  min-height: 1px;
  max-height: 1px;
  padding: 0;
  margin: 0 0 0 2.4em;
  border-left: 1px solid #e0e0e0;
  text-shadow: 0 0 0 transparent;
  box-shadow: 0 0 0 transparent;
  border-radius: 0;
}
.vakata-context .vakata-contextmenu-disabled a,
.vakata-context .vakata-contextmenu-disabled a:hover {
  color: silver;
  background-color: transparent;
  border: 0;
  box-shadow: 0 0 0;
}
.vakata-context li > a > i {
  text-decoration: none;
  display: inline-block;
  width: 2.4em;
  height: 2.4em;
  background: transparent;
  margin: 0 0 0 -2em;
  vertical-align: top;
  text-align: center;
  line-height: 2.4em;
}
.vakata-context li > a > i:empty {
  width: 2.4em;
  line-height: 2.4em;
}
.vakata-context li > a .vakata-contextmenu-sep {
  display: inline-block;
  width: 1px;
  height: 2.4em;
  background: white;
  margin: 0 0.5em 0 0;
  border-left: 1px solid #e2e3e3;
}
.vakata-context .vakata-contextmenu-shortcut {
  font-size: 0.8em;
  color: silver;
  opacity: 0.5;
  display: none;
}
.vakata-context-rtl ul {
  left: auto;
  right: 100%;
  margin-left: auto;
  margin-right: -4px;
}
.vakata-context-rtl li > a.vakata-context-parent {
  background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7");
  background-position: left center;
  background-repeat: no-repeat;
}
.vakata-context-rtl .vakata-context-separator > a {
  margin: 0 2.4em 0 0;
  border-left: 0;
  border-right: 1px solid #e2e3e3;
}
.vakata-context-rtl .vakata-context-left ul {
  right: auto;
  left: 100%;
  margin-left: -4px;
  margin-right: auto;
}
.vakata-context-rtl li > a > i {
  margin: 0 -2em 0 0;
}
.vakata-context-rtl li > a .vakata-contextmenu-sep {
  margin: 0 0 0 0.5em;
  border-left-color: white;
  background: #e2e3e3;
}
#jstree-marker {
  position: absolute;
  top: 0;
  left: 0;
  margin: -5px 0 0 0;
  padding: 0;
  border-right: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 5px solid;
  width: 0;
  height: 0;
  font-size: 0;
  line-height: 0;
}
#jstree-dnd {
  line-height: 16px;
  margin: 0;
  padding: 4px;
}
#jstree-dnd .jstree-icon,
#jstree-dnd .jstree-copy {
  display: inline-block;
  text-decoration: none;
  margin: 0 2px 0 0;
  padding: 0;
  width: 16px;
  height: 16px;
}
#jstree-dnd .jstree-ok {
  background: green;
}
#jstree-dnd .jstree-er {
  background: red;
}
#jstree-dnd .jstree-copy {
  margin: 0 2px 0 2px;
}
.jstree-default .jstree-node,
.jstree-default .jstree-icon {
  background-repeat: no-repeat;
  background-color: transparent;
}
.jstree-default .jstree-anchor,
.jstree-default .jstree-animated,
.jstree-default .jstree-wholerow {
  transition: background-color 0.15s, box-shadow 0.15s;
}
.jstree-default .jstree-hovered {
  background: #e7f4f9;
  border-radius: 2px;
  box-shadow: inset 0 0 1px #cccccc;
}
.jstree-default .jstree-context {
  background: #e7f4f9;
  border-radius: 2px;
  box-shadow: inset 0 0 1px #cccccc;
}
.jstree-default .jstree-clicked {
  background: #beebff;
  border-radius: 2px;
  box-shadow: inset 0 0 1px #999999;
}
.jstree-default .jstree-no-icons .jstree-anchor > .jstree-themeicon {
  display: none;
}
.jstree-default .jstree-disabled {
  background: transparent;
  color: #666666;
}
.jstree-default .jstree-disabled.jstree-hovered {
  background: transparent;
  box-shadow: none;
}
.jstree-default .jstree-disabled.jstree-clicked {
  background: #efefef;
}
.jstree-default .jstree-disabled > .jstree-icon {
  opacity: 0.8;
  filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");
  /* Firefox 10+ */
  filter: gray;
  /* IE6-9 */
  -webkit-filter: grayscale(100%);
  /* Chrome 19+ & Safari 6+ */
}
.jstree-default .jstree-search {
  font-style: italic;
  color: #8b0000;
  font-weight: bold;
}
.jstree-default .jstree-no-checkboxes .jstree-checkbox {
  display: none !important;
}
.jstree-default.jstree-checkbox-no-clicked .jstree-clicked {
  background: transparent;
  box-shadow: none;
}
.jstree-default.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered {
  background: #e7f4f9;
}
.jstree-default.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked {
  background: transparent;
}
.jstree-default.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered {
  background: #e7f4f9;
}
.jstree-default > .jstree-striped {
  min-width: 100%;
  display: inline-block;
  background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==") left top repeat;
}
.jstree-default > .jstree-wholerow-ul .jstree-hovered,
.jstree-default > .jstree-wholerow-ul .jstree-clicked {
  background: transparent;
  box-shadow: none;
  border-radius: 0;
}
.jstree-default .jstree-wholerow {
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.jstree-default .jstree-wholerow-hovered {
  background: #e7f4f9;
}
.jstree-default .jstree-wholerow-clicked {
  background: #beebff;
  background: -webkit-linear-gradient(top, #beebff 0%, #a8e4ff 100%);
  background: linear-gradient(to bottom, #beebff 0%, #a8e4ff 100%);
}
.jstree-default .jstree-node {
  min-height: 24px;
  line-height: 24px;
  margin-left: 24px;
  min-width: 24px;
}
.jstree-default .jstree-anchor {
  line-height: 24px;
  height: 24px;
}
.jstree-default .jstree-icon {
  width: 24px;
  height: 24px;
  line-height: 24px;
}
.jstree-default .jstree-icon:empty {
  width: 24px;
  height: 24px;
  line-height: 24px;
}
.jstree-default.jstree-rtl .jstree-node {
  margin-right: 24px;
}
.jstree-default .jstree-wholerow {
  height: 24px;
}
.jstree-default .jstree-node,
.jstree-default .jstree-icon {
  background-image: url("32px.png");
}
.jstree-default .jstree-node {
  background-position: -292px -4px;
  background-repeat: repeat-y;
}
.jstree-default .jstree-last {
  background: transparent;
}
.jstree-default .jstree-open > .jstree-ocl {
  background-position: -132px -4px;
}
.jstree-default .jstree-closed > .jstree-ocl {
  background-position: -100px -4px;
}
.jstree-default .jstree-leaf > .jstree-ocl {
  background-position: -68px -4px;
}
.jstree-default .jstree-themeicon {
  background-position: -260px -4px;
}
.jstree-default > .jstree-no-dots .jstree-node,
.jstree-default > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -36px -4px;
}
.jstree-default > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: -4px -4px;
}
.jstree-default .jstree-disabled {
  background: transparent;
}
.jstree-default .jstree-disabled.jstree-hovered {
  background: transparent;
}
.jstree-default .jstree-disabled.jstree-clicked {
  background: #efefef;
}
.jstree-default .jstree-checkbox {
  background-position: -164px -4px;
}
.jstree-default .jstree-checkbox:hover {
  background-position: -164px -36px;
}
.jstree-default.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default .jstree-checked > .jstree-checkbox {
  background-position: -228px -4px;
}
.jstree-default.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default .jstree-checked > .jstree-checkbox:hover {
  background-position: -228px -36px;
}
.jstree-default .jstree-anchor > .jstree-undetermined {
  background-position: -196px -4px;
}
.jstree-default .jstree-anchor > .jstree-undetermined:hover {
  background-position: -196px -36px;
}
.jstree-default .jstree-checkbox-disabled {
  opacity: 0.8;
  filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");
  /* Firefox 10+ */
  filter: gray;
  /* IE6-9 */
  -webkit-filter: grayscale(100%);
  /* Chrome 19+ & Safari 6+ */
}
.jstree-default > .jstree-striped {
  background-size: auto 48px;
}
.jstree-default.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
  background-position: 100% 1px;
  background-repeat: repeat-y;
}
.jstree-default.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default.jstree-rtl .jstree-open > .jstree-ocl {
  background-position: -132px -36px;
}
.jstree-default.jstree-rtl .jstree-closed > .jstree-ocl {
  background-position: -100px -36px;
}
.jstree-default.jstree-rtl .jstree-leaf > .jstree-ocl {
  background-position: -68px -36px;
}
.jstree-default.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -36px -36px;
}
.jstree-default.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: -4px -36px;
}
.jstree-default .jstree-themeicon-custom {
  background-color: transparent;
  background-image: none;
  background-position: 0 0;
}
.jstree-default > .jstree-container-ul .jstree-loading > .jstree-ocl {
  background: url("throbber.gif") center center no-repeat;
}
.jstree-default .jstree-file {
  background: url("32px.png") -100px -68px no-repeat;
}
.jstree-default .jstree-folder {
  background: url("32px.png") -260px -4px no-repeat;
}
.jstree-default > .jstree-container-ul > .jstree-node {
  margin-left: 0;
  margin-right: 0;
}
#jstree-dnd.jstree-default {
  line-height: 24px;
  padding: 0 4px;
}
#jstree-dnd.jstree-default .jstree-ok,
#jstree-dnd.jstree-default .jstree-er {
  background-image: url("32px.png");
  background-repeat: no-repeat;
  background-color: transparent;
}
#jstree-dnd.jstree-default i {
  background: transparent;
  width: 24px;
  height: 24px;
  line-height: 24px;
}
#jstree-dnd.jstree-default .jstree-ok {
  background-position: -4px -68px;
}
#jstree-dnd.jstree-default .jstree-er {
  background-position: -36px -68px;
}
.jstree-default .jstree-ellipsis {
  overflow: hidden;
}
.jstree-default .jstree-ellipsis .jstree-anchor {
  width: calc(100% - 29px);
  text-overflow: ellipsis;
  overflow: hidden;
}
.jstree-default .jstree-ellipsis.jstree-no-icons .jstree-anchor {
  width: calc(100% - 5px);
}
.jstree-default.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
}
.jstree-default.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-small .jstree-node {
  min-height: 18px;
  line-height: 18px;
  margin-left: 18px;
  min-width: 18px;
}
.jstree-default-small .jstree-anchor {
  line-height: 18px;
  height: 18px;
}
.jstree-default-small .jstree-icon {
  width: 18px;
  height: 18px;
  line-height: 18px;
}
.jstree-default-small .jstree-icon:empty {
  width: 18px;
  height: 18px;
  line-height: 18px;
}
.jstree-default-small.jstree-rtl .jstree-node {
  margin-right: 18px;
}
.jstree-default-small .jstree-wholerow {
  height: 18px;
}
.jstree-default-small .jstree-node,
.jstree-default-small .jstree-icon {
  background-image: url("32px.png");
}
.jstree-default-small .jstree-node {
  background-position: -295px -7px;
  background-repeat: repeat-y;
}
.jstree-default-small .jstree-last {
  background: transparent;
}
.jstree-default-small .jstree-open > .jstree-ocl {
  background-position: -135px -7px;
}
.jstree-default-small .jstree-closed > .jstree-ocl {
  background-position: -103px -7px;
}
.jstree-default-small .jstree-leaf > .jstree-ocl {
  background-position: -71px -7px;
}
.jstree-default-small .jstree-themeicon {
  background-position: -263px -7px;
}
.jstree-default-small > .jstree-no-dots .jstree-node,
.jstree-default-small > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-small > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -39px -7px;
}
.jstree-default-small > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: -7px -7px;
}
.jstree-default-small .jstree-disabled {
  background: transparent;
}
.jstree-default-small .jstree-disabled.jstree-hovered {
  background: transparent;
}
.jstree-default-small .jstree-disabled.jstree-clicked {
  background: #efefef;
}
.jstree-default-small .jstree-checkbox {
  background-position: -167px -7px;
}
.jstree-default-small .jstree-checkbox:hover {
  background-position: -167px -39px;
}
.jstree-default-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default-small .jstree-checked > .jstree-checkbox {
  background-position: -231px -7px;
}
.jstree-default-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default-small .jstree-checked > .jstree-checkbox:hover {
  background-position: -231px -39px;
}
.jstree-default-small .jstree-anchor > .jstree-undetermined {
  background-position: -199px -7px;
}
.jstree-default-small .jstree-anchor > .jstree-undetermined:hover {
  background-position: -199px -39px;
}
.jstree-default-small .jstree-checkbox-disabled {
  opacity: 0.8;
  filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");
  /* Firefox 10+ */
  filter: gray;
  /* IE6-9 */
  -webkit-filter: grayscale(100%);
  /* Chrome 19+ & Safari 6+ */
}
.jstree-default-small > .jstree-striped {
  background-size: auto 36px;
}
.jstree-default-small.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
  background-position: 100% 1px;
  background-repeat: repeat-y;
}
.jstree-default-small.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-small.jstree-rtl .jstree-open > .jstree-ocl {
  background-position: -135px -39px;
}
.jstree-default-small.jstree-rtl .jstree-closed > .jstree-ocl {
  background-position: -103px -39px;
}
.jstree-default-small.jstree-rtl .jstree-leaf > .jstree-ocl {
  background-position: -71px -39px;
}
.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -39px -39px;
}
.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: -7px -39px;
}
.jstree-default-small .jstree-themeicon-custom {
  background-color: transparent;
  background-image: none;
  background-position: 0 0;
}
.jstree-default-small > .jstree-container-ul .jstree-loading > .jstree-ocl {
  background: url("throbber.gif") center center no-repeat;
}
.jstree-default-small .jstree-file {
  background: url("32px.png") -103px -71px no-repeat;
}
.jstree-default-small .jstree-folder {
  background: url("32px.png") -263px -7px no-repeat;
}
.jstree-default-small > .jstree-container-ul > .jstree-node {
  margin-left: 0;
  margin-right: 0;
}
#jstree-dnd.jstree-default-small {
  line-height: 18px;
  padding: 0 4px;
}
#jstree-dnd.jstree-default-small .jstree-ok,
#jstree-dnd.jstree-default-small .jstree-er {
  background-image: url("32px.png");
  background-repeat: no-repeat;
  background-color: transparent;
}
#jstree-dnd.jstree-default-small i {
  background: transparent;
  width: 18px;
  height: 18px;
  line-height: 18px;
}
#jstree-dnd.jstree-default-small .jstree-ok {
  background-position: -7px -71px;
}
#jstree-dnd.jstree-default-small .jstree-er {
  background-position: -39px -71px;
}
.jstree-default-small .jstree-ellipsis {
  overflow: hidden;
}
.jstree-default-small .jstree-ellipsis .jstree-anchor {
  width: calc(100% - 23px);
  text-overflow: ellipsis;
  overflow: hidden;
}
.jstree-default-small .jstree-ellipsis.jstree-no-icons .jstree-anchor {
  width: calc(100% - 5px);
}
.jstree-default-small.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==");
}
.jstree-default-small.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-large .jstree-node {
  min-height: 32px;
  line-height: 32px;
  margin-left: 32px;
  min-width: 32px;
}
.jstree-default-large .jstree-anchor {
  line-height: 32px;
  height: 32px;
}
.jstree-default-large .jstree-icon {
  width: 32px;
  height: 32px;
  line-height: 32px;
}
.jstree-default-large .jstree-icon:empty {
  width: 32px;
  height: 32px;
  line-height: 32px;
}
.jstree-default-large.jstree-rtl .jstree-node {
  margin-right: 32px;
}
.jstree-default-large .jstree-wholerow {
  height: 32px;
}
.jstree-default-large .jstree-node,
.jstree-default-large .jstree-icon {
  background-image: url("32px.png");
}
.jstree-default-large .jstree-node {
  background-position: -288px 0px;
  background-repeat: repeat-y;
}
.jstree-default-large .jstree-last {
  background: transparent;
}
.jstree-default-large .jstree-open > .jstree-ocl {
  background-position: -128px 0px;
}
.jstree-default-large .jstree-closed > .jstree-ocl {
  background-position: -96px 0px;
}
.jstree-default-large .jstree-leaf > .jstree-ocl {
  background-position: -64px 0px;
}
.jstree-default-large .jstree-themeicon {
  background-position: -256px 0px;
}
.jstree-default-large > .jstree-no-dots .jstree-node,
.jstree-default-large > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-large > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -32px 0px;
}
.jstree-default-large > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: 0px 0px;
}
.jstree-default-large .jstree-disabled {
  background: transparent;
}
.jstree-default-large .jstree-disabled.jstree-hovered {
  background: transparent;
}
.jstree-default-large .jstree-disabled.jstree-clicked {
  background: #efefef;
}
.jstree-default-large .jstree-checkbox {
  background-position: -160px 0px;
}
.jstree-default-large .jstree-checkbox:hover {
  background-position: -160px -32px;
}
.jstree-default-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default-large .jstree-checked > .jstree-checkbox {
  background-position: -224px 0px;
}
.jstree-default-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default-large .jstree-checked > .jstree-checkbox:hover {
  background-position: -224px -32px;
}
.jstree-default-large .jstree-anchor > .jstree-undetermined {
  background-position: -192px 0px;
}
.jstree-default-large .jstree-anchor > .jstree-undetermined:hover {
  background-position: -192px -32px;
}
.jstree-default-large .jstree-checkbox-disabled {
  opacity: 0.8;
  filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");
  /* Firefox 10+ */
  filter: gray;
  /* IE6-9 */
  -webkit-filter: grayscale(100%);
  /* Chrome 19+ & Safari 6+ */
}
.jstree-default-large > .jstree-striped {
  background-size: auto 64px;
}
.jstree-default-large.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
  background-position: 100% 1px;
  background-repeat: repeat-y;
}
.jstree-default-large.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-large.jstree-rtl .jstree-open > .jstree-ocl {
  background-position: -128px -32px;
}
.jstree-default-large.jstree-rtl .jstree-closed > .jstree-ocl {
  background-position: -96px -32px;
}
.jstree-default-large.jstree-rtl .jstree-leaf > .jstree-ocl {
  background-position: -64px -32px;
}
.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -32px -32px;
}
.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: 0px -32px;
}
.jstree-default-large .jstree-themeicon-custom {
  background-color: transparent;
  background-image: none;
  background-position: 0 0;
}
.jstree-default-large > .jstree-container-ul .jstree-loading > .jstree-ocl {
  background: url("throbber.gif") center center no-repeat;
}
.jstree-default-large .jstree-file {
  background: url("32px.png") -96px -64px no-repeat;
}
.jstree-default-large .jstree-folder {
  background: url("32px.png") -256px 0px no-repeat;
}
.jstree-default-large > .jstree-container-ul > .jstree-node {
  margin-left: 0;
  margin-right: 0;
}
#jstree-dnd.jstree-default-large {
  line-height: 32px;
  padding: 0 4px;
}
#jstree-dnd.jstree-default-large .jstree-ok,
#jstree-dnd.jstree-default-large .jstree-er {
  background-image: url("32px.png");
  background-repeat: no-repeat;
  background-color: transparent;
}
#jstree-dnd.jstree-default-large i {
  background: transparent;
  width: 32px;
  height: 32px;
  line-height: 32px;
}
#jstree-dnd.jstree-default-large .jstree-ok {
  background-position: 0px -64px;
}
#jstree-dnd.jstree-default-large .jstree-er {
  background-position: -32px -64px;
}
.jstree-default-large .jstree-ellipsis {
  overflow: hidden;
}
.jstree-default-large .jstree-ellipsis .jstree-anchor {
  width: calc(100% - 37px);
  text-overflow: ellipsis;
  overflow: hidden;
}
.jstree-default-large .jstree-ellipsis.jstree-no-icons .jstree-anchor {
  width: calc(100% - 5px);
}
.jstree-default-large.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==");
}
.jstree-default-large.jstree-rtl .jstree-last {
  background: transparent;
}
@media (max-width: 768px) {
  #jstree-dnd.jstree-dnd-responsive {
    line-height: 40px;
    font-weight: bold;
    font-size: 1.1em;
    text-shadow: 1px 1px white;
  }
  #jstree-dnd.jstree-dnd-responsive > i {
    background: transparent;
    width: 40px;
    height: 40px;
  }
  #jstree-dnd.jstree-dnd-responsive > .jstree-ok {
    background-image: url("40px.png");
    background-position: 0 -200px;
    background-size: 120px 240px;
  }
  #jstree-dnd.jstree-dnd-responsive > .jstree-er {
    background-image: url("40px.png");
    background-position: -40px -200px;
    background-size: 120px 240px;
  }
  #jstree-marker.jstree-dnd-responsive {
    border-left-width: 10px;
    border-top-width: 10px;
    border-bottom-width: 10px;
    margin-top: -10px;
  }
}
@media (max-width: 768px) {
  .jstree-default-responsive {
    /*
	.jstree-open > .jstree-ocl,
	.jstree-closed > .jstree-ocl { border-radius:20px; background-color:white; }
	*/
  }
  .jstree-default-responsive .jstree-icon {
    background-image: url("40px.png");
  }
  .jstree-default-responsive .jstree-node,
  .jstree-default-responsive .jstree-leaf > .jstree-ocl {
    background: transparent;
  }
  .jstree-default-responsive .jstree-node {
    min-height: 40px;
    line-height: 40px;
    margin-left: 40px;
    min-width: 40px;
    white-space: nowrap;
  }
  .jstree-default-responsive .jstree-anchor {
    line-height: 40px;
    height: 40px;
  }
  .jstree-default-responsive .jstree-icon,
  .jstree-default-responsive .jstree-icon:empty {
    width: 40px;
    height: 40px;
    line-height: 40px;
  }
  .jstree-default-responsive > .jstree-container-ul > .jstree-node {
    margin-left: 0;
  }
  .jstree-default-responsive.jstree-rtl .jstree-node {
    margin-left: 0;
    margin-right: 40px;
    background: transparent;
  }
  .jstree-default-responsive.jstree-rtl .jstree-container-ul > .jstree-node {
    margin-right: 0;
  }
  .jstree-default-responsive .jstree-ocl,
  .jstree-default-responsive .jstree-themeicon,
  .jstree-default-responsive .jstree-checkbox {
    background-size: 120px 240px;
  }
  .jstree-default-responsive .jstree-leaf > .jstree-ocl,
  .jstree-default-responsive.jstree-rtl .jstree-leaf > .jstree-ocl {
    background: transparent;
  }
  .jstree-default-responsive .jstree-open > .jstree-ocl {
    background-position: 0 0px !important;
  }
  .jstree-default-responsive .jstree-closed > .jstree-ocl {
    background-position: 0 -40px !important;
  }
  .jstree-default-responsive.jstree-rtl .jstree-closed > .jstree-ocl {
    background-position: -40px 0px !important;
  }
  .jstree-default-responsive .jstree-themeicon {
    background-position: -40px -40px;
  }
  .jstree-default-responsive .jstree-checkbox,
  .jstree-default-responsive .jstree-checkbox:hover {
    background-position: -40px -80px;
  }
  .jstree-default-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
  .jstree-default-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
  .jstree-default-responsive .jstree-checked > .jstree-checkbox,
  .jstree-default-responsive .jstree-checked > .jstree-checkbox:hover {
    background-position: 0 -80px;
  }
  .jstree-default-responsive .jstree-anchor > .jstree-undetermined,
  .jstree-default-responsive .jstree-anchor > .jstree-undetermined:hover {
    background-position: 0 -120px;
  }
  .jstree-default-responsive .jstree-anchor {
    font-weight: bold;
    font-size: 1.1em;
    text-shadow: 1px 1px white;
  }
  .jstree-default-responsive > .jstree-striped {
    background: transparent;
  }
  .jstree-default-responsive .jstree-wholerow {
    border-top: 1px solid rgba(255, 255, 255, 0.7);
    border-bottom: 1px solid rgba(64, 64, 64, 0.2);
    background: #ebebeb;
    height: 40px;
  }
  .jstree-default-responsive .jstree-wholerow-hovered {
    background: #e7f4f9;
  }
  .jstree-default-responsive .jstree-wholerow-clicked {
    background: #beebff;
  }
  .jstree-default-responsive .jstree-children .jstree-last > .jstree-wholerow {
    box-shadow: inset 0 -6px 3px -5px #666666;
  }
  .jstree-default-responsive .jstree-children .jstree-open > .jstree-wholerow {
    box-shadow: inset 0 6px 3px -5px #666666;
    border-top: 0;
  }
  .jstree-default-responsive .jstree-children .jstree-open + .jstree-open {
    box-shadow: none;
  }
  .jstree-default-responsive .jstree-node,
  .jstree-default-responsive .jstree-icon,
  .jstree-default-responsive .jstree-node > .jstree-ocl,
  .jstree-default-responsive .jstree-themeicon,
  .jstree-default-responsive .jstree-checkbox {
    background-image: url("40px.png");
    background-size: 120px 240px;
  }
  .jstree-default-responsive .jstree-node {
    background-position: -80px 0;
    background-repeat: repeat-y;
  }
  .jstree-default-responsive .jstree-last {
    background: transparent;
  }
  .jstree-default-responsive .jstree-leaf > .jstree-ocl {
    background-position: -40px -120px;
  }
  .jstree-default-responsive .jstree-last > .jstree-ocl {
    background-position: -40px -160px;
  }
  .jstree-default-responsive .jstree-themeicon-custom {
    background-color: transparent;
    background-image: none;
    background-position: 0 0;
  }
  .jstree-default-responsive .jstree-file {
    background: url("40px.png") 0 -160px no-repeat;
    background-size: 120px 240px;
  }
  .jstree-default-responsive .jstree-folder {
    background: url("40px.png") -40px -40px no-repeat;
    background-size: 120px 240px;
  }
  .jstree-default-responsive > .jstree-container-ul > .jstree-node {
    margin-left: 0;
    margin-right: 0;
  }
}
.jstree-node,.jstree-children,.jstree-container-ul{display:block;margin:0;padding:0;list-style-type:none;list-style-image:none}.jstree-node{white-space:nowrap}.jstree-anchor{display:inline-block;color:#000;white-space:nowrap;padding:0 4px 0 1px;margin:0;vertical-align:top}.jstree-anchor:focus{outline:0}.jstree-anchor,.jstree-anchor:link,.jstree-anchor:visited,.jstree-anchor:hover,.jstree-anchor:active{text-decoration:none;color:inherit}.jstree-icon{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-icon:empty{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-ocl{cursor:pointer}.jstree-leaf>.jstree-ocl{cursor:default}.jstree .jstree-open>.jstree-children{display:block}.jstree .jstree-closed>.jstree-children,.jstree .jstree-leaf>.jstree-children{display:none}.jstree-anchor>.jstree-themeicon{margin-right:2px}.jstree-no-icons .jstree-themeicon,.jstree-anchor>.jstree-themeicon-hidden{display:none}.jstree-hidden,.jstree-node.jstree-hidden{display:none}.jstree-rtl .jstree-anchor{padding:0 1px 0 4px}.jstree-rtl .jstree-anchor>.jstree-themeicon{margin-left:2px;margin-right:0}.jstree-rtl .jstree-node{margin-left:0}.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-wholerow-ul{position:relative;display:inline-block;min-width:100%}.jstree-wholerow-ul .jstree-leaf>.jstree-ocl{cursor:pointer}.jstree-wholerow-ul .jstree-anchor,.jstree-wholerow-ul .jstree-icon{position:relative}.jstree-wholerow-ul .jstree-wholerow{width:100%;cursor:pointer;position:absolute;left:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.vakata-context{display:none}.vakata-context,.vakata-context ul{margin:0;padding:2px;position:absolute;background:#f5f5f5;border:1px solid #979797;box-shadow:2px 2px 2px #999}.vakata-context ul{list-style:none;left:100%;margin-top:-2.7em;margin-left:-4px}.vakata-context .vakata-context-right ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context li{list-style:none}.vakata-context li>a{display:block;padding:0 2em;text-decoration:none;width:auto;color:#000;white-space:nowrap;line-height:2.4em;text-shadow:1px 1px 0 #fff;border-radius:1px}.vakata-context li>a:hover{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context li>a.vakata-context-parent{background-image:url(data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==);background-position:right center;background-repeat:no-repeat}.vakata-context li>a:focus{outline:0}.vakata-context .vakata-context-hover>a{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context .vakata-context-separator>a,.vakata-context .vakata-context-separator>a:hover{background:#fff;border:0;border-top:1px solid #e2e3e3;height:1px;min-height:1px;max-height:1px;padding:0;margin:0 0 0 2.4em;border-left:1px solid #e0e0e0;text-shadow:0 0 0 transparent;box-shadow:0 0 0 transparent;border-radius:0}.vakata-context .vakata-contextmenu-disabled a,.vakata-context .vakata-contextmenu-disabled a:hover{color:silver;background-color:transparent;border:0;box-shadow:0 0 0}.vakata-context li>a>i{text-decoration:none;display:inline-block;width:2.4em;height:2.4em;background:0 0;margin:0 0 0 -2em;vertical-align:top;text-align:center;line-height:2.4em}.vakata-context li>a>i:empty{width:2.4em;line-height:2.4em}.vakata-context li>a .vakata-contextmenu-sep{display:inline-block;width:1px;height:2.4em;background:#fff;margin:0 .5em 0 0;border-left:1px solid #e2e3e3}.vakata-context .vakata-contextmenu-shortcut{font-size:.8em;color:silver;opacity:.5;display:none}.vakata-context-rtl ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context-rtl li>a.vakata-context-parent{background-image:url(data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7);background-position:left center;background-repeat:no-repeat}.vakata-context-rtl .vakata-context-separator>a{margin:0 2.4em 0 0;border-left:0;border-right:1px solid #e2e3e3}.vakata-context-rtl .vakata-context-left ul{right:auto;left:100%;margin-left:-4px;margin-right:auto}.vakata-context-rtl li>a>i{margin:0 -2em 0 0}.vakata-context-rtl li>a .vakata-contextmenu-sep{margin:0 0 0 .5em;border-left-color:#fff;background:#e2e3e3}#jstree-marker{position:absolute;top:0;left:0;margin:-5px 0 0 0;padding:0;border-right:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid;width:0;height:0;font-size:0;line-height:0}#jstree-dnd{line-height:16px;margin:0;padding:4px}#jstree-dnd .jstree-icon,#jstree-dnd .jstree-copy{display:inline-block;text-decoration:none;margin:0 2px 0 0;padding:0;width:16px;height:16px}#jstree-dnd .jstree-ok{background:green}#jstree-dnd .jstree-er{background:red}#jstree-dnd .jstree-copy{margin:0 2px}.jstree-default .jstree-node,.jstree-default .jstree-icon{background-repeat:no-repeat;background-color:transparent}.jstree-default .jstree-anchor,.jstree-default .jstree-animated,.jstree-default .jstree-wholerow{transition:background-color .15s,box-shadow .15s}.jstree-default .jstree-hovered{background:#e7f4f9;border-radius:2px;box-shadow:inset 0 0 1px #ccc}.jstree-default .jstree-context{background:#e7f4f9;border-radius:2px;box-shadow:inset 0 0 1px #ccc}.jstree-default .jstree-clicked{background:#beebff;border-radius:2px;box-shadow:inset 0 0 1px #999}.jstree-default .jstree-no-icons .jstree-anchor>.jstree-themeicon{display:none}.jstree-default .jstree-disabled{background:0 0;color:#666}.jstree-default .jstree-disabled.jstree-hovered{background:0 0;box-shadow:none}.jstree-default .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default .jstree-disabled>.jstree-icon{opacity:.8;filter:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default .jstree-search{font-style:italic;color:#8b0000;font-weight:700}.jstree-default .jstree-no-checkboxes .jstree-checkbox{display:none!important}.jstree-default.jstree-checkbox-no-clicked .jstree-clicked{background:0 0;box-shadow:none}.jstree-default.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered{background:#e7f4f9}.jstree-default.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked{background:0 0}.jstree-default.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered{background:#e7f4f9}.jstree-default>.jstree-striped{min-width:100%;display:inline-block;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==) left top repeat}.jstree-default>.jstree-wholerow-ul .jstree-hovered,.jstree-default>.jstree-wholerow-ul .jstree-clicked{background:0 0;box-shadow:none;border-radius:0}.jstree-default .jstree-wholerow{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.jstree-default .jstree-wholerow-hovered{background:#e7f4f9}.jstree-default .jstree-wholerow-clicked{background:#beebff;background:-webkit-linear-gradient(top,#beebff 0,#a8e4ff 100%);background:linear-gradient(to bottom,#beebff 0,#a8e4ff 100%)}.jstree-default .jstree-node{min-height:24px;line-height:24px;margin-left:24px;min-width:24px}.jstree-default .jstree-anchor{line-height:24px;height:24px}.jstree-default .jstree-icon{width:24px;height:24px;line-height:24px}.jstree-default .jstree-icon:empty{width:24px;height:24px;line-height:24px}.jstree-default.jstree-rtl .jstree-node{margin-right:24px}.jstree-default .jstree-wholerow{height:24px}.jstree-default .jstree-node,.jstree-default .jstree-icon{background-image:url(32px.png)}.jstree-default .jstree-node{background-position:-292px -4px;background-repeat:repeat-y}.jstree-default .jstree-last{background:0 0}.jstree-default .jstree-open>.jstree-ocl{background-position:-132px -4px}.jstree-default .jstree-closed>.jstree-ocl{background-position:-100px -4px}.jstree-default .jstree-leaf>.jstree-ocl{background-position:-68px -4px}.jstree-default .jstree-themeicon{background-position:-260px -4px}.jstree-default>.jstree-no-dots .jstree-node,.jstree-default>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -4px}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -4px}.jstree-default .jstree-disabled{background:0 0}.jstree-default .jstree-disabled.jstree-hovered{background:0 0}.jstree-default .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default .jstree-checkbox{background-position:-164px -4px}.jstree-default .jstree-checkbox:hover{background-position:-164px -36px}.jstree-default.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default .jstree-checked>.jstree-checkbox{background-position:-228px -4px}.jstree-default.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default .jstree-checked>.jstree-checkbox:hover{background-position:-228px -36px}.jstree-default .jstree-anchor>.jstree-undetermined{background-position:-196px -4px}.jstree-default .jstree-anchor>.jstree-undetermined:hover{background-position:-196px -36px}.jstree-default .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default>.jstree-striped{background-size:auto 48px}.jstree-default.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default.jstree-rtl .jstree-last{background:0 0}.jstree-default.jstree-rtl .jstree-open>.jstree-ocl{background-position:-132px -36px}.jstree-default.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-100px -36px}.jstree-default.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-68px -36px}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -36px}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -36px}.jstree-default .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default .jstree-file{background:url(32px.png) -100px -68px no-repeat}.jstree-default .jstree-folder{background:url(32px.png) -260px -4px no-repeat}.jstree-default>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default{line-height:24px;padding:0 4px}#jstree-dnd.jstree-default .jstree-ok,#jstree-dnd.jstree-default .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default i{background:0 0;width:24px;height:24px;line-height:24px}#jstree-dnd.jstree-default .jstree-ok{background-position:-4px -68px}#jstree-dnd.jstree-default .jstree-er{background-position:-36px -68px}.jstree-default .jstree-ellipsis{overflow:hidden}.jstree-default .jstree-ellipsis .jstree-anchor{width:calc(100% - 29px);text-overflow:ellipsis;overflow:hidden}.jstree-default .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==)}.jstree-default.jstree-rtl .jstree-last{background:0 0}.jstree-default-small .jstree-node{min-height:18px;line-height:18px;margin-left:18px;min-width:18px}.jstree-default-small .jstree-anchor{line-height:18px;height:18px}.jstree-default-small .jstree-icon{width:18px;height:18px;line-height:18px}.jstree-default-small .jstree-icon:empty{width:18px;height:18px;line-height:18px}.jstree-default-small.jstree-rtl .jstree-node{margin-right:18px}.jstree-default-small .jstree-wholerow{height:18px}.jstree-default-small .jstree-node,.jstree-default-small .jstree-icon{background-image:url(32px.png)}.jstree-default-small .jstree-node{background-position:-295px -7px;background-repeat:repeat-y}.jstree-default-small .jstree-last{background:0 0}.jstree-default-small .jstree-open>.jstree-ocl{background-position:-135px -7px}.jstree-default-small .jstree-closed>.jstree-ocl{background-position:-103px -7px}.jstree-default-small .jstree-leaf>.jstree-ocl{background-position:-71px -7px}.jstree-default-small .jstree-themeicon{background-position:-263px -7px}.jstree-default-small>.jstree-no-dots .jstree-node,.jstree-default-small>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-small>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -7px}.jstree-default-small>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -7px}.jstree-default-small .jstree-disabled{background:0 0}.jstree-default-small .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-small .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-small .jstree-checkbox{background-position:-167px -7px}.jstree-default-small .jstree-checkbox:hover{background-position:-167px -39px}.jstree-default-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-small .jstree-checked>.jstree-checkbox{background-position:-231px -7px}.jstree-default-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-small .jstree-checked>.jstree-checkbox:hover{background-position:-231px -39px}.jstree-default-small .jstree-anchor>.jstree-undetermined{background-position:-199px -7px}.jstree-default-small .jstree-anchor>.jstree-undetermined:hover{background-position:-199px -39px}.jstree-default-small .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-small>.jstree-striped{background-size:auto 36px}.jstree-default-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-small.jstree-rtl .jstree-open>.jstree-ocl{background-position:-135px -39px}.jstree-default-small.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-103px -39px}.jstree-default-small.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-71px -39px}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -39px}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -39px}.jstree-default-small .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-small>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-small .jstree-file{background:url(32px.png) -103px -71px no-repeat}.jstree-default-small .jstree-folder{background:url(32px.png) -263px -7px no-repeat}.jstree-default-small>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-small{line-height:18px;padding:0 4px}#jstree-dnd.jstree-default-small .jstree-ok,#jstree-dnd.jstree-default-small .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-small i{background:0 0;width:18px;height:18px;line-height:18px}#jstree-dnd.jstree-default-small .jstree-ok{background-position:-7px -71px}#jstree-dnd.jstree-default-small .jstree-er{background-position:-39px -71px}.jstree-default-small .jstree-ellipsis{overflow:hidden}.jstree-default-small .jstree-ellipsis .jstree-anchor{width:calc(100% - 23px);text-overflow:ellipsis;overflow:hidden}.jstree-default-small .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==)}.jstree-default-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-large .jstree-node{min-height:32px;line-height:32px;margin-left:32px;min-width:32px}.jstree-default-large .jstree-anchor{line-height:32px;height:32px}.jstree-default-large .jstree-icon{width:32px;height:32px;line-height:32px}.jstree-default-large .jstree-icon:empty{width:32px;height:32px;line-height:32px}.jstree-default-large.jstree-rtl .jstree-node{margin-right:32px}.jstree-default-large .jstree-wholerow{height:32px}.jstree-default-large .jstree-node,.jstree-default-large .jstree-icon{background-image:url(32px.png)}.jstree-default-large .jstree-node{background-position:-288px 0;background-repeat:repeat-y}.jstree-default-large .jstree-last{background:0 0}.jstree-default-large .jstree-open>.jstree-ocl{background-position:-128px 0}.jstree-default-large .jstree-closed>.jstree-ocl{background-position:-96px 0}.jstree-default-large .jstree-leaf>.jstree-ocl{background-position:-64px 0}.jstree-default-large .jstree-themeicon{background-position:-256px 0}.jstree-default-large>.jstree-no-dots .jstree-node,.jstree-default-large>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-large>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px 0}.jstree-default-large>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 0}.jstree-default-large .jstree-disabled{background:0 0}.jstree-default-large .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-large .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-large .jstree-checkbox{background-position:-160px 0}.jstree-default-large .jstree-checkbox:hover{background-position:-160px -32px}.jstree-default-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-large .jstree-checked>.jstree-checkbox{background-position:-224px 0}.jstree-default-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-large .jstree-checked>.jstree-checkbox:hover{background-position:-224px -32px}.jstree-default-large .jstree-anchor>.jstree-undetermined{background-position:-192px 0}.jstree-default-large .jstree-anchor>.jstree-undetermined:hover{background-position:-192px -32px}.jstree-default-large .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-large>.jstree-striped{background-size:auto 64px}.jstree-default-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-large.jstree-rtl .jstree-last{background:0 0}.jstree-default-large.jstree-rtl .jstree-open>.jstree-ocl{background-position:-128px -32px}.jstree-default-large.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-96px -32px}.jstree-default-large.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-64px -32px}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px -32px}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 -32px}.jstree-default-large .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-large>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-large .jstree-file{background:url(32px.png) -96px -64px no-repeat}.jstree-default-large .jstree-folder{background:url(32px.png) -256px 0 no-repeat}.jstree-default-large>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-large{line-height:32px;padding:0 4px}#jstree-dnd.jstree-default-large .jstree-ok,#jstree-dnd.jstree-default-large .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-large i{background:0 0;width:32px;height:32px;line-height:32px}#jstree-dnd.jstree-default-large .jstree-ok{background-position:0 -64px}#jstree-dnd.jstree-default-large .jstree-er{background-position:-32px -64px}.jstree-default-large .jstree-ellipsis{overflow:hidden}.jstree-default-large .jstree-ellipsis .jstree-anchor{width:calc(100% - 37px);text-overflow:ellipsis;overflow:hidden}.jstree-default-large .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==)}.jstree-default-large.jstree-rtl .jstree-last{background:0 0}@media (max-width:768px){#jstree-dnd.jstree-dnd-responsive{line-height:40px;font-weight:700;font-size:1.1em;text-shadow:1px 1px #fff}#jstree-dnd.jstree-dnd-responsive>i{background:0 0;width:40px;height:40px}#jstree-dnd.jstree-dnd-responsive>.jstree-ok{background-image:url(40px.png);background-position:0 -200px;background-size:120px 240px}#jstree-dnd.jstree-dnd-responsive>.jstree-er{background-image:url(40px.png);background-position:-40px -200px;background-size:120px 240px}#jstree-marker.jstree-dnd-responsive{border-left-width:10px;border-top-width:10px;border-bottom-width:10px;margin-top:-10px}}@media (max-width:768px){.jstree-default-responsive .jstree-icon{background-image:url(40px.png)}.jstree-default-responsive .jstree-node,.jstree-default-responsive .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-responsive .jstree-node{min-height:40px;line-height:40px;margin-left:40px;min-width:40px;white-space:nowrap}.jstree-default-responsive .jstree-anchor{line-height:40px;height:40px}.jstree-default-responsive .jstree-icon,.jstree-default-responsive .jstree-icon:empty{width:40px;height:40px;line-height:40px}.jstree-default-responsive>.jstree-container-ul>.jstree-node{margin-left:0}.jstree-default-responsive.jstree-rtl .jstree-node{margin-left:0;margin-right:40px;background:0 0}.jstree-default-responsive.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-default-responsive .jstree-ocl,.jstree-default-responsive .jstree-themeicon,.jstree-default-responsive .jstree-checkbox{background-size:120px 240px}.jstree-default-responsive .jstree-leaf>.jstree-ocl,.jstree-default-responsive.jstree-rtl .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-responsive .jstree-open>.jstree-ocl{background-position:0 0!important}.jstree-default-responsive .jstree-closed>.jstree-ocl{background-position:0 -40px!important}.jstree-default-responsive.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-40px 0!important}.jstree-default-responsive .jstree-themeicon{background-position:-40px -40px}.jstree-default-responsive .jstree-checkbox,.jstree-default-responsive .jstree-checkbox:hover{background-position:-40px -80px}.jstree-default-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-responsive .jstree-checked>.jstree-checkbox,.jstree-default-responsive .jstree-checked>.jstree-checkbox:hover{background-position:0 -80px}.jstree-default-responsive .jstree-anchor>.jstree-undetermined,.jstree-default-responsive .jstree-anchor>.jstree-undetermined:hover{background-position:0 -120px}.jstree-default-responsive .jstree-anchor{font-weight:700;font-size:1.1em;text-shadow:1px 1px #fff}.jstree-default-responsive>.jstree-striped{background:0 0}.jstree-default-responsive .jstree-wholerow{border-top:1px solid rgba(255,255,255,.7);border-bottom:1px solid rgba(64,64,64,.2);background:#ebebeb;height:40px}.jstree-default-responsive .jstree-wholerow-hovered{background:#e7f4f9}.jstree-default-responsive .jstree-wholerow-clicked{background:#beebff}.jstree-default-responsive .jstree-children .jstree-last>.jstree-wholerow{box-shadow:inset 0 -6px 3px -5px #666}.jstree-default-responsive .jstree-children .jstree-open>.jstree-wholerow{box-shadow:inset 0 6px 3px -5px #666;border-top:0}.jstree-default-responsive .jstree-children .jstree-open+.jstree-open{box-shadow:none}.jstree-default-responsive .jstree-node,.jstree-default-responsive .jstree-icon,.jstree-default-responsive .jstree-node>.jstree-ocl,.jstree-default-responsive .jstree-themeicon,.jstree-default-responsive .jstree-checkbox{background-image:url(40px.png);background-size:120px 240px}.jstree-default-responsive .jstree-node{background-position:-80px 0;background-repeat:repeat-y}.jstree-default-responsive .jstree-last{background:0 0}.jstree-default-responsive .jstree-leaf>.jstree-ocl{background-position:-40px -120px}.jstree-default-responsive .jstree-last>.jstree-ocl{background-position:-40px -160px}.jstree-default-responsive .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-responsive .jstree-file{background:url(40px.png) 0 -160px no-repeat;background-size:120px 240px}.jstree-default-responsive .jstree-folder{background:url(40px.png) -40px -40px no-repeat;background-size:120px 240px}.jstree-default-responsive>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}}GIF89a    FFFzzz   XXX$$$666hhh!NETSCAPE2.0   !Created with ajaxload.info !	
   ,       qH;uj01P4HЈ
`+I1q|@Pp@\ 84QQZ^-	g8) fl9C`O Tp%#
c%

U !	
   ,       pHhy91KI*,Al`h|&@sHBvD~UM^)H6?
xMl<L&klj('K' !	
   ,       rH(9NICsa(0F&pP$@08(ZF	( (iDN1:Hd%M 2	x2GOyo0Oyrn;bq'1b' !	
   ,       pH(c9J&
QN,J5P3`0J';)l_@H$vb ]M+

g@ͧ)v1[oz
;|zM1n&&& !	
   ,       fHh)9N&
}@APu,
\+I
4E|
q؜ɱ]
MkI7JيH!8esyVny& !	
   ,       xHRRB&' RrT( 2c d&08	g0`lAa~6ܦK!~M} <U\o
]Ym ]UK !	
   ,       sHT1&X"2DUX6+t9
ls$lj$G,Nis2-<`coO:x.OL

7]'| !	
   ,       qHpPJ$&}=%W
!8<
uu%Ew7SpZ& %&@M<5y4e `p
ml-	!5Q%\ !	
   ,       sHht{ǸJQ_(a	eqBj<3	i` hޒAa(
$ՀT
c`@00HBl|
	&YJgSg !	
   ,       sHhtuJX0^`Y
	sh(>BAbp$SIJǙCp	AH4zY
*f !	
   ,       oHht{6aWba(5U@ NA;4Br$P(EHTC!@80YQcLzYSY>Z gt !	
   ,       sHhtj(6a$_!_^5FBX
8ZXU(I@ѫ
(.AQX%(Q,!DM	Sa[	0 ;PNG

   
IHDR  @   `   [[   PLTE                              ǵ   ???   Ƌ   hhh虙ȏxxxn   tRNS +Oa%1=;Dy̐9Fiq  IDATx휋Z:)6M
(uNbZz}w;	=M/4k?*YSe4WO_?@@'uO9s}D, 2Tkzkg:P:>#hL̟UEIX)|桲^<J/,tDoMPl^{'w}NP JѲ)Uy	: O)iiX@<?`+qM7p&}n#$Vʀ@} J'-H[;ty6X:.+){p2  H8
TePzRu󄱄hKCA5 GdXUY&iẓPc=.af[4j GXuGQo^Q.$-7P9f 
q	L\T܃!-Rczrxy.rHLbQJDU?[E)\P@Ĭgn\ǡ 5 0"ZZyu\ɵ? \7=Ѓz=@@ }.z]@ @z	8t7.JXmF`S_>[{oWi{-h>h!{⯹m||
~=m-iy%TS+pDੁ>2ho?cF
|m*y}yL[F{\04߲Jp=p(' @ A  E|܉d1Q}7븞;Ѻ?B4u77"x=w.-ٷI59K4yA-W'} ;V޵"sIj?@":B<](=.e%7so
DU'tN݁.gSex`p	\Al"qs>K#xsjO[9zn4@MQĳag]Ji1`z*b~wQr$Z/"?x<WaB5w4<JH,$0wy(duIBIr h%~hb
h+x$
y|p
ÁD<5DS\Lh^L8C$Юq9t@@ >;e=/    IENDB`PNG

   
IHDR        *  FPLTE   sssVVV                              ^^^   oooNNN      UUUTTTRRRPPP   


SSS   OOO]]]VVVWWWQQQddd   000   }}}{{{uuutttwwwyyy\\\[[[ZZZYYYXXX   hhh      ~~~vvv|||zzzxxxLLLKKKUUU      TTT   $$$[[[   XXX૫Юǰï̾ղֵܺſĴɱ3[   itRNS \I%1=0TG\\\\\3\\TTT\G&KTTTTT>GaU>
hhTEC(6XG\y:9N+8  UIDATx݉_ip{N2&W>ٙ9(N)@dEQCiqu6پUPuP/<OZKP
I`  0`|yb_'V5L ׼^\b-/&1`p0ËE,R(TbKZZ%|(/=^.yDz+eMnTwnU͓+	L7uΛ'W
ibΛ'WDW7O^{DMUWUnټBbݤb$xq~h5`d{1~-5j|h/ /]A:x|EϋpreiiV&r\2u)I`$^rMOYjz5$tTʅʕN&]¦XPU)}~ဩUI<zzAr[ri1Iי/`uNRK[欟VYsԢ;1m
s9Kr9{4}kK*ubdj=q#̹Qkyw.AԵ%Zu&CsFͅΨ^ɬ&{{Zv
-9AXJJuNʗkLԵ%:Q+9vvBB	y+nyRG+Gj%xᥢP+o׫<W)/umI;=9rgykbנ,*6u%-O϶R5qpjݩm~IkKq133Y4JVߌ&mgQ"M-u7M,ޚ/՚Ȝ^&f2V7ǶٽT{=l,p+[{;3ˇ_}uq7ڤǤ_73凙MM>zPϿ+i*Q_KSGglԫSKU|ߟqmKuqz~>;^V*ޫraM^K7ۇGq`  0`  0`  0` `43jXT
%p
5Jj|@X7J!->`LWwSvw=^[h'&cJݰBtWXukd1Yì!-Lއ.Zq-&K<0ZK<X`KXxRŃ(@4 &qWTdڑҴ&Ib"aAX(	kgLcʕZ-
b`MNML<N
2v\y?,Z<RߗyRb}S1a5,khz_ރXnȳfwY%_i>[gQN]F0+͇9?J\Y?l`,W"ݲ_i>BJiJo'F^wWUBp#;ϳvKqL`U4NvZWg6|myxіLז[N2+zE|i>v.?YOsYݮa=jivK-Ӵ\|٫'^%@i&Oç]>nzWFqWGx֣]p$^i2~γ,gBwdxS_[6_#箥+͝M{V#+!:u?Pj7BHǳBl2'^
_Uon0`  0`  0`  0` K(f ,:Pl,Wq,{ҹ\yk*,Y:Y*;k
_E^{}^{'T>yWX
X!ѯUoH}ݯQ*Y(¼B)d+/xz/Gr/zv^>mZ<*x/ KئŃ]T)ڻ,=Q<pXǉix
6U\ݤ lʼj}I9Ocz%`d$-Ml)O*ށk	X(٪㱂\(w |LS(%[Mw-D@5.-`w?qe1*u*cT;VRT?Q8+Z,X*5JBa ށR)`p!`b0Ocz^^;x:߀=bu<&>Ʋ 82(gT|rd7{k'bJG

q*[xri e,o4
֠R|?cY*;XRs:lu sΊ-~qa{u=o~&~cʼavRwZW>-J9a+r֎M>qvB~qy+3oC҈ll@C:qU|ᓾ.sI,
yaf'e몭Huޮ?~7f>JK먽'M<F#4.Ffxl}3F?{ld(d|a<K[īϿ|慮ﻐ-d13-$vpF2ohu7r'^yN#!p*EI+{ ~cvs``  0`  0`  0`  ޢ@ Wā{EW{EWDE/W$E?WEık#0MÙi$oƒRMc	.Q8F `  0+m1vERȿ"ζ,IWV)JE!1_ȶFiQZÍl+݌͐EmG3h3_n_#eL@W7ȥO

R%__Yj+2kdڪHο"̿BG{>dT6o_@x7Փx"pWKο"̿;MKIο"ȿ*Oʒ7$ץd!9 p;7PTQeq_KI_GY>_}i_uQt
 0`  0` ?@  0` cB8a`EC,+YCV]SX(F
<+/xEI<@`<AcX믌Xג񀁵*U<h`11֯vkJKze^hRǰVo;~0bx20KVG
%I5rQbg®ߑ&&Ovz
.@wIO9w?.p@oG+opry8}e&~svk.C'.:
%<\`1]!8AJ?.p;].yΖ஌<.ع]pY{]_zx?tp
;>g;6\Jf/35dkl㬟v6dky2sg7d;qw3T`E6+nfI
' 0`  0`  0` ">-3[ã~qHb&|	 0RTo]:8\=d2*VKb^WU%Ku;&{($bNqdMV+*K1]Z-{}ā^bRX&fz;j.60pkBw{k/Y]4SfY
ؚe]p|A`^E9'&km.~$"~{|iXރ_ye7w/4I,	KcxjYOGѥ1,bƻ`B5Ycc	~SFZO{R,MR.W*5`	V5&JX}p Ux
F}M=՜МkOz:NWGx
ZyhR8/
ӚS"Z*n%rv}E[.SլcD<k|"ȷua`l֮Q	묕	AMvjO?/+6;}ICg%}N;<|[l$E|divhd18N9߷Ev=WICv؊Y*T\-nCTyC=VO3}e`4:39尥NK4BxO3'Nh|	.;+3sNwSLRgɜ96)^!Ȇو.,~/N=fg񜞕*O`4s$23wpp7;9	׽ǻ].YT1MΔduï<:?svfgM2Xf6c/Wf,/PXFqaNNͺ'#ŗ?w+e62%_ و8c
|m9`N~W>0x}=]f3]<rX9׏܎>-+x^XO^pߔSz}pE n`
u C>j1  0GoU    IENDB`/* jsTree default dark theme */
.jstree-node,
.jstree-children,
.jstree-container-ul {
  display: block;
  margin: 0;
  padding: 0;
  list-style-type: none;
  list-style-image: none;
}
.jstree-node {
  white-space: nowrap;
}
.jstree-anchor {
  display: inline-block;
  color: black;
  white-space: nowrap;
  padding: 0 4px 0 1px;
  margin: 0;
  vertical-align: top;
}
.jstree-anchor:focus {
  outline: 0;
}
.jstree-anchor,
.jstree-anchor:link,
.jstree-anchor:visited,
.jstree-anchor:hover,
.jstree-anchor:active {
  text-decoration: none;
  color: inherit;
}
.jstree-icon {
  display: inline-block;
  text-decoration: none;
  margin: 0;
  padding: 0;
  vertical-align: top;
  text-align: center;
}
.jstree-icon:empty {
  display: inline-block;
  text-decoration: none;
  margin: 0;
  padding: 0;
  vertical-align: top;
  text-align: center;
}
.jstree-ocl {
  cursor: pointer;
}
.jstree-leaf > .jstree-ocl {
  cursor: default;
}
.jstree .jstree-open > .jstree-children {
  display: block;
}
.jstree .jstree-closed > .jstree-children,
.jstree .jstree-leaf > .jstree-children {
  display: none;
}
.jstree-anchor > .jstree-themeicon {
  margin-right: 2px;
}
.jstree-no-icons .jstree-themeicon,
.jstree-anchor > .jstree-themeicon-hidden {
  display: none;
}
.jstree-hidden,
.jstree-node.jstree-hidden {
  display: none;
}
.jstree-rtl .jstree-anchor {
  padding: 0 1px 0 4px;
}
.jstree-rtl .jstree-anchor > .jstree-themeicon {
  margin-left: 2px;
  margin-right: 0;
}
.jstree-rtl .jstree-node {
  margin-left: 0;
}
.jstree-rtl .jstree-container-ul > .jstree-node {
  margin-right: 0;
}
.jstree-wholerow-ul {
  position: relative;
  display: inline-block;
  min-width: 100%;
}
.jstree-wholerow-ul .jstree-leaf > .jstree-ocl {
  cursor: pointer;
}
.jstree-wholerow-ul .jstree-anchor,
.jstree-wholerow-ul .jstree-icon {
  position: relative;
}
.jstree-wholerow-ul .jstree-wholerow {
  width: 100%;
  cursor: pointer;
  position: absolute;
  left: 0;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
.vakata-context {
  display: none;
}
.vakata-context,
.vakata-context ul {
  margin: 0;
  padding: 2px;
  position: absolute;
  background: #f5f5f5;
  border: 1px solid #979797;
  box-shadow: 2px 2px 2px #999999;
}
.vakata-context ul {
  list-style: none;
  left: 100%;
  margin-top: -2.7em;
  margin-left: -4px;
}
.vakata-context .vakata-context-right ul {
  left: auto;
  right: 100%;
  margin-left: auto;
  margin-right: -4px;
}
.vakata-context li {
  list-style: none;
}
.vakata-context li > a {
  display: block;
  padding: 0 2em 0 2em;
  text-decoration: none;
  width: auto;
  color: black;
  white-space: nowrap;
  line-height: 2.4em;
  text-shadow: 1px 1px 0 white;
  border-radius: 1px;
}
.vakata-context li > a:hover {
  position: relative;
  background-color: #e8eff7;
  box-shadow: 0 0 2px #0a6aa1;
}
.vakata-context li > a.vakata-context-parent {
  background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==");
  background-position: right center;
  background-repeat: no-repeat;
}
.vakata-context li > a:focus {
  outline: 0;
}
.vakata-context .vakata-context-hover > a {
  position: relative;
  background-color: #e8eff7;
  box-shadow: 0 0 2px #0a6aa1;
}
.vakata-context .vakata-context-separator > a,
.vakata-context .vakata-context-separator > a:hover {
  background: white;
  border: 0;
  border-top: 1px solid #e2e3e3;
  height: 1px;
  min-height: 1px;
  max-height: 1px;
  padding: 0;
  margin: 0 0 0 2.4em;
  border-left: 1px solid #e0e0e0;
  text-shadow: 0 0 0 transparent;
  box-shadow: 0 0 0 transparent;
  border-radius: 0;
}
.vakata-context .vakata-contextmenu-disabled a,
.vakata-context .vakata-contextmenu-disabled a:hover {
  color: silver;
  background-color: transparent;
  border: 0;
  box-shadow: 0 0 0;
}
.vakata-context li > a > i {
  text-decoration: none;
  display: inline-block;
  width: 2.4em;
  height: 2.4em;
  background: transparent;
  margin: 0 0 0 -2em;
  vertical-align: top;
  text-align: center;
  line-height: 2.4em;
}
.vakata-context li > a > i:empty {
  width: 2.4em;
  line-height: 2.4em;
}
.vakata-context li > a .vakata-contextmenu-sep {
  display: inline-block;
  width: 1px;
  height: 2.4em;
  background: white;
  margin: 0 0.5em 0 0;
  border-left: 1px solid #e2e3e3;
}
.vakata-context .vakata-contextmenu-shortcut {
  font-size: 0.8em;
  color: silver;
  opacity: 0.5;
  display: none;
}
.vakata-context-rtl ul {
  left: auto;
  right: 100%;
  margin-left: auto;
  margin-right: -4px;
}
.vakata-context-rtl li > a.vakata-context-parent {
  background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7");
  background-position: left center;
  background-repeat: no-repeat;
}
.vakata-context-rtl .vakata-context-separator > a {
  margin: 0 2.4em 0 0;
  border-left: 0;
  border-right: 1px solid #e2e3e3;
}
.vakata-context-rtl .vakata-context-left ul {
  right: auto;
  left: 100%;
  margin-left: -4px;
  margin-right: auto;
}
.vakata-context-rtl li > a > i {
  margin: 0 -2em 0 0;
}
.vakata-context-rtl li > a .vakata-contextmenu-sep {
  margin: 0 0 0 0.5em;
  border-left-color: white;
  background: #e2e3e3;
}
#jstree-marker {
  position: absolute;
  top: 0;
  left: 0;
  margin: -5px 0 0 0;
  padding: 0;
  border-right: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 5px solid;
  width: 0;
  height: 0;
  font-size: 0;
  line-height: 0;
}
#jstree-dnd {
  line-height: 16px;
  margin: 0;
  padding: 4px;
}
#jstree-dnd .jstree-icon,
#jstree-dnd .jstree-copy {
  display: inline-block;
  text-decoration: none;
  margin: 0 2px 0 0;
  padding: 0;
  width: 16px;
  height: 16px;
}
#jstree-dnd .jstree-ok {
  background: green;
}
#jstree-dnd .jstree-er {
  background: red;
}
#jstree-dnd .jstree-copy {
  margin: 0 2px 0 2px;
}
.jstree-default-dark .jstree-node,
.jstree-default-dark .jstree-icon {
  background-repeat: no-repeat;
  background-color: transparent;
}
.jstree-default-dark .jstree-anchor,
.jstree-default-dark .jstree-animated,
.jstree-default-dark .jstree-wholerow {
  transition: background-color 0.15s, box-shadow 0.15s;
}
.jstree-default-dark .jstree-hovered {
  background: #555555;
  border-radius: 2px;
  box-shadow: inset 0 0 1px #555555;
}
.jstree-default-dark .jstree-context {
  background: #555555;
  border-radius: 2px;
  box-shadow: inset 0 0 1px #555555;
}
.jstree-default-dark .jstree-clicked {
  background: #5fa2db;
  border-radius: 2px;
  box-shadow: inset 0 0 1px #666666;
}
.jstree-default-dark .jstree-no-icons .jstree-anchor > .jstree-themeicon {
  display: none;
}
.jstree-default-dark .jstree-disabled {
  background: transparent;
  color: #666666;
}
.jstree-default-dark .jstree-disabled.jstree-hovered {
  background: transparent;
  box-shadow: none;
}
.jstree-default-dark .jstree-disabled.jstree-clicked {
  background: #333333;
}
.jstree-default-dark .jstree-disabled > .jstree-icon {
  opacity: 0.8;
  filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");
  /* Firefox 10+ */
  filter: gray;
  /* IE6-9 */
  -webkit-filter: grayscale(100%);
  /* Chrome 19+ & Safari 6+ */
}
.jstree-default-dark .jstree-search {
  font-style: italic;
  color: #ffffff;
  font-weight: bold;
}
.jstree-default-dark .jstree-no-checkboxes .jstree-checkbox {
  display: none !important;
}
.jstree-default-dark.jstree-checkbox-no-clicked .jstree-clicked {
  background: transparent;
  box-shadow: none;
}
.jstree-default-dark.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered {
  background: #555555;
}
.jstree-default-dark.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked {
  background: transparent;
}
.jstree-default-dark.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered {
  background: #555555;
}
.jstree-default-dark > .jstree-striped {
  min-width: 100%;
  display: inline-block;
  background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==") left top repeat;
}
.jstree-default-dark > .jstree-wholerow-ul .jstree-hovered,
.jstree-default-dark > .jstree-wholerow-ul .jstree-clicked {
  background: transparent;
  box-shadow: none;
  border-radius: 0;
}
.jstree-default-dark .jstree-wholerow {
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.jstree-default-dark .jstree-wholerow-hovered {
  background: #555555;
}
.jstree-default-dark .jstree-wholerow-clicked {
  background: #5fa2db;
  background: -webkit-linear-gradient(top, #5fa2db 0%, #5fa2db 100%);
  background: linear-gradient(to bottom, #5fa2db 0%, #5fa2db 100%);
}
.jstree-default-dark .jstree-node {
  min-height: 24px;
  line-height: 24px;
  margin-left: 24px;
  min-width: 24px;
}
.jstree-default-dark .jstree-anchor {
  line-height: 24px;
  height: 24px;
}
.jstree-default-dark .jstree-icon {
  width: 24px;
  height: 24px;
  line-height: 24px;
}
.jstree-default-dark .jstree-icon:empty {
  width: 24px;
  height: 24px;
  line-height: 24px;
}
.jstree-default-dark.jstree-rtl .jstree-node {
  margin-right: 24px;
}
.jstree-default-dark .jstree-wholerow {
  height: 24px;
}
.jstree-default-dark .jstree-node,
.jstree-default-dark .jstree-icon {
  background-image: url("32px.png");
}
.jstree-default-dark .jstree-node {
  background-position: -292px -4px;
  background-repeat: repeat-y;
}
.jstree-default-dark .jstree-last {
  background: transparent;
}
.jstree-default-dark .jstree-open > .jstree-ocl {
  background-position: -132px -4px;
}
.jstree-default-dark .jstree-closed > .jstree-ocl {
  background-position: -100px -4px;
}
.jstree-default-dark .jstree-leaf > .jstree-ocl {
  background-position: -68px -4px;
}
.jstree-default-dark .jstree-themeicon {
  background-position: -260px -4px;
}
.jstree-default-dark > .jstree-no-dots .jstree-node,
.jstree-default-dark > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-dark > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -36px -4px;
}
.jstree-default-dark > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: -4px -4px;
}
.jstree-default-dark .jstree-disabled {
  background: transparent;
}
.jstree-default-dark .jstree-disabled.jstree-hovered {
  background: transparent;
}
.jstree-default-dark .jstree-disabled.jstree-clicked {
  background: #efefef;
}
.jstree-default-dark .jstree-checkbox {
  background-position: -164px -4px;
}
.jstree-default-dark .jstree-checkbox:hover {
  background-position: -164px -36px;
}
.jstree-default-dark.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default-dark .jstree-checked > .jstree-checkbox {
  background-position: -228px -4px;
}
.jstree-default-dark.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default-dark .jstree-checked > .jstree-checkbox:hover {
  background-position: -228px -36px;
}
.jstree-default-dark .jstree-anchor > .jstree-undetermined {
  background-position: -196px -4px;
}
.jstree-default-dark .jstree-anchor > .jstree-undetermined:hover {
  background-position: -196px -36px;
}
.jstree-default-dark .jstree-checkbox-disabled {
  opacity: 0.8;
  filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");
  /* Firefox 10+ */
  filter: gray;
  /* IE6-9 */
  -webkit-filter: grayscale(100%);
  /* Chrome 19+ & Safari 6+ */
}
.jstree-default-dark > .jstree-striped {
  background-size: auto 48px;
}
.jstree-default-dark.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
  background-position: 100% 1px;
  background-repeat: repeat-y;
}
.jstree-default-dark.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-dark.jstree-rtl .jstree-open > .jstree-ocl {
  background-position: -132px -36px;
}
.jstree-default-dark.jstree-rtl .jstree-closed > .jstree-ocl {
  background-position: -100px -36px;
}
.jstree-default-dark.jstree-rtl .jstree-leaf > .jstree-ocl {
  background-position: -68px -36px;
}
.jstree-default-dark.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default-dark.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-dark.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -36px -36px;
}
.jstree-default-dark.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: -4px -36px;
}
.jstree-default-dark .jstree-themeicon-custom {
  background-color: transparent;
  background-image: none;
  background-position: 0 0;
}
.jstree-default-dark > .jstree-container-ul .jstree-loading > .jstree-ocl {
  background: url("throbber.gif") center center no-repeat;
}
.jstree-default-dark .jstree-file {
  background: url("32px.png") -100px -68px no-repeat;
}
.jstree-default-dark .jstree-folder {
  background: url("32px.png") -260px -4px no-repeat;
}
.jstree-default-dark > .jstree-container-ul > .jstree-node {
  margin-left: 0;
  margin-right: 0;
}
#jstree-dnd.jstree-default-dark {
  line-height: 24px;
  padding: 0 4px;
}
#jstree-dnd.jstree-default-dark .jstree-ok,
#jstree-dnd.jstree-default-dark .jstree-er {
  background-image: url("32px.png");
  background-repeat: no-repeat;
  background-color: transparent;
}
#jstree-dnd.jstree-default-dark i {
  background: transparent;
  width: 24px;
  height: 24px;
  line-height: 24px;
}
#jstree-dnd.jstree-default-dark .jstree-ok {
  background-position: -4px -68px;
}
#jstree-dnd.jstree-default-dark .jstree-er {
  background-position: -36px -68px;
}
.jstree-default-dark .jstree-ellipsis {
  overflow: hidden;
}
.jstree-default-dark .jstree-ellipsis .jstree-anchor {
  width: calc(100% - 29px);
  text-overflow: ellipsis;
  overflow: hidden;
}
.jstree-default-dark .jstree-ellipsis.jstree-no-icons .jstree-anchor {
  width: calc(100% - 5px);
}
.jstree-default-dark.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
}
.jstree-default-dark.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-dark-small .jstree-node {
  min-height: 18px;
  line-height: 18px;
  margin-left: 18px;
  min-width: 18px;
}
.jstree-default-dark-small .jstree-anchor {
  line-height: 18px;
  height: 18px;
}
.jstree-default-dark-small .jstree-icon {
  width: 18px;
  height: 18px;
  line-height: 18px;
}
.jstree-default-dark-small .jstree-icon:empty {
  width: 18px;
  height: 18px;
  line-height: 18px;
}
.jstree-default-dark-small.jstree-rtl .jstree-node {
  margin-right: 18px;
}
.jstree-default-dark-small .jstree-wholerow {
  height: 18px;
}
.jstree-default-dark-small .jstree-node,
.jstree-default-dark-small .jstree-icon {
  background-image: url("32px.png");
}
.jstree-default-dark-small .jstree-node {
  background-position: -295px -7px;
  background-repeat: repeat-y;
}
.jstree-default-dark-small .jstree-last {
  background: transparent;
}
.jstree-default-dark-small .jstree-open > .jstree-ocl {
  background-position: -135px -7px;
}
.jstree-default-dark-small .jstree-closed > .jstree-ocl {
  background-position: -103px -7px;
}
.jstree-default-dark-small .jstree-leaf > .jstree-ocl {
  background-position: -71px -7px;
}
.jstree-default-dark-small .jstree-themeicon {
  background-position: -263px -7px;
}
.jstree-default-dark-small > .jstree-no-dots .jstree-node,
.jstree-default-dark-small > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-dark-small > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -39px -7px;
}
.jstree-default-dark-small > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: -7px -7px;
}
.jstree-default-dark-small .jstree-disabled {
  background: transparent;
}
.jstree-default-dark-small .jstree-disabled.jstree-hovered {
  background: transparent;
}
.jstree-default-dark-small .jstree-disabled.jstree-clicked {
  background: #efefef;
}
.jstree-default-dark-small .jstree-checkbox {
  background-position: -167px -7px;
}
.jstree-default-dark-small .jstree-checkbox:hover {
  background-position: -167px -39px;
}
.jstree-default-dark-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default-dark-small .jstree-checked > .jstree-checkbox {
  background-position: -231px -7px;
}
.jstree-default-dark-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default-dark-small .jstree-checked > .jstree-checkbox:hover {
  background-position: -231px -39px;
}
.jstree-default-dark-small .jstree-anchor > .jstree-undetermined {
  background-position: -199px -7px;
}
.jstree-default-dark-small .jstree-anchor > .jstree-undetermined:hover {
  background-position: -199px -39px;
}
.jstree-default-dark-small .jstree-checkbox-disabled {
  opacity: 0.8;
  filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");
  /* Firefox 10+ */
  filter: gray;
  /* IE6-9 */
  -webkit-filter: grayscale(100%);
  /* Chrome 19+ & Safari 6+ */
}
.jstree-default-dark-small > .jstree-striped {
  background-size: auto 36px;
}
.jstree-default-dark-small.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
  background-position: 100% 1px;
  background-repeat: repeat-y;
}
.jstree-default-dark-small.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-dark-small.jstree-rtl .jstree-open > .jstree-ocl {
  background-position: -135px -39px;
}
.jstree-default-dark-small.jstree-rtl .jstree-closed > .jstree-ocl {
  background-position: -103px -39px;
}
.jstree-default-dark-small.jstree-rtl .jstree-leaf > .jstree-ocl {
  background-position: -71px -39px;
}
.jstree-default-dark-small.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default-dark-small.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-dark-small.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -39px -39px;
}
.jstree-default-dark-small.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: -7px -39px;
}
.jstree-default-dark-small .jstree-themeicon-custom {
  background-color: transparent;
  background-image: none;
  background-position: 0 0;
}
.jstree-default-dark-small > .jstree-container-ul .jstree-loading > .jstree-ocl {
  background: url("throbber.gif") center center no-repeat;
}
.jstree-default-dark-small .jstree-file {
  background: url("32px.png") -103px -71px no-repeat;
}
.jstree-default-dark-small .jstree-folder {
  background: url("32px.png") -263px -7px no-repeat;
}
.jstree-default-dark-small > .jstree-container-ul > .jstree-node {
  margin-left: 0;
  margin-right: 0;
}
#jstree-dnd.jstree-default-dark-small {
  line-height: 18px;
  padding: 0 4px;
}
#jstree-dnd.jstree-default-dark-small .jstree-ok,
#jstree-dnd.jstree-default-dark-small .jstree-er {
  background-image: url("32px.png");
  background-repeat: no-repeat;
  background-color: transparent;
}
#jstree-dnd.jstree-default-dark-small i {
  background: transparent;
  width: 18px;
  height: 18px;
  line-height: 18px;
}
#jstree-dnd.jstree-default-dark-small .jstree-ok {
  background-position: -7px -71px;
}
#jstree-dnd.jstree-default-dark-small .jstree-er {
  background-position: -39px -71px;
}
.jstree-default-dark-small .jstree-ellipsis {
  overflow: hidden;
}
.jstree-default-dark-small .jstree-ellipsis .jstree-anchor {
  width: calc(100% - 23px);
  text-overflow: ellipsis;
  overflow: hidden;
}
.jstree-default-dark-small .jstree-ellipsis.jstree-no-icons .jstree-anchor {
  width: calc(100% - 5px);
}
.jstree-default-dark-small.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==");
}
.jstree-default-dark-small.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-dark-large .jstree-node {
  min-height: 32px;
  line-height: 32px;
  margin-left: 32px;
  min-width: 32px;
}
.jstree-default-dark-large .jstree-anchor {
  line-height: 32px;
  height: 32px;
}
.jstree-default-dark-large .jstree-icon {
  width: 32px;
  height: 32px;
  line-height: 32px;
}
.jstree-default-dark-large .jstree-icon:empty {
  width: 32px;
  height: 32px;
  line-height: 32px;
}
.jstree-default-dark-large.jstree-rtl .jstree-node {
  margin-right: 32px;
}
.jstree-default-dark-large .jstree-wholerow {
  height: 32px;
}
.jstree-default-dark-large .jstree-node,
.jstree-default-dark-large .jstree-icon {
  background-image: url("32px.png");
}
.jstree-default-dark-large .jstree-node {
  background-position: -288px 0px;
  background-repeat: repeat-y;
}
.jstree-default-dark-large .jstree-last {
  background: transparent;
}
.jstree-default-dark-large .jstree-open > .jstree-ocl {
  background-position: -128px 0px;
}
.jstree-default-dark-large .jstree-closed > .jstree-ocl {
  background-position: -96px 0px;
}
.jstree-default-dark-large .jstree-leaf > .jstree-ocl {
  background-position: -64px 0px;
}
.jstree-default-dark-large .jstree-themeicon {
  background-position: -256px 0px;
}
.jstree-default-dark-large > .jstree-no-dots .jstree-node,
.jstree-default-dark-large > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-dark-large > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -32px 0px;
}
.jstree-default-dark-large > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: 0px 0px;
}
.jstree-default-dark-large .jstree-disabled {
  background: transparent;
}
.jstree-default-dark-large .jstree-disabled.jstree-hovered {
  background: transparent;
}
.jstree-default-dark-large .jstree-disabled.jstree-clicked {
  background: #efefef;
}
.jstree-default-dark-large .jstree-checkbox {
  background-position: -160px 0px;
}
.jstree-default-dark-large .jstree-checkbox:hover {
  background-position: -160px -32px;
}
.jstree-default-dark-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default-dark-large .jstree-checked > .jstree-checkbox {
  background-position: -224px 0px;
}
.jstree-default-dark-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default-dark-large .jstree-checked > .jstree-checkbox:hover {
  background-position: -224px -32px;
}
.jstree-default-dark-large .jstree-anchor > .jstree-undetermined {
  background-position: -192px 0px;
}
.jstree-default-dark-large .jstree-anchor > .jstree-undetermined:hover {
  background-position: -192px -32px;
}
.jstree-default-dark-large .jstree-checkbox-disabled {
  opacity: 0.8;
  filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");
  /* Firefox 10+ */
  filter: gray;
  /* IE6-9 */
  -webkit-filter: grayscale(100%);
  /* Chrome 19+ & Safari 6+ */
}
.jstree-default-dark-large > .jstree-striped {
  background-size: auto 64px;
}
.jstree-default-dark-large.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
  background-position: 100% 1px;
  background-repeat: repeat-y;
}
.jstree-default-dark-large.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-dark-large.jstree-rtl .jstree-open > .jstree-ocl {
  background-position: -128px -32px;
}
.jstree-default-dark-large.jstree-rtl .jstree-closed > .jstree-ocl {
  background-position: -96px -32px;
}
.jstree-default-dark-large.jstree-rtl .jstree-leaf > .jstree-ocl {
  background-position: -64px -32px;
}
.jstree-default-dark-large.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default-dark-large.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
  background: transparent;
}
.jstree-default-dark-large.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
  background-position: -32px -32px;
}
.jstree-default-dark-large.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
  background-position: 0px -32px;
}
.jstree-default-dark-large .jstree-themeicon-custom {
  background-color: transparent;
  background-image: none;
  background-position: 0 0;
}
.jstree-default-dark-large > .jstree-container-ul .jstree-loading > .jstree-ocl {
  background: url("throbber.gif") center center no-repeat;
}
.jstree-default-dark-large .jstree-file {
  background: url("32px.png") -96px -64px no-repeat;
}
.jstree-default-dark-large .jstree-folder {
  background: url("32px.png") -256px 0px no-repeat;
}
.jstree-default-dark-large > .jstree-container-ul > .jstree-node {
  margin-left: 0;
  margin-right: 0;
}
#jstree-dnd.jstree-default-dark-large {
  line-height: 32px;
  padding: 0 4px;
}
#jstree-dnd.jstree-default-dark-large .jstree-ok,
#jstree-dnd.jstree-default-dark-large .jstree-er {
  background-image: url("32px.png");
  background-repeat: no-repeat;
  background-color: transparent;
}
#jstree-dnd.jstree-default-dark-large i {
  background: transparent;
  width: 32px;
  height: 32px;
  line-height: 32px;
}
#jstree-dnd.jstree-default-dark-large .jstree-ok {
  background-position: 0px -64px;
}
#jstree-dnd.jstree-default-dark-large .jstree-er {
  background-position: -32px -64px;
}
.jstree-default-dark-large .jstree-ellipsis {
  overflow: hidden;
}
.jstree-default-dark-large .jstree-ellipsis .jstree-anchor {
  width: calc(100% - 37px);
  text-overflow: ellipsis;
  overflow: hidden;
}
.jstree-default-dark-large .jstree-ellipsis.jstree-no-icons .jstree-anchor {
  width: calc(100% - 5px);
}
.jstree-default-dark-large.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==");
}
.jstree-default-dark-large.jstree-rtl .jstree-last {
  background: transparent;
}
@media (max-width: 768px) {
  #jstree-dnd.jstree-dnd-responsive {
    line-height: 40px;
    font-weight: bold;
    font-size: 1.1em;
    text-shadow: 1px 1px white;
  }
  #jstree-dnd.jstree-dnd-responsive > i {
    background: transparent;
    width: 40px;
    height: 40px;
  }
  #jstree-dnd.jstree-dnd-responsive > .jstree-ok {
    background-image: url("40px.png");
    background-position: 0 -200px;
    background-size: 120px 240px;
  }
  #jstree-dnd.jstree-dnd-responsive > .jstree-er {
    background-image: url("40px.png");
    background-position: -40px -200px;
    background-size: 120px 240px;
  }
  #jstree-marker.jstree-dnd-responsive {
    border-left-width: 10px;
    border-top-width: 10px;
    border-bottom-width: 10px;
    margin-top: -10px;
  }
}
@media (max-width: 768px) {
  .jstree-default-dark-responsive {
    /*
	.jstree-open > .jstree-ocl,
	.jstree-closed > .jstree-ocl { border-radius:20px; background-color:white; }
	*/
  }
  .jstree-default-dark-responsive .jstree-icon {
    background-image: url("40px.png");
  }
  .jstree-default-dark-responsive .jstree-node,
  .jstree-default-dark-responsive .jstree-leaf > .jstree-ocl {
    background: transparent;
  }
  .jstree-default-dark-responsive .jstree-node {
    min-height: 40px;
    line-height: 40px;
    margin-left: 40px;
    min-width: 40px;
    white-space: nowrap;
  }
  .jstree-default-dark-responsive .jstree-anchor {
    line-height: 40px;
    height: 40px;
  }
  .jstree-default-dark-responsive .jstree-icon,
  .jstree-default-dark-responsive .jstree-icon:empty {
    width: 40px;
    height: 40px;
    line-height: 40px;
  }
  .jstree-default-dark-responsive > .jstree-container-ul > .jstree-node {
    margin-left: 0;
  }
  .jstree-default-dark-responsive.jstree-rtl .jstree-node {
    margin-left: 0;
    margin-right: 40px;
    background: transparent;
  }
  .jstree-default-dark-responsive.jstree-rtl .jstree-container-ul > .jstree-node {
    margin-right: 0;
  }
  .jstree-default-dark-responsive .jstree-ocl,
  .jstree-default-dark-responsive .jstree-themeicon,
  .jstree-default-dark-responsive .jstree-checkbox {
    background-size: 120px 240px;
  }
  .jstree-default-dark-responsive .jstree-leaf > .jstree-ocl,
  .jstree-default-dark-responsive.jstree-rtl .jstree-leaf > .jstree-ocl {
    background: transparent;
  }
  .jstree-default-dark-responsive .jstree-open > .jstree-ocl {
    background-position: 0 0px !important;
  }
  .jstree-default-dark-responsive .jstree-closed > .jstree-ocl {
    background-position: 0 -40px !important;
  }
  .jstree-default-dark-responsive.jstree-rtl .jstree-closed > .jstree-ocl {
    background-position: -40px 0px !important;
  }
  .jstree-default-dark-responsive .jstree-themeicon {
    background-position: -40px -40px;
  }
  .jstree-default-dark-responsive .jstree-checkbox,
  .jstree-default-dark-responsive .jstree-checkbox:hover {
    background-position: -40px -80px;
  }
  .jstree-default-dark-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
  .jstree-default-dark-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
  .jstree-default-dark-responsive .jstree-checked > .jstree-checkbox,
  .jstree-default-dark-responsive .jstree-checked > .jstree-checkbox:hover {
    background-position: 0 -80px;
  }
  .jstree-default-dark-responsive .jstree-anchor > .jstree-undetermined,
  .jstree-default-dark-responsive .jstree-anchor > .jstree-undetermined:hover {
    background-position: 0 -120px;
  }
  .jstree-default-dark-responsive .jstree-anchor {
    font-weight: bold;
    font-size: 1.1em;
    text-shadow: 1px 1px white;
  }
  .jstree-default-dark-responsive > .jstree-striped {
    background: transparent;
  }
  .jstree-default-dark-responsive .jstree-wholerow {
    border-top: 1px solid #666666;
    border-bottom: 1px solid #000000;
    background: #333333;
    height: 40px;
  }
  .jstree-default-dark-responsive .jstree-wholerow-hovered {
    background: #555555;
  }
  .jstree-default-dark-responsive .jstree-wholerow-clicked {
    background: #5fa2db;
  }
  .jstree-default-dark-responsive .jstree-children .jstree-last > .jstree-wholerow {
    box-shadow: inset 0 -6px 3px -5px #111111;
  }
  .jstree-default-dark-responsive .jstree-children .jstree-open > .jstree-wholerow {
    box-shadow: inset 0 6px 3px -5px #111111;
    border-top: 0;
  }
  .jstree-default-dark-responsive .jstree-children .jstree-open + .jstree-open {
    box-shadow: none;
  }
  .jstree-default-dark-responsive .jstree-node,
  .jstree-default-dark-responsive .jstree-icon,
  .jstree-default-dark-responsive .jstree-node > .jstree-ocl,
  .jstree-default-dark-responsive .jstree-themeicon,
  .jstree-default-dark-responsive .jstree-checkbox {
    background-image: url("40px.png");
    background-size: 120px 240px;
  }
  .jstree-default-dark-responsive .jstree-node {
    background-position: -80px 0;
    background-repeat: repeat-y;
  }
  .jstree-default-dark-responsive .jstree-last {
    background: transparent;
  }
  .jstree-default-dark-responsive .jstree-leaf > .jstree-ocl {
    background-position: -40px -120px;
  }
  .jstree-default-dark-responsive .jstree-last > .jstree-ocl {
    background-position: -40px -160px;
  }
  .jstree-default-dark-responsive .jstree-themeicon-custom {
    background-color: transparent;
    background-image: none;
    background-position: 0 0;
  }
  .jstree-default-dark-responsive .jstree-file {
    background: url("40px.png") 0 -160px no-repeat;
    background-size: 120px 240px;
  }
  .jstree-default-dark-responsive .jstree-folder {
    background: url("40px.png") -40px -40px no-repeat;
    background-size: 120px 240px;
  }
  .jstree-default-dark-responsive > .jstree-container-ul > .jstree-node {
    margin-left: 0;
    margin-right: 0;
  }
}
.jstree-default-dark {
  background: #333;
}
.jstree-default-dark .jstree-anchor {
  color: #999;
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5);
}
.jstree-default-dark .jstree-clicked,
.jstree-default-dark .jstree-checked {
  color: white;
}
.jstree-default-dark .jstree-hovered {
  color: white;
}
#jstree-marker.jstree-default-dark {
  border-left-color: #999;
  background: transparent;
}
.jstree-default-dark .jstree-anchor > .jstree-icon {
  opacity: 0.75;
}
.jstree-default-dark .jstree-clicked > .jstree-icon,
.jstree-default-dark .jstree-hovered > .jstree-icon,
.jstree-default-dark .jstree-checked > .jstree-icon {
  opacity: 1;
}
.jstree-default-dark.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
}
.jstree-default-dark.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-dark-small.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==");
}
.jstree-default-dark-small.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-default-dark-large.jstree-rtl .jstree-node {
  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==");
}
.jstree-default-dark-large.jstree-rtl .jstree-last {
  background: transparent;
}
.jstree-node,.jstree-children,.jstree-container-ul{display:block;margin:0;padding:0;list-style-type:none;list-style-image:none}.jstree-node{white-space:nowrap}.jstree-anchor{display:inline-block;color:#000;white-space:nowrap;padding:0 4px 0 1px;margin:0;vertical-align:top}.jstree-anchor:focus{outline:0}.jstree-anchor,.jstree-anchor:link,.jstree-anchor:visited,.jstree-anchor:hover,.jstree-anchor:active{text-decoration:none;color:inherit}.jstree-icon{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-icon:empty{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-ocl{cursor:pointer}.jstree-leaf>.jstree-ocl{cursor:default}.jstree .jstree-open>.jstree-children{display:block}.jstree .jstree-closed>.jstree-children,.jstree .jstree-leaf>.jstree-children{display:none}.jstree-anchor>.jstree-themeicon{margin-right:2px}.jstree-no-icons .jstree-themeicon,.jstree-anchor>.jstree-themeicon-hidden{display:none}.jstree-hidden,.jstree-node.jstree-hidden{display:none}.jstree-rtl .jstree-anchor{padding:0 1px 0 4px}.jstree-rtl .jstree-anchor>.jstree-themeicon{margin-left:2px;margin-right:0}.jstree-rtl .jstree-node{margin-left:0}.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-wholerow-ul{position:relative;display:inline-block;min-width:100%}.jstree-wholerow-ul .jstree-leaf>.jstree-ocl{cursor:pointer}.jstree-wholerow-ul .jstree-anchor,.jstree-wholerow-ul .jstree-icon{position:relative}.jstree-wholerow-ul .jstree-wholerow{width:100%;cursor:pointer;position:absolute;left:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.vakata-context{display:none}.vakata-context,.vakata-context ul{margin:0;padding:2px;position:absolute;background:#f5f5f5;border:1px solid #979797;box-shadow:2px 2px 2px #999}.vakata-context ul{list-style:none;left:100%;margin-top:-2.7em;margin-left:-4px}.vakata-context .vakata-context-right ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context li{list-style:none}.vakata-context li>a{display:block;padding:0 2em;text-decoration:none;width:auto;color:#000;white-space:nowrap;line-height:2.4em;text-shadow:1px 1px 0 #fff;border-radius:1px}.vakata-context li>a:hover{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context li>a.vakata-context-parent{background-image:url(data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==);background-position:right center;background-repeat:no-repeat}.vakata-context li>a:focus{outline:0}.vakata-context .vakata-context-hover>a{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context .vakata-context-separator>a,.vakata-context .vakata-context-separator>a:hover{background:#fff;border:0;border-top:1px solid #e2e3e3;height:1px;min-height:1px;max-height:1px;padding:0;margin:0 0 0 2.4em;border-left:1px solid #e0e0e0;text-shadow:0 0 0 transparent;box-shadow:0 0 0 transparent;border-radius:0}.vakata-context .vakata-contextmenu-disabled a,.vakata-context .vakata-contextmenu-disabled a:hover{color:silver;background-color:transparent;border:0;box-shadow:0 0 0}.vakata-context li>a>i{text-decoration:none;display:inline-block;width:2.4em;height:2.4em;background:0 0;margin:0 0 0 -2em;vertical-align:top;text-align:center;line-height:2.4em}.vakata-context li>a>i:empty{width:2.4em;line-height:2.4em}.vakata-context li>a .vakata-contextmenu-sep{display:inline-block;width:1px;height:2.4em;background:#fff;margin:0 .5em 0 0;border-left:1px solid #e2e3e3}.vakata-context .vakata-contextmenu-shortcut{font-size:.8em;color:silver;opacity:.5;display:none}.vakata-context-rtl ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context-rtl li>a.vakata-context-parent{background-image:url(data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7);background-position:left center;background-repeat:no-repeat}.vakata-context-rtl .vakata-context-separator>a{margin:0 2.4em 0 0;border-left:0;border-right:1px solid #e2e3e3}.vakata-context-rtl .vakata-context-left ul{right:auto;left:100%;margin-left:-4px;margin-right:auto}.vakata-context-rtl li>a>i{margin:0 -2em 0 0}.vakata-context-rtl li>a .vakata-contextmenu-sep{margin:0 0 0 .5em;border-left-color:#fff;background:#e2e3e3}#jstree-marker{position:absolute;top:0;left:0;margin:-5px 0 0 0;padding:0;border-right:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid;width:0;height:0;font-size:0;line-height:0}#jstree-dnd{line-height:16px;margin:0;padding:4px}#jstree-dnd .jstree-icon,#jstree-dnd .jstree-copy{display:inline-block;text-decoration:none;margin:0 2px 0 0;padding:0;width:16px;height:16px}#jstree-dnd .jstree-ok{background:green}#jstree-dnd .jstree-er{background:red}#jstree-dnd .jstree-copy{margin:0 2px}.jstree-default-dark .jstree-node,.jstree-default-dark .jstree-icon{background-repeat:no-repeat;background-color:transparent}.jstree-default-dark .jstree-anchor,.jstree-default-dark .jstree-animated,.jstree-default-dark .jstree-wholerow{transition:background-color .15s,box-shadow .15s}.jstree-default-dark .jstree-hovered{background:#555;border-radius:2px;box-shadow:inset 0 0 1px #555}.jstree-default-dark .jstree-context{background:#555;border-radius:2px;box-shadow:inset 0 0 1px #555}.jstree-default-dark .jstree-clicked{background:#5fa2db;border-radius:2px;box-shadow:inset 0 0 1px #666}.jstree-default-dark .jstree-no-icons .jstree-anchor>.jstree-themeicon{display:none}.jstree-default-dark .jstree-disabled{background:0 0;color:#666}.jstree-default-dark .jstree-disabled.jstree-hovered{background:0 0;box-shadow:none}.jstree-default-dark .jstree-disabled.jstree-clicked{background:#333}.jstree-default-dark .jstree-disabled>.jstree-icon{opacity:.8;filter:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-dark .jstree-search{font-style:italic;color:#fff;font-weight:700}.jstree-default-dark .jstree-no-checkboxes .jstree-checkbox{display:none!important}.jstree-default-dark.jstree-checkbox-no-clicked .jstree-clicked{background:0 0;box-shadow:none}.jstree-default-dark.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered{background:#555}.jstree-default-dark.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked{background:0 0}.jstree-default-dark.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered{background:#555}.jstree-default-dark>.jstree-striped{min-width:100%;display:inline-block;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==) left top repeat}.jstree-default-dark>.jstree-wholerow-ul .jstree-hovered,.jstree-default-dark>.jstree-wholerow-ul .jstree-clicked{background:0 0;box-shadow:none;border-radius:0}.jstree-default-dark .jstree-wholerow{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.jstree-default-dark .jstree-wholerow-hovered{background:#555}.jstree-default-dark .jstree-wholerow-clicked{background:#5fa2db;background:-webkit-linear-gradient(top,#5fa2db 0,#5fa2db 100%);background:linear-gradient(to bottom,#5fa2db 0,#5fa2db 100%)}.jstree-default-dark .jstree-node{min-height:24px;line-height:24px;margin-left:24px;min-width:24px}.jstree-default-dark .jstree-anchor{line-height:24px;height:24px}.jstree-default-dark .jstree-icon{width:24px;height:24px;line-height:24px}.jstree-default-dark .jstree-icon:empty{width:24px;height:24px;line-height:24px}.jstree-default-dark.jstree-rtl .jstree-node{margin-right:24px}.jstree-default-dark .jstree-wholerow{height:24px}.jstree-default-dark .jstree-node,.jstree-default-dark .jstree-icon{background-image:url(32px.png)}.jstree-default-dark .jstree-node{background-position:-292px -4px;background-repeat:repeat-y}.jstree-default-dark .jstree-last{background:0 0}.jstree-default-dark .jstree-open>.jstree-ocl{background-position:-132px -4px}.jstree-default-dark .jstree-closed>.jstree-ocl{background-position:-100px -4px}.jstree-default-dark .jstree-leaf>.jstree-ocl{background-position:-68px -4px}.jstree-default-dark .jstree-themeicon{background-position:-260px -4px}.jstree-default-dark>.jstree-no-dots .jstree-node,.jstree-default-dark>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -4px}.jstree-default-dark>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -4px}.jstree-default-dark .jstree-disabled{background:0 0}.jstree-default-dark .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-dark .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-dark .jstree-checkbox{background-position:-164px -4px}.jstree-default-dark .jstree-checkbox:hover{background-position:-164px -36px}.jstree-default-dark.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-dark .jstree-checked>.jstree-checkbox{background-position:-228px -4px}.jstree-default-dark.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-dark .jstree-checked>.jstree-checkbox:hover{background-position:-228px -36px}.jstree-default-dark .jstree-anchor>.jstree-undetermined{background-position:-196px -4px}.jstree-default-dark .jstree-anchor>.jstree-undetermined:hover{background-position:-196px -36px}.jstree-default-dark .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-dark>.jstree-striped{background-size:auto 48px}.jstree-default-dark.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-dark.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark.jstree-rtl .jstree-open>.jstree-ocl{background-position:-132px -36px}.jstree-default-dark.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-100px -36px}.jstree-default-dark.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-68px -36px}.jstree-default-dark.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-dark.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -36px}.jstree-default-dark.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -36px}.jstree-default-dark .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-dark>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-dark .jstree-file{background:url(32px.png) -100px -68px no-repeat}.jstree-default-dark .jstree-folder{background:url(32px.png) -260px -4px no-repeat}.jstree-default-dark>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-dark{line-height:24px;padding:0 4px}#jstree-dnd.jstree-default-dark .jstree-ok,#jstree-dnd.jstree-default-dark .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-dark i{background:0 0;width:24px;height:24px;line-height:24px}#jstree-dnd.jstree-default-dark .jstree-ok{background-position:-4px -68px}#jstree-dnd.jstree-default-dark .jstree-er{background-position:-36px -68px}.jstree-default-dark .jstree-ellipsis{overflow:hidden}.jstree-default-dark .jstree-ellipsis .jstree-anchor{width:calc(100% - 29px);text-overflow:ellipsis;overflow:hidden}.jstree-default-dark .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-dark.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==)}.jstree-default-dark.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-small .jstree-node{min-height:18px;line-height:18px;margin-left:18px;min-width:18px}.jstree-default-dark-small .jstree-anchor{line-height:18px;height:18px}.jstree-default-dark-small .jstree-icon{width:18px;height:18px;line-height:18px}.jstree-default-dark-small .jstree-icon:empty{width:18px;height:18px;line-height:18px}.jstree-default-dark-small.jstree-rtl .jstree-node{margin-right:18px}.jstree-default-dark-small .jstree-wholerow{height:18px}.jstree-default-dark-small .jstree-node,.jstree-default-dark-small .jstree-icon{background-image:url(32px.png)}.jstree-default-dark-small .jstree-node{background-position:-295px -7px;background-repeat:repeat-y}.jstree-default-dark-small .jstree-last{background:0 0}.jstree-default-dark-small .jstree-open>.jstree-ocl{background-position:-135px -7px}.jstree-default-dark-small .jstree-closed>.jstree-ocl{background-position:-103px -7px}.jstree-default-dark-small .jstree-leaf>.jstree-ocl{background-position:-71px -7px}.jstree-default-dark-small .jstree-themeicon{background-position:-263px -7px}.jstree-default-dark-small>.jstree-no-dots .jstree-node,.jstree-default-dark-small>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-small>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -7px}.jstree-default-dark-small>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -7px}.jstree-default-dark-small .jstree-disabled{background:0 0}.jstree-default-dark-small .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-dark-small .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-dark-small .jstree-checkbox{background-position:-167px -7px}.jstree-default-dark-small .jstree-checkbox:hover{background-position:-167px -39px}.jstree-default-dark-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-dark-small .jstree-checked>.jstree-checkbox{background-position:-231px -7px}.jstree-default-dark-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-dark-small .jstree-checked>.jstree-checkbox:hover{background-position:-231px -39px}.jstree-default-dark-small .jstree-anchor>.jstree-undetermined{background-position:-199px -7px}.jstree-default-dark-small .jstree-anchor>.jstree-undetermined:hover{background-position:-199px -39px}.jstree-default-dark-small .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-dark-small>.jstree-striped{background-size:auto 36px}.jstree-default-dark-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-dark-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-small.jstree-rtl .jstree-open>.jstree-ocl{background-position:-135px -39px}.jstree-default-dark-small.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-103px -39px}.jstree-default-dark-small.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-71px -39px}.jstree-default-dark-small.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-dark-small.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-small.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -39px}.jstree-default-dark-small.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -39px}.jstree-default-dark-small .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-dark-small>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-dark-small .jstree-file{background:url(32px.png) -103px -71px no-repeat}.jstree-default-dark-small .jstree-folder{background:url(32px.png) -263px -7px no-repeat}.jstree-default-dark-small>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-dark-small{line-height:18px;padding:0 4px}#jstree-dnd.jstree-default-dark-small .jstree-ok,#jstree-dnd.jstree-default-dark-small .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-dark-small i{background:0 0;width:18px;height:18px;line-height:18px}#jstree-dnd.jstree-default-dark-small .jstree-ok{background-position:-7px -71px}#jstree-dnd.jstree-default-dark-small .jstree-er{background-position:-39px -71px}.jstree-default-dark-small .jstree-ellipsis{overflow:hidden}.jstree-default-dark-small .jstree-ellipsis .jstree-anchor{width:calc(100% - 23px);text-overflow:ellipsis;overflow:hidden}.jstree-default-dark-small .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-dark-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==)}.jstree-default-dark-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-large .jstree-node{min-height:32px;line-height:32px;margin-left:32px;min-width:32px}.jstree-default-dark-large .jstree-anchor{line-height:32px;height:32px}.jstree-default-dark-large .jstree-icon{width:32px;height:32px;line-height:32px}.jstree-default-dark-large .jstree-icon:empty{width:32px;height:32px;line-height:32px}.jstree-default-dark-large.jstree-rtl .jstree-node{margin-right:32px}.jstree-default-dark-large .jstree-wholerow{height:32px}.jstree-default-dark-large .jstree-node,.jstree-default-dark-large .jstree-icon{background-image:url(32px.png)}.jstree-default-dark-large .jstree-node{background-position:-288px 0;background-repeat:repeat-y}.jstree-default-dark-large .jstree-last{background:0 0}.jstree-default-dark-large .jstree-open>.jstree-ocl{background-position:-128px 0}.jstree-default-dark-large .jstree-closed>.jstree-ocl{background-position:-96px 0}.jstree-default-dark-large .jstree-leaf>.jstree-ocl{background-position:-64px 0}.jstree-default-dark-large .jstree-themeicon{background-position:-256px 0}.jstree-default-dark-large>.jstree-no-dots .jstree-node,.jstree-default-dark-large>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-large>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px 0}.jstree-default-dark-large>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 0}.jstree-default-dark-large .jstree-disabled{background:0 0}.jstree-default-dark-large .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-dark-large .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-dark-large .jstree-checkbox{background-position:-160px 0}.jstree-default-dark-large .jstree-checkbox:hover{background-position:-160px -32px}.jstree-default-dark-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-dark-large .jstree-checked>.jstree-checkbox{background-position:-224px 0}.jstree-default-dark-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-dark-large .jstree-checked>.jstree-checkbox:hover{background-position:-224px -32px}.jstree-default-dark-large .jstree-anchor>.jstree-undetermined{background-position:-192px 0}.jstree-default-dark-large .jstree-anchor>.jstree-undetermined:hover{background-position:-192px -32px}.jstree-default-dark-large .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'jstree-grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-dark-large>.jstree-striped{background-size:auto 64px}.jstree-default-dark-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-dark-large.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-large.jstree-rtl .jstree-open>.jstree-ocl{background-position:-128px -32px}.jstree-default-dark-large.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-96px -32px}.jstree-default-dark-large.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-64px -32px}.jstree-default-dark-large.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-dark-large.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-large.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px -32px}.jstree-default-dark-large.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 -32px}.jstree-default-dark-large .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-dark-large>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-dark-large .jstree-file{background:url(32px.png) -96px -64px no-repeat}.jstree-default-dark-large .jstree-folder{background:url(32px.png) -256px 0 no-repeat}.jstree-default-dark-large>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-dark-large{line-height:32px;padding:0 4px}#jstree-dnd.jstree-default-dark-large .jstree-ok,#jstree-dnd.jstree-default-dark-large .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-dark-large i{background:0 0;width:32px;height:32px;line-height:32px}#jstree-dnd.jstree-default-dark-large .jstree-ok{background-position:0 -64px}#jstree-dnd.jstree-default-dark-large .jstree-er{background-position:-32px -64px}.jstree-default-dark-large .jstree-ellipsis{overflow:hidden}.jstree-default-dark-large .jstree-ellipsis .jstree-anchor{width:calc(100% - 37px);text-overflow:ellipsis;overflow:hidden}.jstree-default-dark-large .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-dark-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==)}.jstree-default-dark-large.jstree-rtl .jstree-last{background:0 0}@media (max-width:768px){#jstree-dnd.jstree-dnd-responsive{line-height:40px;font-weight:700;font-size:1.1em;text-shadow:1px 1px #fff}#jstree-dnd.jstree-dnd-responsive>i{background:0 0;width:40px;height:40px}#jstree-dnd.jstree-dnd-responsive>.jstree-ok{background-image:url(40px.png);background-position:0 -200px;background-size:120px 240px}#jstree-dnd.jstree-dnd-responsive>.jstree-er{background-image:url(40px.png);background-position:-40px -200px;background-size:120px 240px}#jstree-marker.jstree-dnd-responsive{border-left-width:10px;border-top-width:10px;border-bottom-width:10px;margin-top:-10px}}@media (max-width:768px){.jstree-default-dark-responsive .jstree-icon{background-image:url(40px.png)}.jstree-default-dark-responsive .jstree-node,.jstree-default-dark-responsive .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-responsive .jstree-node{min-height:40px;line-height:40px;margin-left:40px;min-width:40px;white-space:nowrap}.jstree-default-dark-responsive .jstree-anchor{line-height:40px;height:40px}.jstree-default-dark-responsive .jstree-icon,.jstree-default-dark-responsive .jstree-icon:empty{width:40px;height:40px;line-height:40px}.jstree-default-dark-responsive>.jstree-container-ul>.jstree-node{margin-left:0}.jstree-default-dark-responsive.jstree-rtl .jstree-node{margin-left:0;margin-right:40px;background:0 0}.jstree-default-dark-responsive.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-default-dark-responsive .jstree-ocl,.jstree-default-dark-responsive .jstree-themeicon,.jstree-default-dark-responsive .jstree-checkbox{background-size:120px 240px}.jstree-default-dark-responsive .jstree-leaf>.jstree-ocl,.jstree-default-dark-responsive.jstree-rtl .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-responsive .jstree-open>.jstree-ocl{background-position:0 0!important}.jstree-default-dark-responsive .jstree-closed>.jstree-ocl{background-position:0 -40px!important}.jstree-default-dark-responsive.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-40px 0!important}.jstree-default-dark-responsive .jstree-themeicon{background-position:-40px -40px}.jstree-default-dark-responsive .jstree-checkbox,.jstree-default-dark-responsive .jstree-checkbox:hover{background-position:-40px -80px}.jstree-default-dark-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-dark-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-dark-responsive .jstree-checked>.jstree-checkbox,.jstree-default-dark-responsive .jstree-checked>.jstree-checkbox:hover{background-position:0 -80px}.jstree-default-dark-responsive .jstree-anchor>.jstree-undetermined,.jstree-default-dark-responsive .jstree-anchor>.jstree-undetermined:hover{background-position:0 -120px}.jstree-default-dark-responsive .jstree-anchor{font-weight:700;font-size:1.1em;text-shadow:1px 1px #fff}.jstree-default-dark-responsive>.jstree-striped{background:0 0}.jstree-default-dark-responsive .jstree-wholerow{border-top:1px solid #666;border-bottom:1px solid #000;background:#333;height:40px}.jstree-default-dark-responsive .jstree-wholerow-hovered{background:#555}.jstree-default-dark-responsive .jstree-wholerow-clicked{background:#5fa2db}.jstree-default-dark-responsive .jstree-children .jstree-last>.jstree-wholerow{box-shadow:inset 0 -6px 3px -5px #111}.jstree-default-dark-responsive .jstree-children .jstree-open>.jstree-wholerow{box-shadow:inset 0 6px 3px -5px #111;border-top:0}.jstree-default-dark-responsive .jstree-children .jstree-open+.jstree-open{box-shadow:none}.jstree-default-dark-responsive .jstree-node,.jstree-default-dark-responsive .jstree-icon,.jstree-default-dark-responsive .jstree-node>.jstree-ocl,.jstree-default-dark-responsive .jstree-themeicon,.jstree-default-dark-responsive .jstree-checkbox{background-image:url(40px.png);background-size:120px 240px}.jstree-default-dark-responsive .jstree-node{background-position:-80px 0;background-repeat:repeat-y}.jstree-default-dark-responsive .jstree-last{background:0 0}.jstree-default-dark-responsive .jstree-leaf>.jstree-ocl{background-position:-40px -120px}.jstree-default-dark-responsive .jstree-last>.jstree-ocl{background-position:-40px -160px}.jstree-default-dark-responsive .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-dark-responsive .jstree-file{background:url(40px.png) 0 -160px no-repeat;background-size:120px 240px}.jstree-default-dark-responsive .jstree-folder{background:url(40px.png) -40px -40px no-repeat;background-size:120px 240px}.jstree-default-dark-responsive>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}}.jstree-default-dark{background:#333}.jstree-default-dark .jstree-anchor{color:#999;text-shadow:1px 1px 0 rgba(0,0,0,.5)}.jstree-default-dark .jstree-clicked,.jstree-default-dark .jstree-checked{color:#fff}.jstree-default-dark .jstree-hovered{color:#fff}#jstree-marker.jstree-default-dark{border-left-color:#999;background:0 0}.jstree-default-dark .jstree-anchor>.jstree-icon{opacity:.75}.jstree-default-dark .jstree-clicked>.jstree-icon,.jstree-default-dark .jstree-hovered>.jstree-icon,.jstree-default-dark .jstree-checked>.jstree-icon{opacity:1}.jstree-default-dark.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==)}.jstree-default-dark.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==)}.jstree-default-dark-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==)}.jstree-default-dark-large.jstree-rtl .jstree-last{background:0 0}GIF89a    333999hhh@@@pppXXXPPP```xxx!NETSCAPE2.0   !Created with ajaxload.info !	
   ,       qH;uj01P4HЈ
`+I1q|@Pp@\ 84QQZ^-	g8) fl9C`O Tp%#
c%

U !	
   ,       pHhy91KI*,Al`h|&@sHBvD~UM^)H6?
xMl<L&klj('K' !	
   ,       rH(9NICsa(0F&pP$@08(ZF	( (iDN1:Hd%M 2	x2GOyo0Oyrn;bq'1b' !	
   ,       pH(c9J&
QN,J5P3`0J';)l_@H$vb ]M+

g@ͧ)v1[oz
;|zM1n&&& !	
   ,       fHh)9N&
}@APu,
\+I
4E|
q؜ɱ]
MkI7JيH!8esyVny& !	
   ,       xHRRB&' RrT( 2c d&08	g0`lAa~6ܦK!~M} <U\o
]Ym ]UK !	
   ,       sHT1&X"2DUX6+t9
ls$lj$G,Nis2-<`coO:x.OL

7]'| !	
   ,       qHpPJ$&}=%W
!8<
uu%Ew7SpZ& %&@M<5y4e `p
ml-	!5Q%\ !	
   ,       sHht{ǸJQ_(a	eqBj<3	i` hޒAa(
$ՀT
c`@00HBl|
	&YJgSg !	
   ,       sHhtuJX0^`Y
	sh(>BAbp$SIJǙCp	AH4zY
*f !	
   ,       oHht{6aWba(5U@ NA;4Br$P(EHTC!@80YQcLzYSY>Z gt !	
   ,       sHhtj(6a$_!_^5FBX
8ZXU(I@ѫ
(.AQX%(Q,!DM	Sa[	0 ;/*global module:false, require:false, __dirname:false*/

module.exports = function(grunt) {
  grunt.util.linefeed = "\n";

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      options : {
        separator : "\n"
      },
      dist: {
        src: ['src/<%= pkg.name %>.js', 'src/<%= pkg.name %>.*.js', 'src/vakata-jstree.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },
    copy: {
      libs : {
        files : [
          { expand: true, cwd : 'libs/', src: ['*'], dest: 'dist/libs/' }
        ]
      },
      docs : {
        files : [
          { expand: true, cwd : 'dist/', src: ['**/*'], dest: 'docs/assets/dist/' }
        ]
      }
    },
    uglify: {
      options: {
        banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> - (<%= _.pluck(pkg.licenses, "type").join(", ") %>) */\n',
        preserveComments: false,
        //sourceMap: "dist/jstree.min.map",
        //sourceMappingURL: "jstree.min.map",
        report: "min",
        beautify: {
                ascii_only: true
        },
        compress: {
                hoist_funs: false,
                loops: false,
                unused: false
        }
      },
      dist: {
        src: ['<%= concat.dist.dest %>'],
        dest: 'dist/<%= pkg.name %>.min.js'
      }
    },
    qunit: {
      files: ['test/unit/**/*.html']
    },
    jshint: {
      options: {
        'curly' : true,
        'eqeqeq' : true,
        'latedef' : true,
        'newcap' : true,
        'noarg' : true,
        'sub' : true,
        'undef' : true,
        'boss' : true,
        'eqnull' : true,
        'browser' : true,
        'trailing' : true,
        'globals' : {
          'console' : true,
          'jQuery' : true,
          'browser' : true,
          'XSLTProcessor' : true,
          'ActiveXObject' : true
        }
      },
      beforeconcat: ['src/<%= pkg.name %>.js', 'src/<%= pkg.name %>.*.js'],
      afterconcat: ['dist/<%= pkg.name %>.js']
    },
    dox: {
      files: {
        src: ['src/*.js'],
        dest: 'docs'
      }
    },
    amd : {
      files: {
        src: ['dist/jstree.js'],
        dest: 'dist/jstree.js'
      }
    },
    less: {
      production: {
        options : {
          cleancss : true,
          compress : true
        },
        files: {
          "dist/themes/default/style.min.css" : "src/themes/default/style.less",
          "dist/themes/default-dark/style.min.css" : "src/themes/default-dark/style.less"
        }
      },
      development: {
        files: {
          "src/themes/default/style.css" : "src/themes/default/style.less",
          "dist/themes/default/style.css" : "src/themes/default/style.less",
          "src/themes/default-dark/style.css" : "src/themes/default-dark/style.less",
          "dist/themes/default-dark/style.css" : "src/themes/default-dark/style.less"
        }
      }
    },
    watch: {
      js : {
        files: ['src/**/*.js'],
        tasks: ['js'],
        options : {
          atBegin : true
        }
      },
      css : {
        files: ['src/**/*.less','src/**/*.png','src/**/*.gif'],
        tasks: ['css'],
        options : {
          atBegin : true
        }
      },
    },
    resemble: {
      options: {
        screenshotRoot: 'test/visual/screenshots/',
        url: 'http://127.0.0.1/jstree/test/visual/',
        gm: false
      },
      desktop: {
        options: {
          width: 1280,
        },
        src: ['desktop'],
        dest: 'desktop',
      },
      mobile: {
        options: {
          width: 360,
        },
        src: ['mobile'],
        dest: 'mobile'
      }
    },
    imagemin: {
      dynamic: {
        options: {                       // Target options
          optimizationLevel: 7,
          pngquant : true
        },
        files: [{
          expand: true,                  // Enable dynamic expansion
          cwd:  'src/themes/default/',    // Src matches are relative to this path
          src: ['**/*.{png,jpg,gif}'],   // Actual patterns to match
          dest: 'dist/themes/default/'   // Destination path prefix
        },{
          expand: true,                  // Enable dynamic expansion
          cwd:  'src/themes/default-dark/',    // Src matches are relative to this path
          src: ['**/*.{png,jpg,gif}'],   // Actual patterns to match
          dest: 'dist/themes/default-dark/'   // Destination path prefix
        }]
      }
    },
    replace: {
      files: {
        src: ['dist/*.js', 'bower.json', 'component.json', 'jstree.jquery.json'],
        overwrite: true,
        replacements: [
          {
            from: '{{VERSION}}',
            to: "<%= pkg.version %>"
          },
          {
            from: /"version": "[^"]+"/g,
            to: "\"version\": \"<%= pkg.version %>\""
          },
        ]
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-qunit');
  grunt.loadNpmTasks('grunt-resemble-cli');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-imagemin');
  grunt.loadNpmTasks('grunt-text-replace');

  grunt.registerMultiTask('amd', 'Clean up AMD', function () {
    var s, d;
    this.files.forEach(function (f) {
      s = f.src;
      d = f.dest;
    });
    grunt.file.copy(s, d, {
      process: function (contents) {
        contents = contents.replace(/\s*if\(\$\.jstree\.plugins\.[a-z]+\)\s*\{\s*return;\s*\}/ig, '');
        contents = contents.replace(/\/\*globals[^\/]+\//ig, '');
        //contents = contents.replace(/\(function \(factory[\s\S]*?undefined/mig, '(function ($, undefined');
        //contents = contents.replace(/\}\)\);/g, '}(jQuery));');
        contents = contents.replace(/\(function \(factory[\s\S]*?undefined\s*\)[^\n]+/mig, '');
        contents = contents.replace(/\}\)\);/g, '');
        contents = contents.replace(/\s*("|')use strict("|');/g, '');
        contents = contents.replace(/\s*return \$\.fn\.jstree;/g, '');
        return grunt.file.read('src/intro.js') + contents + grunt.file.read('src/outro.js');
      }
    });
  });

  grunt.registerMultiTask('dox', 'Generate dox output ', function() {
    var exec = require('child_process').exec,
        path = require('path'),
        done = this.async(),
        doxPath = path.resolve(__dirname),
        formatter = [doxPath, 'node_modules', '.bin', 'dox'].join(path.sep);
    exec(formatter + ' < "dist/jstree.js" > "docs/jstree.json"', {maxBuffer: 5000*1024}, function(error, stout, sterr){
      if (error) {
        grunt.log.error(formatter);
        grunt.log.error("WARN: "+ error);
      }
      if (!error) {
        grunt.log.writeln('dist/jstree.js doxxed.');
        done();
      }
    });
  });

  grunt.util.linefeed = "\n";
  
  // Default task.
  grunt.registerTask('default', ['jshint:beforeconcat','concat','amd','jshint:afterconcat','copy:libs','uglify','less','imagemin','replace','copy:docs','qunit','resemble','dox']);
  grunt.registerTask('js', ['concat','amd','uglify']);
  grunt.registerTask('css', ['copy','less']);

};
{
	"name": "jstree",
	"title": "jsTree",
	"description": "Tree view for jQuery",
	"version": "3.3.3",
	"homepage": "http://jstree.com",
	"keywords": [
		"ui",
		"tree",
		"jstree"
	],
	"author": {
		"name": "Ivan Bozhanov",
		"email": "jstree@jstree.com",
		"url": "http://vakata.com"
	},
	"licenses": [
		{
			"type": "MIT",
			"url": "https://github.com/vakata/jstree/blob/master/LICENSE-MIT"
		}
	],
	"bugs": "https://github.com/vakata/jstree/issues",
	"demo": "http://jstree.com/demo",
	"dependencies": {
		"jquery": ">=1.9.1"
	}
}Copyright (c) 2014 Ivan Bozhanov

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.
{
  "name": "jstree",
  "title": "jsTree",
  "description": "jQuery tree plugin",
  "version": "3.3.3",
  "homepage": "http://jstree.com",
  "main": "./dist/jstree.js",
  "author": {
    "name": "Ivan Bozhanov",
    "email": "jstree@jstree.com",
    "url": "http://vakata.com"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/vakata/jstree.git"
  },
  "bugs": {
    "url": "https://github.com/vakata/jstree/issues"
  },
  "license": "MIT",
  "licenses": [
    {
      "type": "MIT",
      "url": "https://github.com/vakata/jstree/blob/master/LICENSE-MIT"
    }
  ],
  "keywords": [],
  "devDependencies": {
    "dox": "~0.4.4",
    "grunt": "~0.4.0",
    "grunt-contrib-concat": "*",
    "grunt-contrib-copy": "*",
    "grunt-contrib-imagemin": "~0.4.0",
    "grunt-contrib-jshint": "*",
    "grunt-contrib-less": "~0.8.2",
    "grunt-contrib-qunit": "~v0.3.0",
    "grunt-contrib-uglify": "*",
    "grunt-contrib-watch": "~0.5.3",
    "grunt-phantomcss-gitdiff": "0.0.7",
    "grunt-resemble-cli": "0.0.8",
    "grunt-text-replace": "~0.3.11"
  },
  "dependencies": {
    "jquery": ">=1.9.1"
  },
  "npmName": "jstree",
  "npmFileMap": [
    {
      "basePath": "/dist/",
      "files": [
        "jstree.min.js",
        "themes/**/*.png",
        "themes/**/*.gif",
        "themes/**/*.min.css"
      ]
    }
  ]
}
TtEognl   GBMB